@@ -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=
|
||||
|
||||
19
src/App.jsx
19
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 = () => {
|
||||
<Route path="blank" element={<Blank />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/dashboard-svg" element={<ProtectedRoute />}>
|
||||
<Route path="overview" element={<SvgOverview />} />
|
||||
<Route path="compressor-a" element={<SvgCompressorA />} />
|
||||
<Route path="compressor-b" element={<SvgCompressorB />} />
|
||||
<Route path="compressor-c" element={<SvgCompressorC />} />
|
||||
<Route path="airdryer-a" element={<SvgAirDryerA />} />
|
||||
<Route path="airdryer-b" element={<SvgAirDryerB />} />
|
||||
<Route path="airdryer-c" element={<SvgAirDryerC />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/master" element={<ProtectedRoute />}>
|
||||
<Route path="device" element={<IndexDevice />} />
|
||||
<Route path="tag" element={<IndexTag />} />
|
||||
<Route path="unit" element={<IndexUnit />} />
|
||||
<Route path="brand-device" element={<IndexBrandDevice />} />
|
||||
<Route path="brand-device/add" element={<AddBrandDevice />} />
|
||||
<Route path="plant-section" element={<IndexPlantSection />} />
|
||||
<Route path="shift" element={<IndexShift />} />
|
||||
<Route path="status" element={<IndexStatus />} />
|
||||
|
||||
71
src/Utils/validate.js
Normal file
71
src/Utils/validate.js
Normal file
@@ -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
|
||||
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
50
src/api/master-status.jsx
Normal file
50
src/api/master-status.jsx
Normal file
@@ -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 };
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
164
src/api/role.jsx
164
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 };
|
||||
|
||||
@@ -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 };
|
||||
export { getAllUser, getUserDetail, updateUser, deleteUser, approvalUser };
|
||||
|
||||
169
src/api/user.jsx
169
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 };
|
||||
export {
|
||||
getAllUser,
|
||||
getUserById,
|
||||
createUser,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
approveUser,
|
||||
rejectUser,
|
||||
toggleActiveUser,
|
||||
changePassword,
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -37,7 +37,7 @@ const CardList = ({
|
||||
return (
|
||||
<Row gutter={[16, 16]} style={{ marginTop: '16px', justifyContent: 'left' }}>
|
||||
{data.map((item) => (
|
||||
<Col xs={24} sm={24} md={12} lg={8} key={item.device_id}>
|
||||
<Col xs={24} sm={24} md={12} lg={6} key={item.device_id}>
|
||||
<Card
|
||||
title={
|
||||
<div
|
||||
@@ -80,16 +80,18 @@ const CardList = ({
|
||||
]}
|
||||
>
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
{column.map((itemCard) => (
|
||||
<>
|
||||
{!itemCard.hidden && !itemCard.render && (
|
||||
<p>
|
||||
{column.map((itemCard, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{!itemCard.hidden && itemCard.title !== 'No' && itemCard.title !== 'Aksi' && (
|
||||
<p style={{ margin: '8px 0' }}>
|
||||
<Text strong>{itemCard.title}:</Text>{' '}
|
||||
{item[itemCard.key]}
|
||||
{itemCard.render
|
||||
? itemCard.render(item[itemCard.dataIndex], item, index)
|
||||
: item[itemCard.dataIndex] || item[itemCard.key] || '-'
|
||||
}
|
||||
</p>
|
||||
)}
|
||||
{itemCard.render && itemCard.render}
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -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({
|
||||
<Row justify="space-between" align="middle">
|
||||
<Col>
|
||||
<div>
|
||||
Menampilkan {pagingResponse.totalPage} Data dari {pagingResponse.perPage}{' '}
|
||||
Halaman
|
||||
Menampilkan {pagination.current_limit} data halaman{' '}
|
||||
{pagination.current_page} dari total {pagination.total_limit} data
|
||||
</div>
|
||||
</Col>
|
||||
<Col>
|
||||
@@ -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}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -16,6 +16,7 @@ const NotifOk = ({ icon, title, message }) => {
|
||||
icon: icon,
|
||||
title: title,
|
||||
text: message,
|
||||
html: message.replace(/\n/g, '<br/>'),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ import {
|
||||
TeamOutlined,
|
||||
ClockCircleOutlined,
|
||||
CalendarOutlined,
|
||||
DesktopOutlined,
|
||||
NodeExpandOutlined,
|
||||
GroupOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
@@ -40,6 +43,48 @@ const allItems = [
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg',
|
||||
icon: <GroupOutlined style={{ fontSize: '19px' }} />,
|
||||
label: 'Dashboard',
|
||||
children: [
|
||||
{
|
||||
key: 'dashboard-svg-overview',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/overview">Overview</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg-compressor-a',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/compressor-a">Compressor A</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg-compressor-b',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/compressor-b">Compressor B</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg-compressor-c',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/compressor-c">Compressor C</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg-airdryer-a',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/airdryer-a">Air Dryer A</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg-airdryer-b',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/airdryer-b">Air Dryer B</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dashboard-svg-airdryer-c',
|
||||
icon: <NodeExpandOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/dashboard-svg/airdryer-c">Air Dryer C</Link>,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'master',
|
||||
icon: <DatabaseOutlined style={{ fontSize: '19px' }} />,
|
||||
@@ -60,16 +105,16 @@ const allItems = [
|
||||
icon: <MobileOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/master/device">Device</Link>,
|
||||
},
|
||||
{
|
||||
key: 'master-tag',
|
||||
icon: <TagOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/master/tag">Tag</Link>,
|
||||
},
|
||||
{
|
||||
key: 'master-unit',
|
||||
icon: <AppstoreOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/master/unit">Unit</Link>,
|
||||
},
|
||||
{
|
||||
key: 'master-tag',
|
||||
icon: <TagOutlined style={{ fontSize: '19px' }} />,
|
||||
label: <Link to="/master/tag">Tag</Link>,
|
||||
},
|
||||
{
|
||||
key: 'master-status',
|
||||
icon: <SafetyOutlined style={{ fontSize: '19px' }} />,
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
// <Flex
|
||||
// align="center"
|
||||
// justify="center"
|
||||
// style={{
|
||||
// minHeight: '100vh',
|
||||
// backgroundImage: `url(${sypiu_ggcp})`,
|
||||
// backgroundSize: 'cover',
|
||||
// backgroundPosition: 'center',
|
||||
// padding: '20px',
|
||||
// }}
|
||||
// >
|
||||
// <Card
|
||||
// style={{
|
||||
// width: '100%',
|
||||
// maxWidth: 800,
|
||||
// background: 'rgba(255, 255, 255, 0.9)',
|
||||
// backdropFilter: 'blur(10px)',
|
||||
// borderRadius: '12px',
|
||||
// boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
|
||||
// padding: '24px',
|
||||
// }}
|
||||
// title={
|
||||
// <Flex align="center" justify="space-between">
|
||||
// <h2 style={{ margin: 0, color: '#1a3c34' }}>Formulir Pendaftaran</h2>
|
||||
// <Button
|
||||
// type="link"
|
||||
// icon={<InfoCircleOutlined />}
|
||||
// onClick={() => navigate('/signin')}
|
||||
// >
|
||||
// Kembali
|
||||
// </Button>
|
||||
// </Flex>
|
||||
// }
|
||||
// >
|
||||
// <Form
|
||||
// form={form}
|
||||
// onFinish={onFinish}
|
||||
// layout="horizontal"
|
||||
// labelCol={{ span: 8 }}
|
||||
// wrapperCol={{ span: 16 }}
|
||||
// labelAlign="left"
|
||||
// style={{ maxWidth: 800 }}
|
||||
// >
|
||||
// {/* Informasi Perusahaan */}
|
||||
// <Divider
|
||||
// orientation="left"
|
||||
// orientationMargin={0}
|
||||
// style={{
|
||||
// color: '#23A55A',
|
||||
// fontWeight: 'bold',
|
||||
// marginLeft: 0,
|
||||
// paddingLeft: 0,
|
||||
// }}
|
||||
// >
|
||||
// Informasi Perusahaan
|
||||
// </Divider>
|
||||
// <Item
|
||||
// label="Nama Perusahaan"
|
||||
// name="namaPerusahaan"
|
||||
// rules={[{ required: true, message: 'Masukkan Nama Perusahaan!' }]}
|
||||
// >
|
||||
// <Input
|
||||
// prefix={<UserOutlined />}
|
||||
// placeholder="Masukkan Nama Perusahaan"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="Durasi Pekerjaan (Hari)"
|
||||
// name="durasiPekerjaan"
|
||||
// rules={[{ required: true, message: 'Masukkan Durasi Pekerjaan!' }]}
|
||||
// >
|
||||
// <InputNumber
|
||||
// min={1}
|
||||
// style={{ width: '100%' }}
|
||||
// placeholder="Masukkan Durasi Pekerjaan"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="No Kontrak Kerja / Agreement"
|
||||
// name="noKontakWo"
|
||||
// rules={[
|
||||
// { required: true, message: 'Masukkan No Kontrak Kerja / Agreement!' },
|
||||
// ]}
|
||||
// >
|
||||
// <Input
|
||||
// style={{
|
||||
// width: '100%',
|
||||
// }}
|
||||
// placeholder="Masukkan No Kontrak Kerja / Agreement"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="Lampiran Kontrak Kerja"
|
||||
// name="lampiranKontrak"
|
||||
// rules={[{ required: true, message: 'Unggah Lampiran Kontrak Kerja!' }]}
|
||||
// >
|
||||
// <Upload
|
||||
// beforeUpload={(file) => beforeUpload(file, 'path_kontrak')}
|
||||
// fileList={fileListKontrak}
|
||||
// onChange={handleChangeKontrak}
|
||||
// maxCount={1}
|
||||
// >
|
||||
// <Button icon={<UploadOutlined />} size="large">
|
||||
// Unggah PDF/JPG
|
||||
// </Button>
|
||||
// </Upload>
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="HSSE Plan"
|
||||
// name="hssePlan"
|
||||
// rules={[{ required: true, message: 'Unggah HSSE Plan!' }]}
|
||||
// >
|
||||
// <Upload
|
||||
// beforeUpload={(file) => beforeUpload(file, 'path_hse_plant')}
|
||||
// fileList={fileListHsse}
|
||||
// onChange={handleChangeHsse}
|
||||
// maxCount={1}
|
||||
// >
|
||||
// <Button icon={<UploadOutlined />} size="large">
|
||||
// Unggah PDF/JPG
|
||||
// </Button>
|
||||
// </Upload>
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="Nilai CSMS"
|
||||
// name="nilaiCsms"
|
||||
// rules={[{ required: true, message: 'Masukkan Nilai CSMS!' }]}
|
||||
// >
|
||||
// <InputNumber
|
||||
// min={0}
|
||||
// max={100}
|
||||
// style={{ width: '100%' }}
|
||||
// placeholder="Masukkan Nilai CSMS"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="Jenis Vendor"
|
||||
// name="jenisVendor"
|
||||
// rules={[{ required: true, message: 'Pilih Jenis Vendor!' }]}
|
||||
// >
|
||||
// <Select
|
||||
// placeholder="Pilih Jenis Vendor"
|
||||
// size="large"
|
||||
// style={{ width: '100%' }}
|
||||
// >
|
||||
// {vendorTypes.map((vendor) => (
|
||||
// <Option key={vendor.vendor_type} value={vendor.vendor_type}>
|
||||
// {vendor.vendor_type_name}
|
||||
// </Option>
|
||||
// ))}
|
||||
// </Select>
|
||||
// </Item>
|
||||
|
||||
// {/* Informasi Penanggung Jawab */}
|
||||
// <Divider
|
||||
// orientation="left"
|
||||
// orientationMargin={0}
|
||||
// style={{
|
||||
// color: '#23A55A',
|
||||
// fontWeight: 'bold',
|
||||
// marginLeft: 0,
|
||||
// paddingLeft: 0,
|
||||
// }}
|
||||
// >
|
||||
// Informasi Penanggung Jawab
|
||||
// </Divider>
|
||||
// <Item
|
||||
// label="Nama Penanggung Jawab"
|
||||
// name="penanggungJawab"
|
||||
// rules={[{ required: true, message: 'Masukkan Nama Penanggung Jawab!' }]}
|
||||
// >
|
||||
// <Input
|
||||
// prefix={<UserOutlined />}
|
||||
// placeholder="Masukkan Nama Penanggung Jawab"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="No Handphone"
|
||||
// name="noHandphone"
|
||||
// rules={[
|
||||
// { required: true, message: 'Masukkan No Handphone!' },
|
||||
// {
|
||||
// pattern: /^(\+62|0)[0-9]{9,12}$/,
|
||||
// message:
|
||||
// 'Format nomor telepon tidak valid! (Contoh: +62.... atau 0....)',
|
||||
// },
|
||||
// ]}
|
||||
// >
|
||||
// <Input
|
||||
// prefix={<PhoneOutlined />}
|
||||
// placeholder="Masukkan No Handphone (+62)"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="No Identitas"
|
||||
// name="noIdentitas"
|
||||
// rules={[{ required: true, message: 'Masukkan No Identitas!' }]}
|
||||
// >
|
||||
// <Input
|
||||
// prefix={<IdcardOutlined />}
|
||||
// placeholder="Masukkan No Identitas"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
|
||||
// {/* Akun Pengguna */}
|
||||
// <Divider
|
||||
// orientation="left"
|
||||
// orientationMargin={0}
|
||||
// style={{
|
||||
// color: '#23A55A',
|
||||
// fontWeight: 'bold',
|
||||
// marginLeft: 0,
|
||||
// paddingLeft: 0,
|
||||
// }}
|
||||
// >
|
||||
// Akun Pengguna (digunakan sebagai user login SYPIU)
|
||||
// </Divider>
|
||||
// <Item
|
||||
// label="Email"
|
||||
// name="username"
|
||||
// rules={[
|
||||
// { required: true, message: 'Masukkan Email!' },
|
||||
// { type: 'email', message: 'Format email tidak valid!' },
|
||||
// ]}
|
||||
// >
|
||||
// <Input
|
||||
// prefix={<MailOutlined />}
|
||||
// placeholder="Masukkan Email"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
// <Item
|
||||
// label="Password"
|
||||
// name="password"
|
||||
// rules={[
|
||||
// { required: true, message: 'Masukkan Password!' },
|
||||
// { min: 6, message: 'Password minimal 6 karakter!' },
|
||||
// ]}
|
||||
// >
|
||||
// <Input.Password
|
||||
// prefix={<LockOutlined />}
|
||||
// placeholder="Masukkan Password"
|
||||
// size="large"
|
||||
// />
|
||||
// </Item>
|
||||
|
||||
// {/* Tombol */}
|
||||
// <Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
// <Space style={{ marginTop: '24px', width: '100%' }}>
|
||||
// <Button
|
||||
// type="primary"
|
||||
// htmlType="submit"
|
||||
// size="large"
|
||||
// loading={loading}
|
||||
// style={{
|
||||
// backgroundColor: '#23A55A',
|
||||
// borderColor: '#23A55A',
|
||||
// width: 120,
|
||||
// }}
|
||||
// >
|
||||
// Simpan
|
||||
// </Button>
|
||||
// <Button onClick={onCancel} size="large" style={{ width: 120 }}>
|
||||
// Batal
|
||||
// </Button>
|
||||
// </Space>
|
||||
// </Item>
|
||||
// </Form>
|
||||
// </Card>
|
||||
// </Flex>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default Registration;
|
||||
@@ -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);
|
||||
|
||||
20
src/pages/home/SvgAirDryerA.jsx
Normal file
20
src/pages/home/SvgAirDryerA.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgAirDryerA;
|
||||
20
src/pages/home/SvgAirDryerB.jsx
Normal file
20
src/pages/home/SvgAirDryerB.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgAirDryerB;
|
||||
20
src/pages/home/SvgAirDryerC.jsx
Normal file
20
src/pages/home/SvgAirDryerC.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgAirDryerC;
|
||||
20
src/pages/home/SvgCompressorA.jsx
Normal file
20
src/pages/home/SvgCompressorA.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgCompressorA;
|
||||
20
src/pages/home/SvgCompressorB.jsx
Normal file
20
src/pages/home/SvgCompressorB.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgCompressorB;
|
||||
20
src/pages/home/SvgCompressorC.jsx
Normal file
20
src/pages/home/SvgCompressorC.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgCompressorC;
|
||||
20
src/pages/home/SvgOverview.jsx
Normal file
20
src/pages/home/SvgOverview.jsx
Normal file
@@ -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 (
|
||||
<SvgTemplate>
|
||||
<SvgViewer filePathSvg={filePathSvg} topicMqtt={topicMqtt} setValSvg={setValSvg} />
|
||||
</SvgTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgOverview;
|
||||
19
src/pages/home/SvgTemplate.jsx
Normal file
19
src/pages/home/SvgTemplate.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
const SvgTemplate = ({ children }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '80vh', // penuh 1 layar
|
||||
width: '80vw', // penuh 1 layar lebar
|
||||
overflow: 'hidden', // hilangkan scroll
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff', // opsional
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SvgTemplate;
|
||||
@@ -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;
|
||||
|
||||
|
||||
19
src/pages/home/SvgViewer.jsx
Normal file
19
src/pages/home/SvgViewer.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
// SvgViewer.jsx
|
||||
import { ReactSVG } from 'react-svg';
|
||||
|
||||
const SvgViewer = ({ filePathSvg, topicMqtt, setValSvg }) => {
|
||||
return (
|
||||
<ReactSVG
|
||||
src={filePathSvg}
|
||||
beforeInjection={(svg) => {
|
||||
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;
|
||||
@@ -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) => (
|
||||
<Tag color={isActive ? 'green' : 'red'}>
|
||||
{isActive ? 'Aktif' : 'Tidak Aktif'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
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) {
|
||||
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<Input.Search
|
||||
placeholder="Cari berdasarkan nama atau username..."
|
||||
placeholder="Cari jadwal shift..."
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
@@ -263,11 +258,11 @@ const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||
<TableList
|
||||
mobile
|
||||
cardColor={'#42AAFF'}
|
||||
header={'nama_employee'}
|
||||
header={'schedule_date'}
|
||||
showPreviewModal={showPreviewModal}
|
||||
showEditModal={showEditModal}
|
||||
showDeleteDialog={showDeleteDialog}
|
||||
getData={getDummyData}
|
||||
getData={getData}
|
||||
queryParams={formDataFilter}
|
||||
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||
triger={trigerFilter}
|
||||
|
||||
151
src/pages/master/brand/ErrorCode.jsx
Normal file
151
src/pages/master/brand/ErrorCode.jsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Card, Typography, Button, Modal, Form, Input, message } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import TableList from '../../../components/Global/TableList';
|
||||
// import { getAllErrorCodesByBrand, createErrorCode, updateErrorCode, deleteErrorCode } from '../../api/master-errorcode'; // Mock this later
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
// Mock API functions for now
|
||||
const mockApi = {
|
||||
errorCodes: [
|
||||
{ error_code_id: 1, brand_id: 1, error_code: 'E-001', description: 'Paper Jam' },
|
||||
{ error_code_id: 2, brand_id: 1, error_code: 'E-002', description: 'Low Ink' },
|
||||
],
|
||||
getAllErrorCodesByBrand: async (brandId) => {
|
||||
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) => (
|
||||
<>
|
||||
<Button type="link" onClick={() => handleEdit(record)}>Edit</Button>
|
||||
<Button type="link" danger onClick={() => handleDelete(record.error_code_id)}>Delete</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
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 (
|
||||
<Card>
|
||||
<Title level={4}>Manage Error Codes for Brand ID: {brandId}</Title>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAdd}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
Add Error Code
|
||||
</Button>
|
||||
<TableList
|
||||
columns={columns}
|
||||
getData={async () => ({ data: { data: errorCodes } })}
|
||||
triger={brandId}
|
||||
/>
|
||||
<Modal
|
||||
title={editingErrorCode ? 'Edit Error Code' : 'Add Error Code'}
|
||||
visible={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={() => setIsModalVisible(false)}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="error_code" label="Error Code" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="description" label="Description" rules={[{ required: true }]}>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorCodePage;
|
||||
59
src/pages/master/brand/FormBrand.jsx
Normal file
59
src/pages/master/brand/FormBrand.jsx
Normal file
@@ -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 (
|
||||
<Card>
|
||||
<Title level={4}>Add New Brand</Title>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
name="brand_name"
|
||||
label="Brand Name"
|
||||
rules={[{ required: true, message: 'Please input the brand name!' }]}
|
||||
>
|
||||
<Input placeholder="Enter brand name" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
Lanjut ke Error Code
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormBrand;
|
||||
337
src/pages/master/brandDevice/AddBrandDevice.jsx
Normal file
337
src/pages/master/brandDevice/AddBrandDevice.jsx
Normal file
@@ -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) => (
|
||||
<Tag color={status ? '#23A55A' : 'red'}>
|
||||
{status ? 'Active' : 'Inactive'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Button type="link" danger onClick={() => handleDeleteErrorCode(record.key)}>Delete</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
brandForm.setFieldsValue(formData);
|
||||
}, [formData, brandForm]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbItems([
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>• Master</Text> },
|
||||
{ title: <Text strong style={{ fontSize: '14px' }} onClick={() => navigate('/master/brand-device')}>Brand Device</Text> },
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>Tambah Brand Device</Text> }
|
||||
]);
|
||||
}, [setBreadcrumbItems, navigate]);
|
||||
|
||||
const renderStepContent = () => {
|
||||
if (currentStep === 0) {
|
||||
return (
|
||||
<Form layout="vertical" form={brandForm} onValuesChange={(changedValues, allValues) => setFormData(prev => ({...prev, ...allValues}))} initialValues={formData}>
|
||||
<Form.Item label="Status" name="status" valuePropName="checked">
|
||||
<Switch
|
||||
checked={formData.status}
|
||||
style={{ backgroundColor: formData.status ? '#23A55A' : '#bfbfbf' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Brand Code" name="brand_code">
|
||||
<Input placeholder={'Brand Code Auto Fill'} disabled style={{ backgroundColor: '#f5f5f5', cursor: 'not-allowed' }} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Brand Name" name="brandName" rules={[{ required: true, message: 'Brand Name wajib diisi!' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Brand Type" name="brandType" rules={[{ required: true, message: 'Type wajib diisi!' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Model" name="model" rules={[{ required: true, message: 'Model wajib diisi!' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Manufacturer" name="manufacturer" rules={[{ required: true, message: 'Manufacturer wajib diisi!' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Country" name="country" rules={[{ required: true, message: 'Country wajib diisi!' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name="description">
|
||||
<Input.TextArea rows={4} placeholder="Enter Description (Optional)" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
if (currentStep === 1) {
|
||||
return (
|
||||
<div>
|
||||
<Title level={5}>Tambah Error Code {errorCodes.length + 1}</Title>
|
||||
<Form form={errorCodeForm} layout="vertical" initialValues={{ status: true }}>
|
||||
<Form.Item label="Status" name="status" valuePropName="checked">
|
||||
<Switch
|
||||
style={{ backgroundColor: statusValue ? '#23A55A' : '#bfbfbf' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="error_code" label="Error Code" rules={[{ required: true, message: 'Error Code wajib diisi' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="description" label="Trouble Description" rules={[{ required: true, message: 'Trouble Description wajib diisi' }]}>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item name="detected_method" label="Detected Method" rules={[{ required: true, message: 'Detected Method wajib diisi' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item name="indicator_light" label="Indicator Light">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="detector" label="Detector">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item name="auto_shutdown" label="Auto Shutdown">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item name="what_action_to_take" label="What Action to Take" rules={[{ required: true, message: 'What Action to Take wajib diisi' }]}>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item name="another_solution" label="Another Solution (opsional)">
|
||||
<Radio.Group onChange={(e) => setAnotherSolutionType(e.target.value)}>
|
||||
<Radio value="image">Image</Radio>
|
||||
<Radio value="other">Other</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{anotherSolutionType === 'image' && (
|
||||
<Form.Item label="Upload Image">
|
||||
<Upload {...uploadProps}>
|
||||
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
)}
|
||||
{anotherSolutionType === 'other' && (
|
||||
<Form.Item name="another_solution_text" label="Enter Solution Text">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item>
|
||||
<Button type="dashed" icon={<PlusOutlined />} onClick={handleAddErrorCode} style={{ width: '100%' }}>
|
||||
Tambah Error Code Lain
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Divider />
|
||||
<Title level={5}>Daftar Error Code</Title>
|
||||
<Table columns={errorCodeColumns} dataSource={errorCodes} rowKey="key" pagination={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Title level={4}>Tambah Brand Device</Title>
|
||||
<Divider />
|
||||
<Steps current={currentStep} style={{ marginBottom: 24 }}>
|
||||
<Step title="Brand Device Details" />
|
||||
<Step title="Error Codes" />
|
||||
</Steps>
|
||||
<div style={{ marginTop: 24 }}>
|
||||
{renderStepContent()}
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleCancel}>Batal</Button>
|
||||
{currentStep > 0 && (
|
||||
<Button onClick={() => setCurrentStep(currentStep - 1)} style={{ marginRight: 8 }}>Kembali</Button>
|
||||
)}
|
||||
</ConfigProvider>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: '#23a55a',
|
||||
defaultColor: '#FFFFFF',
|
||||
defaultBorderColor: '#23a55a',
|
||||
defaultHoverBg: '#209652', // A slightly darker shade for hover
|
||||
defaultHoverColor: '#FFFFFF',
|
||||
defaultHoverBorderColor: '#23a55a',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{currentStep < 1 && (
|
||||
<Button loading={confirmLoading} onClick={handleNextStep}>Lanjut</Button>
|
||||
)}
|
||||
{currentStep === 1 && (
|
||||
<Button loading={confirmLoading} onClick={handleFinish}>Simpan</Button>
|
||||
)}
|
||||
</ConfigProvider>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddBrandDevice;
|
||||
@@ -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,21 +24,7 @@ const IndexBrandDevice = memo(function IndexBrandDevice() {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListBrandDevice
|
||||
actionMode={actionMode}
|
||||
setActionMode={setMode}
|
||||
selectedData={selectedData}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<DetailBrandDevice
|
||||
setActionMode={setMode}
|
||||
selectedData={selectedData}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
showModal={showModal}
|
||||
actionMode={actionMode}
|
||||
/>
|
||||
<ListBrandDevice />
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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 (
|
||||
<Modal
|
||||
title={`${
|
||||
props.actionMode === 'add'
|
||||
? 'Tambah'
|
||||
: props.actionMode === 'preview'
|
||||
? 'Preview'
|
||||
: 'Edit'
|
||||
} Brand Device`}
|
||||
open={props.showModal}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleCancel}>Batal</Button>
|
||||
</ConfigProvider>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
colorBgContainer: '#209652',
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: '#23a55a',
|
||||
defaultColor: '#FFFFFF',
|
||||
defaultBorderColor: '#23a55a',
|
||||
defaultHoverColor: '#FFFFFF',
|
||||
defaultHoverBorderColor: '#23a55a',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{!props.readOnly && (
|
||||
<Button loading={confirmLoading} onClick={handleSave}>
|
||||
Simpan
|
||||
</Button>
|
||||
)}
|
||||
</ConfigProvider>
|
||||
</>,
|
||||
]}
|
||||
>
|
||||
{FormData && (
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<Text strong>Status</Text>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<Switch
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.status === true ? '#23A55A' : '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.status === true}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>{FormData.status === true ? 'Active' : 'Inactive'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
<div hidden>
|
||||
<Text strong>Brand ID</Text>
|
||||
<Input
|
||||
name="brand_id"
|
||||
value={FormData.brand_id}
|
||||
onChange={handleInputChange}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Brand Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="brandName"
|
||||
value={FormData.brandName}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Brand Name"
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Type</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="brandType"
|
||||
value={FormData.brandType}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Type (e.g., PLC)"
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Manufacturer</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="manufacturer"
|
||||
value={FormData.manufacturer}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Manufacturer"
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Model</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="model"
|
||||
value={FormData.model}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Model"
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailBrandDevice;
|
||||
@@ -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) {
|
||||
>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => showAddModal()}
|
||||
onClick={() => navigate('/master/brand-device/add')}
|
||||
size="large"
|
||||
>
|
||||
Tambah Brand Device
|
||||
@@ -338,7 +285,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) {
|
||||
showPreviewModal={showPreviewModal}
|
||||
showEditModal={showEditModal}
|
||||
showDeleteDialog={showDeleteDialog}
|
||||
getData={getAllBrandDevice}
|
||||
getData={getAllBrands}
|
||||
queryParams={formDataFilter}
|
||||
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||
triger={trigerFilter}
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Input,
|
||||
Divider,
|
||||
Typography,
|
||||
Switch,
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Radio,
|
||||
Select,
|
||||
} from 'antd';
|
||||
import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider } from 'antd';
|
||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import { createApd, getJenisPermit, updateApd } from '../../../../api/master-apd';
|
||||
import { createDevice, updateDevice } from '../../../../api/master-device';
|
||||
import { Checkbox } from 'antd';
|
||||
const CheckboxGroup = Checkbox.Group;
|
||||
import { validateRun } from '../../../../Utils/validate';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
@@ -27,155 +15,61 @@ const DetailDevice = (props) => {
|
||||
device_code: '',
|
||||
device_name: '',
|
||||
is_active: true,
|
||||
device_location: 'Building A',
|
||||
device_location: '',
|
||||
device_description: '',
|
||||
ip_address: '',
|
||||
};
|
||||
|
||||
const [FormData, setFormData] = useState(defaultData);
|
||||
|
||||
const [jenisPermit, setJenisPermit] = useState([]);
|
||||
const [checkedList, setCheckedList] = useState([]);
|
||||
|
||||
const onChange = (list) => {
|
||||
setCheckedList(list);
|
||||
};
|
||||
|
||||
const onChangeRadio = (e) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
type_input: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const getDataJenisPermit = async () => {
|
||||
setCheckedList([]);
|
||||
const result = await getJenisPermit();
|
||||
const data = result.data ?? [];
|
||||
const names = data.map((item) => ({
|
||||
value: item.id_jenis_permit,
|
||||
label: item.nama_jenis_permit,
|
||||
}));
|
||||
setJenisPermit(names);
|
||||
};
|
||||
const [formData, setFormData] = useState(defaultData);
|
||||
|
||||
const handleCancel = () => {
|
||||
props.setSelectedData(null);
|
||||
props.setActionMode('list');
|
||||
};
|
||||
|
||||
const validateIPAddress = (ip) => {
|
||||
const ipRegex =
|
||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
return ipRegex.test(ip);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setConfirmLoading(true);
|
||||
|
||||
// Validasi required fields
|
||||
// if (!FormData.device_code) {
|
||||
// NotifOk({
|
||||
// icon: 'warning',
|
||||
// title: 'Peringatan',
|
||||
// message: 'Kolom Device Code Tidak Boleh Kosong',
|
||||
// });
|
||||
// setConfirmLoading(false);
|
||||
// return;
|
||||
// }
|
||||
// Daftar aturan validasi
|
||||
const validationRules = [
|
||||
{ field: 'device_name', label: 'Device Name', required: true },
|
||||
{ field: 'ip_address', label: 'Ip Address', required: true, ip: true },
|
||||
];
|
||||
|
||||
if (!FormData.device_name) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Device Name Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
if (
|
||||
validateRun(formData, validationRules, (errorMessages) => {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: errorMessages,
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
})
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FormData.device_location) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Device Location Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FormData.ip_address) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom IP Address Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validasi format IP
|
||||
if (!validateIPAddress(FormData.ip_address)) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Format IP Address Tidak Valid',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.permitDefault && checkedList.length === 0) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Jenis Permit Tidak Boleh Kosong',
|
||||
});
|
||||
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Backend validation schema doesn't include device_code
|
||||
const payload = {
|
||||
device_name: FormData.device_name,
|
||||
is_active: FormData.is_active,
|
||||
device_location: FormData.device_location,
|
||||
ip_address: FormData.ip_address,
|
||||
};
|
||||
|
||||
// For CREATE: device_description is required (cannot be empty)
|
||||
// For UPDATE: device_description is optional
|
||||
if (!FormData.device_id) {
|
||||
// Creating - ensure description is not empty
|
||||
payload.device_description = FormData.device_description || '-';
|
||||
} else {
|
||||
// Updating - include description as-is
|
||||
payload.device_description = FormData.device_description;
|
||||
}
|
||||
|
||||
console.log('Payload to send:', payload);
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (!FormData.device_id) {
|
||||
response = await createDevice(payload);
|
||||
} else {
|
||||
response = await updateDevice(FormData.device_id, payload);
|
||||
}
|
||||
const payload = {
|
||||
device_name: formData.device_name,
|
||||
is_active: formData.is_active,
|
||||
device_location: formData.device_location,
|
||||
device_description: formData.device_description,
|
||||
ip_address: formData.ip_address,
|
||||
};
|
||||
|
||||
console.log('Save Device Response:', response);
|
||||
const response = formData.device_id
|
||||
? await updateDevice(formData.device_id, payload)
|
||||
: await createDevice(payload);
|
||||
|
||||
// Check if response is successful
|
||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||
// Response.data is now a single object (already extracted from array)
|
||||
const deviceName = response.data?.device_name || FormData.device_name;
|
||||
const deviceName = response.data?.device_name || formData.device_name;
|
||||
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Device "${deviceName}" berhasil ${
|
||||
FormData.device_id ? 'diubah' : 'ditambahkan'
|
||||
formData.device_id ? 'diubah' : 'ditambahkan'
|
||||
}.`,
|
||||
});
|
||||
|
||||
@@ -202,7 +96,7 @@ const DetailDevice = (props) => {
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData({
|
||||
...FormData,
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
@@ -210,35 +104,22 @@ const DetailDevice = (props) => {
|
||||
const handleStatusToggle = (event) => {
|
||||
const isChecked = event;
|
||||
setFormData({
|
||||
...FormData,
|
||||
...formData,
|
||||
is_active: isChecked ? true : false,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
// Only call getDataJenisPermit if permitDefault is enabled
|
||||
if (props.permitDefault) {
|
||||
getDataJenisPermit();
|
||||
}
|
||||
|
||||
if (props.selectedData != null) {
|
||||
setFormData(props.selectedData);
|
||||
if (props.permitDefault && props.selectedData.jenis_permit_default_arr) {
|
||||
setCheckedList(props.selectedData.jenis_permit_default_arr);
|
||||
}
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
}
|
||||
if (props.selectedData) {
|
||||
setFormData(props.selectedData);
|
||||
} else {
|
||||
// navigate('/signin'); // Uncomment if useNavigate is imported
|
||||
setFormData(defaultData);
|
||||
}
|
||||
}, [props.showModal]);
|
||||
}, [props.showModal, props.selectedData, props.actionMode]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
// title={`${FormData.id_apd === '' ? 'Tambah' : 'Edit'} APD`}
|
||||
// title={`${formData.id_apd === '' ? 'Tambah' : 'Edit'} APD`}
|
||||
title={`${
|
||||
props.actionMode === 'add'
|
||||
? 'Tambah'
|
||||
@@ -293,7 +174,7 @@ const DetailDevice = (props) => {
|
||||
</React.Fragment>,
|
||||
]}
|
||||
>
|
||||
{FormData && (
|
||||
{formData && (
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
@@ -311,14 +192,14 @@ const DetailDevice = (props) => {
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.is_active === true ? '#23A55A' : '#bfbfbf',
|
||||
formData.is_active === true ? '#23A55A' : '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.is_active === true}
|
||||
checked={formData.is_active === true}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>{FormData.is_active === true ? 'Running' : 'Offline'}</Text>
|
||||
<Text>{formData.is_active === true ? 'Running' : 'Offline'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -327,28 +208,32 @@ const DetailDevice = (props) => {
|
||||
<Text strong>Device ID</Text>
|
||||
<Input
|
||||
name="device_id"
|
||||
value={FormData.device_id}
|
||||
value={formData.device_id}
|
||||
onChange={handleInputChange}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
{/* <div style={{ marginBottom: 12 }}>
|
||||
{/* Device Code - Auto Increment & Read Only */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Device Code</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="device_code"
|
||||
value={FormData.device_code}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Device Code"
|
||||
readOnly={props.readOnly}
|
||||
value={formData.device_code}
|
||||
placeholder={'Device Code Auto Fill'}
|
||||
disabled
|
||||
style={{
|
||||
backgroundColor: '#f5f5f5',
|
||||
cursor: 'not-allowed',
|
||||
color: formData.device_code ? '#000000' : '#bfbfbf',
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Device Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="device_name"
|
||||
value={FormData.device_name}
|
||||
value={formData.device_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Device Name"
|
||||
readOnly={props.readOnly}
|
||||
@@ -356,11 +241,10 @@ const DetailDevice = (props) => {
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Device Location</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
type="text"
|
||||
name="device_location"
|
||||
value={FormData.device_location}
|
||||
value={formData.device_location}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Device Location"
|
||||
readOnly={props.readOnly}
|
||||
@@ -371,7 +255,7 @@ const DetailDevice = (props) => {
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="ip_address"
|
||||
value={FormData.ip_address}
|
||||
value={formData.ip_address}
|
||||
onChange={handleInputChange}
|
||||
placeholder="e.g. 192.168.1.1"
|
||||
readOnly={props.readOnly}
|
||||
@@ -381,26 +265,13 @@ const DetailDevice = (props) => {
|
||||
<Text strong>Device Description</Text>
|
||||
<TextArea
|
||||
name="device_description"
|
||||
value={FormData.device_description}
|
||||
value={formData.device_description}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Device Description (Optional)"
|
||||
readOnly={props.readOnly}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
{props.permitDefault && (
|
||||
<div>
|
||||
<Text strong>Jenis Permit</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
|
||||
<CheckboxGroup
|
||||
options={jenisPermit}
|
||||
value={checkedList}
|
||||
onChange={onChange}
|
||||
disabled={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
@@ -48,13 +48,13 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'device_status',
|
||||
key: 'device_status',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
width: '10%',
|
||||
align: 'center',
|
||||
render: (_, { device_status }) => (
|
||||
render: (_, { is_active }) => (
|
||||
<>
|
||||
{device_status === true ? (
|
||||
{is_active === true ? (
|
||||
<Tag color={'green'} key={'status'}>
|
||||
Running
|
||||
</Tag>
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Input,
|
||||
Typography,
|
||||
Switch,
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Divider,
|
||||
} from 'antd';
|
||||
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider } from 'antd';
|
||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import { createPlantSection, updatePlantSection } from '../../../../api/master-plant-section';
|
||||
import { validateRun } from '../../../../Utils/validate';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -23,12 +16,12 @@ const DetailPlantSection = (props) => {
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const [FormData, setFormData] = useState(defaultData);
|
||||
const [formData, setFormData] = useState(defaultData);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData({
|
||||
...FormData,
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
@@ -41,52 +34,53 @@ const DetailPlantSection = (props) => {
|
||||
const handleSave = async () => {
|
||||
setConfirmLoading(true);
|
||||
|
||||
if (!FormData.sub_section_name) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Plant Sub Section Name Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
// Daftar aturan validasi
|
||||
const validationRules = [
|
||||
{ field: 'sub_section_name', label: 'Plant Sub Section Name', required: true },
|
||||
];
|
||||
|
||||
if (
|
||||
validateRun(formData, validationRules, (errorMessages) => {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: errorMessages,
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
})
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let response;
|
||||
let payload;
|
||||
const payload = {
|
||||
is_active: formData.is_active,
|
||||
sub_section_name: formData.sub_section_name,
|
||||
};
|
||||
|
||||
if (props.actionMode === 'edit') {
|
||||
payload = {
|
||||
is_active: FormData.is_active,
|
||||
sub_section_name: FormData.sub_section_name
|
||||
};
|
||||
response = await updatePlantSection(FormData.sub_section_id, payload);
|
||||
} else {
|
||||
// Backend generates the code, so we only send the name and status
|
||||
payload = {
|
||||
sub_section_name: FormData.sub_section_name,
|
||||
is_active: FormData.is_active,
|
||||
}
|
||||
response = await createPlantSection(payload);
|
||||
}
|
||||
const response =
|
||||
props.actionMode === 'edit'
|
||||
? await updatePlantSection(formData.sub_section_id, payload)
|
||||
: await createPlantSection(payload);
|
||||
|
||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||
const action = props.actionMode === 'edit' ? 'diubah' : 'ditambahkan';
|
||||
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Plant Section berhasil ${action}.`,
|
||||
});
|
||||
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
NotifAlert({
|
||||
NotifOk({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response?.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
NotifAlert({
|
||||
NotifOk({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: error.message || 'Terjadi kesalahan pada server.',
|
||||
@@ -98,19 +92,18 @@ const DetailPlantSection = (props) => {
|
||||
|
||||
const handleStatusToggle = (checked) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
...formData,
|
||||
is_active: checked,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (props.selectedData) {
|
||||
setFormData(props.selectedData);
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
}
|
||||
}, [props.showModal, props.selectedData]);
|
||||
}, [props.showModal, props.selectedData, props.actionMode]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -158,7 +151,7 @@ const DetailPlantSection = (props) => {
|
||||
</React.Fragment>,
|
||||
]}
|
||||
>
|
||||
{FormData && (
|
||||
{formData && (
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
@@ -169,38 +162,41 @@ const DetailPlantSection = (props) => {
|
||||
<Switch
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor: FormData.is_active ? '#23A55A' : '#bfbfbf',
|
||||
backgroundColor: formData.is_active ? '#23A55A' : '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.is_active}
|
||||
checked={formData.is_active}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>
|
||||
{FormData.is_active ? 'Active' : 'Inactive'}
|
||||
</Text>
|
||||
<Text>{formData.is_active ? 'Active' : 'Inactive'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
|
||||
{props.actionMode !== 'add' && (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Plant Section Code</Text>
|
||||
<Input
|
||||
name="sub_section_code"
|
||||
value={FormData.sub_section_code}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Plant Section Code - Auto Increment & Read Only */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Plant Section Code</Text>
|
||||
<Input
|
||||
name="sub_section_code"
|
||||
value={formData.sub_section_code || ''}
|
||||
placeholder={'Plant Sub Section Code Auto Fill'}
|
||||
disabled
|
||||
style={{
|
||||
backgroundColor: '#f5f5f5',
|
||||
cursor: 'not-allowed',
|
||||
color: formData.sub_section_code ? '#000000' : '#bfbfbf',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Plant Sub Section Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="sub_section_name"
|
||||
value={FormData.sub_section_name}
|
||||
value={formData.sub_section_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Plant Sub Section Name"
|
||||
readOnly={props.readOnly}
|
||||
|
||||
@@ -1,83 +1,76 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ListShift from './component/ListShift';
|
||||
import DetailShift from './component/DetailShift';
|
||||
import { getAllShift } from '../../../api/master-shift';
|
||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const IndexShift = memo(function IndexShift() {
|
||||
const navigate = useNavigate();
|
||||
const { setBreadcrumbItems } = useBreadcrumb();
|
||||
|
||||
const IndexShift = () => {
|
||||
const [actionMode, setActionMode] = useState('list');
|
||||
const [selectedData, setSelectedData] = useState(null);
|
||||
const [shiftData, setShiftData] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [readOnly, setReadOnly] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const localData = localStorage.getItem('shiftData');
|
||||
if (localData) {
|
||||
setShiftData(JSON.parse(localData));
|
||||
} else {
|
||||
const response = await getAllShift();
|
||||
if (response.data) {
|
||||
setShiftData(response.data.data);
|
||||
localStorage.setItem('shiftData', JSON.stringify(response.data.data));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching shift data:", error);
|
||||
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;
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
setBreadcrumbItems([
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>• Master</Text> },
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>Shift</Text> }
|
||||
]);
|
||||
} else {
|
||||
navigate('/signin');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAddShift = (newShift) => {
|
||||
const newData = { ...newShift, id: Date.now() }; // Simulate adding an ID
|
||||
const updatedData = [newData, ...shiftData];
|
||||
setShiftData(updatedData);
|
||||
localStorage.setItem('shiftData', JSON.stringify(updatedData));
|
||||
setActionMode('list');
|
||||
};
|
||||
|
||||
const handleUpdateShift = (updatedShift) => {
|
||||
const updatedData = shiftData.map(shift => shift.id === updatedShift.id ? updatedShift : shift);
|
||||
setShiftData(updatedData);
|
||||
localStorage.setItem('shiftData', JSON.stringify(updatedData));
|
||||
setActionMode('list');
|
||||
};
|
||||
|
||||
const handleDeleteShift = (id) => {
|
||||
const updatedData = shiftData.filter(shift => shift.id !== id);
|
||||
setShiftData(updatedData);
|
||||
localStorage.setItem('shiftData', JSON.stringify(updatedData));
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{actionMode === 'list' && (
|
||||
<ListShift
|
||||
setActionMode={setActionMode}
|
||||
setSelectedData={setSelectedData}
|
||||
shiftData={shiftData}
|
||||
loading={loading}
|
||||
onDelete={handleDeleteShift}
|
||||
fetchData={fetchData}
|
||||
/>
|
||||
)}
|
||||
{(actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') && (
|
||||
<DetailShift
|
||||
actionMode={actionMode}
|
||||
selectedData={selectedData}
|
||||
setActionMode={setActionMode}
|
||||
setSelectedData={setSelectedData}
|
||||
onAdd={handleAddShift}
|
||||
onUpdate={handleUpdateShift}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<React.Fragment>
|
||||
<ListShift
|
||||
actionMode={actionMode}
|
||||
setActionMode={setMode}
|
||||
selectedData={selectedData}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<DetailShift
|
||||
setActionMode={setMode}
|
||||
selectedData={selectedData}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
showModal={showModal}
|
||||
actionMode={actionMode}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default IndexShift;
|
||||
|
||||
@@ -2,18 +2,22 @@ import { useEffect, useState } from 'react';
|
||||
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider } from 'antd';
|
||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import { createShift, updateShift } from '../../../../api/master-shift';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const DetailShift = (props) => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const readOnly = props.actionMode === 'preview';
|
||||
|
||||
const defaultData = {
|
||||
id: '',
|
||||
nama_shift: '',
|
||||
jam_shift: '',
|
||||
status: true, // default to active
|
||||
shift_id: '',
|
||||
shift_name: '',
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const [FormData, setFormData] = useState(defaultData);
|
||||
@@ -26,68 +30,225 @@ const DetailShift = (props) => {
|
||||
const handleSave = async () => {
|
||||
setConfirmLoading(true);
|
||||
|
||||
if (!FormData.nama_shift) {
|
||||
NotifOk({ icon: 'warning', title: 'Peringatan', message: 'Kolom Nama Shift Tidak Boleh Kosong' });
|
||||
// Validasi required fields
|
||||
if (!FormData.shift_name || FormData.shift_name.trim() === '') {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Nama Shift Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FormData.jam_shift) {
|
||||
NotifOk({ icon: 'warning', title: 'Peringatan', message: 'Kolom Jam Shift Tidak Boleh Kosong' });
|
||||
if (!FormData.start_time || FormData.start_time.trim() === '') {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Jam Mulai Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
nama_shift: FormData.nama_shift,
|
||||
jam_shift: FormData.jam_shift,
|
||||
status: FormData.status,
|
||||
};
|
||||
if (!FormData.end_time || FormData.end_time.trim() === '') {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Jam Selesai Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate time format
|
||||
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/;
|
||||
if (!timePattern.test(FormData.start_time)) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message:
|
||||
'Format Jam Mulai tidak valid. Gunakan format HH:mm atau HH:mm:ss (contoh: 08:00)',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!timePattern.test(FormData.end_time)) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message:
|
||||
'Format Jam Selesai tidak valid. Gunakan format HH:mm atau HH:mm:ss (contoh: 17:00)',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (FormData.id) {
|
||||
props.onUpdate(payload);
|
||||
if (FormData.shift_id) {
|
||||
// Update existing shift
|
||||
const payload = {
|
||||
shift_name: FormData.shift_name,
|
||||
start_time: FormData.start_time,
|
||||
end_time: FormData.end_time,
|
||||
is_active: FormData.is_active,
|
||||
};
|
||||
|
||||
const response = await updateShift(FormData.shift_id, payload);
|
||||
console.log('updateShift response:', response);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Shift "${FormData.shift_name}" berhasil diubah.`,
|
||||
});
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response.message || 'Gagal mengubah data Shift.',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
props.onAdd(payload);
|
||||
// Create new shift
|
||||
const payload = {
|
||||
shift_name: FormData.shift_name,
|
||||
start_time: FormData.start_time,
|
||||
end_time: FormData.end_time,
|
||||
is_active: FormData.is_active,
|
||||
};
|
||||
|
||||
const response = await createShift(payload);
|
||||
console.log('createShift response:', response);
|
||||
|
||||
if (response.statusCode === 200 || response.statusCode === 201) {
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Shift "${FormData.shift_name}" berhasil ditambahkan.`,
|
||||
});
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response.message || 'Gagal menambahkan data Shift.',
|
||||
});
|
||||
}
|
||||
}
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Shift "${payload.nama_shift}" berhasil ${FormData.id ? 'diubah' : 'ditambahkan'}.`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Save Shift Error:', error);
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.',
|
||||
message: error.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||
});
|
||||
}
|
||||
|
||||
setConfirmLoading(false);
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData({ ...FormData, [name]: value });
|
||||
// Helper function to format time input
|
||||
const formatTimeInput = (value) => {
|
||||
if (!value) return value;
|
||||
|
||||
// Remove any whitespace
|
||||
value = value.trim();
|
||||
|
||||
// If user inputs single digit hour like "8:00", convert to "08:00"
|
||||
const timeRegex = /^(\d{1,2}):(\d{2})(:\d{2})?$/;
|
||||
const match = value.match(timeRegex);
|
||||
|
||||
if (match) {
|
||||
const hours = match[1].padStart(2, '0');
|
||||
const minutes = match[2];
|
||||
const seconds = match[3] || '';
|
||||
return `${hours}:${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const handleStatusToggle = (checked) => {
|
||||
setFormData({ ...FormData, status: checked });
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
// Just set the value without formatting during typing
|
||||
setFormData({
|
||||
...FormData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
// Format time when user leaves the input field (onBlur)
|
||||
const handleTimeBlur = (e) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
if (name === 'start_time' || name === 'end_time') {
|
||||
const formattedValue = formatTimeInput(value);
|
||||
setFormData({
|
||||
...FormData,
|
||||
[name]: formattedValue,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusToggle = (isChecked) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
is_active: isChecked,
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to extract time from ISO timestamp using dayjs
|
||||
const extractTime = (timeString) => {
|
||||
if (!timeString) return '';
|
||||
|
||||
// If it's ISO timestamp like "1970-01-01T08:00:00.000Z"
|
||||
if (timeString.includes('T')) {
|
||||
return dayjs.utc(timeString).format('HH:mm');
|
||||
}
|
||||
|
||||
// If it's already in HH:mm:ss format, remove seconds
|
||||
if (timeString.includes(':')) {
|
||||
const parts = timeString.split(':');
|
||||
return `${parts[0]}:${parts[1]}`;
|
||||
}
|
||||
|
||||
return timeString;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.selectedData) {
|
||||
setFormData(props.selectedData);
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
if (props.selectedData != null) {
|
||||
// Only set fields that are in defaultData
|
||||
const filteredData = {
|
||||
shift_id: props.selectedData.shift_id || '',
|
||||
shift_name: props.selectedData.shift_name || '',
|
||||
start_time: extractTime(props.selectedData.start_time) || '',
|
||||
end_time: extractTime(props.selectedData.end_time) || '',
|
||||
is_active: props.selectedData.is_active ?? true,
|
||||
};
|
||||
setFormData(filteredData);
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
}
|
||||
}
|
||||
}, [props.actionMode, props.selectedData]);
|
||||
}, [props.showModal]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`${props.actionMode === 'add' ? 'Tambah' : props.actionMode === 'preview' ? 'Preview' : 'Edit'} Shift`}
|
||||
open={props.actionMode !== 'list'}
|
||||
title={`${
|
||||
props.actionMode === 'add'
|
||||
? 'Tambah'
|
||||
: props.actionMode === 'preview'
|
||||
? 'Preview'
|
||||
: 'Edit'
|
||||
} Shift`}
|
||||
open={props.showModal}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<>
|
||||
@@ -100,7 +261,6 @@ const DetailShift = (props) => {
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
@@ -123,7 +283,7 @@ const DetailShift = (props) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{!readOnly && (
|
||||
{!props.readOnly && (
|
||||
<Button loading={confirmLoading} onClick={handleSave}>
|
||||
Simpan
|
||||
</Button>
|
||||
@@ -134,7 +294,8 @@ const DetailShift = (props) => {
|
||||
>
|
||||
{FormData && (
|
||||
<div>
|
||||
<div>
|
||||
{/* Status Toggle */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div>
|
||||
<Text strong>Status</Text>
|
||||
</div>
|
||||
@@ -147,42 +308,66 @@ const DetailShift = (props) => {
|
||||
>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<Switch
|
||||
disabled={readOnly}
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.status === true ? '#23A55A' : '#bfbfbf',
|
||||
FormData.is_active === true ? '#23A55A' : '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.status === true}
|
||||
checked={FormData.is_active === true}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>{FormData.status === true ? 'Active' : 'Inactive'}</Text>
|
||||
<Text>{FormData.is_active === true ? 'Active' : 'Inactive'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Nama Shift</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="nama_shift"
|
||||
value={FormData.nama_shift}
|
||||
name="shift_name"
|
||||
value={FormData.shift_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan Nama Shift"
|
||||
readOnly={readOnly}
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Jam Shift</Text>
|
||||
<Text strong>Jam Mulai</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="jam_shift"
|
||||
value={FormData.jam_shift}
|
||||
name="start_time"
|
||||
value={FormData.start_time}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Contoh: 08:00 - 17:00"
|
||||
readOnly={readOnly}
|
||||
placeholder="Masukkan Jam Mulai"
|
||||
readOnly={props.readOnly}
|
||||
maxLength={8}
|
||||
/>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}
|
||||
>
|
||||
Contoh: 08:00 atau 08:00:00
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Jam Selesai</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="end_time"
|
||||
value={FormData.end_time}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan Jam Selesai"
|
||||
readOnly={props.readOnly}
|
||||
maxLength={8}
|
||||
/>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}
|
||||
>
|
||||
Contoh: 17:00 atau 17:00:00
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,183 +1,305 @@
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { Button, Col, Row, Input, ConfigProvider, Card, Tag, Table, Space } from 'antd';
|
||||
import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input } from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
SearchOutlined,
|
||||
EyeOutlined,
|
||||
SyncOutlined,
|
||||
SearchOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
||||
import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TableList from '../../../../components/Global/TableList';
|
||||
import { getAllShift, deleteShift } from '../../../../api/master-shift';
|
||||
|
||||
const ListShift = (props) => {
|
||||
// Helper function to extract time from ISO timestamp
|
||||
const extractTime = (timeString) => {
|
||||
if (!timeString) return '-';
|
||||
|
||||
// If it's ISO timestamp like "1970-01-01T08:00:00.000Z"
|
||||
if (timeString.includes('T')) {
|
||||
const date = new Date(timeString);
|
||||
const hours = String(date.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
// If it's already in HH:mm or HH:mm:ss format
|
||||
if (timeString.includes(':')) {
|
||||
const parts = timeString.split(':');
|
||||
return `${parts[0]}:${parts[1]}`; // Return HH:mm only
|
||||
}
|
||||
|
||||
return timeString;
|
||||
};
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'Nama Shift',
|
||||
dataIndex: 'shift_name',
|
||||
key: 'shift_name',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: 'Jam Mulai',
|
||||
dataIndex: 'start_time',
|
||||
key: 'start_time',
|
||||
width: '15%',
|
||||
render: (time) => extractTime(time),
|
||||
},
|
||||
{
|
||||
title: 'Jam Selesai',
|
||||
dataIndex: 'end_time',
|
||||
key: 'end_time',
|
||||
width: '15%',
|
||||
render: (time) => extractTime(time),
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
width: '10%',
|
||||
align: 'center',
|
||||
render: (_, { is_active }) => {
|
||||
const color = is_active ? 'green' : 'red';
|
||||
const text = is_active ? 'Active' : 'Inactive';
|
||||
return (
|
||||
<Tag color={color} key={'status'}>
|
||||
{text}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
key: 'aksi',
|
||||
align: 'center',
|
||||
width: '20%',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => showPreviewModal(record)}
|
||||
style={{
|
||||
color: '#1890ff',
|
||||
borderColor: '#1890ff',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showEditModal(record)}
|
||||
style={{
|
||||
color: '#faad14',
|
||||
borderColor: '#faad14',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => showDeleteDialog(record)}
|
||||
style={{
|
||||
borderColor: '#ff4d4f',
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const ListShift = memo(function ListShift(props) {
|
||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||
|
||||
const defaultFilter = {
|
||||
criteria: '',
|
||||
};
|
||||
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
if (token) {
|
||||
if (props.actionMode == 'list') {
|
||||
doFilter();
|
||||
}
|
||||
} else {
|
||||
navigate('/signin');
|
||||
}
|
||||
}, [navigate]);
|
||||
}, [props.actionMode]);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
// This will be handled by the parent component if server-side search is needed
|
||||
console.log('Search value:', value);
|
||||
const doFilter = () => {
|
||||
setTrigerFilter((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
setFormDataFilter((prev) => ({ ...prev, criteria: searchValue }));
|
||||
doFilter();
|
||||
};
|
||||
|
||||
const handleSearchClear = () => {
|
||||
setSearchValue('');
|
||||
// This will be handled by the parent component if server-side search is needed
|
||||
setFormDataFilter((prev) => ({ ...prev, criteria: '' }));
|
||||
doFilter();
|
||||
};
|
||||
|
||||
const showPreviewModal = (record) => {
|
||||
props.setSelectedData(record);
|
||||
const showPreviewModal = (param) => {
|
||||
props.setSelectedData(param);
|
||||
props.setActionMode('preview');
|
||||
};
|
||||
|
||||
const showEditModal = (record) => {
|
||||
props.setSelectedData(record);
|
||||
const showEditModal = (param = null) => {
|
||||
props.setSelectedData(param);
|
||||
props.setActionMode('edit');
|
||||
};
|
||||
|
||||
const showAddModal = () => {
|
||||
props.setSelectedData(null);
|
||||
const showAddModal = (param = null) => {
|
||||
props.setSelectedData(param);
|
||||
props.setActionMode('add');
|
||||
};
|
||||
|
||||
const showDeleteDialog = (record) => {
|
||||
const showDeleteDialog = (param) => {
|
||||
NotifConfirmDialog({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi',
|
||||
message: `Apakah anda yakin ingin menghapus data shift "${record.nama_shift}"?`,
|
||||
onConfirm: () => props.onDelete(record.id),
|
||||
message: `Apakah anda yakin hapus data "${param.shift_name}" ?`,
|
||||
onConfirm: () => handleDelete(param),
|
||||
onCancel: () => props.setSelectedData(null),
|
||||
});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'Nama Shift',
|
||||
dataIndex: 'nama_shift',
|
||||
key: 'nama_shift',
|
||||
},
|
||||
{
|
||||
title: 'Jam Shift',
|
||||
dataIndex: 'jam_shift',
|
||||
key: 'jam_shift',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
render: (status) => (
|
||||
<Tag color={status ? 'green' : 'red'}>
|
||||
{status ? 'Active' : 'Inactive'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: '15%',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => showPreviewModal(record)}
|
||||
style={{
|
||||
color: '#1890ff',
|
||||
borderColor: '#1890ff',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showEditModal(record)}
|
||||
style={{
|
||||
color: '#faad14',
|
||||
borderColor: '#faad14',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => showDeleteDialog(record)}
|
||||
style={{
|
||||
borderColor: '#ff4d4f',
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
const handleDelete = async (param) => {
|
||||
try {
|
||||
const response = await deleteShift(param.shift_id);
|
||||
console.log('deleteShift response:', response);
|
||||
|
||||
const filteredData = props.shiftData.filter(item =>
|
||||
item.nama_shift.toLowerCase().includes(searchValue.toLowerCase())
|
||||
);
|
||||
if (response.statusCode === 200) {
|
||||
NotifAlert({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Shift "${param.shift_name}" berhasil dihapus.`,
|
||||
});
|
||||
// Refresh table
|
||||
doFilter();
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response.message || 'Gagal menghapus data Shift.',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Delete Shift Error:', error);
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: error.message || 'Terjadi kesalahan saat menghapus data.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Function untuk dipanggil dari DetailShift setelah create/update
|
||||
const refreshData = () => {
|
||||
doFilter();
|
||||
};
|
||||
|
||||
// Pass refresh function to props
|
||||
if (props.setRefreshData) {
|
||||
props.setRefreshData(refreshData);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Row justify="space-between" align="middle" gutter={[16, 16]}>
|
||||
<Col xs={24} sm={12} md={10} lg={8}>
|
||||
<Input.Search
|
||||
placeholder="Cari nama shift..."
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
onSearch={handleSearch}
|
||||
allowClear={{
|
||||
clearIcon: <span onClick={handleSearchClear}>x</span>,
|
||||
}}
|
||||
enterButton={<Button type="primary" icon={<SearchOutlined />} style={{ backgroundColor: '#23A55A', borderColor: '#23A55A' }}>Cari</Button>}
|
||||
size="large"
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={showAddModal}
|
||||
size="large"
|
||||
>
|
||||
Tambah Shift
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginTop: 16 }}>
|
||||
<Col span={24}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredData}
|
||||
loading={props.loading}
|
||||
rowKey="id"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
<React.Fragment>
|
||||
<Card>
|
||||
<Row>
|
||||
<Col xs={24}>
|
||||
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<Input.Search
|
||||
placeholder="Search shift by name..."
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSearchValue(value);
|
||||
// Auto search when clearing by backspace/delete
|
||||
if (value === '') {
|
||||
handleSearchClear();
|
||||
}
|
||||
}}
|
||||
onSearch={handleSearch}
|
||||
allowClear
|
||||
onClear={handleSearchClear}
|
||||
enterButton={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
style={{
|
||||
backgroundColor: '#23A55A',
|
||||
borderColor: '#23A55A',
|
||||
}}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
}
|
||||
size="large"
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Space wrap size="small">
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => showAddModal()}
|
||||
size="large"
|
||||
>
|
||||
Tambah Data
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
||||
<TableList
|
||||
mobile
|
||||
cardColor={'#42AAFF'}
|
||||
header={'shift_name'}
|
||||
showPreviewModal={showPreviewModal}
|
||||
showEditModal={showEditModal}
|
||||
showDeleteDialog={showDeleteDialog}
|
||||
getData={getAllShift}
|
||||
queryParams={formDataFilter}
|
||||
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||
triger={trigerFilter}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(ListShift);
|
||||
export default ListShift;
|
||||
@@ -5,87 +5,23 @@ import { Typography } from 'antd';
|
||||
import ListStatus from './component/ListStatus';
|
||||
import DetailStatus from './component/DetailStatus';
|
||||
|
||||
import { NotifConfirmDialog, NotifAlert } from '../../../components/Global/ToastNotif';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// Mock Data
|
||||
const initialData = [
|
||||
{
|
||||
key: '3',
|
||||
statusCode: 3,
|
||||
statusName: 'Done',
|
||||
description: 'Indicates that the process is complete.',
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
statusCode: 1,
|
||||
statusName: 'Warning',
|
||||
description: 'Indicates a warning condition.',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
statusCode: 2,
|
||||
statusName: 'Alarm',
|
||||
description: 'Indicates an alarm condition.',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
statusCode: 4,
|
||||
statusName: 'Critical',
|
||||
description: 'Indicates a critical condition.',
|
||||
},
|
||||
];
|
||||
|
||||
const IndexStatus = memo(function IndexStatus() {
|
||||
const navigate = useNavigate();
|
||||
const { setBreadcrumbItems } = useBreadcrumb();
|
||||
|
||||
const [data, setData] = useState(initialData);
|
||||
const [actionMode, setActionMode] = useState('list');
|
||||
const [selectedData, setSelectedData] = useState(null);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [readOnly, setReadOnly] = useState(false);
|
||||
|
||||
// Mock API function
|
||||
const getAllStatus = async (params) => {
|
||||
const { page = 1, limit = 10, search = '' } = Object.fromEntries(params.entries());
|
||||
|
||||
let filteredData = data;
|
||||
if (search) {
|
||||
filteredData = data.filter(item =>
|
||||
item.statusName.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
const paginatedData = filteredData.slice(start, end);
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
status: 200,
|
||||
data: {
|
||||
data: paginatedData,
|
||||
total: filteredData.length,
|
||||
paging: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: filteredData.length,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
setBreadcrumbItems([
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>• Master</Text> },
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>Status</Text> }
|
||||
{ title: <Text strong>• Master</Text> },
|
||||
{ title: <Text strong>Status</Text> }
|
||||
]);
|
||||
} else {
|
||||
navigate('/signin');
|
||||
@@ -101,64 +37,21 @@ const IndexStatus = memo(function IndexStatus() {
|
||||
}
|
||||
}, [actionMode]);
|
||||
|
||||
const handleDataSaved = (values) => {
|
||||
let newData = [...data];
|
||||
if (values.key) { // Editing
|
||||
const index = newData.findIndex((item) => values.key === item.key);
|
||||
if (index > -1) {
|
||||
newData.splice(index, 1, values);
|
||||
}
|
||||
} else { // Adding
|
||||
const newKey = (Math.max(...data.map(item => parseInt(item.key))) + 1).toString();
|
||||
newData = [{ key: newKey, ...values }, ...newData];
|
||||
}
|
||||
setData(newData);
|
||||
};
|
||||
|
||||
const handleEdit = (record) => {
|
||||
setSelectedData(record);
|
||||
setActionMode('edit');
|
||||
};
|
||||
|
||||
const handlePreview = (record) => {
|
||||
setSelectedData(record);
|
||||
setActionMode('preview');
|
||||
};
|
||||
|
||||
const handleDelete = (record) => {
|
||||
NotifConfirmDialog({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi',
|
||||
message: `Apakah anda yakin ingin menghapus status "${record.statusName}"?`,
|
||||
onConfirm: () => {
|
||||
const newData = data.filter((item) => item.key !== record.key);
|
||||
setData(newData);
|
||||
NotifAlert({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Status "${record.statusName}" berhasil dihapus.`,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListStatus
|
||||
setActionMode={setActionMode}
|
||||
handleEdit={handleEdit}
|
||||
handleDelete={handleDelete}
|
||||
handlePreview={handlePreview}
|
||||
getAllStatus={getAllStatus}
|
||||
data={data}
|
||||
/>
|
||||
{actionMode === 'list' &&
|
||||
<ListStatus
|
||||
setActionMode={setActionMode}
|
||||
setSelectedData={setSelectedData}
|
||||
actionMode={actionMode}
|
||||
/>
|
||||
}
|
||||
<DetailStatus
|
||||
showModal={isModalVisible}
|
||||
setActionMode={setActionMode}
|
||||
selectedData={selectedData}
|
||||
readOnly={readOnly}
|
||||
onDataSaved={handleDataSaved}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -1,66 +1,113 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Input, Divider, Typography, Button, ConfigProvider, InputNumber, Form } from 'antd';
|
||||
import { Modal, Input, Divider, Typography, Button, ConfigProvider, InputNumber, Switch } from 'antd';
|
||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import { validateRun } from '../../../../Utils/validate';
|
||||
import { createStatus, updateStatus } from '../../../../api/master-status';
|
||||
|
||||
const { Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
const DetailStatus = (props) => {
|
||||
const [form] = Form.useForm();
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const defaultData = {
|
||||
key: '',
|
||||
statusCode: '',
|
||||
statusName: '',
|
||||
description: '',
|
||||
status_id: '',
|
||||
status_number: null,
|
||||
status_name: '',
|
||||
status_color: '',
|
||||
status_description: '',
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const [FormData, setFormData] = useState(defaultData);
|
||||
const [formData, setFormData] = useState(defaultData);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData({ ...formData, [name]: value });
|
||||
};
|
||||
|
||||
const handleInputNumberChange = (value) => {
|
||||
setFormData({ ...formData, status_number: value });
|
||||
};
|
||||
|
||||
const handleStatusToggle = (checked) => {
|
||||
setFormData({ ...formData, is_active: checked });
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
props.setSelectedData(null);
|
||||
props.setActionMode('list');
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setConfirmLoading(true);
|
||||
setConfirmLoading(true);
|
||||
|
||||
const validationRules = [
|
||||
{ field: 'status_number', label: 'Status Number', required: true },
|
||||
{ field: 'status_name', label: 'Status Name', required: true },
|
||||
{ field: 'status_color', label: 'Status Color', required: true },
|
||||
{ field: 'status_description', label: 'Description', required: true },
|
||||
];
|
||||
|
||||
if (
|
||||
validateRun(formData, validationRules, (errorMessages) => {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: errorMessages,
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
key: FormData.key,
|
||||
...values,
|
||||
status_number: formData.status_number,
|
||||
status_name: formData.status_name,
|
||||
status_color: formData.status_color,
|
||||
status_description: formData.status_description,
|
||||
is_active: formData.is_active,
|
||||
};
|
||||
|
||||
props.onDataSaved(payload);
|
||||
const response = formData.status_id
|
||||
? await updateStatus(formData.status_id, payload)
|
||||
: await createStatus(payload);
|
||||
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Status "${payload.statusName}" berhasil ${
|
||||
payload.key ? 'diubah' : 'ditambahkan'
|
||||
}.`,
|
||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||
const action = formData.status_id ? 'diubah' : 'ditambahkan';
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Status "${payload.status_name}" berhasil ${action}.`,
|
||||
});
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response?.message || 'Gagal menyimpan data.',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: error.message || 'Terjadi kesalahan pada server.',
|
||||
});
|
||||
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
props.setActionMode('list');
|
||||
form.resetFields();
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.selectedData) {
|
||||
setFormData(props.selectedData);
|
||||
form.setFieldsValue(props.selectedData);
|
||||
setFormData({ ...defaultData, ...props.selectedData });
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
form.resetFields();
|
||||
}
|
||||
}, [props.showModal, props.selectedData, form]);
|
||||
}, [props.showModal, props.selectedData]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -78,81 +125,72 @@ const DetailStatus = (props) => {
|
||||
footer={
|
||||
!props.readOnly && (
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', paddingTop: '15px' }}>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorPrimary: '#23A55A' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: 'white',
|
||||
defaultHoverBg: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleCancel}>Batal</Button>
|
||||
</ConfigProvider>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorPrimary: '#23A55A' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: '#23A55A',
|
||||
defaultColor: 'white',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: 'white',
|
||||
defaultHoverBg: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button type="primary" loading={confirmLoading} onClick={handleSave}>
|
||||
Simpan
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
<Button onClick={handleCancel}>Batal</Button>
|
||||
<Button type="primary" loading={confirmLoading} onClick={handleSave}>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Divider />
|
||||
<Form form={form} layout="vertical" name="detailStatusForm">
|
||||
<Form.Item
|
||||
name="statusCode"
|
||||
label={<Text strong>Status Code</Text>}
|
||||
rules={[{ required: true, message: 'Silakan masukkan kode status!' }]}
|
||||
>
|
||||
<InputNumber
|
||||
placeholder="Masukan code status"
|
||||
readOnly={props.readOnly}
|
||||
style={{ width: '100%' }}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Status</Text>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginTop: '8px' }}>
|
||||
<Switch
|
||||
disabled={props.readOnly}
|
||||
checked={formData.is_active}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="statusName"
|
||||
label={<Text strong>Status Name</Text>}
|
||||
rules={[{ required: true, message: 'Silakan masukkan nama status!' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="Masukan nama status"
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="description"
|
||||
label={<Text strong>Description</Text>}
|
||||
rules={[{ required: true, message: 'Silakan masukkan deskripsi!' }]}
|
||||
>
|
||||
<TextArea
|
||||
placeholder="Masukan deskripsi"
|
||||
readOnly={props.readOnly}
|
||||
rows={4}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Text style={{ marginLeft: 8 }}>{formData.is_active ? 'Active' : 'Inactive'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Status Number</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<InputNumber
|
||||
name="status_number"
|
||||
value={formData.status_number}
|
||||
placeholder="Masukan nomor status"
|
||||
readOnly={props.readOnly}
|
||||
style={{ width: '100%' }}
|
||||
onChange={handleInputNumberChange}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Status Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="status_name"
|
||||
value={formData.status_name}
|
||||
placeholder="Masukan nama status"
|
||||
readOnly={props.readOnly}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Status Color</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="status_color"
|
||||
value={formData.status_color}
|
||||
placeholder="Masukan warna status (e.g., hijau, #00ff00)"
|
||||
readOnly={props.readOnly}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Description</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<TextArea
|
||||
name="status_description"
|
||||
value={formData.status_description}
|
||||
placeholder="Masukan deskripsi"
|
||||
readOnly={props.readOnly}
|
||||
rows={4}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,68 +1,152 @@
|
||||
import React from 'react';
|
||||
import { Card, Button, Row, Col, Typography, Space, ConfigProvider } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { Space, ConfigProvider, Button, Row, Col, Card, Input, Segmented, Table, Pagination } from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
EyeOutlined,
|
||||
SearchOutlined,
|
||||
AppstoreOutlined,
|
||||
TableOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { deleteStatus, getAllStatuss } from '../../../../api/master-status';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const ListStatus = memo(function ListStatus(props) {
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [viewMode, setViewMode] = useState('card');
|
||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
||||
const navigate = useNavigate();
|
||||
|
||||
const ListStatus = ({
|
||||
setActionMode,
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
handlePreview,
|
||||
data,
|
||||
}) => {
|
||||
|
||||
const getCardStyle = (statusName) => {
|
||||
let color;
|
||||
switch (statusName.toLowerCase()) {
|
||||
case 'done':
|
||||
color = '#52c41a'; // green
|
||||
break;
|
||||
case 'warning':
|
||||
color = '#faad14'; // orange
|
||||
break;
|
||||
case 'alarm':
|
||||
color = '#f5222d'; // red
|
||||
break;
|
||||
case 'critical':
|
||||
color = '#000000'; // black
|
||||
break;
|
||||
default:
|
||||
color = '#d9d9d9'; // default antd border color
|
||||
const fetchData = async (page = 1, pageSize = 10) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.append('page', page);
|
||||
params.append('limit', pageSize);
|
||||
if (searchValue) {
|
||||
params.append('search', searchValue);
|
||||
}
|
||||
const response = await getAllStatuss(params);
|
||||
setData(response.data || []);
|
||||
setPagination(prev => ({ ...prev, total: response.paging?.total || 0, current: page, pageSize: pageSize }));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch status data:", error);
|
||||
setData([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return { border: `2px solid ${color}` };
|
||||
};
|
||||
|
||||
const getTitleStyle = (statusName) => {
|
||||
let backgroundColor;
|
||||
switch (statusName.toLowerCase()) {
|
||||
case 'done':
|
||||
backgroundColor = '#52c41a'; // green
|
||||
break;
|
||||
case 'warning':
|
||||
backgroundColor = '#faad14'; // orange
|
||||
break;
|
||||
case 'alarm':
|
||||
backgroundColor = '#f5222d'; // red
|
||||
break;
|
||||
case 'critical':
|
||||
backgroundColor = '#000000'; // black
|
||||
break;
|
||||
default:
|
||||
backgroundColor = 'transparent';
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
navigate('/signin');
|
||||
return;
|
||||
}
|
||||
return {
|
||||
backgroundColor,
|
||||
color: '#fff',
|
||||
padding: '2px 8px',
|
||||
borderRadius: '4px',
|
||||
display: 'inline-block'
|
||||
};
|
||||
fetchData(pagination.current, pagination.pageSize);
|
||||
}, [props.actionMode, trigerFilter, navigate]);
|
||||
|
||||
const doFilter = () => {
|
||||
setTrigerFilter(prev => !prev);
|
||||
};
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setSearchValue(value);
|
||||
setPagination(prev => ({ ...prev, current: 1 })); // Reset to first page on search
|
||||
doFilter();
|
||||
};
|
||||
|
||||
const handlePaginationChange = (page, pageSize) => {
|
||||
fetchData(page, pageSize);
|
||||
};
|
||||
|
||||
const showPreviewModal = (record) => {
|
||||
props.setSelectedData(record);
|
||||
props.setActionMode('preview');
|
||||
};
|
||||
|
||||
const showEditModal = (record) => {
|
||||
props.setSelectedData(record);
|
||||
props.setActionMode('edit');
|
||||
};
|
||||
|
||||
const showAddModal = () => {
|
||||
props.setSelectedData(null);
|
||||
props.setActionMode('add');
|
||||
};
|
||||
|
||||
const showDeleteDialog = (record) => {
|
||||
NotifConfirmDialog({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi Hapus',
|
||||
message: `Status "${record.status_name}" akan dihapus?`,
|
||||
onConfirm: () => handleDelete(record.status_id),
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (status_id) => {
|
||||
try {
|
||||
const response = await deleteStatus(status_id);
|
||||
if (response.statusCode === 200) {
|
||||
NotifAlert({ icon: 'success', title: 'Berhasil', message: 'Data Status berhasil dihapus.' });
|
||||
doFilter();
|
||||
} else {
|
||||
NotifAlert({ icon: 'error', title: 'Gagal', message: response?.message || 'Gagal Menghapus Data' });
|
||||
}
|
||||
} catch (error) {
|
||||
NotifAlert({ icon: 'error', title: 'Error', message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: 'Number', dataIndex: 'status_number', key: 'status_number', width: '15%' },
|
||||
{ title: 'Name', dataIndex: 'status_name', key: 'status_name', width: '25%' },
|
||||
{ title: 'Description', dataIndex: 'status_description', key: 'status_description', width: '40%' },
|
||||
{
|
||||
title: 'Aksi', key: 'aksi', align: 'center', width: '20%',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="text" icon={<EyeOutlined />} onClick={() => showPreviewModal(record)} />
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => showEditModal(record)} />
|
||||
<Button danger type="text" icon={<DeleteOutlined />} onClick={() => showDeleteDialog(record)} />
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const getCardStyle = (color) => {
|
||||
return { border: `2px solid ${color || '#d9d9d9'}`, height: '100%' };
|
||||
};
|
||||
|
||||
const getTitleStyle = (color) => {
|
||||
return { backgroundColor: color || 'transparent', color: '#fff', padding: '2px 8px', borderRadius: '4px', display: 'inline-block' };
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24, minHeight: 360 }}>
|
||||
<Row justify="end" style={{ marginBottom: 16 }}>
|
||||
<Card>
|
||||
<Row justify="space-between" align="middle" gutter={[16, 16]}>
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<Input.Search
|
||||
placeholder="Search by status name..."
|
||||
onSearch={handleSearch}
|
||||
allowClear
|
||||
enterButton={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
style={{ backgroundColor: '#23A55A', borderColor: '#23A55A' }}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
}
|
||||
size="large"
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
@@ -78,37 +162,64 @@ const ListStatus = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setActionMode('add')}
|
||||
>
|
||||
<Button icon={<PlusOutlined />} onClick={showAddModal} size="large">
|
||||
Tambah Data
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
{data.map(item => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={item.key}>
|
||||
<Card
|
||||
title={<span style={getTitleStyle(item.statusName)}>{item.statusName}</span>}
|
||||
style={getCardStyle(item.statusName)}
|
||||
actions={[
|
||||
<Space size="middle" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Button style={{ border: '1px solid #1890ff', color: '#1890ff', borderRadius: '6px', padding: '4px 8px' }} icon={<EyeOutlined />} onClick={() => handlePreview(item)} />
|
||||
<Button style={{ border: '1px solid #faad14', color: '#faad14', borderRadius: '6px', padding: '4px 8px' }} icon={<EditOutlined />} onClick={() => handleEdit(item)} />
|
||||
<Button danger style={{ border: '1px solid red', borderRadius: '6px', padding: '4px 8px' }} icon={<DeleteOutlined />} onClick={() => handleDelete(item)} />
|
||||
</Space>
|
||||
]}
|
||||
>
|
||||
<p><Text strong>Code:</Text> {item.statusCode}</p>
|
||||
<p><Text strong>Description:</Text> {item.description}</p>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
|
||||
<Row style={{ marginTop: 16 }}>
|
||||
<Col>
|
||||
<Segmented
|
||||
options={[{ value: 'card', icon: <AppstoreOutlined /> }, { value: 'table', icon: <TableOutlined /> }]}
|
||||
value={viewMode}
|
||||
onChange={setViewMode}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 24 }}>
|
||||
{viewMode === 'card' ? (
|
||||
<Row gutter={[16, 16]}>
|
||||
{data.map(item => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={item.status_id}>
|
||||
<Card
|
||||
title={<span style={getTitleStyle(item.status_color)}>{item.status_name}</span>}
|
||||
style={getCardStyle(item.status_color)}
|
||||
actions={[
|
||||
<EyeOutlined key="preview" onClick={() => showPreviewModal(item)} />,
|
||||
<EditOutlined key="edit" onClick={() => showEditModal(item)} />,
|
||||
<DeleteOutlined key="delete" onClick={() => showDeleteDialog(item)} />,
|
||||
]}
|
||||
>
|
||||
<p><b>Number:</b> {item.status_number}</p>
|
||||
<p><b>Description:</b> {item.status_description}</p>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data.map(item => ({ ...item, key: item.status_id }))}
|
||||
pagination={false}
|
||||
loading={loading}
|
||||
/>
|
||||
<Pagination
|
||||
style={{ marginTop: 16, textAlign: 'right' }}
|
||||
current={pagination.current}
|
||||
pageSize={pagination.pageSize}
|
||||
total={pagination.total}
|
||||
onChange={handlePaginationChange}
|
||||
showSizeChanger
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default ListStatus;
|
||||
@@ -26,12 +26,19 @@ const DetailTag = (props) => {
|
||||
unit: '',
|
||||
is_active: true,
|
||||
is_alarm: false,
|
||||
is_report: false,
|
||||
is_history: false,
|
||||
lim_low_crash: '',
|
||||
lim_low: '',
|
||||
lim_high: '',
|
||||
lim_high_crash: '',
|
||||
device_id: null,
|
||||
|
||||
sub_section_id: null,
|
||||
};
|
||||
|
||||
const [FormData, setFormData] = useState(defaultData);
|
||||
const [nextTagCode, setNextTagCode] = useState('Auto-fill');
|
||||
|
||||
const handleCancel = () => {
|
||||
props.setSelectedData(null);
|
||||
@@ -120,13 +127,13 @@ const DetailTag = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validasi data type harus Diskrit atau Analog
|
||||
const validDataTypes = ['Diskrit', 'Analog'];
|
||||
// Validasi data type harus Discrete atau Analog
|
||||
const validDataTypes = ['Discrete', 'Analog'];
|
||||
if (!validDataTypes.includes(FormData.data_type)) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: `Data Type harus "Diskrit" atau "Analog". Nilai "${FormData.data_type}" tidak valid. Silakan pilih dari dropdown.`,
|
||||
message: `Data Type harus "Discrete" atau "Analog". Nilai "${FormData.data_type}" tidak valid. Silakan pilih dari dropdown.`,
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
@@ -153,6 +160,17 @@ const DetailTag = (props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Plant Sub Section validation
|
||||
if (!FormData.sub_section_id) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Plant Sub Section harus dipilih',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare payload berdasarkan backend validation schema
|
||||
const payload = {
|
||||
tag_name: FormData.tag_name.trim(),
|
||||
@@ -161,9 +179,25 @@ const DetailTag = (props) => {
|
||||
unit: FormData.unit.trim(),
|
||||
is_active: FormData.is_active,
|
||||
is_alarm: FormData.is_alarm,
|
||||
is_report: FormData.is_report,
|
||||
is_history: FormData.is_history,
|
||||
device_id: parseInt(FormData.device_id),
|
||||
};
|
||||
|
||||
// Add limit fields only if they have values
|
||||
if (FormData.lim_low_crash !== '' && FormData.lim_low_crash !== null) {
|
||||
payload.lim_low_crash = parseFloat(FormData.lim_low_crash);
|
||||
}
|
||||
if (FormData.lim_low !== '' && FormData.lim_low !== null) {
|
||||
payload.lim_low = parseFloat(FormData.lim_low);
|
||||
}
|
||||
if (FormData.lim_high !== '' && FormData.lim_high !== null) {
|
||||
payload.lim_high = parseFloat(FormData.lim_high);
|
||||
}
|
||||
if (FormData.lim_high_crash !== '' && FormData.lim_high_crash !== null) {
|
||||
payload.lim_high_crash = parseFloat(FormData.lim_high_crash);
|
||||
}
|
||||
|
||||
// Add sub_section_id only if it's selected
|
||||
if (FormData.sub_section_id) {
|
||||
payload.sub_section_id = parseInt(FormData.sub_section_id);
|
||||
@@ -184,14 +218,17 @@ const DetailTag = (props) => {
|
||||
|
||||
// Check if response is successful
|
||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||
// response.data is already an object (converted in master-tag.jsx API)
|
||||
const tagCode = response.data?.tag_code || '';
|
||||
const tagName = response.data?.tag_name || FormData.tag_name || '';
|
||||
const tagDisplay = tagCode ? `${tagCode} - ${tagName}` : tagName;
|
||||
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message:
|
||||
response.message ||
|
||||
`Data Tag "${response.data?.tag_name || FormData.tag_name}" berhasil ${
|
||||
FormData.tag_id ? 'diubah' : 'ditambahkan'
|
||||
}.`,
|
||||
message: `Data Tag "${tagDisplay}" berhasil ${
|
||||
FormData.tag_id ? 'diubah' : 'ditambahkan'
|
||||
}.`,
|
||||
});
|
||||
|
||||
props.setActionMode('list');
|
||||
@@ -252,6 +289,20 @@ const DetailTag = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleReportToggle = (isChecked) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
is_report: isChecked,
|
||||
});
|
||||
};
|
||||
|
||||
const handleHistoryToggle = (isChecked) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
is_history: isChecked,
|
||||
});
|
||||
};
|
||||
|
||||
const loadDevices = async () => {
|
||||
setLoadingDevices(true);
|
||||
try {
|
||||
@@ -314,6 +365,42 @@ const DetailTag = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const generateNextTagCode = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams({ limit: 10000 });
|
||||
const response = await getAllTag(params);
|
||||
|
||||
if (response && response.data && response.data.data) {
|
||||
const tags = response.data.data;
|
||||
|
||||
if (tags.length === 0) {
|
||||
setNextTagCode('TAG001');
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract numeric part from tag codes and find the maximum
|
||||
const tagNumbers = tags
|
||||
.map((tag) => {
|
||||
const match = tag.tag_code?.match(/tag(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
})
|
||||
.filter((num) => !isNaN(num));
|
||||
|
||||
const maxNumber = tagNumbers.length > 0 ? Math.max(...tagNumbers) : 0;
|
||||
const nextNumber = maxNumber + 1;
|
||||
|
||||
// Format with leading zeros (TAG001, TAG002, etc.)
|
||||
const nextCode = `TAG${String(nextNumber).padStart(3, '0')}`;
|
||||
setNextTagCode(nextCode);
|
||||
} else {
|
||||
setNextTagCode('TAG001');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating next tag code:', error);
|
||||
setNextTagCode('Auto-fill');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
@@ -322,6 +409,11 @@ const DetailTag = (props) => {
|
||||
loadDevices();
|
||||
loadPlantSubSections();
|
||||
loadUnits();
|
||||
|
||||
// Generate next tag code only for add mode
|
||||
if (props.actionMode === 'add' && !props.selectedData) {
|
||||
generateNextTagCode();
|
||||
}
|
||||
}
|
||||
|
||||
if (props.selectedData != null) {
|
||||
@@ -335,6 +427,12 @@ const DetailTag = (props) => {
|
||||
unit: props.selectedData.unit || '',
|
||||
is_active: props.selectedData.is_active ?? true,
|
||||
is_alarm: props.selectedData.is_alarm ?? false,
|
||||
is_report: props.selectedData.is_report ?? false,
|
||||
is_history: props.selectedData.is_history ?? false,
|
||||
lim_low_crash: props.selectedData.lim_low_crash ?? '',
|
||||
lim_low: props.selectedData.lim_low ?? '',
|
||||
lim_high: props.selectedData.lim_high ?? '',
|
||||
lim_high_crash: props.selectedData.lim_high_crash ?? '',
|
||||
device_id: props.selectedData.device_id || null,
|
||||
device_code: props.selectedData.device_code || '',
|
||||
device_name: props.selectedData.device_name || '',
|
||||
@@ -347,7 +445,7 @@ const DetailTag = (props) => {
|
||||
} else {
|
||||
// navigate('/signin'); // Uncomment if useNavigate is imported
|
||||
}
|
||||
}, [props.showModal]);
|
||||
}, [props.showModal, props.actionMode]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -360,6 +458,7 @@ const DetailTag = (props) => {
|
||||
} Tag`}
|
||||
open={props.showModal}
|
||||
onCancel={handleCancel}
|
||||
width={800}
|
||||
footer={[
|
||||
<>
|
||||
<ConfigProvider
|
||||
@@ -413,19 +512,6 @@ const DetailTag = (props) => {
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
{/* Tag Code hanya ditampilkan saat EDIT atau PREVIEW */}
|
||||
{(props.actionMode === 'edit' || props.actionMode === 'preview') && (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Tag Code</Text>
|
||||
<Input
|
||||
name="tag_code"
|
||||
value={FormData.tag_code}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Auto Generate"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Status dan Alarm dalam satu baris */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div
|
||||
@@ -500,6 +586,94 @@ const DetailTag = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Report dan History dalam satu baris */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '16px',
|
||||
}}
|
||||
>
|
||||
{/* Report Toggle */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<div>
|
||||
<Text strong>Report</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<Switch
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.is_report === true
|
||||
? '#23A55A'
|
||||
: '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.is_report === true}
|
||||
onChange={handleReportToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>{FormData.is_report === true ? 'Yes' : 'No'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* History Toggle */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<div>
|
||||
<Text strong>History</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<Switch
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.is_history === true
|
||||
? '#23A55A'
|
||||
: '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.is_history === true}
|
||||
onChange={handleHistoryToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>{FormData.is_history === true ? 'Yes' : 'No'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Tag Code - Auto Increment & Read Only */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Tag Code</Text>
|
||||
<Input
|
||||
name="tag_code"
|
||||
value={FormData.tag_code || nextTagCode}
|
||||
placeholder={nextTagCode}
|
||||
disabled
|
||||
style={{
|
||||
backgroundColor: '#f5f5f5',
|
||||
cursor: 'not-allowed',
|
||||
color: FormData.tag_code ? '#000000' : '#bfbfbf',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Tag Number</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
@@ -532,7 +706,7 @@ const DetailTag = (props) => {
|
||||
onChange={(value) => handleSelectChange('data_type', value)}
|
||||
disabled={props.readOnly}
|
||||
>
|
||||
<Select.Option value="Diskrit">Diskrit</Select.Option>
|
||||
<Select.Option value="Discrete">Discrete</Select.Option>
|
||||
<Select.Option value="Analog">Analog</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -562,8 +736,58 @@ const DetailTag = (props) => {
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
{/* Limit Fields */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Limit Low Crash</Text>
|
||||
<Input
|
||||
name="lim_low_crash"
|
||||
value={FormData.lim_low_crash}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Limit Low Crash"
|
||||
readOnly={props.readOnly}
|
||||
type="number"
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Limit Low</Text>
|
||||
<Input
|
||||
name="lim_low"
|
||||
value={FormData.lim_low}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Limit Low"
|
||||
readOnly={props.readOnly}
|
||||
type="number"
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Limit High</Text>
|
||||
<Input
|
||||
name="lim_high"
|
||||
value={FormData.lim_high}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Limit High"
|
||||
readOnly={props.readOnly}
|
||||
type="number"
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Limit High Crash</Text>
|
||||
<Input
|
||||
name="lim_high_crash"
|
||||
value={FormData.lim_high_crash}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Limit High Crash"
|
||||
readOnly={props.readOnly}
|
||||
type="number"
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Plant Sub Section</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Select Plant Sub Section"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Modal, Input, Typography, Button, ConfigProvider, Switch } from 'antd';
|
||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider } from 'antd';
|
||||
import { NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import { createUnit, updateUnit } from '../../../../api/master-unit';
|
||||
import { validateRun } from '../../../../Utils/validate';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -15,7 +16,7 @@ const DetailUnit = (props) => {
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const [FormData, setFormData] = useState(defaultData);
|
||||
const [formData, setFormData] = useState(defaultData);
|
||||
|
||||
const handleCancel = () => {
|
||||
props.setSelectedData(null);
|
||||
@@ -25,115 +26,84 @@ const DetailUnit = (props) => {
|
||||
const handleSave = async () => {
|
||||
setConfirmLoading(true);
|
||||
|
||||
// Validasi required fields
|
||||
if (!FormData.unit_name || FormData.unit_name.trim() === '') {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: 'Kolom Name Tidak Boleh Kosong',
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
// Daftar aturan validasi
|
||||
const validationRules = [
|
||||
{ field: 'unit_name', label: 'Unit Name', required: true },
|
||||
];
|
||||
|
||||
if (
|
||||
validateRun(formData, validationRules, (errorMessages) => {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
title: 'Peringatan',
|
||||
message: errorMessages,
|
||||
});
|
||||
setConfirmLoading(false);
|
||||
})
|
||||
)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (FormData.unit_id) {
|
||||
// Update existing unit
|
||||
const payload = {
|
||||
name: FormData.unit_name,
|
||||
is_active: FormData.is_active,
|
||||
};
|
||||
const payload = {
|
||||
is_active: formData.is_active,
|
||||
unit_name: formData.unit_name,
|
||||
};
|
||||
|
||||
const response = await updateUnit(FormData.unit_id, payload);
|
||||
console.log('updateUnit response:', response);
|
||||
const response =
|
||||
props.actionMode === 'edit'
|
||||
? await updateUnit(formData.unit_id, payload)
|
||||
: await createUnit(payload);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
// Get updated data to show unit_code in notification
|
||||
const unitCode = response.data?.unit_code || FormData.unit_code;
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Unit "${unitCode} - ${FormData.unit_name}" berhasil diubah.`,
|
||||
});
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response.message || 'Gagal mengubah data Unit.',
|
||||
});
|
||||
}
|
||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||
const action = props.actionMode === 'edit' ? 'diubah' : 'ditambahkan';
|
||||
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Unit berhasil ${action}.`,
|
||||
});
|
||||
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
// Create new unit
|
||||
const payload = {
|
||||
name: FormData.unit_name,
|
||||
is_active: FormData.is_active,
|
||||
};
|
||||
|
||||
const response = await createUnit(payload);
|
||||
console.log('createUnit response:', response);
|
||||
|
||||
if (response.statusCode === 200 || response.statusCode === 201) {
|
||||
// Get unit_code from response
|
||||
const unitCode = response.data?.unit_code || 'N/A';
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: `Data Unit "${unitCode} - ${FormData.unit_name}" berhasil ditambahkan.`,
|
||||
});
|
||||
props.setActionMode('list');
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response.message || 'Gagal menambahkan data Unit.',
|
||||
});
|
||||
}
|
||||
NotifOk({
|
||||
icon: 'error',
|
||||
title: 'Gagal',
|
||||
message: response?.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save Unit Error:', error);
|
||||
NotifAlert({
|
||||
NotifOk({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: error.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||
message: error.message || 'Terjadi kesalahan pada server.',
|
||||
});
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
|
||||
setConfirmLoading(false);
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData({
|
||||
...FormData,
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleStatusToggle = (isChecked) => {
|
||||
const handleStatusToggle = (checked) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
is_active: isChecked,
|
||||
...formData,
|
||||
is_active: checked,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
if (props.selectedData != null) {
|
||||
// Only set fields that are in defaultData
|
||||
const filteredData = {
|
||||
unit_id: props.selectedData.unit_id || '',
|
||||
unit_code: props.selectedData.unit_code || '',
|
||||
unit_name: props.selectedData.unit_name || '',
|
||||
is_active: props.selectedData.is_active ?? true,
|
||||
};
|
||||
setFormData(filteredData);
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
}
|
||||
if (props.selectedData) {
|
||||
setFormData(props.selectedData);
|
||||
} else {
|
||||
setFormData(defaultData);
|
||||
}
|
||||
}, [props.showModal]);
|
||||
}, [props.showModal, props.selectedData, props.actionMode]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -147,34 +117,27 @@ const DetailUnit = (props) => {
|
||||
open={props.showModal}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<>
|
||||
<React.Fragment key="modal-footer">
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button onClick={handleCancel}>Batal</Button>
|
||||
<Button onClick={handleCancel}>{props.readOnly ? 'Tutup' : 'Batal'}</Button>
|
||||
</ConfigProvider>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
colorBgContainer: '#209652',
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: '#23a55a',
|
||||
defaultColor: '#FFFFFF',
|
||||
defaultBorderColor: '#23a55a',
|
||||
defaultHoverColor: '#FFFFFF',
|
||||
defaultHoverBorderColor: '#23a55a',
|
||||
},
|
||||
},
|
||||
}}
|
||||
@@ -185,60 +148,55 @@ const DetailUnit = (props) => {
|
||||
</Button>
|
||||
)}
|
||||
</ConfigProvider>
|
||||
</>,
|
||||
</React.Fragment>,
|
||||
]}
|
||||
>
|
||||
{FormData && (
|
||||
{formData && (
|
||||
<div>
|
||||
{/* Status Toggle */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div>
|
||||
<div>
|
||||
<Text strong>Status</Text>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginTop: '8px' }}>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<Switch
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.is_active === true
|
||||
? '#23A55A'
|
||||
: '#bfbfbf',
|
||||
backgroundColor: formData.is_active ? '#23A55A' : '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.is_active === true}
|
||||
checked={formData.is_active}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>
|
||||
{FormData.is_active === true ? 'Active' : 'Inactive'}
|
||||
</Text>
|
||||
<Text>{formData.is_active ? 'Active' : 'Inactive'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Unit Code - Display only for edit/preview */}
|
||||
{FormData.unit_code && (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Unit Code</Text>
|
||||
<Input
|
||||
name="unit_code"
|
||||
value={FormData.unit_code}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
|
||||
{/* Unit Code - Auto Increment & Read Only */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Name</Text>
|
||||
<Text strong>Unit Code</Text>
|
||||
<Input
|
||||
name="unit_code"
|
||||
value={formData.unit_code || ''}
|
||||
placeholder={'Unit Code Auto Fill'}
|
||||
disabled
|
||||
style={{
|
||||
backgroundColor: '#f5f5f5',
|
||||
cursor: 'not-allowed',
|
||||
color: formData.unit_code ? '#000000' : '#bfbfbf',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Unit Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="unit_name"
|
||||
value={FormData.unit_name}
|
||||
value={formData.unit_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Unit Name"
|
||||
readOnly={props.readOnly}
|
||||
|
||||
238
svg/air_dryer_A_rev.svg
Normal file
238
svg/air_dryer_A_rev.svg
Normal file
@@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 500" xmlns:bx="https://boxy-svg.com">
|
||||
<defs>
|
||||
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||
</defs>
|
||||
<rect y="10.407" width="972.648" height="440.159" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" x="12.119"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 74.03907, 53.375034)">
|
||||
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||
</g>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 500.726135, 53.375034)">
|
||||
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||
</g>
|
||||
<rect x="371.728" y="182.483" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-box: fill-box; transform-origin: 50% 50%;" d="M 551.111 -16.154 L 551.202 389.079" transform="matrix(0, -1.184039, 0.844567, 0, 0.000036, 0.000096)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 479.043 149.568 L 495.765 149.568 L 495.765 166.035 L 479.043 166.035 L 479.043 149.568 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 598.485px 226.003px;" d="M 478.737 156.666 L 495.763 156.666"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 465.169 193.204 L 511.13 179.759 L 511.13 193.204 L 465.169 179.759 L 465.169 193.204 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 487.934 186.514 L 487.934 157.095"/>
|
||||
<rect x="715.724" y="182.138" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, -2.11712, 3.138935)">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 443.701px 171.141px;" d="M 443.542 155.983 L 443.859 186.298"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 0.443817, 3.138935)">
|
||||
<rect x="752" y="355.455" width="42.438" height="3.527" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<rect x="756.328" y="359.271" width="34.034" height="53.968" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<rect x="756.146" y="352.019" width="34.034" height="3.38" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="773.446" cy="384.7" rx="11.751" ry="11.009"/>
|
||||
</g>
|
||||
<rect x="461.861" y="211.956" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px;" x="561" y="309.954" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp SP</text>
|
||||
<rect x="461.861" y="221.924" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="564.279" y="330.561" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="609.476" y="330.521" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<rect x="461.424" y="242.149" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="567.471" y="352.188" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||
<rect x="461.424" y="252.117" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="563.75" y="373.795" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="Dry1_HeatTempCelsius">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="608.947" y="373.755" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<rect x="535.456" y="242.272" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="659" y="352.363" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||
<rect x="535.456" y="252.24" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="653.279" y="373.97" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="698.476" y="373.93" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1; font-weight: bold;" x="748" y="347.676" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER</text>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 678.512px 258.693px;" d="M 678.467 229.321 L 678.558 288.066" transform="matrix(0, 1.184039, -0.844567, 0, -0.000022, -0.000005)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.162px 309.166px;" d="M 703.004 258.049 L 703.32 360.282"/>
|
||||
<rect x="371.639" y="358.212" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.133px 361.256px;" d="M 692.126 397.022 L 692.14 325.49" transform="matrix(0, -1.184039, 0.844567, 0, 0.000011, 0.00005)"/>
|
||||
<rect x="715.635" y="357.867" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 798px 493.641px;" d="M 661.975 334.874 L 661.975 360.911"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 552.59px 334.782px;" d="M 552.508 244.429 L 552.665 425.136" transform="matrix(0, 1.184039, -0.844567, 0, -0.000058, 0.000018)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 601.786px 283.137px;" d="M 601.741 312.51 L 601.832 253.764" transform="matrix(0, 1.184039, -0.844567, 0, -0.000063, 0.000028)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 577.266px 308.682px;" d="M 577.237 334.87 L 577.295 282.492" transform="matrix(-1, 0, 0, -1, -0.000041, -0.00003)"/>
|
||||
<g transform="matrix(-0.491177, 0, 0, 0.523644, 491.13504, 29.785091)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 406.945px 361.188px;" d="M 406.92 329.322 L 406.969 393.054" transform="matrix(0, 1.184039, -0.844567, 0, 0.00001, 0.00007)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 521.472px 494.156px;" d="M 433.307 334.116 L 433.308 362.382"/>
|
||||
<g transform="matrix(0.491177, 0, 0, 0.523644, 419.010895, 56.68491)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<g transform="matrix(0.491177, 0, 0, 0.523644, 603.674133, 29.692444)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<g transform="matrix(-0.491177, 0, 0, 0.523644, 677.00824, 56.209183)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 548.454px 361.62px;" d="M 548.372 271.267 L 548.529 451.974" transform="matrix(0, 1.184039, -0.844567, 0, 0.00006, -0.000036)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 578.392px 383.459px;" d="M 578.329 405.407 L 578.456 361.51" transform="matrix(-1, 0, 0, -1, -0.000055, -0.000003)"/>
|
||||
<g transform="matrix(0.826913, 0, 0, -0.698383, 0.257882, 545.083069)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 704.638px 576.106px;" d="M 621.828 405.49 L 634.485 405.49"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 584.749px 405.496px;" d="M 591.042 405.497 L 578.456 405.498"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 606.436px 405.492px;" d="M 591.042 398.364 L 591.042 412.623 L 621.831 398.364 L 621.831 412.623 L 591.042 398.364 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 599.432px 413.831px;" d="M 592.464 422.169 L 592.464 405.493 L 606.401 405.493" transform="matrix(-1, 0, 0, -1, -0.000078, -0.000049)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 648.376px 405.65px;" d="M 648.369 435.621 L 648.383 375.678" transform="matrix(0, -1.184039, 0.844567, 0, 0.000012, -0.00004)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 673.882px 406.067px;" d="M 673.869 398.172 L 673.893 413.963" transform="matrix(-1, 0, 0, -1, -0.000092, -0.000022)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.821px 404.67px;" d="M 692.786 392.381 L 692.854 416.962" transform="matrix(0, 1.184039, -0.844567, 0, 0.000001, -0.000097)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.406px 383.726px;" d="M 703.396 405.334 L 703.415 362.116"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 681.807px 406.051px;" d="M 681.793 398.157 L 681.818 413.948" transform="matrix(-1, 0, 0, -1, 0.000055, 0.000046)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.987px 397.326px;" d="M 677.977 403.041 L 677.995 391.61"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.933px 411.78px;" d="M 677.924 417.977 L 677.941 405.582"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="650.811" cy="385.504" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(0.000343, -1, 1, 0.000266, 743.834961, 473.853179)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 651.034px 399.693px;" d="M 651.025 405.408 L 651.042 393.977"/>
|
||||
<path d="M -546.834 -405.87 L -543.785 -398.138 L -550.56 -398.138 L -546.834 -405.87 Z" bx:shape="triangle -550.56 -405.87 6.775 7.732 0.55 0 1@11f2e68a" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(-1, 0, 0, -1, 1094.344971, 804.007996)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 546.953px 390.056px;" d="M 546.94 382.161 L 546.965 397.952" transform="matrix(-1, 0, 0, -1, -0.000013, 0.000046)"/>
|
||||
<rect x="427.269" y="377.282" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.167" y="545.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||
<rect x="427.269" y="387.25" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.446" y="567.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.643" y="567.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||
<rect x="427.27" y="412.201" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.168" y="595.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||
<rect x="427.27" y="422.169" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.447" y="617.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.644" y="617.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 24.207672, -7.192523)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>OUTLET</text>
|
||||
<g transform="matrix(-0.387768, 0, 0, -0.200385, 743.634644, -199.991287)" style="transform-origin: 72.2405px 412.5px;">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||
</g>
|
||||
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, -0.563979, 0, 588.703491, -326.882202)">
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 807.67px 264.838px;" d="M 807.657 276.289 L 807.693 253.387" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.000091)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 816.592px 247.829px;" d="M 816.56 230.114 L 816.655 265.543" transform="matrix(-1, 0, 0, -1, 0.000101, 0.000009)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="840.215" cy="233.608" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 933.237163, 321.956912)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="839.876" cy="254.847" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 932.895305, 343.197025)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 823.288px 234.151px;" d="M 823.282 242.163 L 823.294 226.137" transform="matrix(0, 1.184039, -0.844567, 0, -0.000084, 0.000084)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 822.751px 254.694px;" d="M 822.745 262.706 L 822.763 246.68" transform="matrix(0, 1.184039, -0.844567, 0, -0.00002, 0.000032)"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 909.661358, 277.142276)"/>
|
||||
<g transform="matrix(0.387768, 0, 0, -0.200385, 207.60318, -199.315506)" style="transform-origin: 72.2406px 412.5px;">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||
</g>
|
||||
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, 0.563979, 0, 51.251434, -326.206329)">
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 288.049px 265.514px;" d="M 288.037 254.064 L 288.073 276.966" transform="matrix(0, -1.184039, 0.844567, 0, -0.000019, 0.000042)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 279.127px 248.505px;" d="M 279.095 266.219 L 279.19 230.79"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.5" cy="234.284" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.108874, 140.490195)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.84" cy="255.523" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.450457, 161.730307)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.43px 234.827px;" d="M 272.424 226.815 L 272.436 242.841" transform="matrix(0, 1.184039, -0.844567, 0, 0.000012, -0.000015)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.968px 255.37px;" d="M 272.962 247.358 L 272.98 263.384" transform="matrix(0, 1.184039, -0.844567, 0, -0.000011, 0.000034)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 170.68468, 95.675559)"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 47.595016, -269.416931)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>INLET</text>
|
||||
<path d="M -544.544 -165.9 L -541.495 -158.168 L -548.27 -158.168 L -544.544 -165.9 Z" bx:shape="triangle -548.27 -165.9 6.775 7.732 0.55 0 1@12a1c9af" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -544.876px -162.033px;" transform="matrix(-1, 0, 0, -1, 1089.751221, 324.066467)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 544.653px 150.086px;" d="M 544.639 142.192 L 544.664 157.983" transform="matrix(-1, 0, 0, -1, -0.000086, 0.000018)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 404.306 125.878 L 416.234 125.878 L 416.234 148.965 L 404.306 148.965 L 404.306 125.878 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000039, -0.000007)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 498.152px 180.679px;" d="M 408.92 144.738 L 408.92 130.357"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 427.836 146.217 L 460.619 127.367 L 460.619 146.217 L 427.836 127.367 L 427.836 146.217 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000009)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 426.851 157.594 L 426.851 116.351" transform="matrix(0, -1.18404, 0.844567, 0, -0.000439, -0.000081)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 647.335px 171.745px;" d="M 647.177 186.902 L 647.494 156.588" transform="matrix(-1, 0, 0, -1, -0.000021, -0.000027)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 680.766px 138.025px;" d="M 674.802 149.569 L 686.73 149.569 L 686.73 126.482 L 674.802 126.482 L 674.802 149.569 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000004, 0.000043)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 897.148px 118.148px;" d="M 682.129 145.344 L 682.129 130.964"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 646.809px 137.396px;" d="M 630.417 127.971 L 663.201 146.821 L 663.201 127.971 L 630.417 146.821 L 630.417 127.971 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000004, -0.000033)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 664.185px 137.577px;" d="M 664.185 116.956 L 664.186 158.199" transform="matrix(0, -1.184039, 0.844567, 0, -0.000026, -0.000041)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 590.855 148.509 L 607.577 148.509 L 607.577 164.977 L 590.855 164.977 L 590.855 148.509 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 733.701px 224.488px;" d="M 590.555 155.608 L 607.58 155.608"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 576.981 192.146 L 622.941 178.701 L 622.941 192.146 L 576.981 178.701 L 576.981 192.146 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 599.746 185.455 L 599.746 156.037"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 640.474 90.952 L 652.913 90.952 L 652.913 107.419 L 640.474 107.419 L 640.474 90.952 Z"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 646.819px 113.115px;" d="M 646.799 107.896 L 646.839 118.334"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 650.466px 95.308px;" d="M 650.455 97.624 L 650.476 92.992" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.00002)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 642.857px 102.712px;" d="M 642.846 105.028 L 642.867 100.396" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000035)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 437.929 90.244 L 450.367 90.244 L 450.367 106.712 L 437.929 106.712 L 437.929 90.244 Z"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 444.274px 112.408px;" d="M 444.253 107.189 L 444.293 117.627"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 447.919px 94.601px;" d="M 447.909 96.917 L 447.93 92.285" transform="matrix(0, -1.184039, 0.844567, 0, -0.000022, 0.000005)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 440.31px 102.004px;" d="M 440.3 104.321 L 440.32 99.689" transform="matrix(0, -1.184039, 0.844567, 0, -0.000028, -0.000004)"/>
|
||||
<rect x="43.443" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="53.987" y="423.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RUN HOUR</text>
|
||||
<rect x="126.135" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="424.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="424.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="461.382" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">PURGE HOUR</text>
|
||||
<rect x="126.135" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="463.397" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="463.357" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; font-weight: 700; white-space: pre;" x="53.987" y="498.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER HOUR</text>
|
||||
<rect x="126.135" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="499.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="499.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.65" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(248, 213, 14);"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="536.777" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Info</text>
|
||||
<rect x="126.341" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.3" y="537.792" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="537.752" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="56.987" y="250.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT or LT Dry</text>
|
||||
<rect x="126.135" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="180.05" y="251.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">LT Dry</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177" y="251.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT Dry</text>
|
||||
<rect x="43.443" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="288.876" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Opmode</text>
|
||||
<rect x="126.135" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HTD</text>
|
||||
<rect x="43.443" y="214.051" width="165.383" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="53.987" y="322.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Step</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177.05" y="323.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="233" y="323.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||
<rect x="43.443" y="241.422" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="364.271" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Cycle Timer</text>
|
||||
<rect x="126.341" y="241.068" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="168.775" y="365.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="365.246" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="141.894" y="324.069" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Time</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="92.151" y="325.554" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">##</text>
|
||||
<rect x="870.356" y="142.816" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1060.06" y="224.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dryer Status</text>
|
||||
<rect x="870.356" y="170.304" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="192.401" rx="20.673" ry="17.46"/>
|
||||
<rect x="870.356" y="230.113" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1069.06" y="349.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Shutdown</text>
|
||||
<rect x="870.356" y="257.602" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="279.699" rx="20.673" ry="17.46"/>
|
||||
<rect x="870.356" y="317.411" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1059.06" y="474.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Status</text>
|
||||
<rect x="870.356" y="344.9" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="366.997" rx="20.673" ry="17.46"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="380.451" y="296.591" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="334.165" cy="232.104" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="379.214" y="423.395" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.623" cy="320.662" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="897.237" y="299.014" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="761.502" cy="233.796" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="896" y="425.818" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.96" cy="322.354" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 35px; stroke-width: 1; font-weight: bold;" x="348.875" y="78.242" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">AIR DRYER UNIT A (01-CL-10532-A)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 40 KiB |
238
svg/air_dryer_B_rev.svg
Normal file
238
svg/air_dryer_B_rev.svg
Normal file
@@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 500" xmlns:bx="https://boxy-svg.com">
|
||||
<defs>
|
||||
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||
</defs>
|
||||
<rect y="10.407" width="972.648" height="439.023" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" x="12.119"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 74.03907, 53.375034)">
|
||||
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||
</g>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 500.726135, 53.375034)">
|
||||
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||
</g>
|
||||
<rect x="371.728" y="182.483" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-box: fill-box; transform-origin: 50% 50%;" d="M 551.111 -16.154 L 551.202 389.079" transform="matrix(0, -1.184039, 0.844567, 0, 0.000036, 0.000096)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 479.043 149.568 L 495.765 149.568 L 495.765 166.035 L 479.043 166.035 L 479.043 149.568 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 598.485px 226.003px;" d="M 478.737 156.666 L 495.763 156.666"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 465.169 193.204 L 511.13 179.759 L 511.13 193.204 L 465.169 179.759 L 465.169 193.204 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 487.934 186.514 L 487.934 157.095"/>
|
||||
<rect x="715.724" y="182.138" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, -2.11712, 3.138935)">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 443.701px 171.141px;" d="M 443.542 155.983 L 443.859 186.298"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 0.443817, 3.138935)">
|
||||
<rect x="752" y="355.455" width="42.438" height="3.527" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<rect x="756.328" y="359.271" width="34.034" height="53.968" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<rect x="756.146" y="352.019" width="34.034" height="3.38" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="773.446" cy="384.7" rx="11.751" ry="11.009"/>
|
||||
</g>
|
||||
<rect x="461.861" y="211.956" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px;" x="561" y="309.954" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp SP</text>
|
||||
<rect x="461.861" y="221.924" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="564.279" y="330.561" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="609.476" y="330.521" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<rect x="461.424" y="242.149" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="567.471" y="352.188" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||
<rect x="461.424" y="252.117" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="563.75" y="373.795" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="Dry1_HeatTempCelsius">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="608.947" y="373.755" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<rect x="535.456" y="242.272" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="659" y="352.363" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||
<rect x="535.456" y="252.24" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="653.279" y="373.97" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="698.476" y="373.93" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1; font-weight: bold;" x="748" y="347.676" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER</text>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 678.512px 258.693px;" d="M 678.467 229.321 L 678.558 288.066" transform="matrix(0, 1.184039, -0.844567, 0, -0.000022, -0.000005)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.162px 309.166px;" d="M 703.004 258.049 L 703.32 360.282"/>
|
||||
<rect x="371.639" y="358.212" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.133px 361.256px;" d="M 692.126 397.022 L 692.14 325.49" transform="matrix(0, -1.184039, 0.844567, 0, 0.000011, 0.00005)"/>
|
||||
<rect x="715.635" y="357.867" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 798px 493.641px;" d="M 661.975 334.874 L 661.975 360.911"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 552.59px 334.782px;" d="M 552.508 244.429 L 552.665 425.136" transform="matrix(0, 1.184039, -0.844567, 0, -0.000058, 0.000018)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 601.786px 283.137px;" d="M 601.741 312.51 L 601.832 253.764" transform="matrix(0, 1.184039, -0.844567, 0, -0.000063, 0.000028)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 577.266px 308.682px;" d="M 577.237 334.87 L 577.295 282.492" transform="matrix(-1, 0, 0, -1, -0.000041, -0.00003)"/>
|
||||
<g transform="matrix(-0.491177, 0, 0, 0.523644, 491.13504, 29.785091)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 406.945px 361.188px;" d="M 406.92 329.322 L 406.969 393.054" transform="matrix(0, 1.184039, -0.844567, 0, 0.00001, 0.00007)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 521.472px 494.156px;" d="M 433.307 334.116 L 433.308 362.382"/>
|
||||
<g transform="matrix(0.491177, 0, 0, 0.523644, 419.010895, 56.68491)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<g transform="matrix(0.491177, 0, 0, 0.523644, 603.674133, 29.692444)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<g transform="matrix(-0.491177, 0, 0, 0.523644, 677.00824, 56.209183)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 548.454px 361.62px;" d="M 548.372 271.267 L 548.529 451.974" transform="matrix(0, 1.184039, -0.844567, 0, 0.00006, -0.000036)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 578.392px 383.459px;" d="M 578.329 405.407 L 578.456 361.51" transform="matrix(-1, 0, 0, -1, -0.000055, -0.000003)"/>
|
||||
<g transform="matrix(0.826913, 0, 0, -0.698383, 0.257882, 545.083069)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 704.638px 576.106px;" d="M 621.828 405.49 L 634.485 405.49"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 584.749px 405.496px;" d="M 591.042 405.497 L 578.456 405.498"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 606.436px 405.492px;" d="M 591.042 398.364 L 591.042 412.623 L 621.831 398.364 L 621.831 412.623 L 591.042 398.364 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 599.432px 413.831px;" d="M 592.464 422.169 L 592.464 405.493 L 606.401 405.493" transform="matrix(-1, 0, 0, -1, -0.000078, -0.000049)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 648.376px 405.65px;" d="M 648.369 435.621 L 648.383 375.678" transform="matrix(0, -1.184039, 0.844567, 0, 0.000012, -0.00004)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 673.882px 406.067px;" d="M 673.869 398.172 L 673.893 413.963" transform="matrix(-1, 0, 0, -1, -0.000092, -0.000022)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.821px 404.67px;" d="M 692.786 392.381 L 692.854 416.962" transform="matrix(0, 1.184039, -0.844567, 0, 0.000001, -0.000097)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.406px 383.726px;" d="M 703.396 405.334 L 703.415 362.116"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 681.807px 406.051px;" d="M 681.793 398.157 L 681.818 413.948" transform="matrix(-1, 0, 0, -1, 0.000055, 0.000046)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.987px 397.326px;" d="M 677.977 403.041 L 677.995 391.61"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.933px 411.78px;" d="M 677.924 417.977 L 677.941 405.582"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="650.811" cy="385.504" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(0.000343, -1, 1, 0.000266, 743.834961, 473.853179)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 651.034px 399.693px;" d="M 651.025 405.408 L 651.042 393.977"/>
|
||||
<path d="M -546.834 -405.87 L -543.785 -398.138 L -550.56 -398.138 L -546.834 -405.87 Z" bx:shape="triangle -550.56 -405.87 6.775 7.732 0.55 0 1@11f2e68a" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(-1, 0, 0, -1, 1094.344971, 804.007996)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 546.953px 390.056px;" d="M 546.94 382.161 L 546.965 397.952" transform="matrix(-1, 0, 0, -1, -0.000013, 0.000046)"/>
|
||||
<rect x="427.269" y="377.282" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.167" y="545.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||
<rect x="427.269" y="387.25" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.446" y="567.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.643" y="567.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||
<rect x="427.27" y="412.201" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.168" y="595.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||
<rect x="427.27" y="422.169" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.447" y="617.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.644" y="617.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 24.207672, -7.192523)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>OUTLET</text>
|
||||
<g transform="matrix(-0.387768, 0, 0, -0.200385, 743.634644, -199.991287)" style="transform-origin: 72.2405px 412.5px;">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||
</g>
|
||||
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, -0.563979, 0, 588.703491, -326.882202)">
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 807.67px 264.838px;" d="M 807.657 276.289 L 807.693 253.387" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.000091)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 816.592px 247.829px;" d="M 816.56 230.114 L 816.655 265.543" transform="matrix(-1, 0, 0, -1, 0.000101, 0.000009)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="840.215" cy="233.608" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 933.237163, 321.956912)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="839.876" cy="254.847" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 932.895305, 343.197025)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 823.288px 234.151px;" d="M 823.282 242.163 L 823.294 226.137" transform="matrix(0, 1.184039, -0.844567, 0, -0.000084, 0.000084)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 822.751px 254.694px;" d="M 822.745 262.706 L 822.763 246.68" transform="matrix(0, 1.184039, -0.844567, 0, -0.00002, 0.000032)"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 909.661358, 277.142276)"/>
|
||||
<g transform="matrix(0.387768, 0, 0, -0.200385, 207.60318, -199.315506)" style="transform-origin: 72.2406px 412.5px;">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||
</g>
|
||||
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, 0.563979, 0, 51.251434, -326.206329)">
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 288.049px 265.514px;" d="M 288.037 254.064 L 288.073 276.966" transform="matrix(0, -1.184039, 0.844567, 0, -0.000019, 0.000042)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 279.127px 248.505px;" d="M 279.095 266.219 L 279.19 230.79"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.5" cy="234.284" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.108874, 140.490195)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.84" cy="255.523" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.450457, 161.730307)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.43px 234.827px;" d="M 272.424 226.815 L 272.436 242.841" transform="matrix(0, 1.184039, -0.844567, 0, 0.000012, -0.000015)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.968px 255.37px;" d="M 272.962 247.358 L 272.98 263.384" transform="matrix(0, 1.184039, -0.844567, 0, -0.000011, 0.000034)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 170.68468, 95.675559)"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 47.595016, -269.416931)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>INLET</text>
|
||||
<path d="M -544.544 -165.9 L -541.495 -158.168 L -548.27 -158.168 L -544.544 -165.9 Z" bx:shape="triangle -548.27 -165.9 6.775 7.732 0.55 0 1@12a1c9af" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -544.876px -162.033px;" transform="matrix(-1, 0, 0, -1, 1089.751221, 324.066467)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 544.653px 150.086px;" d="M 544.639 142.192 L 544.664 157.983" transform="matrix(-1, 0, 0, -1, -0.000086, 0.000018)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 404.306 125.878 L 416.234 125.878 L 416.234 148.965 L 404.306 148.965 L 404.306 125.878 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000039, -0.000007)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 498.152px 180.679px;" d="M 408.92 144.738 L 408.92 130.357"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 427.836 146.217 L 460.619 127.367 L 460.619 146.217 L 427.836 127.367 L 427.836 146.217 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000009)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 426.851 157.594 L 426.851 116.351" transform="matrix(0, -1.18404, 0.844567, 0, -0.000439, -0.000081)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 647.335px 171.745px;" d="M 647.177 186.902 L 647.494 156.588" transform="matrix(-1, 0, 0, -1, -0.000021, -0.000027)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 680.766px 138.025px;" d="M 674.802 149.569 L 686.73 149.569 L 686.73 126.482 L 674.802 126.482 L 674.802 149.569 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000004, 0.000043)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 897.148px 118.148px;" d="M 682.129 145.344 L 682.129 130.964"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 646.809px 137.396px;" d="M 630.417 127.971 L 663.201 146.821 L 663.201 127.971 L 630.417 146.821 L 630.417 127.971 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000004, -0.000033)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 664.185px 137.577px;" d="M 664.185 116.956 L 664.186 158.199" transform="matrix(0, -1.184039, 0.844567, 0, -0.000026, -0.000041)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 590.855 148.509 L 607.577 148.509 L 607.577 164.977 L 590.855 164.977 L 590.855 148.509 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 733.701px 224.488px;" d="M 590.555 155.608 L 607.58 155.608"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 576.981 192.146 L 622.941 178.701 L 622.941 192.146 L 576.981 178.701 L 576.981 192.146 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 599.746 185.455 L 599.746 156.037"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 640.474 90.952 L 652.913 90.952 L 652.913 107.419 L 640.474 107.419 L 640.474 90.952 Z"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 646.819px 113.115px;" d="M 646.799 107.896 L 646.839 118.334"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 650.466px 95.308px;" d="M 650.455 97.624 L 650.476 92.992" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.00002)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 642.857px 102.712px;" d="M 642.846 105.028 L 642.867 100.396" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000035)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 437.929 90.244 L 450.367 90.244 L 450.367 106.712 L 437.929 106.712 L 437.929 90.244 Z"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 444.274px 112.408px;" d="M 444.253 107.189 L 444.293 117.627"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 447.919px 94.601px;" d="M 447.909 96.917 L 447.93 92.285" transform="matrix(0, -1.184039, 0.844567, 0, -0.000022, 0.000005)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 440.31px 102.004px;" d="M 440.3 104.321 L 440.32 99.689" transform="matrix(0, -1.184039, 0.844567, 0, -0.000028, -0.000004)"/>
|
||||
<rect x="43.443" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="53.987" y="423.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RUN HOUR</text>
|
||||
<rect x="126.135" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="424.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="424.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="461.382" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">PURGE HOUR</text>
|
||||
<rect x="126.135" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="463.397" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="463.357" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; font-weight: 700; white-space: pre;" x="53.987" y="498.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER HOUR</text>
|
||||
<rect x="126.135" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="499.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="499.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.65" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(248, 213, 14);"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="536.777" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Info</text>
|
||||
<rect x="126.341" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.3" y="537.792" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="537.752" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="56.987" y="250.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT or LT Dry</text>
|
||||
<rect x="126.135" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="180.05" y="251.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">LT Dry</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177" y="251.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT Dry</text>
|
||||
<rect x="43.443" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="288.876" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Opmode</text>
|
||||
<rect x="126.135" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HTD</text>
|
||||
<rect x="43.443" y="214.051" width="165.383" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="53.987" y="322.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Step</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177.05" y="323.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="233" y="323.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||
<rect x="43.443" y="241.422" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="364.271" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Cycle Timer</text>
|
||||
<rect x="126.341" y="241.068" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="168.775" y="365.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="365.246" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="141.894" y="324.069" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Time</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="92.151" y="325.554" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">##</text>
|
||||
<rect x="870.356" y="142.816" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1060.06" y="224.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dryer Status</text>
|
||||
<rect x="870.356" y="170.304" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="192.401" rx="20.673" ry="17.46"/>
|
||||
<rect x="870.356" y="230.113" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1069.06" y="349.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Shutdown</text>
|
||||
<rect x="870.356" y="257.602" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="279.699" rx="20.673" ry="17.46"/>
|
||||
<rect x="870.356" y="317.411" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1059.06" y="474.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Status</text>
|
||||
<rect x="870.356" y="344.9" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="366.997" rx="20.673" ry="17.46"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="380.451" y="296.591" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="334.165" cy="232.104" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="379.214" y="423.395" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.623" cy="320.662" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="897.237" y="299.014" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="761.502" cy="233.796" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="896" y="425.818" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.96" cy="322.354" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 35px; stroke-width: 1; font-weight: bold;" x="348.875" y="78.242" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">AIR DRYER UNIT B (01-CL-10535-B)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 40 KiB |
238
svg/air_dryer_C_rev.svg
Normal file
238
svg/air_dryer_C_rev.svg
Normal file
@@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 500" xmlns:bx="https://boxy-svg.com">
|
||||
<defs>
|
||||
<bx:grid x="0" y="0" width="25" height="25"/>
|
||||
</defs>
|
||||
<rect y="10.407" width="972.648" height="439.023" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" x="12.119"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 74.03907, 53.375034)">
|
||||
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||
</g>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 500.726135, 53.375034)">
|
||||
<ellipse style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);" cx="315" cy="183.068" rx="45" ry="45"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="315" cy="449.112" rx="45" ry="45"/>
|
||||
<rect x="270" y="180" width="90" height="270" style="stroke: rgb(0, 0, 0); fill: rgb(243, 243, 243);"/>
|
||||
</g>
|
||||
<rect x="371.728" y="182.483" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-box: fill-box; transform-origin: 50% 50%;" d="M 551.111 -16.154 L 551.202 389.079" transform="matrix(0, -1.184039, 0.844567, 0, 0.000036, 0.000096)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 479.043 149.568 L 495.765 149.568 L 495.765 166.035 L 479.043 166.035 L 479.043 149.568 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 598.485px 226.003px;" d="M 478.737 156.666 L 495.763 156.666"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 465.169 193.204 L 511.13 179.759 L 511.13 193.204 L 465.169 179.759 L 465.169 193.204 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 487.934 186.514 L 487.934 157.095"/>
|
||||
<rect x="715.724" y="182.138" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, -2.11712, 3.138935)">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 443.701px 171.141px;" d="M 443.542 155.983 L 443.859 186.298"/>
|
||||
<g transform="matrix(0.826913, 0, 0, 0.698383, 0.443817, 3.138935)">
|
||||
<rect x="752" y="355.455" width="42.438" height="3.527" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<rect x="756.328" y="359.271" width="34.034" height="53.968" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<rect x="756.146" y="352.019" width="34.034" height="3.38" style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);"/>
|
||||
<ellipse style="stroke: rgb(0, 0, 0); stroke-width: 1; fill: rgb(243, 243, 243);" cx="773.446" cy="384.7" rx="11.751" ry="11.009"/>
|
||||
</g>
|
||||
<rect x="461.861" y="211.956" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px;" x="561" y="309.954" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp SP</text>
|
||||
<rect x="461.861" y="221.924" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="564.279" y="330.561" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="609.476" y="330.521" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<rect x="461.424" y="242.149" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="567.471" y="352.188" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||
<rect x="461.424" y="252.117" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="563.75" y="373.795" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)" id="Dry1_HeatTempCelsius">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="608.947" y="373.755" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<rect x="535.456" y="242.272" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="659" y="352.363" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Heater Temp</text>
|
||||
<rect x="535.456" y="252.24" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="653.279" y="373.97" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="698.476" y="373.93" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1; font-weight: bold;" x="748" y="347.676" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER</text>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 678.512px 258.693px;" d="M 678.467 229.321 L 678.558 288.066" transform="matrix(0, 1.184039, -0.844567, 0, -0.000022, -0.000005)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.162px 309.166px;" d="M 703.004 258.049 L 703.32 360.282"/>
|
||||
<rect x="371.639" y="358.212" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.133px 361.256px;" d="M 692.126 397.022 L 692.14 325.49" transform="matrix(0, -1.184039, 0.844567, 0, 0.000011, 0.00005)"/>
|
||||
<rect x="715.635" y="357.867" width="8.269" height="6.984" style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 798px 493.641px;" d="M 661.975 334.874 L 661.975 360.911"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 552.59px 334.782px;" d="M 552.508 244.429 L 552.665 425.136" transform="matrix(0, 1.184039, -0.844567, 0, -0.000058, 0.000018)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 601.786px 283.137px;" d="M 601.741 312.51 L 601.832 253.764" transform="matrix(0, 1.184039, -0.844567, 0, -0.000063, 0.000028)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 577.266px 308.682px;" d="M 577.237 334.87 L 577.295 282.492" transform="matrix(-1, 0, 0, -1, -0.000041, -0.00003)"/>
|
||||
<g transform="matrix(-0.491177, 0, 0, 0.523644, 491.13504, 29.785091)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 406.945px 361.188px;" d="M 406.92 329.322 L 406.969 393.054" transform="matrix(0, 1.184039, -0.844567, 0, 0.00001, 0.00007)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 521.472px 494.156px;" d="M 433.307 334.116 L 433.308 362.382"/>
|
||||
<g transform="matrix(0.491177, 0, 0, 0.523644, 419.010895, 56.68491)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<g transform="matrix(0.491177, 0, 0, 0.523644, 603.674133, 29.692444)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<g transform="matrix(-0.491177, 0, 0, 0.523644, 677.00824, 56.209183)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 582.89 L 30 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 98.289 582.89 L 118.071 582.89"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 49.894 600 L 49.894 565.78 L 98.289 600 L 98.289 565.78"/>
|
||||
<circle style="fill: rgb(0, 0, 0); stroke: rgb(76, 76, 76); stroke-width: 2;" cx="1094.77" cy="561.359" r="39" transform="matrix(0.17796, 0, 0, 0.155258, -141.765747, 481.674255)"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 548.454px 361.62px;" d="M 548.372 271.267 L 548.529 451.974" transform="matrix(0, 1.184039, -0.844567, 0, 0.00006, -0.000036)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 578.392px 383.459px;" d="M 578.329 405.407 L 578.456 361.51" transform="matrix(-1, 0, 0, -1, -0.000055, -0.000003)"/>
|
||||
<g transform="matrix(0.826913, 0, 0, -0.698383, 0.257882, 545.083069)" style="">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 660.838px 251.447px;" d="M 660.846 262.894 L 660.846 240"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.222; transform-origin: 490.992px 230.229px;" d="M 646.097 240.002 L 676.271 240.002"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 704.638px 576.106px;" d="M 621.828 405.49 L 634.485 405.49"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 584.749px 405.496px;" d="M 591.042 405.497 L 578.456 405.498"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 606.436px 405.492px;" d="M 591.042 398.364 L 591.042 412.623 L 621.831 398.364 L 621.831 412.623 L 591.042 398.364 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 1.525; transform-origin: 599.432px 413.831px;" d="M 592.464 422.169 L 592.464 405.493 L 606.401 405.493" transform="matrix(-1, 0, 0, -1, -0.000078, -0.000049)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 648.376px 405.65px;" d="M 648.369 435.621 L 648.383 375.678" transform="matrix(0, -1.184039, 0.844567, 0, 0.000012, -0.00004)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 673.882px 406.067px;" d="M 673.869 398.172 L 673.893 413.963" transform="matrix(-1, 0, 0, -1, -0.000092, -0.000022)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 692.821px 404.67px;" d="M 692.786 392.381 L 692.854 416.962" transform="matrix(0, 1.184039, -0.844567, 0, 0.000001, -0.000097)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 703.406px 383.726px;" d="M 703.396 405.334 L 703.415 362.116"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 681.807px 406.051px;" d="M 681.793 398.157 L 681.818 413.948" transform="matrix(-1, 0, 0, -1, 0.000055, 0.000046)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.987px 397.326px;" d="M 677.977 403.041 L 677.995 391.61"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 677.933px 411.78px;" d="M 677.924 417.977 L 677.941 405.582"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="650.811" cy="385.504" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(0.000343, -1, 1, 0.000266, 743.834961, 473.853179)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 651.034px 399.693px;" d="M 651.025 405.408 L 651.042 393.977"/>
|
||||
<path d="M -546.834 -405.87 L -543.785 -398.138 L -550.56 -398.138 L -546.834 -405.87 Z" bx:shape="triangle -550.56 -405.87 6.775 7.732 0.55 0 1@11f2e68a" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); transform-box: fill-box; transform-origin: 50% 50%; stroke-width: 0.763;" transform="matrix(-1, 0, 0, -1, 1094.344971, 804.007996)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 546.953px 390.056px;" d="M 546.94 382.161 L 546.965 397.952" transform="matrix(-1, 0, 0, -1, -0.000013, 0.000046)"/>
|
||||
<rect x="427.269" y="377.282" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.167" y="545.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||
<rect x="427.269" y="387.25" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.446" y="567.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.643" y="567.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°C</text>
|
||||
<rect x="427.27" y="412.201" width="62.018" height="9.968" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1;" x="532.168" y="595.681" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dew Temp</text>
|
||||
<rect x="427.27" y="422.169" width="62.018" height="17.46" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="522.447" y="617.288" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 12px; stroke-width: 1;" x="567.644" y="617.248" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">°F</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 24.207672, -7.192523)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>OUTLET</text>
|
||||
<g transform="matrix(-0.387768, 0, 0, -0.200385, 743.634644, -199.991287)" style="transform-origin: 72.2405px 412.5px;">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||
</g>
|
||||
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, -0.563979, 0, 588.703491, -326.882202)">
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 807.67px 264.838px;" d="M 807.657 276.289 L 807.693 253.387" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.000091)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 816.592px 247.829px;" d="M 816.56 230.114 L 816.655 265.543" transform="matrix(-1, 0, 0, -1, 0.000101, 0.000009)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="840.215" cy="233.608" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 933.237163, 321.956912)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="839.876" cy="254.847" rx="10.336" ry="8.73"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 932.895305, 343.197025)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 823.288px 234.151px;" d="M 823.282 242.163 L 823.294 226.137" transform="matrix(0, 1.184039, -0.844567, 0, -0.000084, 0.000084)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 822.751px 254.694px;" d="M 822.745 262.706 L 822.763 246.68" transform="matrix(0, 1.184039, -0.844567, 0, -0.00002, 0.000032)"/>
|
||||
<path d="M -100.83 -89.11 H -94.198 L -94.198 -91.071 L -85.458 -88.012 L -94.198 -84.954 L -94.198 -86.915 H -100.83 V -89.11 Z" bx:shape="arrow -100.83 -91.071 15.372 6.118 2.195 8.74 0 1@7c71f9c2" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -93.144px -88.013px;" transform="matrix(0.000343, -1, 1, 0.000266, 909.661358, 277.142276)"/>
|
||||
<g transform="matrix(0.387768, 0, 0, -0.200385, 207.60318, -199.315506)" style="transform-origin: 72.2406px 412.5px;">
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 53.913 425 L 53.913 400 L 90.568 425 L 90.568 400"/>
|
||||
</g>
|
||||
<g style="transform-origin: 227.882px 539.536px;" transform="matrix(0, 0.626201, 0.563979, 0, 51.251434, -326.206329)">
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 550 L 243.484 529.073 L 243.484 550 L 212.224 529.073 L 212.224 550 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 212.224 539.499 L 200 539.499"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 2;" d="M 243.484 539.499 L 255.764 539.499"/>
|
||||
</g>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 288.049px 265.514px;" d="M 288.037 254.064 L 288.073 276.966" transform="matrix(0, -1.184039, 0.844567, 0, -0.000019, 0.000042)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 279.127px 248.505px;" d="M 279.095 266.219 L 279.19 230.79"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.5" cy="234.284" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.108874, 140.490195)"/>
|
||||
<ellipse style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="-255.84" cy="255.523" rx="10.336" ry="8.73" transform="matrix(-1, 0, 0, 1, 0, 0)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 147.450457, 161.730307)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.43px 234.827px;" d="M 272.424 226.815 L 272.436 242.841" transform="matrix(0, 1.184039, -0.844567, 0, 0.000012, -0.000015)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 272.968px 255.37px;" d="M 272.962 247.358 L 272.98 263.384" transform="matrix(0, 1.184039, -0.844567, 0, -0.000011, 0.000034)"/>
|
||||
<path d="M 100.83 93.032 H 107.463 L 107.463 91.071 L 116.203 94.13 L 107.463 97.189 L 107.463 95.228 H 100.83 V 93.032 Z" bx:shape="arrow 100.83 91.071 15.372 6.118 2.195 8.74 0 1@116e726f" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 108.517px 94.13px;" transform="matrix(-0.000343, -1, -1, 0.000266, 170.68468, 95.675559)"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 9px; stroke-width: 1; text-anchor: middle; font-weight: bolder;" x="602.463" y="573.003" transform="matrix(0.826913, 0, 0, 0.698383, 47.595016, -269.416931)">AIR<tspan x="602.4630126953125" dy="1em"></tspan>INLET</text>
|
||||
<path d="M -544.544 -165.9 L -541.495 -158.168 L -548.27 -158.168 L -544.544 -165.9 Z" bx:shape="triangle -548.27 -165.9 6.775 7.732 0.55 0 1@12a1c9af" style="fill: rgb(0, 0, 0); stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: -544.876px -162.033px;" transform="matrix(-1, 0, 0, -1, 1089.751221, 324.066467)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 544.653px 150.086px;" d="M 544.639 142.192 L 544.664 157.983" transform="matrix(-1, 0, 0, -1, -0.000086, 0.000018)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 404.306 125.878 L 416.234 125.878 L 416.234 148.965 L 404.306 148.965 L 404.306 125.878 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000039, -0.000007)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 498.152px 180.679px;" d="M 408.92 144.738 L 408.92 130.357"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 427.836 146.217 L 460.619 127.367 L 460.619 146.217 L 427.836 127.367 L 427.836 146.217 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000009)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-box: fill-box; transform-origin: 50% 50%;" d="M 426.851 157.594 L 426.851 116.351" transform="matrix(0, -1.18404, 0.844567, 0, -0.000439, -0.000081)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 647.335px 171.745px;" d="M 647.177 186.902 L 647.494 156.588" transform="matrix(-1, 0, 0, -1, -0.000021, -0.000027)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 680.766px 138.025px;" d="M 674.802 149.569 L 686.73 149.569 L 686.73 126.482 L 674.802 126.482 L 674.802 149.569 Z" transform="matrix(0, -1.184039, 0.844567, 0, 0.000004, 0.000043)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 897.148px 118.148px;" d="M 682.129 145.344 L 682.129 130.964"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 646.809px 137.396px;" d="M 630.417 127.971 L 663.201 146.821 L 663.201 127.971 L 630.417 146.821 L 630.417 127.971 Z" transform="matrix(0, -1.184039, 0.844567, 0, -0.000004, -0.000033)"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 664.185px 137.577px;" d="M 664.185 116.956 L 664.186 158.199" transform="matrix(0, -1.184039, 0.844567, 0, -0.000026, -0.000041)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 590.855 148.509 L 607.577 148.509 L 607.577 164.977 L 590.855 164.977 L 590.855 148.509 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932; transform-origin: 733.701px 224.488px;" d="M 590.555 155.608 L 607.58 155.608"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 576.981 192.146 L 622.941 178.701 L 622.941 192.146 L 576.981 178.701 L 576.981 192.146 Z"/>
|
||||
<path style="fill: none; stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 599.746 185.455 L 599.746 156.037"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 640.474 90.952 L 652.913 90.952 L 652.913 107.419 L 640.474 107.419 L 640.474 90.952 Z"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 646.819px 113.115px;" d="M 646.799 107.896 L 646.839 118.334"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 650.466px 95.308px;" d="M 650.455 97.624 L 650.476 92.992" transform="matrix(0, -1.184039, 0.844567, 0, 0.000035, 0.00002)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 642.857px 102.712px;" d="M 642.846 105.028 L 642.867 100.396" transform="matrix(0, -1.184039, 0.844567, 0, 0.000009, 0.000035)"/>
|
||||
<path style="fill: rgb(255, 255, 255); stroke: rgb(76, 76, 76); stroke-width: 0.932;" d="M 437.929 90.244 L 450.367 90.244 L 450.367 106.712 L 437.929 106.712 L 437.929 90.244 Z"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 1.525; transform-origin: 444.274px 112.408px;" d="M 444.253 107.189 L 444.293 117.627"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 447.919px 94.601px;" d="M 447.909 96.917 L 447.93 92.285" transform="matrix(0, -1.184039, 0.844567, 0, -0.000022, 0.000005)"/>
|
||||
<path style="fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.763; transform-origin: 440.31px 102.004px;" d="M 440.3 104.321 L 440.32 99.689" transform="matrix(0, -1.184039, 0.844567, 0, -0.000028, -0.000004)"/>
|
||||
<rect x="43.443" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="53.987" y="423.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RUN HOUR</text>
|
||||
<rect x="126.135" y="280.75" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="424.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="424.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="461.382" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">PURGE HOUR</text>
|
||||
<rect x="126.135" y="308.191" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="463.397" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="463.357" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 12px; font-weight: 700; white-space: pre;" x="53.987" y="498.091" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HEATER HOUR</text>
|
||||
<rect x="126.135" y="333.129" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.05" y="499.106" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225" y="499.066" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.65" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(248, 213, 14);"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="536.777" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Info</text>
|
||||
<rect x="126.341" y="360.147" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="159.3" y="537.792" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="537.752" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">H</text>
|
||||
<rect x="43.443" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="56.987" y="250.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT or LT Dry</text>
|
||||
<rect x="126.135" y="160.275" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="180.05" y="251.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">LT Dry</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177" y="251.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">RT Dry</text>
|
||||
<rect x="43.443" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; stroke-width: 1; font-weight: bold;" x="53.987" y="288.876" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Opmode</text>
|
||||
<rect x="126.135" y="187.715" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="183.349" y="288.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">HTD</text>
|
||||
<rect x="43.443" y="214.051" width="165.383" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="53.987" y="322.585" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Step</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="177.05" y="323.6" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="233" y="323.56" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||
<rect x="43.443" y="241.422" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; font-weight: 700; white-space: pre; stroke-width: 1;" x="54.237" y="364.271" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Cycle Timer</text>
|
||||
<rect x="126.341" y="241.068" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="168.775" y="365.807" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">####.##</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="225.25" y="365.246" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">s</text>
|
||||
<text style="fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 13px; font-weight: 700; white-space: pre; stroke-width: 1;" x="141.894" y="324.069" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Time</text>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Arial, sans-serif; font-size: 15px; stroke-width: 1;" x="92.151" y="325.554" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">##</text>
|
||||
<rect x="870.356" y="142.816" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1060.06" y="224.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Dryer Status</text>
|
||||
<rect x="870.356" y="170.304" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="192.401" rx="20.673" ry="17.46"/>
|
||||
<rect x="870.356" y="230.113" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1069.06" y="349.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Shutdown</text>
|
||||
<rect x="870.356" y="257.602" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="279.699" rx="20.673" ry="17.46"/>
|
||||
<rect x="870.356" y="317.411" width="82.691" height="27.103" style="stroke: rgb(0, 0, 0); fill: rgb(120, 231, 228); stroke-width: 0.763;"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="1059.06" y="474.103" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">Alarm Status</text>
|
||||
<rect x="870.356" y="344.9" width="82.691" height="42.35" style="stroke: rgb(0, 0, 0); stroke-width: 0.763; fill: rgb(244, 248, 248);"/>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="911.702" cy="366.997" rx="20.673" ry="17.46"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="380.451" y="296.591" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="334.165" cy="232.104" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="379.214" y="423.395" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="335.623" cy="320.662" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="897.237" y="299.014" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">REGEN</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="761.502" cy="233.796" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 14px; stroke-width: 1; font-weight: bold;" x="896" y="425.818" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">DRYING</text>
|
||||
<ellipse style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0.763;" cx="762.96" cy="322.354" rx="13.582" ry="12.517"/>
|
||||
<text style="white-space: pre; fill: rgb(51, 51, 51); font-family: Bahnschrift; font-size: 35px; stroke-width: 1; font-weight: bold;" x="348.875" y="78.242" transform="matrix(0.826913, 0, 0, 0.698383, 2.097643, 3.138935)">AIR DRYER UNIT C (01-CL-10539-C)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 40 KiB |
Reference in New Issue
Block a user