Skip to content

Commit 5243669

Browse files
authored
Merge pull request #101 from TaskFlow-CLAP/CLAP-281
Clap-281 히스토리 생성 API 연결
2 parents 615ff90 + c0c3ea3 commit 5243669

20 files changed

+472
-71
lines changed

src/api/user.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,23 @@ export const getHistory = async (taskID: number) => {
5757
const response = await axiosInstance.get(`/api/tasks/${taskID}/histories`)
5858
return response.data
5959
}
60+
61+
export const postComment = async (taskID: number, content: string) => {
62+
const response = await axiosInstance.post(`/api/comment/${taskID}`, { content })
63+
return response.data
64+
}
65+
66+
export const postCommentAttachment = async (taskID: number, formdata: FormData) => {
67+
const response = await formDataAxiosInstance.post(`/api/comment/attachment/${taskID}`, formdata)
68+
return response.data
69+
}
70+
71+
export const patchComment = async (commentId: number, content: string) => {
72+
const response = await axiosInstance.patch(`/api/comment/${commentId}`, { content })
73+
return response.data
74+
}
75+
76+
export const deleteComment = async (commentId: number) => {
77+
const response = await axiosInstance.delete(`/api/comment/${commentId}`)
78+
return response.data
79+
}

src/assets/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
body {
1313
font-family: 'SUIT-Variable', sans-serif;
14+
color: #18181B;
1415
}
1516

