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
6 changes: 5 additions & 1 deletion front/src/features/assistant/api.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import axios from '@/api/axios'

export const getBriefing = (date) => {
return axios.get(`/v1/assistants/briefings/${date}`)
return axios.get(`/v1/assistants/briefings/${encodeURIComponent(date)}`)
}

export const requestFeedback = (payload) => {
return axios.post(`/v1/assistants/feedbacks`, payload)
}

export const getFeedbacks = (date) => {
return axios.get(`/v1/assistants/feedbacks/daily/${encodeURIComponent(date)}`)
}
Comment thread
HanaHww2 marked this conversation as resolved.
5 changes: 5 additions & 0 deletions front/src/features/assistant/assistantService.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ export const requestFeedback = wrapApi(async (payload) => {
const res = await requestFeedbackApi(payload)
return res.data
})

export const getFeedbacks = wrapApi(async (date) => {
const res = await getFeedbacksApi(date)
return res.data
})
31 changes: 29 additions & 2 deletions front/src/features/assistant/component/Messages.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
<script setup>
import { computed } from 'vue'
import { computed, nextTick, ref, watch } from 'vue'
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
import { MOODS } from '../constants'
import { parseMdToHtmlAndSanitize } from '@/shared/utils/htmlContentUtils'

const { messages } = defineProps({
messages: { type: Array, default: [], required: true },
})

const sanitizedMessages = computed(() => {
return messages.map((msg) => ({
...msg,
txt: parseMdToHtmlAndSanitize(msg.txt),
}))
})

const lastUserMessageRef = ref(null)
const setLastUserRef = (el) => {
if (el) lastUserMessageRef.value = el
}

const scrollToLastUserDiv = async () => {
const el = lastUserMessageRef.value
if (!el) return

el.scrollIntoView({
behavior: 'smooth',
block: 'start',
})
}
watch(
() => messages,
async () => {
await nextTick()
await scrollToLastUserDiv()
},
{ flush: 'post', deep: true, immediate: true },
)
</script>

