diff --git a/.env.example b/.env.example index 62bd0b8..914aa0c 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ -VITE_API_SERVER=http://36.66.16.49:9528/api +VITE_API_SERVER=http://localhost:9530/api +# VITE_API_SERVER=https://117.102.231.130:9528/api VITE_MQTT_SERVER=ws://localhost:1884 VITE_MQTT_USERNAME= VITE_MQTT_PASSWORD= diff --git a/src/App.jsx b/src/App.jsx index 511886c..498ca76 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,6 +14,7 @@ import IndexDevice from './pages/master/device/IndexDevice'; import IndexTag from './pages/master/tag/IndexTag'; import IndexUnit from './pages/master/unit/IndexUnit'; import IndexBrandDevice from './pages/master/brandDevice/IndexBrandDevice'; +import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice'; import IndexPlantSection from './pages/master/plantSection/IndexPlantSection'; import IndexStatus from './pages/master/status/IndexStatus'; import IndexShift from './pages/master/shift/IndexShift'; @@ -35,6 +36,13 @@ import IndexUser from './pages/user/IndexUser'; import IndexMember from './pages/shiftManagement/member/IndexMember'; import SvgTest from './pages/home/SvgTest'; +import SvgOverview from './pages/home/SvgOverview'; +import SvgCompressorA from './pages/home/SvgCompressorA'; +import SvgCompressorB from './pages/home/SvgCompressorB'; +import SvgCompressorC from './pages/home/SvgCompressorC'; +import SvgAirDryerA from './pages/home/SvgAirDryerA'; +import SvgAirDryerB from './pages/home/SvgAirDryerB'; +import SvgAirDryerC from './pages/home/SvgAirDryerC'; const App = () => { return ( @@ -52,11 +60,22 @@ const App = () => { } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + }> } /> } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/Utils/validate.js b/src/Utils/validate.js new file mode 100644 index 0000000..8e6012f --- /dev/null +++ b/src/Utils/validate.js @@ -0,0 +1,71 @@ +// utils/validate.js + +// Daftar aturan validasi +const validationRules = [ + { field: 'name', label: 'Nama', required: true }, + { + field: 'email', + label: 'Email', + required: true, + pattern: /\S+@\S+\.\S+/, + patternMessage: 'Format email tidak valid', + }, + { + field: 'age', + label: 'Umur', + required: true, + validator: (v) => !isNaN(v) && Number(v) >= 18, + message: 'Umur harus angka dan minimal 18 tahun', + }, +]; + +/** + * Fungsi validasi dinamis berbasis array objek aturan. + * @param {Object} data - data form (misal { name: '', email: '' }) + * @param {Array} rules - array aturan validasi + * @returns {Object} errors - object berisi pesan error per field + */ + +export const validateRun = (data, rules, onError) => { + const errors = {}; + const messages = []; + + const ipRegex = /^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$/; + + rules.forEach((rule) => { + const value = data[rule.field]?.toString().trim(); + const fieldErrors = []; + + if (rule.required && !value) { + fieldErrors.push(`${rule.label} wajib diisi`); + } + + // ✅ IP Address check + if (rule.ip && value && !ipRegex.test(value)) { + fieldErrors.push(`${rule.label} harus berupa alamat IP yang valid`); + } + + if (rule.pattern && value && !rule.pattern.test(value)) { + fieldErrors.push(rule.patternMessage || `${rule.label} tidak valid`); + } + + if (rule.validator && value && !rule.validator(value)) { + fieldErrors.push(rule.message || `${rule.label} tidak valid`); + } + + // Gabungkan error satu field jadi satu string (pisah baris) + if (fieldErrors.length > 0) { + errors[rule.field] = fieldErrors.join("\n"); + messages.push(...fieldErrors); + } + }); + + // Jika ada error total, tampilkan callback dan return false + + if (messages.length > 0) { + if (onError) onError(messages.join("\n")); + return true; + } + return false + +}; \ No newline at end of file diff --git a/src/api/dashboard-home.jsx b/src/api/dashboard-home.jsx deleted file mode 100644 index 6f14ea0..0000000 --- a/src/api/dashboard-home.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { SendRequest } from '../components/Global/ApiRequest'; - -const getTotal = async (query = '') => { - const prefix = `${query ? `?${query}` : ''}`; - const fullUrl = `${import.meta.env.VITE_API_SERVER}/dashboard/${prefix}`; - try { - const response = await SendRequest({ - method: 'get', - prefix: `dashboard/${prefix}`, - }); - return response; - } catch (error) { - console.error(`[API Call] Failed: GET ${fullUrl}`, error); - throw error; - } -}; - -const getTotalPermit = async (query = '') => { - const prefix = `dashboard/permit-total${query ? `?${query}` : ''}`; - const fullUrl = `${import.meta.env.VITE_API_SERVER}/${prefix}`; - try { - const response = await SendRequest({ - method: 'get', - prefix, - }); - return response; - } catch (error) { - console.error(`[API Call] Failed: GET ${fullUrl}`, error); - throw error; - } -}; - -const getTotalPermitPerYear = async (query = '') => { - const prefix = `dashboard/permit-breakdown${query ? `?${query}` : ''}`; - const fullUrl = `${import.meta.env.VITE_API_SERVER}/${prefix}`; - try { - const response = await SendRequest({ - method: 'get', - prefix, - }); - return response; - } catch (error) { - console.error(`[API Call] Failed: GET ${fullUrl}`, error); - throw error; - } -}; - -export { getTotal, getTotalPermit, getTotalPermitPerYear }; \ No newline at end of file diff --git a/src/api/jadwal-shift.jsx b/src/api/jadwal-shift.jsx index 2ae403a..ce221b3 100644 --- a/src/api/jadwal-shift.jsx +++ b/src/api/jadwal-shift.jsx @@ -1,55 +1,41 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllJadwalShift = async (queryParams) => { - try { - const response = await SendRequest({ - method: 'get', - prefix: `jadwal-shift?${queryParams.toString()}`, - }); - return response; - } catch (error) { - console.error('getAllJadwalShift error:', error); - return { - status: 500, - data: { - data: [], - paging: { - page: 1, - limit: 10, - total: 0, - page_total: 0 - }, - total: 0 - }, - error: error.message - }; - } + const response = await SendRequest({ + method: 'get', + prefix: `jadwal-shift?${queryParams.toString()}`, + }); + + return response.data; +}; + +const getJadwalShiftById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `jadwal-shift/${id}`, + }); + + return response.data; }; const createJadwalShift = async (queryParams) => { const response = await SendRequest({ method: 'post', prefix: `jadwal-shift`, - data: queryParams, + params: queryParams, }); - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const updateJadwalShift = async (id, queryParams) => { const response = await SendRequest({ method: 'put', prefix: `jadwal-shift/${id}`, - data: queryParams, + params: queryParams, }); - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const deleteJadwalShift = async (id) => { @@ -57,11 +43,13 @@ const deleteJadwalShift = async (id) => { method: 'delete', prefix: `jadwal-shift/${id}`, }); - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + return response.data; }; -export { getAllJadwalShift, createJadwalShift, updateJadwalShift, deleteJadwalShift }; +export { + getAllJadwalShift, + getJadwalShiftById, + createJadwalShift, + updateJadwalShift, + deleteJadwalShift, +}; diff --git a/src/api/master-apd.jsx b/src/api/master-brand.jsx similarity index 55% rename from src/api/master-apd.jsx rename to src/api/master-brand.jsx index c39c14a..942e4e3 100644 --- a/src/api/master-apd.jsx +++ b/src/api/master-brand.jsx @@ -1,45 +1,50 @@ import { SendRequest } from '../components/Global/ApiRequest'; -const getAllApd = async (queryParams) => { +const getAllBrands = async (queryParams) => { const response = await SendRequest({ method: 'get', - prefix: `apd?${queryParams.toString()}`, + prefix: `brand?${queryParams.toString()}`, }); - return response; + + return response.data; }; -const createApd = async (queryParams) => { +const getBrandById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `brand/${id}`, + }); + + return response.data; +}; + +const createBrand = async (queryParams) => { const response = await SendRequest({ method: 'post', - prefix: `apd`, + prefix: `brand`, params: queryParams, }); + return response.data; }; -const updateApd = async (vendor_id, queryParams) => { +const updateBrand = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `apd/${vendor_id}`, + prefix: `brand/${id}`, params: queryParams, }); + return response.data; }; -const deleteApd = async (queryParams) => { +const deleteBrand = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `apd/${queryParams}`, + prefix: `brand/${id}`, }); + return response.data; }; -const getJenisPermit = async () => { - const response = await SendRequest({ - method: 'get', - prefix: `apd/jenis-permit`, - }); - return response.data; -}; - -export { getAllApd, createApd, updateApd, deleteApd, getJenisPermit }; +export { getAllBrands, getBrandById, createBrand, updateBrand, deleteBrand }; diff --git a/src/api/master-device.jsx b/src/api/master-device.jsx index 9493cc8..e1b7315 100644 --- a/src/api/master-device.jsx +++ b/src/api/master-device.jsx @@ -1,88 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllDevice = async (queryParams) => { - try { - const response = await SendRequest({ - method: 'get', - prefix: `device?${queryParams.toString()}`, - }); - console.log('getAllDevice response:', response); - console.log('Query params:', queryParams.toString()); + const response = await SendRequest({ + method: 'get', + prefix: `device?${queryParams.toString()}`, + }); - // Backend response structure: - // { - // statusCode: 200, - // data: [...devices], - // 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('getAllDevice error:', error); - return { - status: 500, - data: { - data: [], - paging: { - page: 1, - limit: 10, - total: 0, - page_total: 0 - }, - total: 0 - }, - error: error.message - }; - } + return response.data; }; const getDeviceById = async (id) => { @@ -90,6 +14,7 @@ const getDeviceById = async (id) => { method: 'get', prefix: `device/${id}`, }); + return response.data; }; @@ -99,71 +24,27 @@ const createDevice = async (queryParams) => { prefix: `device`, params: queryParams, }); - console.log('createDevice full response:', response); - 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?.[0] || response.data, - message: response.message, - rows: response.rows - }; + return response.data; }; -const updateDevice = async (device_id, queryParams) => { +const updateDevice = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `device/${device_id}`, + prefix: `device/${id}`, params: queryParams, }); - console.log('updateDevice full response:', response); - 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?.[0] || response.data, - message: response.message, - rows: response.rows - }; + return response.data; }; -const deleteDevice = async (queryParams) => { +const deleteDevice = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `device/${queryParams}`, + prefix: `device/${id}`, }); - console.log('deleteDevice full response:', response); - // Backend returns: { statusCode, message, rows: null, data: true } - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message, - rows: response.rows - }; + + return response.data; }; export { getAllDevice, getDeviceById, createDevice, updateDevice, deleteDevice }; diff --git a/src/api/master-plant-section.jsx b/src/api/master-plant-section.jsx index 594723b..790cf89 100644 --- a/src/api/master-plant-section.jsx +++ b/src/api/master-plant-section.jsx @@ -1,97 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllPlantSection = async (queryParams) => { - try { - // Ensure queryParams is URLSearchParams object - const params = queryParams instanceof URLSearchParams ? queryParams : new URLSearchParams(queryParams); + const response = await SendRequest({ + method: 'get', + prefix: `plant-sub-section?${queryParams.toString()}`, + }); - const response = await SendRequest({ - method: 'get', - prefix: `plant-sub-section?${params.toString()}`, - }); - console.log('getAllPlantSection response:', response); - console.log('Query params:', params.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) { - // Extract total_data from first record, or fallback to total_limit or rows - const totalData = response.data?.[0]?.total_data || response.paging.total_limit || response.rows || response.data?.length || 0; - - // Use total_limit as total count, handle 0 values for page/limit - const currentPage = response.paging.current_page || 1; - const currentLimit = response.paging.current_limit || 10; - const totalPages = response.paging.total_page || Math.ceil(totalData / currentLimit); - - return { - status: response.statusCode || 200, - data: { - data: response.data || [], - paging: { - page: currentPage, - limit: currentLimit, - total: totalData, - page_total: totalPages - }, - total: totalData - } - }; - } - - // Fallback: If backend returns all data without pagination (old behavior) - const parsedParams = Object.fromEntries(params); - const currentPage = parseInt(parsedParams.page) || 1; - const currentLimit = parseInt(parsedParams.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 - }; - } + return response.data; }; const getPlantSectionById = async (id) => { @@ -99,6 +14,7 @@ const getPlantSectionById = async (id) => { method: 'get', prefix: `plant-sub-section/${id}`, }); + return response.data; }; @@ -108,80 +24,33 @@ const createPlantSection = async (queryParams) => { 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 - }; + return response.data; }; -const updatePlantSection = async (plant_section_id, queryParams) => { +const updatePlantSection = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `plant-sub-section/${plant_section_id}`, + prefix: `plant-sub-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 - }; + return response.data; }; -const deletePlantSection = async (queryParams) => { +const deletePlantSection = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `plant-sub-section/${queryParams}`, + prefix: `plant-sub-section/${id}`, }); - 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 - }; + return response.data; }; -export { getAllPlantSection, getPlantSectionById, createPlantSection, updatePlantSection, deletePlantSection }; +export { + getAllPlantSection, + getPlantSectionById, + createPlantSection, + updatePlantSection, + deletePlantSection, +}; diff --git a/src/api/master-shift.jsx b/src/api/master-shift.jsx index a5a3f24..9807dc5 100644 --- a/src/api/master-shift.jsx +++ b/src/api/master-shift.jsx @@ -1,29 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllShift = async (queryParams) => { - try { - const response = await SendRequest({ - method: 'get', - prefix: `shift?${queryParams.toString()}`, - }); - return response; - } catch (error) { - console.error('getAllShift error:', error); - return { - status: 500, - data: { - data: [], - paging: { - page: 1, - limit: 10, - total: 0, - page_total: 0 - }, - total: 0 - }, - error: error.message - }; - } + const response = await SendRequest({ + method: 'get', + prefix: `shift?${queryParams.toString()}`, + }); + + return response.data; }; const getShiftById = async (id) => { @@ -31,6 +14,7 @@ const getShiftById = async (id) => { method: 'get', prefix: `shift/${id}`, }); + return response.data; }; @@ -38,26 +22,20 @@ const createShift = async (queryParams) => { const response = await SendRequest({ method: 'post', prefix: `shift`, - data: queryParams, + params: queryParams, }); - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const updateShift = async (id, queryParams) => { const response = await SendRequest({ method: 'put', prefix: `shift/${id}`, - data: queryParams, + params: queryParams, }); - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const deleteShift = async (id) => { @@ -65,11 +43,8 @@ const deleteShift = async (id) => { method: 'delete', prefix: `shift/${id}`, }); - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; export { getAllShift, getShiftById, createShift, updateShift, deleteShift }; diff --git a/src/api/master-status.jsx b/src/api/master-status.jsx new file mode 100644 index 0000000..4b53e96 --- /dev/null +++ b/src/api/master-status.jsx @@ -0,0 +1,50 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +const getAllStatuss = async (queryParams) => { + const response = await SendRequest({ + method: 'get', + prefix: `status?${queryParams.toString()}`, + }); + + return response.data; +}; + +const getStatusById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `status/${id}`, + }); + + return response.data; +}; + +const createStatus = async (queryParams) => { + const response = await SendRequest({ + method: 'post', + prefix: `status`, + params: queryParams, + }); + + return response.data; +}; + +const updateStatus = async (id, queryParams) => { + const response = await SendRequest({ + method: 'put', + prefix: `status/${id}`, + params: queryParams, + }); + + return response.data; +}; + +const deleteStatus = async (id) => { + const response = await SendRequest({ + method: 'delete', + prefix: `status/${id}`, + }); + + return response.data; +}; + +export { getAllStatuss, getStatusById, createStatus, updateStatus, deleteStatus }; diff --git a/src/api/master-tag.jsx b/src/api/master-tag.jsx index f6a8eca..ba5a55e 100644 --- a/src/api/master-tag.jsx +++ b/src/api/master-tag.jsx @@ -1,93 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllTag = async (queryParams) => { - try { - const response = await SendRequest({ - method: 'get', - prefix: `tags?${queryParams.toString()}`, - }); + const response = await SendRequest({ + method: 'get', + prefix: `tags?${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 - }; - } + return response.data; }; const getTagById = async (id) => { @@ -95,6 +14,7 @@ const getTagById = async (id) => { method: 'get', prefix: `tags/${id}`, }); + return response.data; }; @@ -105,74 +25,26 @@ const createTag = async (queryParams) => { params: 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 - }; + return response.data; }; -const updateTag = async (tag_id, queryParams) => { +const updateTag = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `tags/${tag_id}`, + prefix: `tags/${id}`, params: 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 - }; + return response.data; }; -const deleteTag = async (queryParams) => { +const deleteTag = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `tags/${queryParams}`, + prefix: `tags/${id}`, }); - // 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 - }; + return response.data; }; export { getAllTag, getTagById, createTag, updateTag, deleteTag }; diff --git a/src/api/master-unit.jsx b/src/api/master-unit.jsx index b03c91a..0130502 100644 --- a/src/api/master-unit.jsx +++ b/src/api/master-unit.jsx @@ -1,95 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllUnit = async (queryParams) => { - try { - const response = await SendRequest({ - method: 'get', - prefix: `unit?${queryParams.toString()}`, - }); - console.log('getAllUnit response:', response); - console.log('Query params:', queryParams.toString()); + const response = await SendRequest({ + method: 'get', + prefix: `unit?${queryParams.toString()}`, + }); - // Check if response has error - if (response.error) { - console.error('getAllUnit 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 - 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('getAllUnit catch error:', error); - return { - status: 500, - data: { - data: [], - paging: { - page: 1, - limit: 10, - total: 0, - page_total: 0 - }, - total: 0 - }, - error: error.message - }; - } + return response.data; }; const getUnitById = async (id) => { @@ -97,101 +14,37 @@ const getUnitById = async (id) => { method: 'get', prefix: `unit/${id}`, }); + return response.data; }; const createUnit = async (queryParams) => { - // Map frontend fields to backend fields - const backendParams = { - unit_name: queryParams.name, - is_active: queryParams.is_active, - }; - const response = await SendRequest({ method: 'post', prefix: `unit`, - params: backendParams, + params: queryParams, }); - console.log('createUnit full response:', response); - console.log('createUnit payload sent:', backendParams); - // 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: [unit_object] } - return { - statusCode: response.statusCode || 200, - data: response.data?.[0] || response.data, - message: response.message, - rows: response.rows - }; + return response.data; }; -const updateUnit = async (unit_id, queryParams) => { - // Map frontend fields to backend fields - const backendParams = { - unit_name: queryParams.name, - is_active: queryParams.is_active, - }; - +const updateUnit = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `unit/${unit_id}`, - params: backendParams, + prefix: `unit/${id}`, + params: queryParams, }); - console.log('updateUnit full response:', response); - console.log('updateUnit payload sent:', backendParams); - // 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: [unit_object] } - return { - statusCode: response.statusCode || 200, - data: response.data?.[0] || response.data, - message: response.message, - rows: response.rows - }; + return response.data; }; -const deleteUnit = async (queryParams) => { +const deleteUnit = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `unit/${queryParams}`, + prefix: `unit/${id}`, }); - console.log('deleteUnit 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 - }; + return response.data; }; export { getAllUnit, getUnitById, createUnit, updateUnit, deleteUnit }; diff --git a/src/api/role.jsx b/src/api/role.jsx index 5a0b62f..44e5480 100644 --- a/src/api/role.jsx +++ b/src/api/role.jsx @@ -1,70 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllRole = async (queryParams) => { - try { - const response = await SendRequest({ - method: 'get', - prefix: `roles?${queryParams.toString()}`, - }); + const response = await SendRequest({ + method: 'get', + prefix: `roles?${queryParams.toString()}`, + }); - console.log('Role API Response:', response); - - // Check if backend returns paginated data - if (response.paging) { - // Backend already provides pagination info - return { - status: response.statusCode || 200, - data: { - data: response.data || [], - paging: response.paging, - total: response.paging.total || 0 - } - }; - } - - // Fallback: If backend returns all data without pagination - 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('getAllRole error:', error); - return { - status: 500, - data: { - data: [], - paging: { - page: 1, - limit: 10, - total: 0, - page_total: 0 - }, - total: 0 - }, - error: error.message - }; - } + return response.data; }; const getRoleById = async (id) => { @@ -72,6 +14,7 @@ const getRoleById = async (id) => { method: 'get', prefix: `roles/${id}`, }); + return response.data; }; @@ -82,107 +25,26 @@ const createRole = async (queryParams) => { params: queryParams, }); - console.log('Create Role API Response:', response); - - // Check for error status (not 200, 201, or success) - const isSuccess = - response.statusCode === 200 || response.statusCode === 201 || response.status === 'success'; - - if (!isSuccess && response.statusCode >= 400) { - let errorMessage = response.message || 'Gagal menambahkan role'; - - // Handle SQL unique constraint violation - if ( - errorMessage.includes('UNIQUE KEY constraint') || - errorMessage.includes('duplicate key') - ) { - errorMessage = `Role dengan nama "${queryParams.role_name}" sudah ada. Silakan gunakan nama lain.`; - } - - return { - statusCode: response.statusCode, - data: response.data, - message: errorMessage, - }; - } - - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message || 'Berhasil menambahkan role', - }; + return response.data; }; -const updateRole = async (role_id, queryParams) => { +const updateRole = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `roles/${role_id}`, + prefix: `roles/${id}`, params: queryParams, }); - console.log('Update Role API Response:', response); - - // Check for error status (not 200, 201, or success) - const isSuccess = - response.statusCode === 200 || response.statusCode === 201 || response.status === 'success'; - - if (!isSuccess && response.statusCode >= 400) { - let errorMessage = response.message || 'Gagal mengubah role'; - - // Handle SQL unique constraint violation - if ( - errorMessage.includes('UNIQUE KEY constraint') || - errorMessage.includes('duplicate key') - ) { - errorMessage = `Role dengan nama "${queryParams.role_name}" sudah ada. Silakan gunakan nama lain.`; - } - - return { - statusCode: response.statusCode, - data: response.data, - message: errorMessage, - }; - } - - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message || 'Berhasil mengubah role', - }; + return response.data; }; -const deleteRole = async (queryParams) => { +const deleteRole = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `roles/${queryParams}`, + prefix: `roles/${id}`, }); - console.log('Delete API Response:', response); - - // Check for errors - if (response.statusCode !== 200) { - let errorMessage = response.message || 'Gagal menghapus role'; - - // Handle foreign key constraint - if (errorMessage.includes('REFERENCE constraint') || errorMessage.includes('foreign key')) { - errorMessage = 'Role tidak dapat dihapus karena masih digunakan oleh user.'; - } - - return { - statusCode: response.statusCode, - data: response.data, - message: errorMessage, - }; - } - - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message, - }; + return response.data; }; export { getAllRole, getRoleById, createRole, updateRole, deleteRole }; diff --git a/src/api/user-admin.jsx b/src/api/user-admin.jsx index 72dba82..a1eeef5 100644 --- a/src/api/user-admin.jsx +++ b/src/api/user-admin.jsx @@ -1,15 +1,11 @@ -// user-admin.jsx -import axios from 'axios'; import { SendRequest } from '../components/Global/ApiRequest'; -const baseURL = import.meta.env.VITE_API_SERVER; - const getAllUser = async (queryParams) => { const response = await SendRequest({ method: 'get', prefix: `admin-user?${queryParams.toString()}`, }); - return response; + return response.data; }; const getUserDetail = async (id) => { @@ -17,7 +13,7 @@ const getUserDetail = async (id) => { method: 'get', prefix: `admin-user/${id}`, }); - return response; + return response.data; }; const updateUser = async (id, data) => { @@ -26,7 +22,7 @@ const updateUser = async (id, data) => { prefix: `admin-user/${id}`, params: data, }); - return response; + return response.data; }; const deleteUser = async (id) => { @@ -34,7 +30,7 @@ const deleteUser = async (id) => { method: 'delete', prefix: `admin-user/${id}`, }); - return response; + return response.data; }; const approvalUser = async (id, queryParams) => { @@ -43,42 +39,7 @@ const approvalUser = async (id, queryParams) => { prefix: `admin-user/approve/${id}`, params: queryParams, }); - return response; + return response.data; }; -const uploadFile = async (formData) => { - try { - const token = localStorage.getItem('token')?.replace(/"/g, '') || ''; - const url = `${baseURL}/file-upload`; - - const response = await axios.post(url, formData, { - headers: { - Authorization: `Bearer ${token}`, - 'Accept-Language': 'en_US', - 'Content-Type': 'multipart/form-data', - }, - }); - - return { - statusCode: response.data?.statusCode ?? 0, - message: response.data?.message ?? '', - data: response.data?.data ?? {}, - }; - } catch (error) { - console.error('❌ ERROR di uploadFile:', error); - return { - statusCode: error?.response?.status || 500, - message: error?.response?.data?.message || 'Upload gagal', - data: {}, - }; - } -}; - - - - - - - - -export { getAllUser, getUserDetail, updateUser, deleteUser, approvalUser, uploadFile }; \ No newline at end of file +export { getAllUser, getUserDetail, updateUser, deleteUser, approvalUser }; diff --git a/src/api/user.jsx b/src/api/user.jsx index 208f5ba..7c206e5 100644 --- a/src/api/user.jsx +++ b/src/api/user.jsx @@ -1,97 +1,12 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllUser = async (queryParams) => { - try { - console.log('getAllUser queryParams:', queryParams.toString()); + const response = await SendRequest({ + method: 'get', + prefix: `user?${queryParams.toString()}`, + }); - const response = await SendRequest({ - method: 'get', - prefix: `user?${queryParams.toString()}`, - }); - - console.log('getAllUser response:', response); - - // Backend now handles pagination, just return the response - // Expected backend response structure: - // { - // statusCode: 200, - // data: [...users], - // paging: { page, limit, total, page_total } - // } - - // Check if backend returns paginated data - if (response.paging) { - // Filter out super admin users (is_sa = true) - const allData = response.data || []; - const filteredData = allData.filter(user => user.is_sa !== true && user.is_sa !== 1); - - // Recalculate pagination info after filtering - const totalAfterFilter = filteredData.length; - const currentPage = response.paging.page || 1; - const currentLimit = response.paging.limit || 10; - - return { - status: response.statusCode || 200, - data: { - data: filteredData, - paging: { - page: currentPage, - limit: currentLimit, - total: totalAfterFilter, - page_total: Math.ceil(totalAfterFilter / currentLimit) - }, - total: totalAfterFilter - } - }; - } - - // 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 || []; - - // Filter out users with is_sa = true or 1 (client-side filtering) - const filteredData = allData.filter(user => user.is_sa !== true && user.is_sa !== 1); - const totalData = filteredData.length; - - // Client-side pagination - const startIndex = (currentPage - 1) * currentLimit; - const endIndex = startIndex + currentLimit; - const paginatedData = filteredData.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('getAllUser error:', error); - // Return empty data on error to prevent app crash - return { - status: 500, - data: { - data: [], - paging: { - page: 1, - limit: 10, - total: 0, - page_total: 0 - }, - total: 0 - }, - error: error.message - }; - } + return response.data; }; const getUserById = async (id) => { @@ -99,6 +14,7 @@ const getUserById = async (id) => { method: 'get', prefix: `user/${id}`, }); + return response.data; }; @@ -108,12 +24,8 @@ const createUser = async (queryParams) => { prefix: `user`, params: queryParams, }); - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const updateUser = async (user_id, queryParams) => { @@ -122,12 +34,8 @@ const updateUser = async (user_id, queryParams) => { prefix: `user/${user_id}`, params: queryParams, }); - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const deleteUser = async (queryParams) => { @@ -135,12 +43,8 @@ const deleteUser = async (queryParams) => { method: 'delete', prefix: `user/${queryParams}`, }); - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const approveUser = async (user_id) => { @@ -148,12 +52,8 @@ const approveUser = async (user_id) => { method: 'put', prefix: `user/${user_id}/approve`, }); - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const rejectUser = async (user_id) => { @@ -161,12 +61,8 @@ const rejectUser = async (user_id) => { method: 'put', prefix: `user/${user_id}/reject`, }); - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const toggleActiveUser = async (user_id, is_active) => { @@ -174,15 +70,11 @@ const toggleActiveUser = async (user_id, is_active) => { method: 'put', prefix: `user/${user_id}`, params: { - is_active: is_active + is_active: is_active, }, }); - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message - }; + + return response.data; }; const changePassword = async (user_id, new_password) => { @@ -190,18 +82,21 @@ const changePassword = async (user_id, new_password) => { method: 'put', prefix: `user/change-password/${user_id}`, params: { - new_password: new_password + new_password: new_password, }, }); - console.log('Change Password Response:', response); - - // Return full response with statusCode - return { - statusCode: response.statusCode || 200, - data: response.data, - message: response.message || 'Password berhasil diubah' - }; + return response.data; }; -export { getAllUser, getUserById, createUser, updateUser, deleteUser, approveUser, rejectUser, toggleActiveUser, changePassword }; \ No newline at end of file +export { + getAllUser, + getUserById, + createUser, + updateUser, + deleteUser, + approveUser, + rejectUser, + toggleActiveUser, + changePassword, +}; diff --git a/src/components/Global/ApiRequest.jsx b/src/components/Global/ApiRequest.jsx index 78441e9..cf166cf 100644 --- a/src/components/Global/ApiRequest.jsx +++ b/src/components/Global/ApiRequest.jsx @@ -1,170 +1,171 @@ -import axios from "axios"; -import Swal from "sweetalert2"; +import axios from 'axios'; +import Swal from 'sweetalert2'; const baseURL = import.meta.env.VITE_API_SERVER; const instance = axios.create({ - baseURL, - withCredentials: true, + baseURL, + withCredentials: true, }); // axios khusus refresh const refreshApi = axios.create({ - baseURL, - withCredentials: true, + baseURL, + withCredentials: true, }); - instance.interceptors.response.use( - (response) => response, - async (error) => { - const originalRequest = error.config; + (response) => response, + 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 - }); + 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; + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; - try { - console.log("🔄 Refresh token dipanggil..."); - const refreshRes = await refreshApi.post("/auth/refresh-token"); + try { + console.log('🔄 Refresh token dipanggil...'); + const refreshRes = await refreshApi.post('/auth/refresh-token'); - const newAccessToken = refreshRes.data.data.accessToken; - localStorage.setItem("token", newAccessToken); - console.log("✅ Token refreshed successfully"); + 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}`; + // 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.response?.data || refreshError.message); - localStorage.clear(); - window.location.href = "/signin"; - } + console.log('🔁 Retrying original request...'); + return instance(originalRequest); + } catch (refreshError) { + console.error( + '❌ Refresh token gagal:', + refreshError.response?.data || refreshError.message + ); + localStorage.clear(); + window.location.href = '/signin'; + } + } + + return Promise.reject(error); } - - return Promise.reject(error); - } ); -async function ApiRequest({ - method = "GET", - params = {}, - prefix = "/", - token = true, -} = {}) { - const isFormData = params instanceof FormData; +async function ApiRequest({ method = 'GET', params = {}, prefix = '/', token = true } = {}) { + const isFormData = params instanceof FormData; - const request = { - method, - url: prefix, - data: params, - headers: { - "Accept-Language": "en_US", - ...(isFormData ? {} : { "Content-Type": "application/json" }), - }, - }; + const request = { + method, + url: prefix, + data: params, + headers: { + 'Accept-Language': 'en_US', + ...(isFormData ? {} : { 'Content-Type': 'application/json' }), + }, + }; - const rawToken = localStorage.getItem("token"); - if (token && rawToken) { - 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); + const rawToken = localStorage.getItem('token'); + if (token && rawToken) { + 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'); } - return { ...error.response, error: true }; - } + 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); + } + + return { ...error.response, error: true }; + } } -async function cekError(status, message = "") { - if (status === 403) { - await Swal.fire({ - icon: "warning", - title: "Forbidden", - text: message, - }); - } else if (status >= 500) { - await Swal.fire({ - icon: "error", - title: "Server Error", - text: message, - }); - } else { - await Swal.fire({ - icon: "warning", - title: "Peringatan", - text: message, - }); - } +async function cekError(status, message = '') { + if (status === 403) { + await Swal.fire({ + icon: 'warning', + title: 'Forbidden', + text: message, + }); + } else if (status >= 500) { + await Swal.fire({ + icon: 'error', + title: 'Server Error', + text: message, + }); + } else { + await Swal.fire({ + icon: 'warning', + title: 'Peringatan', + text: message, + }); + } } const SendRequest = async (queryParams) => { - try { - const response = await ApiRequest(queryParams); - console.log("📦 SendRequest response:", { - hasError: response.error, - status: response.status, - statusCode: response.data?.statusCode, - data: response.data - }); + try { + const response = await ApiRequest(queryParams); + console.log('📦 SendRequest response:', { + hasError: response.error, + status: response.status, + statusCode: response.data?.statusCode, + data: response.data, + }); - // 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); + // 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 consistent error structure instead of empty array + return { + statusCode: response.status || 500, + message: errorMsg, + data: null, + error: true, + }; + } + + return response || { 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, + }; } - - 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 - }; - } }; export { ApiRequest, SendRequest }; diff --git a/src/components/Global/CardList.jsx b/src/components/Global/CardList.jsx index 624bca8..ee9cc9e 100644 --- a/src/components/Global/CardList.jsx +++ b/src/components/Global/CardList.jsx @@ -37,7 +37,7 @@ const CardList = ({ return ( {data.map((item) => ( - +
- {column.map((itemCard) => ( - <> - {!itemCard.hidden && !itemCard.render && ( -

+ {column.map((itemCard, index) => ( + + {!itemCard.hidden && itemCard.title !== 'No' && itemCard.title !== 'Aksi' && ( +

{itemCard.title}:{' '} - {item[itemCard.key]} + {itemCard.render + ? itemCard.render(item[itemCard.dataIndex], item, index) + : item[itemCard.dataIndex] || item[itemCard.key] || '-' + }

)} - {itemCard.render && itemCard.render} - + ))}
diff --git a/src/components/Global/TableList.jsx b/src/components/Global/TableList.jsx index 4630bc8..3f50c5e 100644 --- a/src/components/Global/TableList.jsx +++ b/src/components/Global/TableList.jsx @@ -1,18 +1,6 @@ import React, { memo, useState, useEffect, useRef } from 'react'; import { Table, Pagination, Row, Col, Card, Grid, Button, Typography, Tag, Segmented } from 'antd'; -import { - PlusOutlined, - FilterOutlined, - EditOutlined, - DeleteOutlined, - EyeOutlined, - SearchOutlined, - FilePdfOutlined, - AppstoreOutlined, - TableOutlined, -} from '@ant-design/icons'; -import { setFilterData } from './DataFilter'; -import CardDevice from '../../pages/master/device/component/CardDevice'; +import { AppstoreOutlined, TableOutlined } from '@ant-design/icons'; import CardList from './CardList'; const { Text } = Typography; @@ -33,16 +21,12 @@ const TableList = memo(function TableList({ const [gridLoading, setGridLoading] = useState(false); const [data, setData] = useState([]); - const [pagingResponse, setPagingResponse] = useState({ - totalData: '', - perPage: '', - totalPage: '', - }); const [pagination, setPagination] = useState({ - current: 1, - limit: 10, - total: 0, + current_page: 1, + current_limit: 10, + total_limit: 0, + total_page: 1, }); const [viewMode, setViewMode] = useState('card'); @@ -50,42 +34,41 @@ const TableList = memo(function TableList({ const { useBreakpoint } = Grid; useEffect(() => { - filter(1, 10); + filter(1, pagination.current_limit); }, [triger]); const filter = async (currentPage, pageSize) => { setGridLoading(true); const paging = { - page: currentPage, - limit: pageSize, + page: Number(currentPage), + limit: Number(pageSize), }; const param = new URLSearchParams({ ...paging, ...queryParams }); - const resData = await getData(param); + + setData(resData?.data ?? []); + + const pagingData = resData?.paging; + + if (pagingData) { + setPagination((prev) => ({ + ...prev, + current_page: pagingData.current_page || 1, + current_limit: pagingData.current_limit || 10, + total_limit: pagingData.total_limit || 0, + total_page: pagingData.total_page || 1, + })); + } + if (resData) { setTimeout(() => { setGridLoading(false); }, 900); - } - - setData(resData.data.data ?? []); - setFilterData(resData.data.data ?? []); - - if (resData.status == 200) { - setPagingResponse({ - totalData: resData.paging.total_limit, - perPage: resData.paging.page_total, - totalPage: resData.paging.total_page, - }); - - setPagination((prev) => ({ - ...prev, - current: resData.paging.current_page, - limit: resData.paging.current_limit, - total: resData.paging.total_limit, - })); + } else { + setGridLoading(false); + return; } }; @@ -93,7 +76,7 @@ const TableList = memo(function TableList({ setPagination((prev) => ({ ...prev, current: page, - pageSize, + limit: pageSize, })); filter(page, pageSize); }; @@ -138,8 +121,8 @@ const TableList = memo(function TableList({
- Menampilkan {pagingResponse.totalPage} Data dari {pagingResponse.perPage}{' '} - Halaman + Menampilkan {pagination.current_limit} data halaman{' '} + {pagination.current_page} dari total {pagination.total_limit} data
@@ -147,9 +130,9 @@ const TableList = memo(function TableList({ showSizeChanger onChange={handlePaginationChange} onShowSizeChange={handlePaginationChange} - current={pagination.current} - pageSize={pagination.pageSize} - total={pagination.total} + current={pagination.current_page} + pageSize={pagination.current_limit} + total={pagination.total_limit} />
diff --git a/src/components/Global/ToastNotif.jsx b/src/components/Global/ToastNotif.jsx index cf9d8ad..2cdeaf9 100644 --- a/src/components/Global/ToastNotif.jsx +++ b/src/components/Global/ToastNotif.jsx @@ -16,6 +16,7 @@ const NotifOk = ({ icon, title, message }) => { icon: icon, title: title, text: message, + html: message.replace(/\n/g, '
'), }); }; diff --git a/src/layout/LayoutMenu.jsx b/src/layout/LayoutMenu.jsx index 6e0bc57..e3c9d62 100644 --- a/src/layout/LayoutMenu.jsx +++ b/src/layout/LayoutMenu.jsx @@ -26,6 +26,9 @@ import { TeamOutlined, ClockCircleOutlined, CalendarOutlined, + DesktopOutlined, + NodeExpandOutlined, + GroupOutlined, } from '@ant-design/icons'; const { Text } = Typography; @@ -40,6 +43,48 @@ const allItems = [ ), }, + { + key: 'dashboard-svg', + icon: , + label: 'Dashboard', + children: [ + { + key: 'dashboard-svg-overview', + icon: , + label: Overview, + }, + { + key: 'dashboard-svg-compressor-a', + icon: , + label: Compressor A, + }, + { + key: 'dashboard-svg-compressor-b', + icon: , + label: Compressor B, + }, + { + key: 'dashboard-svg-compressor-c', + icon: , + label: Compressor C, + }, + { + key: 'dashboard-svg-airdryer-a', + icon: , + label: Air Dryer A, + }, + { + key: 'dashboard-svg-airdryer-b', + icon: , + label: Air Dryer B, + }, + { + key: 'dashboard-svg-airdryer-c', + icon: , + label: Air Dryer C, + }, + ], + }, { key: 'master', icon: , @@ -60,16 +105,16 @@ const allItems = [ icon: , label: Device, }, - { - key: 'master-tag', - icon: , - label: Tag, - }, { key: 'master-unit', icon: , label: Unit, }, + { + key: 'master-tag', + icon: , + label: Tag, + }, { key: 'master-status', icon: , @@ -163,6 +208,7 @@ const LayoutMenu = () => { if (pathname === '/notification') return 'notification'; if (pathname === '/event-alarm') return 'event-alarm'; if (pathname === '/jadwal-shift') return 'jadwal-shift'; + if (pathname === '/dashboard-svg') return 'dashboard-svg'; // Handle master routes if (pathname.startsWith('/master/')) { @@ -170,6 +216,12 @@ const LayoutMenu = () => { return `master-${subPath}`; } + // Handle master routes + if (pathname.startsWith('/dashboard-svg/')) { + const subPath = pathParts[1]; + return `dashboard-svg-${subPath}`; + } + // Handle history routes if (pathname.startsWith('/history/')) { const subPath = pathParts[1]; @@ -188,6 +240,7 @@ const LayoutMenu = () => { // Function to get parent key from menu key const getParentKey = (key) => { if (key.startsWith('master-')) return 'master'; + if (key.startsWith('dashboard-svg-')) return 'dashboard-svg'; if (key.startsWith('history-')) return 'history'; if (key.startsWith('shift-')) return 'shift-management'; return null; diff --git a/src/pages/auth/Registration.jsx b/src/pages/auth/Registration.jsx deleted file mode 100644 index a836ffd..0000000 --- a/src/pages/auth/Registration.jsx +++ /dev/null @@ -1,461 +0,0 @@ -// import React, { useState } from 'react'; -// import { -// Flex, -// Input, -// InputNumber, -// Form, -// Button, -// Card, -// Space, -// Upload, -// Divider, -// Tooltip, -// message, -// Select, -// } from 'antd'; -// import { -// UploadOutlined, -// UserOutlined, -// IdcardOutlined, -// PhoneOutlined, -// LockOutlined, -// InfoCircleOutlined, -// MailOutlined, -// } from '@ant-design/icons'; -// const { Item } = Form; -// const { Option } = Select; -// import sypiu_ggcp from 'assets/sypiu_ggcp.jpg'; -// import { useNavigate } from 'react-router-dom'; -// import { register, uploadFile, checkUsername } from '../../api/auth'; -// import { NotifAlert } from '../../components/Global/ToastNotif'; - -// const Registration = () => { -// const [form] = Form.useForm(); -// const navigate = useNavigate(); -// const [loading, setLoading] = useState(false); -// const [fileListKontrak, setFileListKontrak] = useState([]); -// const [fileListHsse, setFileListHsse] = useState([]); -// const [fileListIcon, setFileListIcon] = useState([]); - -// // Daftar jenis vendor -// const vendorTypes = [ -// { vendor_type: 1, vendor_type_name: 'One-Time' }, -// { vendor_type: 2, vendor_type_name: 'Rutin' }, -// ]; - -// const onFinish = async (values) => { -// setLoading(true); -// try { -// if (!fileListKontrak.length || !fileListHsse.length) { -// message.error('Harap unggah Lampiran Kontrak Kerja dan HSSE Plan!'); -// setLoading(false); -// return; -// } - -// const formData = new FormData(); -// formData.append('path_kontrak', fileListKontrak[0].originFileObj); -// formData.append('path_hse_plant', fileListHsse[0].originFileObj); -// if (fileListIcon.length) { -// formData.append('path_icon', fileListIcon[0].originFileObj); -// } - -// const uploadResponse = await uploadFile(formData); - -// if (!uploadResponse.data?.pathKontrak && !uploadResponse.data?.pathHsePlant) { -// message.error(uploadResponse.message || 'Gagal mengunggah file.'); -// setLoading(false); -// return; -// } - -// const params = new URLSearchParams({ username: values.username }); -// const usernameCheck = await checkUsername(params); -// if (usernameCheck.data.data && usernameCheck.data.data.available === false) { -// NotifAlert({ -// icon: 'error', -// title: 'Gagal', -// message: usernameCheck.data.message || 'Terjadi kesalahan, silakan coba lagi', -// }); -// setLoading(false); -// return; -// } - -// const registerData = { -// nama_perusahaan: values.namaPerusahaan, -// no_kontak_wo: values.noKontakWo, -// path_kontrak: uploadResponse.data.pathKontrak || '', -// durasi: values.durasiPekerjaan, -// nilai_csms: values.nilaiCsms.toString(), -// vendor_type: values.jenisVendor, // Tambahkan jenis vendor ke registerData -// path_hse_plant: uploadResponse.data.pathHsePlant || '', -// nama_leader: values.penanggungJawab, -// no_identitas: values.noIdentitas, -// no_hp: values.noHandphone, -// email_register: values.username, -// password_register: values.password, -// }; - -// const response = await register(registerData); - -// if (response.data?.id_register) { -// message.success('Data berhasil disimpan!'); - -// try { -// form.resetFields(); -// setFileListKontrak([]); -// setFileListHsse([]); -// setFileListIcon([]); - -// navigate('/registration-submitted'); -// } catch (postSuccessError) { -// message.warning( -// 'Registrasi berhasil, tetapi ada masalah setelahnya. Silakan ke halaman login secara manual.' -// ); -// } -// } else { -// message.error(response.message || 'Pendaftaran gagal, silakan coba lagi.'); -// } -// } catch (error) { -// console.error('Error saat registrasi:', error); -// NotifAlert({ -// icon: 'error', -// title: 'Gagal', -// message: error.message || 'Terjadi kesalahan, silakan coba lagi', -// }); -// } finally { -// setLoading(false); -// } -// }; - -// const onCancel = () => { -// form.resetFields(); -// setFileListKontrak([]); -// setFileListHsse([]); -// setFileListIcon([]); -// navigate('/signin'); -// }; - -// const handleChangeKontrak = ({ fileList }) => { -// setFileListKontrak(fileList); -// }; - -// const handleChangeHsse = ({ fileList }) => { -// setFileListHsse(fileList); -// }; - -// const handleChangeIcon = ({ fileList }) => { -// setFileListIcon(fileList); -// }; - -// const beforeUpload = (file, fieldname) => { -// const isValidType = [ -// 'image/jpeg', -// 'image/jpg', -// 'image/png', -// fieldname !== 'path_icon' ? 'application/pdf' : null, -// ] -// .filter(Boolean) -// .includes(file.type); -// const isNotEmpty = file.size > 0; -// const isSizeValid = file.size / 1024 / 1024 < 10; - -// if (!isValidType) { -// message.error( -// `Hanya file ${ -// fieldname === 'path_icon' ? 'JPG/PNG' : 'PDF/JPG/PNG' -// } yang diperbolehkan!` -// ); -// return false; -// } -// if (!isNotEmpty) { -// message.error('File tidak boleh kosong!'); -// return false; -// } -// if (!isSizeValid) { -// message.error('Ukuran file maksimal 10MB!'); -// return false; -// } -// return true; -// }; - -// return ( -// -// -//

Formulir Pendaftaran

-// -//
-// } -// > -//
-// {/* Informasi Perusahaan */} -// -// Informasi Perusahaan -// -// -// } -// placeholder="Masukkan Nama Perusahaan" -// size="large" -// /> -// -// -// -// -// -// -// -// -// beforeUpload(file, 'path_kontrak')} -// fileList={fileListKontrak} -// onChange={handleChangeKontrak} -// maxCount={1} -// > -// -// -// -// -// beforeUpload(file, 'path_hse_plant')} -// fileList={fileListHsse} -// onChange={handleChangeHsse} -// maxCount={1} -// > -// -// -// -// -// -// -// -// -// - -// {/* Informasi Penanggung Jawab */} -// -// Informasi Penanggung Jawab -// -// -// } -// placeholder="Masukkan Nama Penanggung Jawab" -// size="large" -// /> -// -// -// } -// placeholder="Masukkan No Handphone (+62)" -// size="large" -// /> -// -// -// } -// placeholder="Masukkan No Identitas" -// size="large" -// /> -// - -// {/* Akun Pengguna */} -// -// Akun Pengguna (digunakan sebagai user login SYPIU) -// -// -// } -// placeholder="Masukkan Email" -// size="large" -// /> -// -// -// } -// placeholder="Masukkan Password" -// size="large" -// /> -// - -// {/* Tombol */} -// -// -// -// -// -// -//
-// -// -// ); -// }; - -// export default Registration; diff --git a/src/pages/auth/SignIn.jsx b/src/pages/auth/SignIn.jsx index f63ea0d..3e95747 100644 --- a/src/pages/auth/SignIn.jsx +++ b/src/pages/auth/SignIn.jsx @@ -31,8 +31,9 @@ const SignIn = () => { prefix: 'auth/generate-captcha', token: false, }); - setCaptchaSvg(res.data.svg || ''); - setCaptchaText(res.data.text || ''); + + setCaptchaSvg(res.data?.data?.svg || ''); + setCaptchaText(res.data?.data?.text || ''); } catch (err) { console.error('Error fetching captcha:', err); } @@ -57,8 +58,8 @@ const SignIn = () => { withCredentials: true, }); - const user = res?.data?.user || res?.user; - const accessToken = res?.data?.accessToken || res?.tokens?.accessToken; + const user = res?.data?.data?.user || res?.user; + const accessToken = res?.data?.data?.accessToken || res?.tokens?.accessToken; if (user && accessToken) { localStorage.setItem('token', accessToken); diff --git a/src/pages/home/SvgAirDryerA.jsx b/src/pages/home/SvgAirDryerA.jsx new file mode 100644 index 0000000..37f07a3 --- /dev/null +++ b/src/pages/home/SvgAirDryerA.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/air_dryer_A_rev.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgAirDryerA = () => { + return ( + + + + ); +}; + +export default SvgAirDryerA; diff --git a/src/pages/home/SvgAirDryerB.jsx b/src/pages/home/SvgAirDryerB.jsx new file mode 100644 index 0000000..6250cea --- /dev/null +++ b/src/pages/home/SvgAirDryerB.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/air_dryer_B_rev.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgAirDryerB = () => { + return ( + + + + ); +}; + +export default SvgAirDryerB; diff --git a/src/pages/home/SvgAirDryerC.jsx b/src/pages/home/SvgAirDryerC.jsx new file mode 100644 index 0000000..cdb61ce --- /dev/null +++ b/src/pages/home/SvgAirDryerC.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/air_dryer_C_rev.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgAirDryerC = () => { + return ( + + + + ); +}; + +export default SvgAirDryerC; diff --git a/src/pages/home/SvgCompressorA.jsx b/src/pages/home/SvgCompressorA.jsx new file mode 100644 index 0000000..bc7ffd2 --- /dev/null +++ b/src/pages/home/SvgCompressorA.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/test-new.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgCompressorA = () => { + return ( + + + + ); +}; + +export default SvgCompressorA; diff --git a/src/pages/home/SvgCompressorB.jsx b/src/pages/home/SvgCompressorB.jsx new file mode 100644 index 0000000..fbf5871 --- /dev/null +++ b/src/pages/home/SvgCompressorB.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/test-new.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgCompressorB = () => { + return ( + + + + ); +}; + +export default SvgCompressorB; diff --git a/src/pages/home/SvgCompressorC.jsx b/src/pages/home/SvgCompressorC.jsx new file mode 100644 index 0000000..0983260 --- /dev/null +++ b/src/pages/home/SvgCompressorC.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/test-new.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgCompressorC = () => { + return ( + + + + ); +}; + +export default SvgCompressorC; diff --git a/src/pages/home/SvgOverview.jsx b/src/pages/home/SvgOverview.jsx new file mode 100644 index 0000000..d5b432d --- /dev/null +++ b/src/pages/home/SvgOverview.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'react'; +import { Card, Typography, Flex } from 'antd'; +import { setValSvg } from '../../components/Global/MqttConnection'; +import SvgTemplate from './SvgTemplate'; +import SvgViewer from './SvgViewer'; + +const { Text } = Typography; + +const filePathSvg = '/svg/test-new.svg'; +const topicMqtt = 'PIU_GGCP/Devices/PB'; + +const SvgOverview = () => { + return ( + + + + ); +}; + +export default SvgOverview; diff --git a/src/pages/home/SvgTemplate.jsx b/src/pages/home/SvgTemplate.jsx new file mode 100644 index 0000000..da2a702 --- /dev/null +++ b/src/pages/home/SvgTemplate.jsx @@ -0,0 +1,19 @@ +const SvgTemplate = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + +export default SvgTemplate; diff --git a/src/pages/home/SvgTest.jsx b/src/pages/home/SvgTest.jsx index 1b65a69..87d8beb 100644 --- a/src/pages/home/SvgTest.jsx +++ b/src/pages/home/SvgTest.jsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { Card, Typography, Flex } from 'antd'; // import { ReactSVG } from 'react-svg'; import { setValSvg } from '../../components/Global/MqttConnection'; +import { ReactSVG } from 'react-svg'; const { Text } = Typography; diff --git a/src/pages/home/SvgViewer.jsx b/src/pages/home/SvgViewer.jsx new file mode 100644 index 0000000..242f012 --- /dev/null +++ b/src/pages/home/SvgViewer.jsx @@ -0,0 +1,19 @@ +// SvgViewer.jsx +import { ReactSVG } from 'react-svg'; + +const SvgViewer = ({ filePathSvg, topicMqtt, setValSvg }) => { + return ( + { + svg.setAttribute('width', '100%'); + svg.setAttribute('height', '100%'); + svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); + if (setValSvg) setValSvg(topicMqtt, svg); + }} + style={{ width: '100%', height: '100%' }} + /> + ); +}; + +export default SvgViewer; diff --git a/src/pages/jadwalShift/component/ListJadwalShift.jsx b/src/pages/jadwalShift/component/ListJadwalShift.jsx index 23f5dae..631c3c9 100644 --- a/src/pages/jadwalShift/component/ListJadwalShift.jsx +++ b/src/pages/jadwalShift/component/ListJadwalShift.jsx @@ -10,68 +10,42 @@ import { import { NotifAlert, NotifConfirmDialog } from '../../../components/Global/ToastNotif'; import { useNavigate } from 'react-router-dom'; import TableList from '../../../components/Global/TableList'; - -// --- DUMMY DATA (Initial State) --- // -const initialDummyData = [ - { - id: 1, - nama_shift: 'Shift Pagi', - jam_masuk: '07:00', - jam_pulang: '15:00', - username: 'd.sanjaya', - nama_employee: 'Dede Sanjaya', - whatsapp: '081234567890' - }, - { - id: 2, - nama_shift: 'Shift Siang', - jam_masuk: '15:00', - jam_pulang: '23:00', - username: 'a.wijaya', - nama_employee: 'Andi Wijaya', - whatsapp: '081234567891' - }, - { - id: 3, - nama_shift: 'Shift Malam', - jam_masuk: '23:00', - jam_pulang: '07:00', - username: 'b.cahya', - nama_employee: 'Budi Cahya', - whatsapp: '081234567892' - }, -]; +import { getAllJadwalShift, deleteJadwalShift } from '../../../api/jadwal-shift'; const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ { - title: 'Nama Karyawan', - dataIndex: 'nama_employee', - key: 'nama_employee', - }, - { - title: 'Username', - dataIndex: 'username', - key: 'username', + title: 'Tanggal Jadwal', + dataIndex: 'schedule_date', + key: 'schedule_date', + render: (date) => date ? new Date(date).toLocaleDateString('id-ID') : '-', }, { title: 'Nama Shift', - dataIndex: 'nama_shift', - key: 'nama_shift', + dataIndex: 'shift_name', + key: 'shift_name', + render: (text) => text || '-', }, { title: 'Jam Masuk', - dataIndex: 'jam_masuk', - key: 'jam_masuk', + dataIndex: 'start_time', + key: 'start_time', + render: (time) => time || '-', }, { title: 'Jam Pulang', - dataIndex: 'jam_pulang', - key: 'jam_pulang', + dataIndex: 'end_time', + key: 'end_time', + render: (time) => time || '-', }, { - title: 'Whatsapp', - dataIndex: 'whatsapp', - key: 'whatsapp', + title: 'Status', + dataIndex: 'is_active', + key: 'is_active', + render: (isActive) => ( + + {isActive ? 'Aktif' : 'Tidak Aktif'} + + ), }, { title: 'Aksi', @@ -101,39 +75,42 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ ]; const ListJadwalShift = memo(function ListJadwalShift(props) { - const [dataSource, setDataSource] = useState(initialDummyData); const [trigerFilter, setTrigerFilter] = useState(false); const defaultFilter = { criteria: '' }; const [formDataFilter, setFormDataFilter] = useState(defaultFilter); const [searchValue, setSearchValue] = useState(''); const navigate = useNavigate(); - // --- DUMMY API --- // - const getDummyData = (queryParams) => { - return new Promise((resolve) => { - const { criteria } = queryParams; - let data = dataSource; - if (criteria) { - data = dataSource.filter(item => - item.nama_employee.toLowerCase().includes(criteria.toLowerCase()) || - item.username.toLowerCase().includes(criteria.toLowerCase()) - ); - } - setTimeout(() => { - resolve({ - status: 200, - data: { - data: data, - paging: { - page: 1, - limit: 10, - total: data.length, - page_total: 1 - } + const getData = async (queryParams) => { + try { + const params = new URLSearchParams({ + page: queryParams.page || 1, + limit: queryParams.limit || 10, + criteria: queryParams.criteria || '' + }); + + const response = await getAllJadwalShift(params); + return response; + } catch (error) { + console.error('Error fetching jadwal shift:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: 'Gagal mengambil data jadwal shift.', + }); + return { + status: 500, + data: { + data: [], + paging: { + page: 1, + limit: 10, + total: 0, + page_total: 0 } - }); - }, 100); - }); + } + }; + } }; useEffect(() => { @@ -146,7 +123,7 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { } else { navigate('/signin'); } - }, [props.actionMode, dataSource]); // Added dataSource to dependency array + }, [props.actionMode]); const doFilter = () => { setTrigerFilter((prev) => !prev); @@ -179,23 +156,41 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { }; const showDeleteDialog = (param) => { + const dateStr = param.schedule_date ? new Date(param.schedule_date).toLocaleDateString('id-ID') : 'tanggal tidak diketahui'; NotifConfirmDialog({ icon: 'question', title: 'Konfirmasi Hapus', - message: `Jadwal shift untuk "${param.nama_employee}" akan dihapus?`, - onConfirm: () => handleDelete(param.id), + message: `Jadwal shift tanggal ${dateStr} akan dihapus?`, + onConfirm: () => handleDelete(param.schedule_id), onCancel: () => props.setSelectedData(null), }); }; - const handleDelete = (id) => { - setDataSource(prevData => prevData.filter(item => item.id !== id)); - NotifAlert({ - icon: 'success', - title: 'Berhasil', - message: 'Data Jadwal Shift berhasil dihapus.', - }); - doFilter(); // Trigger a re-fetch from the new state + const handleDelete = async (id) => { + try { + const response = await deleteJadwalShift(id); + if (response.statusCode === 200) { + NotifAlert({ + icon: 'success', + title: 'Berhasil', + message: 'Data Jadwal Shift berhasil dihapus.', + }); + doFilter(); + } else { + NotifAlert({ + icon: 'error', + title: 'Error', + message: response.message || 'Gagal menghapus data jadwal shift.', + }); + } + } catch (error) { + console.error('Error deleting jadwal shift:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: 'Terjadi kesalahan saat menghapus data.', + }); + } }; return ( @@ -206,7 +201,7 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { { const value = e.target.value; @@ -263,11 +258,11 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { { + return { status: 200, data: { data: mockApi.errorCodes.filter(ec => ec.brand_id == brandId) } }; + }, + createErrorCode: async (data) => { + const newId = Math.max(...mockApi.errorCodes.map(ec => ec.error_code_id)) + 1; + const newErrorCode = { ...data, error_code_id: newId }; + mockApi.errorCodes.push(newErrorCode); + return { statusCode: 201, data: newErrorCode }; + }, + updateErrorCode: async (id, data) => { + const index = mockApi.errorCodes.findIndex(ec => ec.error_code_id === id); + if (index !== -1) { + mockApi.errorCodes[index] = { ...mockApi.errorCodes[index], ...data }; + return { statusCode: 200, data: mockApi.errorCodes[index] }; + } + return { statusCode: 404, message: 'Not Found' }; + }, + deleteErrorCode: async (id) => { + const index = mockApi.errorCodes.findIndex(ec => ec.error_code_id === id); + if (index !== -1) { + mockApi.errorCodes.splice(index, 1); + return { statusCode: 200 }; + } + return { statusCode: 404, message: 'Not Found' }; + } +}; + +const ErrorCodePage = () => { + const { brandId } = useParams(); + const navigate = useNavigate(); + const [form] = Form.useForm(); + + const [errorCodes, setErrorCodes] = useState([]); + const [loading, setLoading] = useState(false); + const [isModalVisible, setIsModalVisible] = useState(false); + const [editingErrorCode, setEditingErrorCode] = useState(null); + + const fetchData = async () => { + setLoading(true); + const response = await mockApi.getAllErrorCodesByBrand(brandId); + if (response.status === 200) { + setErrorCodes(response.data.data); + } + setLoading(false); + }; + + useEffect(() => { + fetchData(); + }, [brandId]); + + const columns = [ + { title: 'Error Code', dataIndex: 'error_code', key: 'error_code' }, + { title: 'Description', dataIndex: 'description', key: 'description' }, + { + title: 'Action', + key: 'action', + render: (_, record) => ( + <> + + + + ), + }, + ]; + + const handleAdd = () => { + setEditingErrorCode(null); + form.resetFields(); + setIsModalVisible(true); + }; + + const handleEdit = (errorCode) => { + setEditingErrorCode(errorCode); + form.setFieldsValue(errorCode); + setIsModalVisible(true); + }; + + const handleDelete = async (id) => { + await mockApi.deleteErrorCode(id); + message.success('Error code deleted successfully'); + fetchData(); + }; + + const handleModalOk = async () => { + try { + const values = await form.validateFields(); + if (editingErrorCode) { + await mockApi.updateErrorCode(editingErrorCode.error_code_id, values); + message.success('Error code updated successfully'); + } else { + await mockApi.createErrorCode({ ...values, brand_id: brandId }); + message.success('Error code created successfully'); + } + setIsModalVisible(false); + fetchData(); + } catch (error) { + console.log('Validate Failed:', error); + } + }; + + return ( + + Manage Error Codes for Brand ID: {brandId} + + ({ data: { data: errorCodes } })} + triger={brandId} + /> + setIsModalVisible(false)} + > +
+ + + + + + +
+
+
+ ); +}; + +export default ErrorCodePage; diff --git a/src/pages/master/brand/FormBrand.jsx b/src/pages/master/brand/FormBrand.jsx new file mode 100644 index 0000000..13dabc2 --- /dev/null +++ b/src/pages/master/brand/FormBrand.jsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { Form, Input, Button, Typography, Card, message } from 'antd'; +import { useNavigate } from 'react-router-dom'; +import { createBrand } from '../../api/master-brand'; + +const { Title } = Typography; + +const FormBrand = () => { + const [form] = Form.useForm(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + + const onFinish = async (values) => { + setLoading(true); + try { + const response = await createBrand(values); + if (response.statusCode === 200 || response.statusCode === 201) { + message.success('Brand created successfully!'); + const newBrandId = response.data.brand_id; + // Redirect to the error code page for the new brand + navigate(`/master/brand/${newBrandId}/error-codes`); + } else { + message.error(response.message || 'Failed to create brand.'); + } + } catch (error) { + message.error('An error occurred while creating the brand.'); + console.error(error); + } + setLoading(false); + }; + + return ( + + Add New Brand +
+ + + + + + + +
+
+ ); +}; + +export default FormBrand; diff --git a/src/pages/master/brandDevice/AddBrandDevice.jsx b/src/pages/master/brandDevice/AddBrandDevice.jsx new file mode 100644 index 0000000..2f0f01d --- /dev/null +++ b/src/pages/master/brandDevice/AddBrandDevice.jsx @@ -0,0 +1,337 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Input, Divider, Typography, Switch, Button, Steps, Form, message, Table, Row, Col, Radio, Card, Tag, Upload, ConfigProvider } from 'antd'; +import { PlusOutlined, UploadOutlined } from '@ant-design/icons'; +import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif'; +import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; + +const { Text, Title } = Typography; +const { Step } = Steps; + +// Mock API for Error Codes (can be moved to a separate file later) +const mockErrorCodeApi = { + errorCodes: [], + createErrorCode: async (data) => { + const newId = mockErrorCodeApi.errorCodes.length > 0 ? Math.max(...mockErrorCodeApi.errorCodes.map(ec => ec.error_code_id)) + 1 : 1; + const newErrorCode = { ...data, error_code_id: newId }; + mockErrorCodeApi.errorCodes.push(newErrorCode); + return { statusCode: 201, data: newErrorCode }; + }, +}; + +const AddBrandDevice = () => { + const navigate = useNavigate(); + const { setBreadcrumbItems } = useBreadcrumb(); + const [brandForm] = Form.useForm(); + const [errorCodeForm] = Form.useForm(); + const [confirmLoading, setConfirmLoading] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [anotherSolutionType, setAnotherSolutionType] = useState(null); + const [fileList, setFileList] = useState([]); + + // Watch for form values changes to update the switch color + const statusValue = Form.useWatch('status', errorCodeForm); + + const defaultData = { + brandName: '', + brandType: '', + manufacturer: '', + model: '', + status: true, + brand_code: '', + country: '', + description: '', + }; + + const [formData, setFormData] = useState(defaultData); + const [errorCodes, setErrorCodes] = useState([]); + + const handleCancel = () => { + navigate('/master/brand-device'); + }; + + const handleNextStep = async () => { + try { + await brandForm.validateFields(); + setCurrentStep(1); + } catch (error) { + console.log('Validate Failed:', error); + NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'Harap isi semua kolom wajib untuk brand device!' }); + } + }; + + const handleFinish = async () => { + if (errorCodes.length === 0) { + NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'Silakan tambahkan minimal satu error code.' }); + return; + } + + setConfirmLoading(true); + try { + const finalFormData = { ...formData, status: formData.status ? 'Active' : 'Inactive' }; + console.log("Saving brand device:", finalFormData); + await new Promise((resolve) => setTimeout(resolve, 500)); + const newBrandDeviceId = Date.now(); + console.log("Brand device saved with ID:", newBrandDeviceId); + + console.log("Saving error codes:", errorCodes); + for (const errorCode of errorCodes) { + if (errorCode.another_solution === 'image' && errorCode.image) { + console.log(`Uploading image for error code ${errorCode.error_code}:`, errorCode.image.name); + } + await mockErrorCodeApi.createErrorCode({ + ...errorCode, + brand_device_id: newBrandDeviceId + }); + console.log("Saved error code:", errorCode.error_code); + } + + setConfirmLoading(false); + NotifOk({ icon: 'success', title: 'Berhasil', message: 'Brand Device dan Error Code berhasil disimpan.' }); + navigate('/master/brand-device'); + } catch (error) { + setConfirmLoading(false); + console.error("Failed to save data:", error); + NotifAlert({ + icon: "error", + title: "Gagal", + message: "Gagal menyimpan data. Silakan coba lagi.", + }); + } + }; + + const handleAddErrorCode = async () => { + try { + const values = await errorCodeForm.validateFields(); + const newErrorCode = { + ...values, + status: values.status === undefined ? true : values.status, + image: fileList.length > 0 ? fileList[0] : null, + key: `temp-${Date.now()}` + }; + setErrorCodes([...errorCodes, newErrorCode]); + message.success('Error code berhasil ditambahkan'); + errorCodeForm.resetFields(); + setAnotherSolutionType(null); + setFileList([]); + } catch (error) { + console.log('Validate Failed:', error); + NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'Harap isi semua kolom wajib untuk error code!' }); + } + }; + + const handleDeleteErrorCode = (key) => { + setErrorCodes(errorCodes.filter(item => item.key !== key)); + message.success('Error code berhasil dihapus'); + }; + + const uploadProps = { + onRemove: (file) => { + setFileList([]); + }, + beforeUpload: (file) => { + setFileList([file]); + return false; // Prevent auto-upload + }, + fileList, + }; + + const errorCodeColumns = [ + { title: 'Error Code', dataIndex: 'error_code', key: 'error_code' }, + { title: 'Trouble Description', dataIndex: 'description', key: 'description' }, + { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (status) => ( + + {status ? 'Active' : 'Inactive'} + + ), + }, + { + title: 'Action', + key: 'action', + render: (_, record) => ( + + ), + }, + ]; + + useEffect(() => { + brandForm.setFieldsValue(formData); + }, [formData, brandForm]); + + useEffect(() => { + setBreadcrumbItems([ + { title: • Master }, + { title: navigate('/master/brand-device')}>Brand Device }, + { title: Tambah Brand Device } + ]); + }, [setBreadcrumbItems, navigate]); + + const renderStepContent = () => { + if (currentStep === 0) { + return ( +
setFormData(prev => ({...prev, ...allValues}))} initialValues={formData}> + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); + } + if (currentStep === 1) { + return ( +
+ Tambah Error Code {errorCodes.length + 1} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setAnotherSolutionType(e.target.value)}> + Image + Other + + + {anotherSolutionType === 'image' && ( + + + + + + )} + {anotherSolutionType === 'other' && ( + + + + )} + + + +
+ + Daftar Error Code + + + ); + } + return null; + }; + + return ( + + Tambah Brand Device + + + + + +
+ {renderStepContent()} +
+ +
+ + + {currentStep > 0 && ( + + )} + + + {currentStep < 1 && ( + + )} + {currentStep === 1 && ( + + )} + +
+
+ ); +}; + +export default AddBrandDevice; \ No newline at end of file diff --git a/src/pages/master/brandDevice/IndexBrandDevice.jsx b/src/pages/master/brandDevice/IndexBrandDevice.jsx index 53042c1..2deecdf 100644 --- a/src/pages/master/brandDevice/IndexBrandDevice.jsx +++ b/src/pages/master/brandDevice/IndexBrandDevice.jsx @@ -1,8 +1,6 @@ - -import React, { memo, useState, useEffect } from 'react'; +import React, { memo, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import ListBrandDevice from './component/ListBrandDevice'; -import DetailBrandDevice from './component/DetailBrandDevice'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { Typography } from 'antd'; @@ -12,35 +10,6 @@ const IndexBrandDevice = memo(function IndexBrandDevice() { const navigate = useNavigate(); const { setBreadcrumbItems } = useBreadcrumb(); - const [actionMode, setActionMode] = useState('list'); - const [selectedData, setSelectedData] = useState(null); - const [readOnly, setReadOnly] = useState(false); - const [showModal, setShowmodal] = useState(false); - - const setMode = (param) => { - setActionMode(param); - switch (param) { - case 'add': - setReadOnly(false); - setShowmodal(true); - break; - - case 'edit': - setReadOnly(false); - setShowmodal(true); - break; - - case 'preview': - setReadOnly(true); - setShowmodal(true); - break; - - default: - setShowmodal(false); - break; - } - }; - useEffect(() => { const token = localStorage.getItem('token'); if (token) { @@ -55,23 +24,9 @@ const IndexBrandDevice = memo(function IndexBrandDevice() { return ( - - + ); }); -export default IndexBrandDevice; +export default IndexBrandDevice; \ No newline at end of file diff --git a/src/pages/master/brandDevice/component/DetailBrandDevice.jsx b/src/pages/master/brandDevice/component/DetailBrandDevice.jsx deleted file mode 100644 index 137bf27..0000000 --- a/src/pages/master/brandDevice/component/DetailBrandDevice.jsx +++ /dev/null @@ -1,310 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd'; -import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; - -const { Text } = Typography; - -const DetailBrandDevice = (props) => { - const [confirmLoading, setConfirmLoading] = useState(false); - - const defaultData = { - brand_id: '', - brandName: '', - brandType: '', - manufacturer: '', - model: '', - status: 'Active', - }; - - const [FormData, setFormData] = useState(defaultData); - - const handleCancel = () => { - props.setSelectedData(null); - props.setActionMode('list'); - }; - - const handleSave = async () => { - setConfirmLoading(true); - - // Validasi required fields - if (!FormData.brandName) { - NotifOk({ - icon: 'warning', - title: 'Peringatan', - message: 'Kolom Brand Name Tidak Boleh Kosong', - }); - setConfirmLoading(false); - return; - } - - if (!FormData.brandType) { - NotifOk({ - icon: 'warning', - title: 'Peringatan', - message: 'Kolom Type Tidak Boleh Kosong', - }); - setConfirmLoading(false); - return; - } - - if (!FormData.manufacturer) { - NotifOk({ - icon: 'warning', - title: 'Peringatan', - message: 'Kolom Manufacturer Tidak Boleh Kosong', - }); - setConfirmLoading(false); - return; - } - - if (!FormData.model) { - NotifOk({ - icon: 'warning', - title: 'Peringatan', - message: 'Kolom Model Tidak Boleh Kosong', - }); - setConfirmLoading(false); - return; - } - - if (!FormData.status) { - NotifOk({ - icon: 'warning', - title: 'Peringatan', - message: 'Kolom Status Tidak Boleh Kosong', - }); - setConfirmLoading(false); - return; - } - - const payload = { - brandName: FormData.brandName, - brandType: FormData.brandType, - manufacturer: FormData.manufacturer, - model: FormData.model, - status: FormData.status, - }; - - try { - // Simulate API call - await new Promise((resolve) => setTimeout(resolve, 500)); - - const response = { - statusCode: FormData.brand_id ? 200 : 201, - data: { - brandName: FormData.brandName, - }, - }; - - console.log('Save Brand Device Response:', response); - - // Check if response is successful - if (response && (response.statusCode === 200 || response.statusCode === 201)) { - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: `Data Brand Device "${ - response.data?.brandName || FormData.brandName - }" berhasil ${FormData.brand_id ? 'diubah' : 'ditambahkan'}.`, - }); - - props.setActionMode('list'); - } else { - NotifAlert({ - icon: 'error', - title: 'Gagal', - message: response?.message || 'Terjadi kesalahan saat menyimpan data.', - }); - } - } catch (error) { - console.error('Save Brand Device Error:', error); - NotifAlert({ - icon: 'error', - title: 'Error', - message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.', - }); - } - - setConfirmLoading(false); - }; - - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData({ - ...FormData, - [name]: value, - }); - }; - - const handleSelectChange = (name, value) => { - setFormData({ - ...FormData, - [name]: value, - }); - }; - - const handleStatusToggle = (event) => { - const isChecked = event; - setFormData({ - ...FormData, - status: isChecked ? true : false, - }); - }; - - useEffect(() => { - const token = localStorage.getItem('token'); - if (token) { - if (props.selectedData != null) { - setFormData(props.selectedData); - } else { - setFormData(defaultData); - } - } else { - // navigate('/signin'); // Uncomment if useNavigate is imported - } - }, [props.showModal]); - - return ( - - - - - - {!props.readOnly && ( - - )} - - , - ]} - > - {FormData && ( -
-
-
- Status -
-
-
- -
-
- {FormData.status === true ? 'Active' : 'Inactive'} -
-
-
- - -
- Brand Name - * - -
-
- Type - * - -
-
- Manufacturer - * - -
-
- Model - * - -
-
- )} -
- ); -}; - -export default DetailBrandDevice; diff --git a/src/pages/master/brandDevice/component/ListBrandDevice.jsx b/src/pages/master/brandDevice/component/ListBrandDevice.jsx index f53620f..cd9b654 100644 --- a/src/pages/master/brandDevice/component/ListBrandDevice.jsx +++ b/src/pages/master/brandDevice/component/ListBrandDevice.jsx @@ -10,6 +10,7 @@ import { import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif'; import { useNavigate } from 'react-router-dom'; import TableList from '../../../../components/Global/TableList'; +import { getAllBrands } from '../../../../api/master-brand'; // Dummy data const initialBrandDeviceData = [ @@ -145,55 +146,6 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { const navigate = useNavigate(); - // Dummy data function to simulate API call - now uses state - const getAllBrandDevice = async (params) => { - // Simulate API delay - await new Promise((resolve) => setTimeout(resolve, 300)); - - // Extract URLSearchParams - TableList sends URLSearchParams object - const searchParam = params.get('search') || ''; - const page = parseInt(params.get('page')) || 1; - const limit = parseInt(params.get('limit')) || 10; - - console.log('getAllBrandDevice called with:', { searchParam, page, limit }); - - // Filter by search - let filteredBrandDevices = brandDeviceData; - if (searchParam) { - const searchLower = searchParam.toLowerCase(); - filteredBrandDevices = brandDeviceData.filter( - (brand) => - brand.brandName.toLowerCase().includes(searchLower) || - brand.brandType.toLowerCase().includes(searchLower) || - brand.manufacturer.toLowerCase().includes(searchLower) || - brand.model.toLowerCase().includes(searchLower) - ); - } - - // Pagination logic - const totalData = filteredBrandDevices.length; - const totalPages = Math.ceil(totalData / limit); - const startIndex = (page - 1) * limit; - const endIndex = startIndex + limit; - const paginatedData = filteredBrandDevices.slice(startIndex, endIndex); - - // Return structure that matches TableList expectation - return { - status: 200, - statusCode: 200, - data: { - data: paginatedData, - total: totalData, - paging: { - page: page, - limit: limit, - total: totalData, - page_total: totalPages, - }, - }, - }; - }; - useEffect(() => { const token = localStorage.getItem('token'); if (token) { @@ -231,11 +183,6 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { props.setActionMode('edit'); }; - const showAddModal = (param = null) => { - props.setSelectedData(param); - props.setActionMode('add'); - }; - const showDeleteDialog = (param) => { NotifConfirmDialog({ icon: 'question', @@ -320,7 +267,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) { >