1617
.shadow-custom {

src/components/my-request/MyRequestList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ import { useRequestParamsStore } from '@/stores/params'
2626
import type { MyRequestResponse } from '@/types/user'
2727
import { axiosInstance } from '@/utils/axios'
2828
import { useQuery } from '@tanstack/vue-query'
29+
import { computed } from 'vue'
2930
import { useParseParams } from '../hooks/useParseParams'
3031
import ListContainer from '../lists/ListContainer.vue'
3132
import ListPagination from '../lists/ListPagination.vue'
3233
import NoContent from '../lists/NoContent.vue'
3334
import MyRequestListBar from './MyRequestListBar.vue'
3435
import MyRequestListCard from './MyRequestListCard.vue'
35-
import { computed } from 'vue'
3636
3737
const { params } = useRequestParamsStore()
3838
const onPageChange = (value: number) => {

src/components/request-task/RequestTask.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ const handleSubmit = async () => {
9696
const formData = new FormData()
9797
const taskInfo = {
9898
categoryId: category2.value.id,
99-
10099
title: title.value,
101100
description: description.value
102101
}

src/components/requested/RequestedListCard.vue

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<div class="list-card">
33
<ListCardTab
44
v-for="tab in requestedTabList"
5+
@click="handleModal(info.taskId)"
56
:key="tab.content"
67
:content="tab.content"
78
:width="tab.width"
@@ -19,13 +20,13 @@
1920
class="button-medium-default">
2021
거부
2122
</button>
22-
<button
23-
@click="toggleModal('reject')"
24-
class="button-medium-default">
25-
거부
26-
</button>
2723
</div>
2824
</div>
25+
<TaskDetail
26+
v-if="selectedID"
27+
:is-approved="true"
28+
:selected-id="selectedID"
29+
:close-task-detail="() => handleModal(null)" />
2930

3031
<ModalView
3132
:is-open="isModalVisible.reject"
@@ -59,6 +60,7 @@ import { ref } from 'vue'
5960
import { useRouter } from 'vue-router'
6061
import ListCardTab from '../lists/ListCardTab.vue'
6162
import ModalView from '../ModalView.vue'
63+
import TaskDetail from '../task-detail/TaskDetail.vue'
6264
6365
const { info } = defineProps<{ info: RequestedListData }>()
6466
const requestedTabList: ListCardProps[] = [
@@ -69,6 +71,12 @@ const requestedTabList: ListCardProps[] = [
6971
{ content: info.requesterName, width: 120, profileImg: info.requesterImg }
7072
]
7173
74+
const selectedID = ref<number | null>(null)
75+
76+
const handleModal = (id: number | null) => {
77+
selectedID.value = id
78+
}
79+
7280
const router = useRouter()
7381
const queryClient = useQueryClient()
7482

src/components/task-detail/TaskDetail.vue

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@
33
<div
44
class="flex flex-col overflow-y-auto rounded-lg w-full max-w-[1200px] min-w-[1024px] bg-white p-6">
55
<TaskDetailTopBar
6-
:is-approved="isApproved"
6+
:is-approved="data?.taskStatus !== 'REQUESTED'"
77
:close-task-detail="closeTaskDetail"
88
:id="data?.taskId || 0"
9-
:isProcessor="data?.processorNickName === info.nickname" />
9+
:isProcessor="data?.processorNickName === info.nickname || info.memberRole === 'ROLE_'"
10+
:isRequestor="data?.requesterNickName === info.nickname" />
1011
<div
1112
class="w-full flex gap-6"
1213
v-if="data">
1314
<div class="w-full h-[718px] flex flex-col gap-y-8 overflow-y-auto scrollbar-hide">
1415
<TaskDetailLeft :data="data" />
15-
<div class="w-full border-[0.5px] border-border-1"></div>
16+
<div class="w-full h-[1px] bg-border-1 shrink-0"></div>
1617
<TaskDetailHistory
1718
:historyData="historyData?.histories || []"
18-
:is-approved="isApproved" />
19+
:task-id="selectedId"
20+
:requestor-name="data.requesterNickName" />
1921
</div>
2022
<div class="w-[1px] bg-border-1"></div>
2123
<TaskDetailRight
@@ -27,27 +29,31 @@
2729
</template>
2830

2931
<script setup lang="ts">
30-
import { getHistory, getTaskDetailManager } from '@/api/user'
32+
import { getHistory, getTaskDetailManager, getTaskDetailUser } from '@/api/user'
3133
import { useMemberStore } from '@/stores/member'
32-
import type { TaskDetailDatas, TaskDetailHistoryProps, TaskDetailProps } from '@/types/user'
34+
import type { TaskDetailDatas, TaskDetailHistoryData, TaskDetailProps } from '@/types/user'
3335
import { useQuery } from '@tanstack/vue-query'
3436
import { storeToRefs } from 'pinia'
3537
import TaskDetailHistory from './TaskDetailHistory.vue'
3638
import TaskDetailLeft from './TaskDetailLeft.vue'
3739
import TaskDetailRight from './TaskDetailRight.vue'
3840
import TaskDetailTopBar from './TaskDetailTopBar.vue'
3941
40-
const { isApproved, closeTaskDetail, selectedId } = defineProps<TaskDetailProps>()
42+
const { closeTaskDetail, selectedId } = defineProps<TaskDetailProps>()
4143
4244
const memberStore = useMemberStore()
4345
const { info } = storeToRefs(memberStore)
46+
console.log(info, '인포')
4447
4548
const { data } = useQuery<TaskDetailDatas>({
4649
queryKey: ['taskDetailUser', selectedId],
47-
queryFn: () => getTaskDetailManager(selectedId)
50+
queryFn:
51+
info.value.memberRole === 'ROLE_USER'
52+
? () => getTaskDetailUser(selectedId)
53+
: () => getTaskDetailManager(selectedId)
4854
})
4955
50-
const { data: historyData } = useQuery<TaskDetailHistoryProps>({
56+
const { data: historyData } = useQuery<TaskDetailHistoryData>({
5157
queryKey: ['historyData', selectedId],
5258
queryFn: () => getHistory(selectedId)
5359
})

src/components/task-detail/TaskDetailHistory.vue

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
<template>
22
<div>
33
<p class="task-detail">히스토리</p>
4-
<TaskDetailHistoryInput :history="historyData" />
4+
<TaskDetailHistoryInput
5+
:history="historyData"
6+
:taskId="taskId"
7+
:requestor-name="requestorName" />
58
<div class="flex flex-col w-full items-center gap-6 mt-8">
69
<div
710
class="flex w-full flex-col items-center gap-6"
8-
v-for="item in historyData"
11+
v-for="(item, i) in historyData"
912
:key="item.historyId">
1013
<div
14+
v-if="shouldDisplayDate(i)"
1115
class="flex px-4 h-7 items-center justify-center bg-primary1 rounded-full text-white text-xs font-bold">
1216
{{ formatDateWithDay(item.date) }}
1317
</div>
@@ -26,7 +30,16 @@
2630
class="text-primary1">
2731
{{ item.details.taskDetails?.value }}
2832
</p>
29-
<TaskDetailHistoryChat v-else-if="item.taskHistoryType === 'COMMENT'" />
33+
<TaskDetailHistoryChat
34+
v-else-if="item.taskHistoryType === 'COMMENT'"
35+
:history="item"
36+
:requestor-name="requestorName"
37+
:task-id="taskId" />
38+
<TaskDetailHistoryFile
39+
v-else-if="item.taskHistoryType === 'COMMENT_FILE'"
40+
:history="item"
41+
:requestor-name="requestorName"
42+
:task-id="taskId" />
3043
<p>{{ HistoryMessageAfter[item.taskHistoryType] }}</p>
3144
</div>
3245
</div>
@@ -36,11 +49,28 @@
3649

3750
<script setup lang="ts">
3851
import { HistoryMessageAfter, HistoryMessageBefore } from '@/constants/user'
39-
import type { TaskHistory } from '@/types/user'
52+
import type { TaskDetailHistoryProps } from '@/types/common'
4053
import { formatDateWithDay } from '@/utils/date'
54+
import { watch } from 'vue'
4155
import TaskDetailHistoryChat from './TaskDetailHistoryChat.vue'
56+
import TaskDetailHistoryFile from './TaskDetailHistoryFile.vue'
4257
import TaskDetailHistoryInput from './TaskDetailHistoryInput.vue'
4358
44-
const { historyData } = defineProps<{ historyData: TaskHistory[] }>()
45-
console.log(historyData, '가져온 히스토리')
59+
const { historyData, taskId, requestorName } = defineProps<TaskDetailHistoryProps>()
60+
61+
let displayedDates = new Set<string>()
62+
63+
const shouldDisplayDate = (index: number) => {
64+
const date = formatDateWithDay(historyData[index].date)
65+
if (displayedDates.has(date)) return false
66+
else displayedDates.add(date)
67+
return true
68+
}
69+
70+
watch(
71+
() => historyData,
72+
() => {
73+
displayedDates = new Set<string>()
74+
}
75+
)
4676
</script>
Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,79 @@
11
<template>
2-
<div class="flex w-full justify-start">
3-
<div class="w-10 h-10 rounded-full pt-1.5">
2+
<div :class="['flex w-full', isProcessor ? 'justify-end' : 'justify-start']">
3+
<div :class="['w-10 h-10 rounded-full pt-1.5', isProcessor ? 'order-3' : 'order-1']">
44
<img
5-
src="/images/mockProfile.jpg"
5+
:src="history.details.commentDetails?.profileImageUrl || '/images/mockProfile.jpg'"
66
class="rounded-full" />
77
</div>
8-
<div class="flex max-w-[400px] flex-wrap px-6 py-4 bg-background-2 ml-4 text-black rounded-lg">
9-
네네... 부탁드릴게요 가능하시다면 오늘 오후 6시까지 되었으면 좋겠습니다! 감사합니다.
8+
<div
9+
:class="[
10+
'flex flex-col gap-2 order-2',
11+
isProcessor ? 'items-end pr-4 pl-1' : 'items-start pl-4 pr-1'
12+
]">
13+
<p>{{ history.details.commentDetails?.nickName }}</p>
14+
<p
15+
:class="[
16+
'flex max-w-[400px] flex-wrap px-6 py-4 text-black rounded-lg',
17+
isProcessor ? 'bg-primary2' : 'bg-background-2'
18+
]">
19+
{{ history.details.commentDetails?.comment }}
20+
</p>
21+
</div>
22+
<div
23+
:class="[
24+
'flex flex-col h-full justify-end text-xs font-bold text-body gap-1 relative',
25+
isProcessor ? 'order-1 items-end' : 'order-3 items-start'
26+
]">
27+
<div
28+
v-if="history.details.commentDetails?.nickName === info.nickname"
29+
class="relative cursor-pointer">
30+
<CommonIcons
31+
:name="menuDotIcon"
32+
@click="clickMenuDot" />
33+
<div
34+
v-if="isClicked"
35+
@click="deleteCommentText"
36+
:class="[
37+
'absolute shadow-custom bottom-0 w-20 h-7 flex items-center justify-center text-xs text-red-1 bg-white hover:bg-background-1',
38+
isProcessor ? 'right-6' : 'left-6'
39+
]">
40+
삭제
41+
</div>
42+
</div>
43+
<div class="flex flex-col gap-1">
44+
{{ history.details.commentDetails?.isModified ? '(수정됨)' : '' }}
45+
{{ formatTodayOrNot(history.date, history.time) }}
46+
</div>
1047
</div>
11-
<div class="flex h-full items-end text-xs font-bold text-body ml-2">오후 2:18</div>
1248
</div>
1349
</template>
1450

15-
<script setup lang="ts"></script>
51+
<script setup lang="ts">
52+
import { deleteComment } from '@/api/user'
53+
import { menuDotIcon } from '@/constants/iconPath'
54+
import { useMemberStore } from '@/stores/member'
55+
import type { TaskDetailHistoryChatProps } from '@/types/common'
56+
import { formatTodayOrNot } from '@/utils/date'
57+
import { useQueryClient } from '@tanstack/vue-query'
58+
import { storeToRefs } from 'pinia'
59+
import { computed, defineProps, ref } from 'vue'
60+
import CommonIcons from '../common/CommonIcons.vue'
61+
62+
const memberStore = useMemberStore()
63+
const { info } = storeToRefs(memberStore)
64+
const isClicked = ref(false)
65+
const isProcessor = computed(() => history.details.commentDetails?.nickName !== requestorName)
66+
const queryClient = useQueryClient()
67+
68+
const { taskId, history, requestorName } = defineProps<TaskDetailHistoryChatProps>()
69+
70+
const clickMenuDot = async () => {
71+
isClicked.value = !isClicked.value
72+
}
73+
74+
const deleteCommentText = async () => {
75+
isClicked.value = !isClicked.value
76+
await deleteComment(history.historyId)
77+
queryClient.invalidateQueries({ queryKey: ['historyData', taskId] })
78+
}
79+
</script>

0 commit comments

Comments
 (0)