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 @@
@@ -48,6 +48,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ maxWidth: {
+ type: Number,
+ default: 400,
+ },
})
const emit = defineEmits(['update:showDialog'])
diff --git a/app/components/admin/contactus/composeMailModal.vue b/app/components/admin/contactus/composeMailModal.vue
index 1ae3d9b6d..5a06cb0a6 100644
--- a/app/components/admin/contactus/composeMailModal.vue
+++ b/app/components/admin/contactus/composeMailModal.vue
@@ -73,10 +73,10 @@
@@ -98,21 +98,19 @@
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
+}