| 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 | }; |