<template>
Expand All @@ -24,6 +46,11 @@ const sanitizedMessages = computed(() => {
<div
v-for="(msg, index) in sanitizedMessages"
:key="index"
:ref="
msg.from === 'user' && sanitizedMessages.length - 2 == index
? setLastUserRef
: null
"
:class="[
'flex',
{
Expand Down
4 changes: 4 additions & 0 deletions front/src/features/assistant/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ export const LIST_ERR_MESSAGES = {
noContent: '해당 일자에는 피드백 정보가 존재하지 않습니다.',
invalidRequest: '아직 해당 일자의 피드백을 요청할 수 없습니다.',
}

export const REQ_ERR_MESSAGES = {
EXCEEDED_DAILY_FEEDBACK_USAGE: '피드백의 당일 사용량을 모두 초과하였습니다.',
}
Comment thread
HanaHww2 marked this conversation as resolved.
8 changes: 6 additions & 2 deletions front/src/features/assistant/useFeedbackForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { reactive, ref, watch } from 'vue'
import { requestFeedback } from './assistantService'
import { MAX_LEN } from './constants'
import { MAX_LEN, REQ_ERR_MESSAGES } from './constants'

export const useFeedbackForm = () => {
const selectedMoodKey = ref('soso')
Expand Down Expand Up @@ -30,7 +30,11 @@ export const useFeedbackForm = () => {
'요청하신 피드백을 작성하고 있습니다. <br /> 피드백 작성이 완료되면 알림으로 알려드릴게요!'
resultModal.show = true
} catch (err) {
const msg = err.response?.data?.message || err.message || '저장 실패'
const msg =
err?.response?.data?.message ??
REQ_ERR_MESSAGES[err?.code] ??
(typeof err === 'string' ? err : err?.message) ??
'피드백 요청에 실패하였습니다.'
resultModal.msg = msg
resultModal.show = true
}
Expand Down
77 changes: 77 additions & 0 deletions front/src/features/assistant/useFeedbackList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { nextTick, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { format } from 'date-fns'
import { parseYMD } from '@/shared/utils/dateUtils'
import { getFeedbacks } from './assistantService'
import { useDatePickStore } from '@/stores/useDatePickStore'
import { formatDateTime } from '@/shared/utils/dateTimeUtils'
import { LIST_ERR_MESSAGES } from './constants'

export const useFeedbackList = (initDate = null) => {
const route = useRoute()
const router = useRouter()
const datePickStore = useDatePickStore()

const nomessages = ref(LIST_ERR_MESSAGES.noContent)
const messages = ref([])
const selectedDate = ref(
initDate ? parseYMD(initDate) : new Date(datePickStore.lastSelectedDate),
)

const fetchFeedbacksAsMessageForm = async (date) => {
messages.value = []

try {
const res = await getFeedbacks(format(date, 'yyyy-MM-dd'))
if (res.feedbacks.length < 1) {
nomessages.value = LIST_ERR_MESSAGES.noContent
return
}

messages.value = res.feedbacks.flatMap((r) => [
{
from: 'user',
time: formatDateTime(r.createdAt, { type: 'time12format' }),
txt: r.userInput.replace(/\n/g, '<br>'),
mood: r.userMood,
},
{
from: 'rouby',
time: formatDateTime(r.updatedAt, { type: 'time12format' }),
txt: r.feedbackContent.replace(/\n/g, '<br>'),
},
])
} catch (e) {
if (e.code === 'INVALID_REQUEST') {
nomessages.value = LIST_ERR_MESSAGES.invalidRequest
} else {
nomessages.value =
typeof e === 'string'
? e
: e.message || '피드백을 불러오는 중 오류가 발생했습니다.'
}
}
}

onMounted(() => {
const routeDate = route.params.date
if (routeDate) {
selectedDate.value = parseYMD(routeDate)

router.replace({ name: 'daily-feedback-list' })
}
})

watch(selectedDate, (newDate) => {
if (newDate) {
fetchFeedbacksAsMessageForm(newDate)
}
})

return {
selectedDate,
messages,
nomessages,
fetchFeedbacksAsMessageForm,
}
}
25 changes: 25 additions & 0 deletions front/src/features/assistant/views/DailyFeedbackView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup>
import WeeklyMonthlyDatePicker from '@/components/common/date-picker/WeeklyMonthlyDatePicker.vue'
import Messages from '../component/Messages.vue'
import { useFeedbackList } from '../useFeedbackList'

const { selectedDate, messages, nomessages } = useFeedbackList()
Comment thread
HanaHww2 marked this conversation as resolved.
</script>

<template>
<div class="main-container">
<div class="sub-main-container !pt-0 !px-0">
<WeeklyMonthlyDatePicker v-model="selectedDate" />
<div class="px-8">
<Messages
v-if="messages.length > 0"
:messages="messages"
class="mt-7"
/>
<p v-else class="text-content-color text-center mt-6">
{{ nomessages }}
</p>
</div>
</div>
</div>
</template>
42 changes: 23 additions & 19 deletions front/src/shared/utils/errorUtils.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
function extractErrorData(err) {
return err.response?.data || {};
return err.response?.data || {}
}

export function createApiError(err, {
targetField,
fieldMessages,
fallbackMessage = '오류가 발생했습니다.'
} = {}) {
const { errors: list = [], message: dataMsg } = extractErrorData(err);
export function createApiError(
err,
{ targetField, fieldMessages, fallbackMessage = '오류가 발생했습니다.' } = {},
) {
const { errors: list = [], message: dataMsg, code } = extractErrorData(err)

if (list.length > 0) {
if (fieldMessages) {
const fieldErrors = {};
const fieldErrors = {}
list.forEach(({ value, message }) => {
fieldErrors[value] = fieldMessages?.[value] ?? message;
});
const e = new Error(dataMsg || fallbackMessage);
e.fieldErrors = fieldErrors;
return e;
fieldErrors[value] = fieldMessages?.[value] ?? message
})
const e = new Error(dataMsg || fallbackMessage)
e.fieldErrors = fieldErrors
e.code = code
return e
}

if (targetField) {
const fe = list.find(e => e.value === targetField);
const msg = fe?.message || dataMsg || fallbackMessage;
return new Error(msg);
const fe = list.find((e) => e.value === targetField)
const msg = fe?.message || dataMsg || fallbackMessage
const e = new Error(msg)
e.code = code
return e
}
}

return new Error(dataMsg || err.message || fallbackMessage);
const e = new Error(dataMsg || err.message || fallbackMessage)
e.code = code
return e
}

export function wrapApi(apiFn, options) {
return async (...args) => {
try {
return await apiFn(...args);
return await apiFn(...args)
} catch (err) {
throw createApiError(err, options);
throw createApiError(err, options)
}
}
}