| 1 |
// src/api/admin.js |
| 2 |
// File này chứa các hàm API dùng cho quản trị viên (admin) để quản lý các bài đăng (listing) |
| 3 |
|
| 4 |
import axios from './axios.js'; // Import instance axios đã được cấu hình sẵn (baseURL, interceptor, v.v.) |
| 5 |
|
| 6 |
/** |
| 7 |
* Lấy danh sách các bài đăng đang chờ duyệt |
| 8 |
* params: gồm page, size, sortBy, sortDirection |
| 9 |
*/ |
| 10 |
export const getPendingListings = async (params = {}) => { |
| 11 |
// Giải nén giá trị mặc định cho các tham số nếu người dùng không truyền vào |
| 12 |
const { |
| 13 |
page = 0, |
| 14 |
size = 10, |
| 15 |
} = params; |
| 16 |
|
| 17 |
try { |
| 18 |
// Gửi request GET đến endpoint admin/post-request (API backend dùng để lấy danh sách cần duyệt) |
| 19 |
const response = await axios.get('/admin/post-request', { |
| 20 |
params: { page, size } // Truyền query parameters vào axios |
| 21 |
}); |
| 22 |
|
| 23 |
// Lấy dữ liệu từ response, nếu không có thì dùng object rỗng |
| 24 |
const data = response.data || {}; |
| 25 |
|
| 26 |
// Backend thường trả về object chứa các thuộc tính: content, totalElements, totalPages, size, number |
| 27 |
const content = data.content || data.data || []; // danh sách item thực tế |
| 28 |
const totalElements = data.totalElements ?? (data.length || 0); // tổng số phần tử |
| 29 |
const totalPages = data.totalPages ?? 1; // tổng số trang |
| 30 |
const pageSize = data.size ?? size; // kích thước trang hiện tại |
| 31 |
const pageNumber = data.number ?? page; // trang hiện tại |
| 32 |
|
| 33 |
// Chuyển đổi từng item trong danh sách thành object chuẩn để hiển thị trên UI |
| 34 |
const mapped = (Array.isArray(content) ? content : []).map(item => { |
| 35 |
const id = item.requestId; // ID của yêu cầu duyệt |
| 36 |
const listingId = item.listingId; // ID của bài đăng gốc |
| 37 |
const title = item.title ?? '—'; // tiêu đề bài đăng |
| 38 |
const price = typeof item.price === 'number' |
| 39 |
? item.price |
| 40 |
: (item.price ? Number(item.price) : null); // chuyển giá sang số |
| 41 |
const thumbnail = item.thumbnailUrl || null; // ảnh thumbnail |
| 42 |
const status = item.status ; // trạng thái bài đăng |
| 43 |
return { id, listingId, title, price, thumbnail, status, raw: item }; // trả về object chuẩn |
| 44 |
}); |
| 45 |
|
| 46 |
// Trả về dữ liệu đã được chuẩn hóa |
| 47 |
return { |
| 48 |
content: mapped, |
| 49 |
totalElements, |
| 50 |
totalPages, |
| 51 |
size: pageSize, |
| 52 |
number: pageNumber |
| 53 |
}; |
| 54 |
} catch (error) { |
| 55 |
// Nếu gọi API chính thất bại, log lỗi |
| 56 |
console.error('Admin API failed:', error); |
| 57 |
|
| 58 |
try { |
| 59 |
// Gọi dự phòng đến 2 endpoint lấy EV và battery listings |
| 60 |
const [evResponse, batteryResponse] = await Promise.all([ |
| 61 |
axios.get('/evCart', { params: { page, size } }), |
| 62 |
axios.get('/batteryCart', { params: { page, size } }) |
| 63 |
]); |
| 64 |
|
| 65 |
// Lấy dữ liệu từ 2 response |
| 66 |
const evListings = evResponse.data.content || evResponse.data || []; |
| 67 |
const batteryListings = batteryResponse.data.content || batteryResponse.data || []; |
| 68 |
const allListings = [...evListings, ...batteryListings]; // Gộp lại |
| 69 |
|
| 70 |
// Tạm thời hiển thị tất cả listing để debug |
| 71 |
const filteredListings = allListings.filter(listing => true); |
| 72 |
|
| 73 |
// Chuyển đổi từng listing về dạng chuẩn để hiển thị |
| 74 |
const mapped = filteredListings.map(item => ({ |
| 75 |
id: item.listingId, |
| 76 |
title: item.title ?? '—', |
| 77 |
price: typeof item.price === 'number' |
| 78 |
? item.price |
| 79 |
: (item.price ? Number(item.price) : null), |
| 80 |
thumbnail: item.thumbnailUrl || null, |
| 81 |
status: item.status, |
| 82 |
raw: item |
| 83 |
})); |
| 84 |
|
| 85 |
// Trả về kết quả dự phòng |
| 86 |
return { |
| 87 |
content: mapped, |
| 88 |
totalElements: mapped.length, |
| 89 |
totalPages: Math.ceil(mapped.length / size), |
| 90 |
size: size, |
| 91 |
number: page |
| 92 |
}; |
| 93 |
} catch (fallbackError) { |
| 94 |
console.error('Fallback API also failed:', fallbackError); |
| 95 |
throw error; // Ném lỗi gốc ra ngoài |
| 96 |
} |
| 97 |
} |
| 98 |
}; |
| 99 |
|
| 100 |
// Approve (chấp thuận) một bài đăng |
| 101 |
export const approveListing = async (listingId, approvalNote = '') => { |
| 102 |
try { |
| 103 |
// Gọi endpoint approve-request/{id} để phê duyệt bài đăng |
| 104 |
const response = await axios.get(`/admin/approve-request/${listingId}`); |
| 105 |
return response.data; |
| 106 |
} catch (error) { |
| 107 |
console.error('Error approving listing:', error); |
| 108 |
throw error; |
| 109 |
} |
| 110 |
}; |
| 111 |
|
| 112 |
// Reject (từ chối) một bài đăng |
| 113 |
export const rejectListing = async (listingId, rejectionReason) => { |
| 114 |
// Kiểm tra lý do từ chối có tồn tại hay không |
| 115 |
if (!rejectionReason?.trim()) { |
| 116 |
throw new Error('Rejection reason is required'); |
| 117 |
} |
| 118 |
|
| 119 |
try { |
| 120 |
// Gọi endpoint reject-request/{id}?reason=... |
| 121 |
const response = await axios.get(`/admin/reject-request/${listingId}`, { |
| 122 |
params: { reason: rejectionReason } |
| 123 |
}); |
| 124 |
return response.data; |
| 125 |
} catch (error) { |
| 126 |
console.error('Error rejecting listing:', error); |
| 127 |
throw error; |
| 128 |
} |
| 129 |
}; |
| 130 |
|
| 131 |
// Lấy chi tiết bài đăng (kể cả ở trạng thái PENDING) |
| 132 |
export const getListingDetail = async (listingId) => { |
| 133 |
try { |
| 134 |
// Gọi endpoint admin riêng để xem chi tiết bài đăng |
| 135 |
const response = await axios.get(`/admin/listing-detail/${listingId}`); |
| 136 |
return response.data; |
| 137 |
} catch (error) { |
| 138 |
console.error('Error fetching listing detail:', error); |
| 139 |
throw error; |
| 140 |
} |
| 141 |
}; |
| 142 |
|
| 143 |
// Lấy thống kê tổng quan cho trang dashboard admin |
| 144 |
export const getAdminStats = async () => { |
| 145 |
try { |
| 146 |
// Gọi endpoint thống kê |
| 147 |
const response = await axios.get('/api/admin/stats'); |
| 148 |
return response.data; |
| 149 |
} catch (error) { |
| 150 |
console.error('Error fetching admin stats:', error); |
| 151 |
throw error; |
| 152 |
} |
| 153 |
}; |
| 154 |
|
| 155 |
// Lấy lịch sử xét duyệt (approve/reject) |
| 156 |
export const getApprovalHistory = async (params = {}) => { |
| 157 |
const { |
| 158 |
page = 0, |
| 159 |
size = 10, |
| 160 |
status = null |
| 161 |
} = params; |
| 162 |
|
| 163 |
try { |
| 164 |
// Hiện chưa có endpoint riêng cho lịch sử, nên tạm dùng /admin/post-request |
| 165 |
const response = await axios.get('/admin/post-request', { |
| 166 |
params: { page, size } |
| 167 |
}); |
| 168 |
return response.data; |
| 169 |
} catch (error) { |
| 170 |
console.error('Error fetching approval history:', error); |
| 171 |
throw error; |
| 172 |
} |
| 173 |
}; |