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