From fb3e5001393b968926b41199bd829923d0ebeb97 Mon Sep 17 00:00:00 2001 From: Rafiafrzl Date: Mon, 20 Oct 2025 19:19:59 +0700 Subject: [PATCH] feat: implement field auto-incrementing code --- .../master/device/component/DetailDevice.jsx | 72 ++++- .../component/DetailPlantSection.jsx | 72 ++++- src/pages/master/tag/component/DetailTag.jsx | 270 ++++++++++++++++-- .../master/unit/component/DetailUnit.jsx | 74 ++++- 4 files changed, 428 insertions(+), 60 deletions(-) diff --git a/src/pages/master/device/component/DetailDevice.jsx b/src/pages/master/device/component/DetailDevice.jsx index b8fb6d3..c49a273 100644 --- a/src/pages/master/device/component/DetailDevice.jsx +++ b/src/pages/master/device/component/DetailDevice.jsx @@ -12,7 +12,7 @@ import { } 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 { createDevice, updateDevice, getAllDevice } from '../../../../api/master-device'; import { Checkbox } from 'antd'; const CheckboxGroup = Checkbox.Group; @@ -33,6 +33,7 @@ const DetailDevice = (props) => { }; const [FormData, setFormData] = useState(defaultData); + const [nextDeviceCode, setNextDeviceCode] = useState('Auto-fill'); const [jenisPermit, setJenisPermit] = useState([]); const [checkedList, setCheckedList] = useState([]); @@ -215,12 +216,55 @@ const DetailDevice = (props) => { }); }; + const generateNextDeviceCode = async () => { + try { + const params = new URLSearchParams({ limit: 10000 }); + const response = await getAllDevice(params); + + if (response && response.data && response.data.data) { + const devices = response.data.data; + + if (devices.length === 0) { + setNextDeviceCode('DVC001'); + return; + } + + // Extract numeric part from device codes and find the maximum + const deviceNumbers = devices + .map((device) => { + const match = device.device_code?.match(/dvc(\d+)/i); + return match ? parseInt(match[1], 10) : 0; + }) + .filter((num) => !isNaN(num)); + + const maxNumber = deviceNumbers.length > 0 ? Math.max(...deviceNumbers) : 0; + const nextNumber = maxNumber + 1; + + // Format with leading zeros (DVC001, DVC002, etc.) + const nextCode = `DVC${String(nextNumber).padStart(3, '0')}`; + setNextDeviceCode(nextCode); + } else { + setNextDeviceCode('DVC001'); + } + } catch (error) { + console.error('Error generating next device code:', error); + setNextDeviceCode('Auto-fill'); + } + }; + useEffect(() => { const token = localStorage.getItem('token'); if (token) { - // Only call getDataJenisPermit if permitDefault is enabled - if (props.permitDefault) { - getDataJenisPermit(); + if (props.showModal) { + // Only call getDataJenisPermit if permitDefault is enabled + if (props.permitDefault) { + getDataJenisPermit(); + } + + // Generate next device code only for add mode + if (props.actionMode === 'add' && !props.selectedData) { + generateNextDeviceCode(); + } } if (props.selectedData != null) { @@ -234,7 +278,7 @@ const DetailDevice = (props) => { } else { // navigate('/signin'); // Uncomment if useNavigate is imported } - }, [props.showModal]); + }, [props.showModal, props.actionMode]); return ( { disabled /> - {/*
+ {/* Device Code - Auto Increment & Read Only */} +
Device Code - * -
*/} +
Device Name * diff --git a/src/pages/master/plantSection/component/DetailPlantSection.jsx b/src/pages/master/plantSection/component/DetailPlantSection.jsx index 3f0849d..a4c7c86 100644 --- a/src/pages/master/plantSection/component/DetailPlantSection.jsx +++ b/src/pages/master/plantSection/component/DetailPlantSection.jsx @@ -9,7 +9,7 @@ import { Divider, } from 'antd'; import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; -import { createPlantSection, updatePlantSection } from '../../../../api/master-plant-section'; +import { createPlantSection, updatePlantSection, getAllPlantSection } from '../../../../api/master-plant-section'; const { Text } = Typography; @@ -24,6 +24,7 @@ const DetailPlantSection = (props) => { }; const [FormData, setFormData] = useState(defaultData); + const [nextPlantSectionCode, setNextPlantSectionCode] = useState('Auto-fill'); const handleInputChange = (e) => { const { name, value } = e.target; @@ -103,14 +104,56 @@ const DetailPlantSection = (props) => { }); }; + const generateNextPlantSectionCode = async () => { + try { + const params = new URLSearchParams({ limit: 10000 }); + const response = await getAllPlantSection(params); + + if (response && response.data && response.data.data) { + const sections = response.data.data; + + if (sections.length === 0) { + setNextPlantSectionCode('SUB001'); + return; + } + + // Extract numeric part from plant section codes and find the maximum + const sectionNumbers = sections + .map((section) => { + const match = section.sub_section_code?.match(/sub(\d+)/i); + return match ? parseInt(match[1], 10) : 0; + }) + .filter((num) => !isNaN(num)); + + const maxNumber = sectionNumbers.length > 0 ? Math.max(...sectionNumbers) : 0; + const nextNumber = maxNumber + 1; + + // Format with leading zeros (SUB001, SUB002, etc.) + const nextCode = `SUB${String(nextNumber).padStart(3, '0')}`; + setNextPlantSectionCode(nextCode); + } else { + setNextPlantSectionCode('SUB001'); + } + } catch (error) { + console.error('Error generating next plant section code:', error); + setNextPlantSectionCode('Auto-fill'); + } + }; useEffect(() => { + if (props.showModal) { + // Generate next plant section code only for add mode + if (props.actionMode === 'add' && !props.selectedData) { + generateNextPlantSectionCode(); + } + } + if (props.selectedData) { setFormData(props.selectedData); } else { setFormData(defaultData); } - }, [props.showModal, props.selectedData]); + }, [props.showModal, props.selectedData, props.actionMode]); return ( {
- {props.actionMode !== 'add' && ( -
- Plant Section Code - -
- )} + {/* Plant Section Code - Auto Increment & Read Only */} +
+ Plant Section Code + +
Plant Sub Section Name diff --git a/src/pages/master/tag/component/DetailTag.jsx b/src/pages/master/tag/component/DetailTag.jsx index 6da2556..2bf55ea 100644 --- a/src/pages/master/tag/component/DetailTag.jsx +++ b/src/pages/master/tag/component/DetailTag.jsx @@ -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 ( { } Tag`} open={props.showModal} onCancel={handleCancel} + width={800} footer={[ <> { disabled />
- {/* Tag Code hanya ditampilkan saat EDIT atau PREVIEW */} - {(props.actionMode === 'edit' || props.actionMode === 'preview') && ( -
- Tag Code - -
- )} {/* Status dan Alarm dalam satu baris */}
{
+ {/* Report dan History dalam satu baris */} +
+
+ {/* Report Toggle */} +
+
+ Report + * +
+
+
+ +
+
+ {FormData.is_report === true ? 'Yes' : 'No'} +
+
+
+ {/* History Toggle */} +
+
+ History + * +
+
+
+ +
+
+ {FormData.is_history === true ? 'Yes' : 'No'} +
+
+
+
+
+ {/* Tag Code - Auto Increment & Read Only */} +
+ Tag Code + +
Tag Number * @@ -532,7 +706,7 @@ const DetailTag = (props) => { onChange={(value) => handleSelectChange('data_type', value)} disabled={props.readOnly} > - Diskrit + Discrete Analog
@@ -562,8 +736,58 @@ const DetailTag = (props) => { ))} + {/* Limit Fields */} +
+ Limit Low Crash + +
+
+ Limit Low + +
+
+ Limit High + +
+
+ Limit High Crash + +
Plant Sub Section + * -
- )} + {/* Unit Code - Auto Increment & Read Only */} +
+ Unit Code + +
Name *