From 5e728a6ff349861fca0e0bfe3e1b50688dd6a7d1 Mon Sep 17 00:00:00 2001 From: vinix Date: Sun, 12 Oct 2025 22:08:57 +0700 Subject: [PATCH] refactor: enhance error handling and logging in API requests --- src/api/master-device.jsx | 45 +++++-- src/api/master-plant-section.jsx | 178 ++++++++++++++++++++++++++ src/api/master-tag.jsx | 185 +++++++++++++++++++++++++++ src/components/Global/ApiRequest.jsx | 65 ++++++++-- 4 files changed, 456 insertions(+), 17 deletions(-) create mode 100644 src/api/master-plant-section.jsx create mode 100644 src/api/master-tag.jsx diff --git a/src/api/master-device.jsx b/src/api/master-device.jsx index fa6fa37..9493cc8 100644 --- a/src/api/master-device.jsx +++ b/src/api/master-device.jsx @@ -100,11 +100,25 @@ const createDevice = async (queryParams) => { params: queryParams, }); console.log('createDevice full response:', response); - // Return full response with statusCode + console.log('createDevice payload sent:', queryParams); + + // Backend returns: { statusCode, message, rows, data: [device_object] } + // Check if response is empty array (error from SendRequest) + if (Array.isArray(response) && response.length === 0) { + return { + statusCode: 500, + data: null, + message: 'Request failed', + rows: 0 + }; + } + + // Extract first item from data array return { statusCode: response.statusCode || 200, - data: response.data, - message: response.message + data: response.data?.[0] || response.data, + message: response.message, + rows: response.rows }; }; @@ -115,11 +129,25 @@ const updateDevice = async (device_id, queryParams) => { params: queryParams, }); console.log('updateDevice full response:', response); - // Return full response with statusCode + console.log('updateDevice payload sent:', queryParams); + + // Backend returns: { statusCode, message, rows, data: [device_object] } + // Check if response is empty array (error from SendRequest) + if (Array.isArray(response) && response.length === 0) { + return { + statusCode: 500, + data: null, + message: 'Request failed', + rows: 0 + }; + } + + // Extract first item from data array return { statusCode: response.statusCode || 200, - data: response.data, - message: response.message + data: response.data?.[0] || response.data, + message: response.message, + rows: response.rows }; }; @@ -129,11 +157,12 @@ const deleteDevice = async (queryParams) => { prefix: `device/${queryParams}`, }); console.log('deleteDevice full response:', response); - // Return full response with statusCode + // Backend returns: { statusCode, message, rows: null, data: true } return { statusCode: response.statusCode || 200, data: response.data, - message: response.message + message: response.message, + rows: response.rows }; }; diff --git a/src/api/master-plant-section.jsx b/src/api/master-plant-section.jsx new file mode 100644 index 0000000..5f5f266 --- /dev/null +++ b/src/api/master-plant-section.jsx @@ -0,0 +1,178 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +const getAllPlantSection = async (queryParams) => { + try { + const response = await SendRequest({ + method: 'get', + prefix: `plant-sub-section?${queryParams.toString()}`, + }); + console.log('getAllPlantSection response:', response); + console.log('Query params:', queryParams.toString()); + + // Backend response structure: + // { + // statusCode: 200, + // data: [...plantSections], + // paging: { + // current_page: 1, + // current_limit: 10, + // total_limit: 50, + // total_page: 5 + // } + // } + + // Check if backend returns paginated data + if (response.paging) { + const totalData = response.data?.[0]?.total_data || response.rows || response.data?.length || 0; + + return { + status: response.statusCode || 200, + data: { + data: response.data || [], + paging: { + page: response.paging.current_page || 1, + limit: response.paging.current_limit || 10, + total: totalData, + page_total: response.paging.total_page || Math.ceil(totalData / (response.paging.current_limit || 10)) + }, + total: totalData + } + }; + } + + // Fallback: If backend returns all data without pagination (old behavior) + const params = Object.fromEntries(queryParams); + const currentPage = parseInt(params.page) || 1; + const currentLimit = parseInt(params.limit) || 10; + + const allData = response.data || []; + const totalData = allData.length; + + // Client-side pagination + const startIndex = (currentPage - 1) * currentLimit; + const endIndex = startIndex + currentLimit; + const paginatedData = allData.slice(startIndex, endIndex); + + return { + status: response.statusCode || 200, + data: { + data: paginatedData, + paging: { + page: currentPage, + limit: currentLimit, + total: totalData, + page_total: Math.ceil(totalData / currentLimit) + }, + total: totalData + } + }; + } catch (error) { + console.error('getAllPlantSection error:', error); + return { + status: 500, + data: { + data: [], + paging: { + page: 1, + limit: 10, + total: 0, + page_total: 0 + }, + total: 0 + }, + error: error.message + }; + } +}; + +const getPlantSectionById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `plant-sub-section/${id}`, + }); + return response.data; +}; + +const createPlantSection = async (queryParams) => { + const response = await SendRequest({ + method: 'post', + prefix: `plant-sub-section`, + params: queryParams, + }); + console.log('createPlantSection full response:', response); + console.log('createPlantSection payload sent:', queryParams); + + // Check if response has error flag + if (response.error) { + return { + statusCode: response.statusCode || 500, + data: null, + message: response.message || 'Request failed', + rows: 0 + }; + } + + // Backend returns: { statusCode, message, rows, data: [plantSection_object] } + return { + statusCode: response.statusCode || 200, + data: response.data?.[0] || response.data, + message: response.message, + rows: response.rows + }; +}; + +const updatePlantSection = async (plant_section_id, queryParams) => { + const response = await SendRequest({ + method: 'put', + prefix: `plant-sub-section/${plant_section_id}`, + params: queryParams, + }); + console.log('updatePlantSection full response:', response); + console.log('updatePlantSection payload sent:', queryParams); + + // Check if response has error flag + if (response.error) { + return { + statusCode: response.statusCode || 500, + data: null, + message: response.message || 'Request failed', + rows: 0 + }; + } + + // Backend returns: { statusCode, message, rows, data: [plantSection_object] } + return { + statusCode: response.statusCode || 200, + data: response.data?.[0] || response.data, + message: response.message, + rows: response.rows + }; +}; + +const deletePlantSection = async (queryParams) => { + const response = await SendRequest({ + method: 'delete', + prefix: `plant-sub-section/${queryParams}`, + }); + console.log('deletePlantSection full response:', response); + + // Check if response has error flag + if (response.error) { + return { + statusCode: response.statusCode || 500, + data: null, + message: response.message || 'Request failed', + rows: 0 + }; + } + + // Backend returns: { statusCode, message, rows: null, data: true } + return { + statusCode: response.statusCode || 200, + data: response.data, + message: response.message, + rows: response.rows + }; +}; + +export { getAllPlantSection, getPlantSectionById, createPlantSection, updatePlantSection, deletePlantSection }; diff --git a/src/api/master-tag.jsx b/src/api/master-tag.jsx new file mode 100644 index 0000000..eeec3c8 --- /dev/null +++ b/src/api/master-tag.jsx @@ -0,0 +1,185 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +const getAllTag = async (queryParams) => { + try { + const response = await SendRequest({ + method: 'get', + prefix: `tags?${queryParams.toString()}`, + }); + console.log('getAllTag response:', response); + console.log('Query params:', queryParams.toString()); + + // Check if response has error + if (response.error) { + console.error('getAllTag error response:', response); + return { + status: response.statusCode || 500, + data: { + data: [], + paging: { + page: 1, + limit: 10, + total: 0, + page_total: 0 + }, + total: 0 + }, + error: response.message + }; + } + + // Check if backend returns paginated data + if (response.paging) { + const totalData = response.data?.[0]?.total_data || response.rows || response.data?.length || 0; + + return { + status: response.statusCode || 200, + data: { + data: response.data || [], + paging: { + page: response.paging.current_page || 1, + limit: response.paging.current_limit || 10, + total: totalData, + page_total: response.paging.total_page || Math.ceil(totalData / (response.paging.current_limit || 10)) + }, + total: totalData + } + }; + } + + // Fallback: If backend returns all data without pagination (old behavior) + const params = Object.fromEntries(queryParams); + const currentPage = parseInt(params.page) || 1; + const currentLimit = parseInt(params.limit) || 10; + + const allData = response.data || []; + const totalData = allData.length; + + // Client-side pagination + const startIndex = (currentPage - 1) * currentLimit; + const endIndex = startIndex + currentLimit; + const paginatedData = allData.slice(startIndex, endIndex); + + return { + status: response.statusCode || 200, + data: { + data: paginatedData, + paging: { + page: currentPage, + limit: currentLimit, + total: totalData, + page_total: Math.ceil(totalData / currentLimit) + }, + total: totalData + } + }; + } catch (error) { + console.error('getAllTag catch error:', error); + return { + status: 500, + data: { + data: [], + paging: { + page: 1, + limit: 10, + total: 0, + page_total: 0 + }, + total: 0 + }, + error: error.message + }; + } +}; + +const getTagById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `tags/${id}`, + }); + return response.data; +}; + +const createTag = async (queryParams) => { + const response = await SendRequest({ + method: 'post', + prefix: `tags`, + params: queryParams, + }); + console.log('createTag full response:', response); + console.log('createTag payload sent:', queryParams); + + // Check if response has error flag + if (response.error) { + return { + statusCode: response.statusCode || 500, + data: null, + message: response.message || 'Request failed', + rows: 0 + }; + } + + // Backend returns: { statusCode, message, rows, data: [tag_object] } + return { + statusCode: response.statusCode || 200, + data: response.data?.[0] || response.data, + message: response.message, + rows: response.rows + }; +}; + +const updateTag = async (tag_id, queryParams) => { + const response = await SendRequest({ + method: 'put', + prefix: `tags/${tag_id}`, + params: queryParams, + }); + console.log('updateTag full response:', response); + console.log('updateTag payload sent:', queryParams); + + // Check if response has error flag + if (response.error) { + return { + statusCode: response.statusCode || 500, + data: null, + message: response.message || 'Request failed', + rows: 0 + }; + } + + // Backend returns: { statusCode, message, rows, data: [tag_object] } + return { + statusCode: response.statusCode || 200, + data: response.data?.[0] || response.data, + message: response.message, + rows: response.rows + }; +}; + +const deleteTag = async (queryParams) => { + const response = await SendRequest({ + method: 'delete', + prefix: `tags/${queryParams}`, + }); + console.log('deleteTag full response:', response); + + // Check if response has error flag + if (response.error) { + return { + statusCode: response.statusCode || 500, + data: null, + message: response.message || 'Request failed', + rows: 0 + }; + } + + // Backend returns: { statusCode, message, rows: null, data: true } + return { + statusCode: response.statusCode || 200, + data: response.data, + message: response.message, + rows: response.rows + }; +}; + +export { getAllTag, getTagById, createTag, updateTag, deleteTag }; diff --git a/src/components/Global/ApiRequest.jsx b/src/components/Global/ApiRequest.jsx index 52947a2..78441e9 100644 --- a/src/components/Global/ApiRequest.jsx +++ b/src/components/Global/ApiRequest.jsx @@ -20,6 +20,13 @@ instance.interceptors.response.use( async (error) => { const originalRequest = error.config; + console.error("🚨 Response Error Interceptor:", { + status: error.response?.status, + url: originalRequest.url, + message: error.response?.data?.message, + hasRetried: originalRequest._retry + }); + if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; @@ -29,14 +36,16 @@ instance.interceptors.response.use( const newAccessToken = refreshRes.data.data.accessToken; localStorage.setItem("token", newAccessToken); + console.log("✅ Token refreshed successfully"); // update token di header instance.defaults.headers.common["Authorization"] = `Bearer ${newAccessToken}`; originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`; + console.log("🔁 Retrying original request..."); return instance(originalRequest); } catch (refreshError) { - console.error("Refresh token gagal:", refreshError); + console.error("❌ Refresh token gagal:", refreshError.response?.data || refreshError.message); localStorage.clear(); window.location.href = "/signin"; } @@ -66,15 +75,28 @@ async function ApiRequest({ const rawToken = localStorage.getItem("token"); if (token && rawToken) { - request.headers["Authorization"] = `Bearer ${rawToken.replace(/"/g, "")}`; + const cleanToken = rawToken.replace(/"/g, ""); + request.headers["Authorization"] = `Bearer ${cleanToken}`; + console.log("🔐 Sending request with token:", cleanToken.substring(0, 20) + "..."); + } else { + console.warn("⚠️ No token found in localStorage"); } + console.log("📤 API Request:", { method, url: prefix, hasToken: !!rawToken }); + try { const response = await instance(request); + console.log("✅ API Response:", { url: prefix, status: response.status, statusCode: response.data?.statusCode }); return { ...response, error: false }; } catch (error) { const status = error?.response?.status || 500; const message = error?.response?.data?.message || error.message || "Something Wrong"; + console.error("❌ API Error:", { + url: prefix, + status, + message, + fullError: error?.response?.data + }); if (status !== 401) { await cekError(status, message); @@ -109,14 +131,39 @@ async function cekError(status, message = "") { const SendRequest = async (queryParams) => { try { const response = await ApiRequest(queryParams); - return response?.data || []; - } catch (error) { - console.error("Request error:", error); - await Swal.fire({ - icon: "error", - text: error.message || "Something went wrong", + console.log("📦 SendRequest response:", { + hasError: response.error, + status: response.status, + statusCode: response.data?.statusCode, + data: response.data }); - return []; + + // If ApiRequest returned error flag, return error structure + if (response.error) { + const errorMsg = response.data?.message || response.statusText || "Request failed"; + console.error("❌ SendRequest error response:", errorMsg); + + // Return consistent error structure instead of empty array + return { + statusCode: response.status || 500, + message: errorMsg, + data: null, + error: true + }; + } + + return response?.data || { statusCode: 200, data: [], message: "Success" }; + } catch (error) { + console.error("❌ SendRequest catch error:", error); + + // Don't show Swal here, let the calling code handle it + // This allows better error handling in each API call + return { + statusCode: 500, + message: error.message || "Something went wrong", + data: null, + error: true + }; } };