Skip to content

Commit 7e6dccb

Browse files
authored
Merge pull request #193 from TaskFlow-CLAP/CLAP-424
Clap-424 QA 반영사항 Moya
2 parents f913ca5 + 09c913d commit 7e6dccb

25 files changed

+312
-127
lines changed

src/components/common/EditInformation.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,11 @@ const validateName = () => {
179179
const regex = /[!@#$%^&*(),.?":{}|<>\p{Emoji}]/gu
180180
isInvalid.value = regex.test(name.value)
181181
if (isInvalid.value == true) {
182-
nameError.value = '이름에는 특수문자가 포함될 수 없습니다.'
182+
nameError.value = '이름에는 특수문자가 포함될 수 없습니다'
183183
}
184184
if (name.value.length > 10 || name.value.length < 1) {
185185
isFull.value = true
186-
nameError.value = '이름은 1글자 이상, 10글자이하만 가능합니다.'
186+
nameError.value = '이름은 1글자 이상, 10글자이하만 가능합니다'
187187
} else {
188188
isFull.value = false
189189
}

src/components/common/ModalView.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
<slot name="body"></slot>
3333
</div>
3434
</div>
35-
3635
<textarea
3736
v-if="type == 'inputType' || type === 'terminate'"
3837
v-model="textValue"
3938
:placeholder="
4039
type === 'terminate' ? '종료 사유를 입력해주세요' : '반려 사유를 입력해주세요'
4140
"
41+
:class="{ 'border border-red-1 placeholder-red-500': isEmpty }"
4242
class="flex border w-full border-border-1 px-4 py-3 focus:outline-none resize-none h-[120px]" />
4343
</div>
4444

@@ -86,10 +86,11 @@ import { onUnmounted, ref, watch } from 'vue'
8686
import CommonIcons from './CommonIcons.vue'
8787
import LoadingIcon from './LoadingIcon.vue'
8888
89-
const { isOpen, type, modelValue } = defineProps<{
89+
const { isOpen, type, modelValue, isEmpty } = defineProps<{
9090
isOpen: boolean
9191
type?: string
9292
modelValue?: string
93+
isEmpty?: boolean
9394
}>()
9495
9596
const emit = defineEmits<{

src/components/member-management/MemberManagementAddByCsv.vue

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
type="file"
66
id="file"
77
accept=".csv"
8+
ref="fileInput"
89
@change="handleFileUpload" />
910
<label
1011
for="file"
@@ -15,40 +16,61 @@
1516
파일로 일괄 추가
1617
</label>
1718
<ModalView
18-
:isOpen="isModalVisible"
19+
:isOpen="isModalVisible === 'success'"
1920
:type="'successType'"
2021
@close="handleCancel">
2122
<template #header>회원이 추가되었습니다</template>
2223
</ModalView>
24+
<ModalView
25+
:isOpen="isModalVisible === 'error'"
26+
:type="'failType'"
27+
@close="handleCancel">
28+
<template #header>회원 추가를 실패했습니다</template>
29+
<template #body>{{ errorText }}</template>
30+
</ModalView>
2331
</div>
2432
</template>
2533

2634
<script setup lang="ts">
2735
import { addMemberAdminByCsv } from '@/api/admin'
2836
import { plusIcon } from '@/constants/iconPath'
2937
import { useMemberManagementParamsStore } from '@/stores/params'
38+
import { getErrorCSV } from '@/utils/getErorr'
3039
import { useQueryClient } from '@tanstack/vue-query'
3140
import { ref } from 'vue'
3241
import CommonIcons from '../common/CommonIcons.vue'
3342
import ModalView from '../common/ModalView.vue'
3443
3544
const queryClient = useQueryClient()
36-
const isModalVisible = ref(false)
45+
const isModalVisible = ref('')
3746
const { params } = useMemberManagementParamsStore()
47+
const errorText = ref('')
48+
const fileInput = ref<HTMLInputElement | null>(null)
3849
3950
const handleFileUpload = async (event: Event) => {
40-
const target = event.target as HTMLInputElement
41-
const file = target.files?.[0]
42-
if (!file) return
43-
const formData = new FormData()
44-
formData.append('file', file)
45-
await addMemberAdminByCsv(formData)
46-
queryClient.invalidateQueries({ queryKey: ['member', { ...params }] })
47-
isModalVisible.value = true
48-
target.value = ''
51+
try {
52+
const target = event.target as HTMLInputElement
53+
const file = target.files?.[0]
54+
if (!file) return
55+
56+
const formData = new FormData()
57+
formData.append('file', file)
58+
await addMemberAdminByCsv(formData)
59+
60+
queryClient.invalidateQueries({ queryKey: ['member', { ...params }] })
61+
isModalVisible.value = 'success'
62+
} catch (error) {
63+
errorText.value = getErrorCSV(error)
64+
isModalVisible.value = 'error'
65+
} finally {
66+
if (fileInput.value) {
67+
fileInput.value.value = ''
68+
}
69+
}
4970
}
5071
5172
const handleCancel = () => {
52-
isModalVisible.value = false
73+
isModalVisible.value = ''
74+
errorText.value = ''
5375
}
5476
</script>

src/components/member-management/MemberManagementList.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
<template #listCards>
88
<MemberManagementListCard
99
v-for="info in data?.content"
10-
:key="info.memberId"
10+
:key="
11+
info.memberId + info.name + info.departmentName + info.departmentRole + info.isReviewer
12+
"
1113
:info="info" />
1214
<NoContent v-if="data?.content.length === 0" />
1315
</template>
@@ -22,18 +24,18 @@
2224
</template>
2325

2426
<script setup lang="ts">
27+
import { useMemberStore } from '@/stores/member'
2528
import { useMemberManagementParamsStore } from '@/stores/params'
2629
import type { MemberManagementResponse } from '@/types/admin'
2730
import { axiosInstance } from '@/utils/axios'
2831
import { useQuery } from '@tanstack/vue-query'
32+
import { storeToRefs } from 'pinia'
2933
import { computed } from 'vue'
3034
import ListContainer from '../lists/ListContainer.vue'
3135
import ListPagination from '../lists/ListPagination.vue'
3236
import NoContent from '../lists/NoContent.vue'
3337
import MemberManagementListBar from './MemberManagementListBar.vue'
3438
import MemberManagementListCard from './MemberManagementListCard.vue'
35-
import { useMemberStore } from '@/stores/member'
36-
import { storeToRefs } from 'pinia'
3739
3840
const { params } = useMemberManagementParamsStore()
3941
const onPageChange = (value: number) => {

src/components/member-management/MemberManagementListCard.vue

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@
5050
</template>
5151

5252
<script setup lang="ts">
53+
import { useErrorStore } from '@/stores/error'
54+
import { useMemberStore } from '@/stores/member'
5355
import type { MemberManagementListData } from '@/types/admin'
5456
import type { ListCardProps, Role } from '@/types/common'
5557
import { axiosInstance } from '@/utils/axios'
5658
import { formatDate } from '@/utils/date'
5759
import { useQueryClient } from '@tanstack/vue-query'
60+
import { storeToRefs } from 'pinia'
5861
import { ref } from 'vue'
5962
import { useRouter } from 'vue-router'
63+
import ModalView from '../common/ModalView.vue'
6064
import ResultModal from '../common/ResultModal.vue'
6165
import ListCardTab from '../lists/ListCardTab.vue'
62-
import ModalView from '../common/ModalView.vue'
63-
import { useMemberStore } from '@/stores/member'
64-
import { storeToRefs } from 'pinia'
65-
import { useErrorStore } from '@/stores/error'
6666
6767
const roleContent = (role: Role) => {
6868
return role === 'ROLE_USER' ? '사용자' : role === 'ROLE_MANAGER' ? '담당자' : '관리자'
@@ -108,10 +108,16 @@ const closeModal = () => {
108108
}
109109
110110
const onMemberDelete = async (memberId: number) => {
111-
await axiosInstance.delete(`/api/managements/members`, { data: { memberId } })
112-
resultModalType.value = 'successType'
113-
message.value = '회원을 삭제했습니다'
114-
toggleModal('result')
111+
try {
112+
await axiosInstance.delete(`/api/managements/members`, { data: { memberId } })
113+
resultModalType.value = 'successType'
114+
message.value = '회원을 삭제했습니다'
115+
toggleModal('result')
116+
} catch {
117+
resultModalType.value = 'failType'
118+
message.value = '회원의 잔여작업이 존재합니다'
119+
toggleModal('result')
120+
}
115121
}
116122
117123
const onMemberInvite = async (memberId: number) => {

src/components/request-approve/RequestApprove.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const subCategoryArr = ref<SubCategory[]>([])
8484
const afterSubCategoryArr = ref<SubCategory[]>([])
8585
const approveData = ref(INITIAL_REQUEST_APPROVE_DATA)
8686
const isInvalidate = ref('')
87+
const isApproving = ref(false)
8788
const isFirst = ref(true)
8889
8990
const router = useRouter()
@@ -143,6 +144,7 @@ const handleCancel = () => {
143144
}
144145
145146
const handleSubmit = async () => {
147+
if (isApproving.value || isModalVisible.value) return
146148
if (!category1.value) {
147149
isInvalidate.value = 'category1'
148150
return
@@ -163,6 +165,7 @@ const handleSubmit = async () => {
163165
isInvalidate.value = ''
164166
return
165167
}
168+
isApproving.value = true
166169
167170
const requestData = {
168171
categoryId: category2.value.subCategoryId,
@@ -172,6 +175,7 @@ const handleSubmit = async () => {
172175
: null,
173176
labelId: approveData.value.label?.labelId || null
174177
}
178+
175179
await postTaskApprove(requestId, requestData)
176180
isModalVisible.value = true
177181
}

src/components/request-task/ReRequestTask.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<RequestTaskTextArea
2424
v-model="description"
2525
:is-invalidate="isInvalidate"
26-
:placeholderText="'부가 정보를 입력해주세요'"
26+
:placeholderText="'부가 설명을 입력해주세요'"
2727
:limit-length="200" />
2828
<RequestTaskFileInput
2929
v-model="file"

src/components/request-task/RequestTask.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<RequestTaskTextArea
2424
v-model="description"
2525
:is-invalidate="isInvalidate"
26-
:placeholderText="'부가 정보를 입력해주세요'"
26+
:placeholderText="'부가 설명을 입력해주세요'"
2727
:limit-length="200" />
2828
<RequestTaskFileInput
2929
v-model="file"

src/components/request-task/RequestTaskDropdown.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</div>
2222
<div
2323
v-if="dropdownOpen"
24-
class="absolute w-full h-40 overflow-y-auto scrollbar-hide top-[52px] flex flex-col gap-2 p-2 bg-white rounded z-10 shadow border-t border-t-border-2">
24+
class="absolute w-full max-h-40 overflow-y-auto scrollbar-hide top-[52px] flex flex-col gap-2 p-2 bg-white rounded z-10 shadow border-t border-t-border-2">
2525
<div
2626
v-for="option in options"
2727
:key="option"
@@ -36,10 +36,10 @@
3636

3737
<script lang="ts" setup>
3838
import { dropdownIcon } from '@/constants/iconPath'
39+
import { useOutsideClick } from '@/hooks/useOutsideClick'
3940
import type { RequestTaskDropdownProps } from '@/types/user'
4041
import { ref } from 'vue'
4142
import CommonIcons from '../common/CommonIcons.vue'
42-
import { useOutsideClick } from '@/hooks/useOutsideClick'
4343
4444
const { placeholderText, options, labelName, modelValue, isLabel, disabled, isInvalidate } =
4545
defineProps<RequestTaskDropdownProps>()

src/components/request-task/RequestTaskFileInput.vue

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,14 @@ const handleModal = () => {
6363
isModalVisible.value = !isModalVisible.value
6464
}
6565
66-
const truncateFilename = (name: string, maxLength: number) => {
67-
return [...name].slice(0, maxLength).join('')
68-
}
69-
7066
const handleFileUpload = (event: Event) => {
7167
const target = event.target as HTMLInputElement
7268
if (target.files && target.files.length > 0) {
7369
const newFiles = Array.from(target.files)
7470
.map(file => {
75-
const truncatedName = truncateFilename(file.name, 35)
76-
const newFile = new File([file], truncatedName, { type: file.type })
71+
const normalizedFileName = file.name.normalize('NFC')
72+
const newFileName = normalizedFileName.slice(0, 18)
73+
const newFile = new File([file], newFileName, { type: file.type })
7774
return newFile.size <= 5 * 1024 * 1024 ? newFile : null
7875
})
7976
.filter(file => file !== null) as File[]
@@ -98,8 +95,9 @@ const handleDrop = (event: DragEvent) => {
9895
if (files && files.length > 0) {
9996
const newFiles = Array.from(files)
10097
.map(file => {
101-
const truncatedName = truncateFilename(file.name, 35)
102-
const newFile = new File([file], truncatedName, { type: file.type })
98+
const normalizedFileName = file.name.normalize('NFC')
99+
const newFileName = normalizedFileName.slice(0, 18)
100+
const newFile = new File([file], newFileName, { type: file.type })
103101
return newFile.size <= 5 * 1024 * 1024 ? newFile : null
104102
})
105103
.filter(file => file !== null) as File[]

0 commit comments

Comments
 (0)