c88709a32b
Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local> Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Reviewed-on: KoPKB/My-KOPKB#11
171 lines
4.8 KiB
TypeScript
171 lines
4.8 KiB
TypeScript
import { api } from '@/core/services/api'
|
|
import type { PaginatedApiResponse } from '@/core/types/api'
|
|
import type {
|
|
Feedback,
|
|
FeedbackApiResponse,
|
|
FeedbackListItem,
|
|
FeedbackStatistics,
|
|
ListFeedbackParams,
|
|
SubmitFeedbackPayload,
|
|
UpdateFeedbackPayload,
|
|
} from '../types/feedback.types'
|
|
|
|
function appendIfPresent(formData: FormData, key: string, value: string | undefined | null) {
|
|
if (value !== null && value !== undefined && value !== '') {
|
|
formData.append(key, value)
|
|
}
|
|
}
|
|
|
|
export function buildFeedbackFormData(payload: SubmitFeedbackPayload): FormData {
|
|
const formData = new FormData()
|
|
|
|
formData.append('type', payload.type)
|
|
formData.append('title', payload.title)
|
|
formData.append('description', payload.description)
|
|
formData.append('priority', payload.priority)
|
|
|
|
appendIfPresent(formData, 'page_url', payload.page_url)
|
|
appendIfPresent(formData, 'steps_to_reproduce', payload.steps_to_reproduce)
|
|
appendIfPresent(formData, 'expected_behavior', payload.expected_behavior)
|
|
appendIfPresent(formData, 'actual_behavior', payload.actual_behavior)
|
|
appendIfPresent(formData, 'additional_notes', payload.additional_notes)
|
|
appendIfPresent(formData, 'screen_resolution', payload.screen_resolution)
|
|
appendIfPresent(formData, 'viewport_size', payload.viewport_size)
|
|
appendIfPresent(formData, 'timezone', payload.timezone)
|
|
|
|
payload.images?.forEach((file, index) => {
|
|
formData.append(`images[${index}]`, file)
|
|
})
|
|
|
|
payload.videos?.forEach((file, index) => {
|
|
formData.append(`videos[${index}]`, file)
|
|
})
|
|
|
|
return formData
|
|
}
|
|
|
|
/** Public + authenticated: POST /v1/feedback (auth cookie attaches user when present). */
|
|
export async function submitFeedback(payload: SubmitFeedbackPayload): Promise<FeedbackApiResponse> {
|
|
const formData = buildFeedbackFormData(payload)
|
|
|
|
const { data } = await api.post<FeedbackApiResponse>('/v1/feedback', formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
},
|
|
})
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal menghantar maklum balas.')
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
export async function listFeedback(
|
|
params: ListFeedbackParams = {},
|
|
): Promise<PaginatedApiResponse<FeedbackListItem>> {
|
|
const { data } = await api.get<PaginatedApiResponse<FeedbackListItem>>('/v1/feedback', {
|
|
params,
|
|
})
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal memuatkan senarai maklum balas.')
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
export async function getFeedback(id: string): Promise<Feedback> {
|
|
const { data } = await api.get<FeedbackApiResponse>(`/v1/feedback/${id}`)
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal memuatkan maklum balas.')
|
|
}
|
|
|
|
return data.data
|
|
}
|
|
|
|
export async function updateFeedback(
|
|
id: string,
|
|
payload: UpdateFeedbackPayload,
|
|
): Promise<FeedbackApiResponse> {
|
|
const { data } = await api.patch<FeedbackApiResponse>(`/v1/feedback/${id}`, payload)
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal mengemas kini maklum balas.')
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
export async function deleteFeedback(id: string): Promise<void> {
|
|
const { data } = await api.delete<{ success: boolean; message?: string }>(`/v1/feedback/${id}`)
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal memadam maklum balas.')
|
|
}
|
|
}
|
|
|
|
export async function getMyFeedback(
|
|
params: ListFeedbackParams = {},
|
|
): Promise<PaginatedApiResponse<FeedbackListItem>> {
|
|
const { data } = await api.get<{
|
|
success: boolean
|
|
data: FeedbackListItem[]
|
|
meta: {
|
|
current_page: number
|
|
last_page: number
|
|
per_page: number
|
|
total: number
|
|
}
|
|
message?: string
|
|
}>('/v1/my-feedback', { params })
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal memuatkan maklum balas anda.')
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: data.data,
|
|
pagination: {
|
|
current_page: data.meta.current_page,
|
|
per_page: data.meta.per_page,
|
|
total: data.meta.total,
|
|
last_page: data.meta.last_page,
|
|
from: null,
|
|
to: null,
|
|
has_more_pages: data.meta.current_page < data.meta.last_page,
|
|
},
|
|
}
|
|
}
|
|
|
|
export async function getFeedbackStatistics(): Promise<FeedbackStatistics> {
|
|
const { data } = await api.get<{ success: boolean; data: FeedbackStatistics; message?: string }>(
|
|
'/v1/feedback-statistics',
|
|
)
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.message ?? 'Gagal memuatkan statistik maklum balas.')
|
|
}
|
|
|
|
return data.data
|
|
}
|
|
|
|
export async function fetchFeedbackDocument(
|
|
feedbackId: string,
|
|
documentId: string,
|
|
mimeType?: string | null,
|
|
): Promise<Blob> {
|
|
const response = await api.get(`/v1/feedback/${feedbackId}/documents/${documentId}/download`, {
|
|
responseType: 'blob',
|
|
})
|
|
|
|
const contentType =
|
|
mimeType ||
|
|
(typeof response.headers['content-type'] === 'string' ? response.headers['content-type'] : null) ||
|
|
'application/octet-stream'
|
|
|
|
return new Blob([response.data], { type: contentType })
|
|
}
|