diff --git a/app/components/admin/common/Modal.vue b/app/components/admin/common/Modal.vue index 7472896c8..4cf302ece 100644 --- a/app/components/admin/common/Modal.vue +++ b/app/components/admin/common/Modal.vue @@ -1,7 +1,7 @@ diff --git a/app/components/admin/contactus/viewMessageDetailsModal.vue b/app/components/admin/contactus/viewMessageDetailsModal.vue index e652becca..4fe6789e2 100644 --- a/app/components/admin/contactus/viewMessageDetailsModal.vue +++ b/app/components/admin/contactus/viewMessageDetailsModal.vue @@ -49,7 +49,7 @@ />
@@ -123,7 +123,7 @@ import type { - ApiResult, - AppError, AdminContactUsDetailDTO, - AdminReplyTicketListDTO, } from '@/types' interface IViewMessageDetailsModal { @@ -199,19 +196,26 @@ const emit = defineEmits(['replySuccessFull']) const { $toast } = useNuxtApp() const { formatLocal } = useDateTime() const { required } = useValidationRules() +const { + getItemById, + loadingGetItemById: loadingData, + getEmailAddresses, + loadingGetEmailAddresses: loadingEmailList, + emailAddresses: fromEmailList, + replyTicket, + loadingReplyTicket: loadingReply, + getReplyList: fetchReplyList, + loadingGetReplyList: loadingReplyList, + replyList, + generateAiResponse, +} = useContactUsAdmin() const contactData = ref() -const loadingReply = ref(false) -const loadingData = ref(true) const bodyReply = ref('') -const selectedFromEmail = ref() -const fromEmailList = ref([]) -const loadingEmailList = ref(false) +const selectedFromEmail = ref('') const loadingAiReply = ref(false) const loadingPolishReply = ref(false) -const loadingReplyList = ref(true) -const replyList = ref([]) const replyValid = computed(() => { if (bodyReply.value.length == 0 || selectedFromEmail.value == null || selectedFromEmail.value.length == 0) { @@ -221,28 +225,13 @@ const replyValid = computed(() => { }) const getDetail = async () => { - try { - loadingData.value = true - const response = await useApiService.get< - ApiResult - >( - `/api/v2/admin/tickets/${props.id}`, - ) - if (response.succeeded && response.data) { - contactData.value = response.data - } - else { - $toast.error('The operation failed. Please try again later.') - } - } - catch (err: unknown) { - const error = err as AppError - if (error.response?.status === 400) { - $toast.error(error.response.data?.message || '') - } - } - finally { - loadingData.value = false + if (props.id == null) + return + + const response = await getItemById(props.id) + + if (response.succeeded && response.data) { + contactData.value = response.data } } @@ -316,30 +305,14 @@ const generateAiReply = async () => { try { loadingAiReply.value = true - const response = await useApiService.post<{ - response?: string - }>( - '/api/chatgpt', - { - userComment: buildAiReplyPrompt(), - }, - ) + const response = await generateAiResponse({ + userComment: buildAiReplyPrompt(), + }) if (response.response) { bodyReply.value = response.response $toast.success('AI reply generated successfully!') } - else { - $toast.error('The operation failed. Please try again later.') - } - } - catch (err: unknown) { - const error = err as AppError - $toast.error( - error.response?.data?.message - || error.message - || 'The operation failed. Please try again later.', - ) } finally { loadingAiReply.value = false @@ -375,30 +348,14 @@ const polishReplyWithAi = async () => { try { loadingPolishReply.value = true - const response = await useApiService.post<{ - response?: string - }>( - '/api/chatgpt', - { - userComment: buildPolishReplyPrompt(), - }, - ) + const response = await generateAiResponse({ + userComment: buildPolishReplyPrompt(), + }) if (response.response) { bodyReply.value = response.response $toast.success('Reply polished successfully!') } - else { - $toast.error('The operation failed. Please try again later.') - } - } - catch (err: unknown) { - const error = err as AppError - $toast.error( - error.response?.data?.message - || error.message - || 'The operation failed. Please try again later.', - ) } finally { loadingPolishReply.value = false @@ -407,90 +364,25 @@ const polishReplyWithAi = async () => { const reply = async () => { if (contactData.value) { - try { - loadingReply.value = true - const formData = new FormData() - formData.append('From', selectedFromEmail.value) - formData.append('Body', removeScriptTags(bodyReply.value)) - const response = await useApiService.post< - ApiResult - >( - `/api/v2/admin/tickets/${contactData.value.id}/replys`, - formData, - ) - if (response.succeeded) { - $toast.success('Reply Message Send Successfully!') - emit('replySuccessFull') - } - else { - $toast.error('The operation failed. Please try again later.') - } - } - catch (err: unknown) { - const error = err as AppError - if (error.response?.status === 400) { - $toast.error(error.response.data?.message || '') - } - } - finally { - loadingReply.value = false - } - } - else { - $toast.error('User data not founded!') - } -} + const response = await replyTicket(contactData.value.id, { + from: selectedFromEmail.value, + body: removeScriptTags(bodyReply.value), + }) -const getEmailAddresses = async () => { - try { - loadingEmailList.value = true - const response = await useApiService.get< - ApiResult - >( - '/api/v2/admin/emails/addresses', - ) if (response.succeeded) { - fromEmailList.value = response.data ?? [] - } - else { - $toast.error('The operation get data failed. Please try again later.') - } - } - catch (err: unknown) { - const error = err as AppError - if (error.response?.status === 400) { - $toast.error(error.response.data?.message || '') + emit('replySuccessFull') } } - finally { - loadingEmailList.value = false + else { + $toast.error('User data not founded!') } } const getReplyList = async () => { - try { - loadingReplyList.value = true - const response = await useApiService.get< - ApiResult - >( - `/api/v2/admin/tickets/${props.id}/replys`, - ) - if (response.succeeded) { - replyList.value = response.data ?? [] - } - else { - $toast.error('The operation get data failed. Please try again later.') - } - } - catch (err: unknown) { - const error = err as AppError - if (error.response?.status === 400) { - $toast.error(error.response.data?.message || '') - } - } - finally { - loadingReplyList.value = false - } + if (props.id == null) + return + + await fetchReplyList(props.id) } onMounted(async () => { @@ -528,4 +420,10 @@ onMounted(async () => { right: 10px; bottom: 60px; } +:deep(.container-body img){ + width : 100% !important; + height : auto !important; + max-width : 600px !important; + max-height : 600px !important; +} diff --git a/app/composables/api/contactus/useContactUsAdmin.api.ts b/app/composables/api/contactus/useContactUsAdmin.api.ts new file mode 100644 index 000000000..597c15125 --- /dev/null +++ b/app/composables/api/contactus/useContactUsAdmin.api.ts @@ -0,0 +1,371 @@ +import type { + AdminContactUsAiPromptDTO, + AdminContactUsAiResponseDTO, + AdminContactUsComposeMailDTO, + AdminContactUsCreateTicketDTO, + AdminContactUsDTO, + AdminContactUsDetailDTO, + AdminContactUsReplyDTO, + AdminReplyTicketListDTO, + ApiResult, + AppError, + GetAdminContactUsParams, + ResponseListDTO, +} from '@/types' + +export const useContactUsAdmin = () => { + const { $toast } = useNuxtApp() + + const data = ref([]) + const totalCount = ref(0) + const pageCount = ref(0) + const emailAddresses = ref([]) + const replyList = ref([]) + + const loadingGetData = ref(true) + const loadingDeleteItem = ref(false) + const loadingSendEmail = ref(false) + const loadingCreateTicket = ref(false) + const loadingGetEmailAddresses = ref(false) + const loadingGetItemById = ref(false) + const loadingReplyTicket = ref(false) + const loadingGetReplyList = ref(false) + const loadingGenerateAiResponse = ref(false) + + const handleError = (err: unknown, fallbackMessage = '') => { + const error = err as AppError + if (error.response?.status === 400) { + $toast.error(error.response.data?.message || fallbackMessage) + } + else if (fallbackMessage) { + $toast.error(fallbackMessage) + } + } + + const getData = async (params: GetAdminContactUsParams) => { + loadingGetData.value = true + + try { + const query: Record = { + 'PagingDto.PageFilter.Size': params.pageSize, + 'PagingDto.PageFilter.Skip': (params.page - 1) * params.pageSize, + 'PagingDto.PageFilter.ReturnTotalRecordsCount': true, + } + + if (params.status !== 'All') { + query['PagingDto.SearchFilter.phrase'] = params.status === 'Read' + query['PagingDto.SearchFilter.column'] = 'isReadByAdmin' + } + + const response = await useApiService.get< + ApiResult> + >('/api/v2/admin/tickets', query) + + if (response.data) { + data.value = response.data.list + totalCount.value = response.data.totalRecordsCount + pageCount.value = Math.ceil(totalCount.value / params.pageSize) + } + else { + data.value = [] + totalCount.value = 0 + pageCount.value = 0 + } + } + catch (err: unknown) { + handleError(err) + } + finally { + loadingGetData.value = false + } + } + + const deleteItem = async (id: string | number) => { + loadingDeleteItem.value = true + + try { + const response = await useApiService.remove>( + `/api/v2/admin/tickets/${id}`, + ) + + if (response.succeeded) { + $toast.success('Message deleted successfully!') + } + else { + $toast.error('The operation failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: false, + } + } + finally { + loadingDeleteItem.value = false + } + } + + const sendEmail = async (payload: AdminContactUsComposeMailDTO) => { + loadingSendEmail.value = true + + try { + const response = await useApiService.post>( + '/api/v2/admin/emails', + payload as unknown as Record, + ) + + if (response.succeeded) { + $toast.success('Message Send Successfully!') + } + else { + $toast.error('The operation failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: null, + } + } + finally { + loadingSendEmail.value = false + } + } + + const createTicket = async (payload: AdminContactUsCreateTicketDTO) => { + loadingCreateTicket.value = true + + try { + const formData = new FormData() + + formData.append('From', payload.from) + formData.append('ReceiverName', payload.receiverName) + formData.append('ReceiverEmail', payload.receiverEmail) + formData.append('Subject', payload.subject) + formData.append('Body', payload.body) + + const response = await useApiService.post>( + '/api/v2/admin/tickets', + formData, + ) + + if (response.succeeded) { + $toast.success('Ticket Create Successfully!') + } + else { + $toast.error('The operation failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: null, + } + } + finally { + loadingCreateTicket.value = false + } + } + + const getEmailAddresses = async () => { + loadingGetEmailAddresses.value = true + + try { + const response = await useApiService.get>( + '/api/v2/admin/emails/addresses', + ) + + if (response.succeeded) { + emailAddresses.value = response.data ?? [] + } + else { + $toast.error('The operation get data failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: [], + } + } + finally { + loadingGetEmailAddresses.value = false + } + } + + const getItemById = async (id: string | number) => { + loadingGetItemById.value = true + + try { + const response = await useApiService.get>( + `/api/v2/admin/tickets/${id}`, + ) + + if (!response.succeeded || !response.data) { + $toast.error('The operation failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: null, + } + } + finally { + loadingGetItemById.value = false + } + } + + const replyTicket = async (id: string | number, payload: AdminContactUsReplyDTO) => { + loadingReplyTicket.value = true + + try { + const formData = new FormData() + + formData.append('From', payload.from) + formData.append('Body', payload.body) + + const response = await useApiService.post>( + `/api/v2/admin/tickets/${id}/replys`, + formData, + ) + + if (response.succeeded) { + $toast.success('Reply Message Send Successfully!') + } + else { + $toast.error('The operation failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: null, + } + } + finally { + loadingReplyTicket.value = false + } + } + + const getReplyList = async (id: string | number) => { + loadingGetReplyList.value = true + + try { + const response = await useApiService.get>( + `/api/v2/admin/tickets/${id}/replys`, + ) + + if (response.succeeded) { + replyList.value = response.data ?? [] + } + else { + $toast.error('The operation get data failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + handleError(err) + + return { + succeeded: false, + status: 0, + data: [], + } + } + finally { + loadingGetReplyList.value = false + } + } + + const generateAiResponse = async (payload: AdminContactUsAiPromptDTO) => { + loadingGenerateAiResponse.value = true + + try { + const response = await useApiService.post( + '/api/chatgpt', + payload as unknown as Record, + ) + + if (!response.response) { + $toast.error('The operation failed. Please try again later.') + } + + return response + } + catch (err: unknown) { + const error = err as AppError + + $toast.error( + error.response?.data?.message + || error.message + || 'The operation failed. Please try again later.', + ) + + return { + response: '', + } + } + finally { + loadingGenerateAiResponse.value = false + } + } + + return { + loadingGetData, + data, + getData, + totalCount, + pageCount, + deleteItem, + loadingDeleteItem, + sendEmail, + loadingSendEmail, + createTicket, + loadingCreateTicket, + getEmailAddresses, + loadingGetEmailAddresses, + emailAddresses, + getItemById, + loadingGetItemById, + replyTicket, + loadingReplyTicket, + getReplyList, + loadingGetReplyList, + replyList, + generateAiResponse, + loadingGenerateAiResponse, + } +} diff --git a/app/pages/admin/contact-us/index.vue b/app/pages/admin/contact-us/index.vue index 863baafc2..e577b9716 100644 --- a/app/pages/admin/contact-us/index.vue +++ b/app/pages/admin/contact-us/index.vue @@ -240,6 +240,7 @@ @@ -257,6 +259,7 @@ @@ -264,20 +267,13 @@ diff --git a/app/types/contactus/index.ts b/app/types/contactus/index.ts index 3bee83e65..18a175083 100644 --- a/app/types/contactus/index.ts +++ b/app/types/contactus/index.ts @@ -25,3 +25,38 @@ export interface AdminReplyTicketListDTO { fileUri: string receivers: string[] } + +export interface GetAdminContactUsParams { + page: number + pageSize: number + status: string +} + +export interface AdminContactUsComposeMailDTO { + from: string + body: string + subject: string + users: string[] + emailAddresses: string[] +} + +export interface AdminContactUsCreateTicketDTO { + from: string + receiverName: string + receiverEmail: string + subject: string + body: string +} + +export interface AdminContactUsReplyDTO { + from: string + body: string +} + +export interface AdminContactUsAiPromptDTO { + userComment: string +} + +export interface AdminContactUsAiResponseDTO { + response?: string +}