lavoce #8
@@ -15,7 +15,6 @@ import IndexTag from './pages/master/tag/IndexTag';
|
|||||||
import IndexUnit from './pages/master/unit/IndexUnit';
|
import IndexUnit from './pages/master/unit/IndexUnit';
|
||||||
import IndexBrandDevice from './pages/master/brandDevice/IndexBrandDevice';
|
import IndexBrandDevice from './pages/master/brandDevice/IndexBrandDevice';
|
||||||
import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice';
|
import AddBrandDevice from './pages/master/brandDevice/AddBrandDevice';
|
||||||
import IndexPlantSection from './pages/master/plantSection/IndexPlantSection';
|
|
||||||
import IndexStatus from './pages/master/status/IndexStatus';
|
import IndexStatus from './pages/master/status/IndexStatus';
|
||||||
import IndexShift from './pages/master/shift/IndexShift';
|
import IndexShift from './pages/master/shift/IndexShift';
|
||||||
|
|
||||||
@@ -41,6 +40,7 @@ import SvgAirDryerB from './pages/home/SvgAirDryerB';
|
|||||||
import SvgAirDryerC from './pages/home/SvgAirDryerC';
|
import SvgAirDryerC from './pages/home/SvgAirDryerC';
|
||||||
import IndexHistoryAlarm from './pages/history/alarm/IndexHistoryAlarm';
|
import IndexHistoryAlarm from './pages/history/alarm/IndexHistoryAlarm';
|
||||||
import IndexHistoryEvent from './pages/history/event/IndexHistoryEvent';
|
import IndexHistoryEvent from './pages/history/event/IndexHistoryEvent';
|
||||||
|
import IndexPlantSubSection from './pages/master/plantSubSection/IndexPlantSubSection';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
@@ -74,7 +74,7 @@ const App = () => {
|
|||||||
<Route path="unit" element={<IndexUnit />} />
|
<Route path="unit" element={<IndexUnit />} />
|
||||||
<Route path="brand-device" element={<IndexBrandDevice />} />
|
<Route path="brand-device" element={<IndexBrandDevice />} />
|
||||||
<Route path="brand-device/add" element={<AddBrandDevice />} />
|
<Route path="brand-device/add" element={<AddBrandDevice />} />
|
||||||
<Route path="plant-section" element={<IndexPlantSection />} />
|
<Route path="plant-sub-section" element={<IndexPlantSubSection />} />
|
||||||
<Route path="shift" element={<IndexShift />} />
|
<Route path="shift" element={<IndexShift />} />
|
||||||
<Route path="status" element={<IndexStatus />} />
|
<Route path="status" element={<IndexStatus />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ const allItems = [
|
|||||||
label: 'Master',
|
label: 'Master',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'master-plant-section',
|
key: 'master-plant-sub-section',
|
||||||
icon: <ProductOutlined style={{ fontSize: '19px' }} />,
|
icon: <ProductOutlined style={{ fontSize: '19px' }} />,
|
||||||
label: <Link to="/master/plant-section">Plant Sub Section</Link>,
|
label: <Link to="/master/plant-sub-section">Plant Sub Section</Link>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'master-brand-device',
|
key: 'master-brand-device',
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Card, Button, Row, Col, Typography, Space, Tag } from 'antd';
|
|
||||||
import { EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
const CardDevice = ({ data, showPreviewModal, showEditModal, showDeleteDialog }) => {
|
|
||||||
const getCardStyle = () => {
|
|
||||||
const color = '#FF8C42'; // Orange color
|
|
||||||
return {
|
|
||||||
border: `2px solid ${color}`,
|
|
||||||
borderRadius: '8px',
|
|
||||||
textAlign: 'center' // Center text
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTitleStyle = () => {
|
|
||||||
const backgroundColor = '#FF8C42'; // Orange color
|
|
||||||
return {
|
|
||||||
backgroundColor,
|
|
||||||
color: '#fff',
|
|
||||||
padding: '2px 8px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
display: 'inline-block',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row gutter={[16, 16]} style={{ marginTop: '16px', justifyContent: 'center' }}>
|
|
||||||
{data.map((item) => (
|
|
||||||
<Col xs={24} sm={12} md={8} lg={6} key={item.device_id}>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<span style={getTitleStyle()}>
|
|
||||||
{item.device_name}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
style={getCardStyle()}
|
|
||||||
actions={[
|
|
||||||
<Space size="middle" style={{ display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
style={{ color: '#1890ff' }}
|
|
||||||
icon={<EyeOutlined />}
|
|
||||||
onClick={() => showPreviewModal(item)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
style={{ color: '#faad14' }}
|
|
||||||
icon={<EditOutlined />}
|
|
||||||
onClick={() => showEditModal(item)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
danger
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={() => showDeleteDialog(item)}
|
|
||||||
/>
|
|
||||||
</Space>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<Text strong>Code:</Text> {item.device_code}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Text strong>Location:</Text> {item.device_location}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Text strong>IP Address:</Text> {item.ip_address}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<Text strong>Status:</Text>{' '}
|
|
||||||
<Tag color={item.device_status ? 'green' : 'red'}>
|
|
||||||
{item.device_status ? 'Running' : 'Offline'}
|
|
||||||
</Tag>
|
|
||||||
</p>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CardDevice;
|
|
||||||
@@ -15,6 +15,13 @@ import { deleteDevice, getAllDevice } from '../../../../api/master-device';
|
|||||||
import TableList from '../../../../components/Global/TableList';
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'device_id',
|
dataIndex: 'device_id',
|
||||||
@@ -27,6 +34,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
dataIndex: 'device_code',
|
dataIndex: 'device_code',
|
||||||
key: 'device_code',
|
key: 'device_code',
|
||||||
width: '10%',
|
width: '10%',
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Device Name',
|
title: 'Device Name',
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { memo, useState, useEffect } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import ListPlantSection from './component/ListPlantSection';
|
import ListPlantSection from './component/ListPlantSubSection';
|
||||||
import DetailPlantSection from './component/DetailPlantSection';
|
import DetailPlantSection from './component/DetailPlantSubSection';
|
||||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const IndexPlantSection = memo(function IndexPlantSection() {
|
const IndexPlantSubSection = memo(function IndexPlantSubSection() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setBreadcrumbItems } = useBreadcrumb();
|
const { setBreadcrumbItems } = useBreadcrumb();
|
||||||
|
|
||||||
@@ -71,4 +71,4 @@ const IndexPlantSection = memo(function IndexPlantSection() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IndexPlantSection;
|
export default IndexPlantSubSection;
|
||||||
@@ -3,27 +3,49 @@ import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider } fro
|
|||||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
||||||
import { createPlantSection, updatePlantSection } from '../../../../api/master-plant-section';
|
import { createPlantSection, updatePlantSection } from '../../../../api/master-plant-section';
|
||||||
import { validateRun } from '../../../../Utils/validate';
|
import { validateRun } from '../../../../Utils/validate';
|
||||||
|
import TextArea from 'antd/es/input/TextArea';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const DetailPlantSection = (props) => {
|
const DetailPlantSubSection = (props) => {
|
||||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
|
||||||
const defaultData = {
|
const defaultData = {
|
||||||
sub_section_id: '',
|
plant_sub_section_id: '',
|
||||||
sub_section_code: '',
|
plant_sub_section_code: '',
|
||||||
sub_section_name: '',
|
plant_sub_section_name: '',
|
||||||
|
table_name_value: '', // Fix field name
|
||||||
|
plant_sub_section_description: '',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [formData, setFormData] = useState(defaultData);
|
const [formData, setFormData] = useState(defaultData);
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
const { name, value } = e.target;
|
// Handle different input types
|
||||||
setFormData({
|
let name, value;
|
||||||
...formData,
|
|
||||||
[name]: value,
|
if (e && e.target) {
|
||||||
});
|
// Standard input
|
||||||
|
name = e.target.name;
|
||||||
|
value = e.target.value;
|
||||||
|
} else if (e && e.type === 'change') {
|
||||||
|
// Switch or other components
|
||||||
|
name = e.name || e.target?.name;
|
||||||
|
value = e.value !== undefined ? e.value : e.checked;
|
||||||
|
} else {
|
||||||
|
// Fallback
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📝 Input change: ${name} = ${value}`);
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
@@ -36,7 +58,7 @@ const DetailPlantSection = (props) => {
|
|||||||
|
|
||||||
// Daftar aturan validasi
|
// Daftar aturan validasi
|
||||||
const validationRules = [
|
const validationRules = [
|
||||||
{ field: 'sub_section_name', label: 'Plant Sub Section Name', required: true },
|
{ field: 'plant_sub_section_name', label: 'Plant Sub Section Name', required: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -52,14 +74,20 @@ const DetailPlantSection = (props) => {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('💾 Current formData before save:', formData);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
|
plant_sub_section_name: formData.plant_sub_section_name,
|
||||||
|
plant_sub_section_description: formData.plant_sub_section_description,
|
||||||
|
table_name_value: formData.table_name_value, // Fix field name
|
||||||
is_active: formData.is_active,
|
is_active: formData.is_active,
|
||||||
sub_section_name: formData.sub_section_name,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('📤 Payload to be sent:', payload);
|
||||||
|
|
||||||
const response =
|
const response =
|
||||||
props.actionMode === 'edit'
|
props.actionMode === 'edit'
|
||||||
? await updatePlantSection(formData.sub_section_id, payload)
|
? await updatePlantSection(formData.plant_sub_section_id, payload)
|
||||||
: await createPlantSection(payload);
|
: await createPlantSection(payload);
|
||||||
|
|
||||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||||
@@ -98,9 +126,17 @@ const DetailPlantSection = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('🔄 Modal state changed:', {
|
||||||
|
showModal: props.showModal,
|
||||||
|
actionMode: props.actionMode,
|
||||||
|
selectedData: props.selectedData,
|
||||||
|
});
|
||||||
|
|
||||||
if (props.selectedData) {
|
if (props.selectedData) {
|
||||||
|
console.log('📋 Setting form data from selectedData:', props.selectedData);
|
||||||
setFormData(props.selectedData);
|
setFormData(props.selectedData);
|
||||||
} else {
|
} else {
|
||||||
|
console.log('📋 Resetting to default data');
|
||||||
setFormData(defaultData);
|
setFormData(defaultData);
|
||||||
}
|
}
|
||||||
}, [props.showModal, props.selectedData, props.actionMode]);
|
}, [props.showModal, props.selectedData, props.actionMode]);
|
||||||
@@ -177,7 +213,7 @@ const DetailPlantSection = (props) => {
|
|||||||
|
|
||||||
{/* Plant Section Code - Auto Increment & Read Only */}
|
{/* Plant Section Code - Auto Increment & Read Only */}
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Plant Section Code</Text>
|
<Text strong>Plant Sub Section Code</Text>
|
||||||
<Input
|
<Input
|
||||||
name="sub_section_code"
|
name="sub_section_code"
|
||||||
value={formData.sub_section_code || ''}
|
value={formData.sub_section_code || ''}
|
||||||
@@ -195,17 +231,38 @@ const DetailPlantSection = (props) => {
|
|||||||
<Text strong>Plant Sub Section Name</Text>
|
<Text strong>Plant Sub Section Name</Text>
|
||||||
<Text style={{ color: 'red' }}> *</Text>
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
<Input
|
<Input
|
||||||
name="sub_section_name"
|
name="plant_sub_section_name"
|
||||||
value={formData.sub_section_name}
|
value={formData.plant_sub_section_name}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="Enter Plant Sub Section Name"
|
placeholder="Enter Plant Sub Section Name"
|
||||||
readOnly={props.readOnly}
|
readOnly={props.readOnly}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Table Name Value</Text>
|
||||||
|
<Input
|
||||||
|
name="table_name_value"
|
||||||
|
value={formData.table_name_value}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Enter Table Name Value (Optional)"
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Description</Text>
|
||||||
|
<TextArea
|
||||||
|
name="plant_sub_section_description"
|
||||||
|
value={formData.plant_sub_section_description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Enter Description (Optional)"
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DetailPlantSection;
|
export default DetailPlantSubSection;
|
||||||
@@ -14,27 +14,51 @@ import TableList from '../../../../components/Global/TableList';
|
|||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
{
|
{
|
||||||
title: 'Section Code',
|
title: 'No',
|
||||||
dataIndex: 'sub_section_code',
|
key: 'no',
|
||||||
key: 'sub_section_code',
|
width: '5%',
|
||||||
width: '20%',
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Plant Sub Section Code',
|
||||||
|
dataIndex: 'plant_sub_section_code',
|
||||||
|
key: 'plant_sub_section_code',
|
||||||
|
width: '10%',
|
||||||
|
align: 'center',
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Plant Sub Section Name',
|
title: 'Plant Sub Section Name',
|
||||||
dataIndex: 'sub_section_name',
|
dataIndex: 'plant_sub_section_name',
|
||||||
key: 'sub_section_name',
|
key: 'plant_sub_section_name',
|
||||||
width: '40%',
|
width: '15%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Description',
|
||||||
|
dataIndex: 'plant_sub_section_description',
|
||||||
|
key: 'plant_sub_section_description',
|
||||||
|
width: '30%',
|
||||||
|
render: (text) => text || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
dataIndex: 'is_active',
|
dataIndex: 'is_active',
|
||||||
key: 'is_active',
|
key: 'is_active',
|
||||||
width: '15%',
|
width: '10%',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (status) => (
|
render: (_, { is_active }) => (
|
||||||
<Tag color={status ? 'green' : 'red'}>
|
<>
|
||||||
{status ? 'Active' : 'Inactive'}
|
{is_active === true ? (
|
||||||
</Tag>
|
<Tag color={'green'} key={'status'}>
|
||||||
|
Running
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color={'red'} key={'status'}>
|
||||||
|
Offline
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,29 +70,32 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ borderColor: '#1890ff' }}
|
icon={<EyeOutlined />}
|
||||||
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
|
||||||
onClick={() => showPreviewModal(record)}
|
onClick={() => showPreviewModal(record)}
|
||||||
|
style={{ color: '#1890ff', borderColor: '#1890ff' }}
|
||||||
|
title="View"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
style={{ borderColor: '#faad14' }}
|
icon={<EditOutlined />}
|
||||||
icon={<EditOutlined style={{ color: '#faad14' }} />}
|
|
||||||
onClick={() => showEditModal(record)}
|
onClick={() => showEditModal(record)}
|
||||||
|
style={{ color: '#faad14', borderColor: '#faad14' }}
|
||||||
|
title="Edit"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
danger
|
danger
|
||||||
style={{ borderColor: 'red' }}
|
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={() => showDeleteDialog(record)}
|
onClick={() => showDeleteDialog(record)}
|
||||||
|
style={{ borderColor: '#ff4d4f' }}
|
||||||
|
title="Delete"
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const ListPlantSection = memo(function ListPlantSection(props) {
|
const ListPlantSubSection = memo(function ListPlantSubSection(props) {
|
||||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
const defaultFilter = { criteria: '' };
|
const defaultFilter = { criteria: '' };
|
||||||
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
@@ -121,8 +148,8 @@ const ListPlantSection = memo(function ListPlantSection(props) {
|
|||||||
NotifConfirmDialog({
|
NotifConfirmDialog({
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
title: 'Konfirmasi Hapus',
|
title: 'Konfirmasi Hapus',
|
||||||
message: 'Plant Section "' + param.sub_section_name + '" akan dihapus?',
|
message: `Plant Sub Section "${param.plant_sub_section_name}" akan dihapus?`,
|
||||||
onConfirm: () => handleDelete(param.sub_section_id),
|
onConfirm: () => handleDelete(param.plant_sub_section_id),
|
||||||
onCancel: () => props.setSelectedData(null),
|
onCancel: () => props.setSelectedData(null),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -226,4 +253,4 @@ const ListPlantSection = memo(function ListPlantSection(props) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ListPlantSection;
|
export default ListPlantSubSection;
|
||||||
@@ -28,6 +28,13 @@ const formatTime = (timeValue) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Shift Name',
|
title: 'Shift Name',
|
||||||
dataIndex: 'shift_name',
|
dataIndex: 'shift_name',
|
||||||
@@ -56,8 +63,18 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
key: 'is_active',
|
key: 'is_active',
|
||||||
width: '15%',
|
width: '15%',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (status) => (
|
render: (_, { is_active }) => (
|
||||||
<Tag color={status ? 'green' : 'red'}>{status ? 'Active' : 'Inactive'}</Tag>
|
<>
|
||||||
|
{is_active === true ? (
|
||||||
|
<Tag color={'green'} key={'status'}>
|
||||||
|
Running
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color={'red'} key={'status'}>
|
||||||
|
Offline
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -197,39 +197,43 @@ const DetailStatus = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<div style={{ marginBottom: 12 }}>
|
<Row gutter={16}>
|
||||||
<Text strong>Status Color</Text>
|
<Col span={12}>
|
||||||
<Text style={{ color: 'red' }}> *</Text>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<div style={{ marginTop: '8px' }}>
|
<Text strong>Status Color</Text>
|
||||||
<ColorPicker
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
value={formData.status_color || '#000000'}
|
<div style={{ marginTop: '8px' }}>
|
||||||
onChange={handleColorChange}
|
<ColorPicker
|
||||||
disabled={props.readOnly}
|
value={formData.status_color || '#000000'}
|
||||||
showText={(color) => `color hex: ${color.toHexString()}`}
|
onChange={handleColorChange}
|
||||||
allowClear={false}
|
disabled={props.readOnly}
|
||||||
format="hex"
|
showText={(color) => `color hex: ${color.toHexString()}`}
|
||||||
size="large"
|
allowClear={false}
|
||||||
style={{ width: '100%' }}
|
format="hex"
|
||||||
presets={[
|
style={{ width: '100%' }}
|
||||||
{
|
presets={[
|
||||||
label: 'Recommended',
|
{
|
||||||
colors: [
|
label: 'Recommended',
|
||||||
'#EF4444', // Merah
|
colors: [
|
||||||
'#3B82F6', // Biru
|
'#EF4444', // Merah
|
||||||
'#10B981', // Hijau
|
'#3B82F6', // Biru
|
||||||
'#F59E0B', // Kuning
|
'#10B981', // Hijau
|
||||||
'#8B5CF6', // Ungu
|
'#F59E0B', // Kuning
|
||||||
'#EC4899', // Pink
|
'#8B5CF6', // Ungu
|
||||||
'#F97316', // Orange
|
'#EC4899', // Pink
|
||||||
'#14B8A6', // Teal
|
'#F97316', // Orange
|
||||||
'#6B7280', // Gray
|
'#14B8A6', // Teal
|
||||||
'#000000', // Black
|
'#6B7280', // Gray
|
||||||
],
|
'#000000', // Black
|
||||||
},
|
],
|
||||||
]}
|
},
|
||||||
/>
|
]}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Description</Text>
|
<Text strong>Description</Text>
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@@ -13,8 +13,29 @@ import { deleteStatus, getAllStatuss } from '../../../../api/master-status';
|
|||||||
import TableList from '../../../../components/Global/TableList';
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
{ title: 'Number', dataIndex: 'status_number', key: 'status_number', width: '15%' },
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
|
{ title: 'Status Number', dataIndex: 'status_number', key: 'status_number', width: '15%' },
|
||||||
{ title: 'Name', dataIndex: 'status_name', key: 'status_name', width: '25%' },
|
{ title: 'Name', dataIndex: 'status_name', key: 'status_name', width: '25%' },
|
||||||
|
{
|
||||||
|
title: 'Color',
|
||||||
|
dataIndex: 'status_color',
|
||||||
|
key: 'status_color',
|
||||||
|
align: 'center',
|
||||||
|
width: '10%',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ backgroundColor: record.status_color }}
|
||||||
|
onClick={() => showPreviewModal(record)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
dataIndex: 'status_description',
|
dataIndex: 'status_description',
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ const DetailTag = (props) => {
|
|||||||
lim_high: '',
|
lim_high: '',
|
||||||
lim_high_crash: '',
|
lim_high_crash: '',
|
||||||
device_id: null,
|
device_id: null,
|
||||||
description: '',
|
tag_description: '',
|
||||||
|
|
||||||
sub_section_id: null,
|
plant_sub_section_id: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [formData, setformData] = useState(defaultData);
|
const [formData, setformData] = useState(defaultData);
|
||||||
@@ -68,7 +68,7 @@ const DetailTag = (props) => {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Validasi format number untuk tag_number
|
// Validasi format number untuk tag_number
|
||||||
const tagNumberInt = parseInt(formData.tag_number);
|
const tagNumberInt = Number(formData.tag_number);
|
||||||
if (isNaN(tagNumberInt)) {
|
if (isNaN(tagNumberInt)) {
|
||||||
NotifOk({
|
NotifOk({
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
@@ -88,7 +88,7 @@ const DetailTag = (props) => {
|
|||||||
const existingTags = response.data.data;
|
const existingTags = response.data.data;
|
||||||
|
|
||||||
const isDuplicate = existingTags.some((tag) => {
|
const isDuplicate = existingTags.some((tag) => {
|
||||||
const isSameNumber = parseInt(tag.tag_number) === tagNumberInt;
|
const isSameNumber = Number(tag.tag_number) === tagNumberInt;
|
||||||
const isDifferentTag = formData.tag_id ? tag.tag_id !== formData.tag_id : true;
|
const isDifferentTag = formData.tag_id ? tag.tag_id !== formData.tag_id : true;
|
||||||
return isSameNumber && isDifferentTag;
|
return isSameNumber && isDifferentTag;
|
||||||
});
|
});
|
||||||
@@ -131,7 +131,7 @@ const DetailTag = (props) => {
|
|||||||
// Prepare payload berdasarkan backend validation schema
|
// Prepare payload berdasarkan backend validation schema
|
||||||
const payload = {
|
const payload = {
|
||||||
tag_name: formData.tag_name.trim(),
|
tag_name: formData.tag_name.trim(),
|
||||||
tag_number: parseInt(formData.tag_number),
|
tag_number: Number(formData.tag_number),
|
||||||
is_active: formData.is_active,
|
is_active: formData.is_active,
|
||||||
is_alarm: formData.is_alarm,
|
is_alarm: formData.is_alarm,
|
||||||
is_report: formData.is_report,
|
is_report: formData.is_report,
|
||||||
@@ -148,25 +148,34 @@ const DetailTag = (props) => {
|
|||||||
payload.unit = formData.unit.trim();
|
payload.unit = formData.unit.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add device_id - backend requires this field even if null
|
// Add tag_description only if it has a value
|
||||||
payload.device_id = formData.device_id ? parseInt(formData.device_id) : null;
|
if (formData.tag_description && formData.tag_description.trim() !== '') {
|
||||||
|
payload.tag_description = formData.tag_description.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add device_id only if it has a value
|
||||||
|
if (formData.device_id) {
|
||||||
|
payload.device_id = Number(formData.device_id);
|
||||||
|
}
|
||||||
|
|
||||||
// Add limit fields only if they have values
|
// Add limit fields only if they have values
|
||||||
if (formData.lim_low_crash !== '' && formData.lim_low_crash !== null) {
|
if (formData.lim_low_crash !== '' && formData.lim_low_crash !== null) {
|
||||||
payload.lim_low_crash = parseFloat(formData.lim_low_crash);
|
payload.lim_low_crash = Number(formData.lim_low_crash);
|
||||||
}
|
}
|
||||||
if (formData.lim_low !== '' && formData.lim_low !== null) {
|
if (formData.lim_low !== '' && formData.lim_low !== null) {
|
||||||
payload.lim_low = parseFloat(formData.lim_low);
|
payload.lim_low = Number(formData.lim_low);
|
||||||
}
|
}
|
||||||
if (formData.lim_high !== '' && formData.lim_high !== null) {
|
if (formData.lim_high !== '' && formData.lim_high !== null) {
|
||||||
payload.lim_high = parseFloat(formData.lim_high);
|
payload.lim_high = Number(formData.lim_high);
|
||||||
}
|
}
|
||||||
if (formData.lim_high_crash !== '' && formData.lim_high_crash !== null) {
|
if (formData.lim_high_crash !== '' && formData.lim_high_crash !== null) {
|
||||||
payload.lim_high_crash = parseFloat(formData.lim_high_crash);
|
payload.lim_high_crash = Number(formData.lim_high_crash);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sub_section_id - backend requires this field even if null
|
// Add plant_sub_section_id only if it has a value
|
||||||
payload.sub_section_id = formData.sub_section_id ? parseInt(formData.sub_section_id) : null;
|
if (formData.plant_sub_section_id) {
|
||||||
|
payload.plant_sub_section_id = Number(formData.plant_sub_section_id);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response =
|
const response =
|
||||||
@@ -424,37 +433,47 @@ const DetailTag = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Alarm Checkbox */}
|
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Text strong>Alarm</Text>
|
<div
|
||||||
<div style={{ marginTop: '8px' }}>
|
style={{
|
||||||
<Checkbox
|
display: 'flex',
|
||||||
disabled={props.readOnly}
|
justifyContent: 'space-between',
|
||||||
checked={formData.is_alarm === true}
|
gap: '20px',
|
||||||
onChange={handleAlarmToggle}
|
}}
|
||||||
/>
|
>
|
||||||
</div>
|
{/* Alarm Checkbox */}
|
||||||
</div>
|
<div style={{ flex: 1 }}>
|
||||||
{/* Report Checkbox */}
|
<Text strong>Alarm</Text>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ marginTop: '8px' }}>
|
||||||
<Text strong>Report</Text>
|
<Checkbox
|
||||||
<div style={{ marginTop: '8px' }}>
|
disabled={props.readOnly}
|
||||||
<Checkbox
|
checked={formData.is_alarm === true}
|
||||||
disabled={props.readOnly}
|
onChange={handleAlarmToggle}
|
||||||
checked={formData.is_report === true}
|
/>
|
||||||
onChange={handleReportToggle}
|
</div>
|
||||||
/>
|
</div>
|
||||||
</div>
|
{/* Report Checkbox */}
|
||||||
</div>
|
<div style={{ flex: 1 }}>
|
||||||
{/* History Checkbox */}
|
<Text strong>Report</Text>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ marginTop: '8px' }}>
|
||||||
<Text strong>History</Text>
|
<Checkbox
|
||||||
<div>
|
disabled={props.readOnly}
|
||||||
<Checkbox
|
checked={formData.is_report === true}
|
||||||
disabled={props.readOnly}
|
onChange={handleReportToggle}
|
||||||
checked={formData.is_history === true}
|
/>
|
||||||
onChange={handleHistoryToggle}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
{/* History Checkbox */}
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Text strong>History</Text>
|
||||||
|
<div>
|
||||||
|
<Checkbox
|
||||||
|
disabled={props.readOnly}
|
||||||
|
checked={formData.is_history === true}
|
||||||
|
onChange={handleHistoryToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -510,9 +529,9 @@ const DetailTag = (props) => {
|
|||||||
<Select
|
<Select
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder="Select Plant Sub Section"
|
placeholder="Select Plant Sub Section"
|
||||||
value={formData.sub_section_id || undefined}
|
value={formData.plant_sub_section_id || undefined}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
handleSelectChange('sub_section_id', value)
|
handleSelectChange('plant_sub_section_id', value)
|
||||||
}
|
}
|
||||||
disabled={props.readOnly}
|
disabled={props.readOnly}
|
||||||
loading={loadingPlantSubSections}
|
loading={loadingPlantSubSections}
|
||||||
@@ -527,10 +546,10 @@ const DetailTag = (props) => {
|
|||||||
>
|
>
|
||||||
{plantSubSectionList.map((section) => (
|
{plantSubSectionList.map((section) => (
|
||||||
<Select.Option
|
<Select.Option
|
||||||
key={section.sub_section_id}
|
key={section.plant_sub_section_id}
|
||||||
value={section.sub_section_id}
|
value={section.plant_sub_section_id}
|
||||||
>
|
>
|
||||||
{section.sub_section_name || ''}
|
{section.plant_sub_section_name || ''}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -630,14 +649,14 @@ const DetailTag = (props) => {
|
|||||||
gap: '12px',
|
gap: '12px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Limit Low Crash */}
|
{/* Limit Low Low */}
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Text strong>Limit Low Crash</Text>
|
<Text strong>Limit Low Low</Text>
|
||||||
<Input
|
<Input
|
||||||
name="lim_low_crash"
|
name="lim_low_crash"
|
||||||
value={formData.lim_low_crash}
|
value={formData.lim_low_crash}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="Enter Limit Low Crash"
|
placeholder="Enter Limit Low Low"
|
||||||
readOnly={props.readOnly}
|
readOnly={props.readOnly}
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
@@ -669,14 +688,14 @@ const DetailTag = (props) => {
|
|||||||
step="any"
|
step="any"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* Limit High Crash */}
|
{/* Limit High High */}
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Text strong>Limit High Crash</Text>
|
<Text strong>Limit High High</Text>
|
||||||
<Input
|
<Input
|
||||||
name="lim_high_crash"
|
name="lim_high_crash"
|
||||||
value={formData.lim_high_crash}
|
value={formData.lim_high_crash}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="Enter Limit High Crash"
|
placeholder="Enter Limit High High"
|
||||||
readOnly={props.readOnly}
|
readOnly={props.readOnly}
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
@@ -688,8 +707,8 @@ const DetailTag = (props) => {
|
|||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Description</Text>
|
<Text strong>Description</Text>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
name="description"
|
name="tag_description"
|
||||||
value={formData.description}
|
value={formData.tag_description}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder="Enter Description (Optional)"
|
placeholder="Enter Description (Optional)"
|
||||||
readOnly={props.readOnly}
|
readOnly={props.readOnly}
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ import TableList from '../../../../components/Global/TableList';
|
|||||||
import { getAllTag, deleteTag } from '../../../../api/master-tag';
|
import { getAllTag, deleteTag } from '../../../../api/master-tag';
|
||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'tag_id',
|
dataIndex: 'tag_id',
|
||||||
@@ -25,12 +32,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
dataIndex: 'tag_code',
|
dataIndex: 'tag_code',
|
||||||
key: 'tag_code',
|
key: 'tag_code',
|
||||||
width: '10%',
|
width: '10%',
|
||||||
},
|
hidden: true,
|
||||||
{
|
|
||||||
title: 'Tag Name',
|
|
||||||
dataIndex: 'tag_name',
|
|
||||||
key: 'tag_name',
|
|
||||||
width: '15%',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tag Number',
|
title: 'Tag Number',
|
||||||
@@ -40,10 +42,17 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Data Type',
|
title: 'Tag Name',
|
||||||
|
dataIndex: 'tag_name',
|
||||||
|
key: 'tag_name',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: 'Type',
|
||||||
dataIndex: 'data_type',
|
dataIndex: 'data_type',
|
||||||
key: 'data_type',
|
key: 'data_type',
|
||||||
width: '10%',
|
width: '8%',
|
||||||
render: (text) => text || '-',
|
render: (text) => text || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,9 +64,9 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Sub Section',
|
title: 'Sub Section',
|
||||||
dataIndex: 'sub_section_name',
|
dataIndex: 'plant_sub_section_name',
|
||||||
key: 'sub_section_name',
|
key: 'plant_sub_section_name',
|
||||||
width: '12%',
|
width: '10%',
|
||||||
render: (text) => text || '-',
|
render: (text) => text || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -66,22 +75,27 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
key: 'device_name',
|
key: 'device_name',
|
||||||
width: '12%',
|
width: '12%',
|
||||||
render: (text) => text || '-',
|
render: (text) => text || '-',
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
dataIndex: 'is_active',
|
dataIndex: 'is_active',
|
||||||
key: 'is_active',
|
key: 'is_active',
|
||||||
width: '8%',
|
width: '5%',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (_, { is_active }) => {
|
render: (_, { is_active }) => (
|
||||||
const color = is_active ? 'green' : 'red';
|
<>
|
||||||
const text = is_active ? 'Active' : 'Inactive';
|
{is_active === true ? (
|
||||||
return (
|
<Tag color={'green'} key={'status'}>
|
||||||
<Tag color={color} key={'status'}>
|
Running
|
||||||
{text}
|
</Tag>
|
||||||
</Tag>
|
) : (
|
||||||
);
|
<Tag color={'red'} key={'status'}>
|
||||||
},
|
Offline
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Aksi',
|
title: 'Aksi',
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider, Select } from 'antd';
|
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider, Select } from 'antd';
|
||||||
import { NotifOk } from '../../../../components/Global/ToastNotif';
|
import { NotifOk } from '../../../../components/Global/ToastNotif';
|
||||||
import { createUnit, updateUnit } from '../../../../api/master-unit';
|
import { createUnit, updateUnit } from '../../../../api/master-unit';
|
||||||
import { getAllTag } from '../../../../api/master-tag'; // Import API untuk Tag
|
|
||||||
import { validateRun } from '../../../../Utils/validate';
|
import { validateRun } from '../../../../Utils/validate';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const DetailUnit = (props) => {
|
const DetailUnit = (props) => {
|
||||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
const [tagList, setTagList] = useState([]);
|
|
||||||
const [loadingTags, setLoadingTags] = useState(false);
|
|
||||||
|
|
||||||
const defaultData = {
|
const defaultData = {
|
||||||
unit_id: '',
|
unit_id: '',
|
||||||
@@ -18,28 +15,10 @@ const DetailUnit = (props) => {
|
|||||||
unit_name: '',
|
unit_name: '',
|
||||||
unit_description: '',
|
unit_description: '',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
tag_id: null, // Tambahkan tag_id
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [formData, setFormData] = useState(defaultData);
|
const [formData, setFormData] = useState(defaultData);
|
||||||
|
|
||||||
// Fungsi untuk mengambil data Tag
|
|
||||||
const loadTags = async () => {
|
|
||||||
setLoadingTags(true);
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams({ limit: 1000, criteria: '' });
|
|
||||||
const response = await getAllTag(params);
|
|
||||||
if (response && response.data) {
|
|
||||||
const activeTags = response.data.filter((tag) => tag.is_active === true);
|
|
||||||
setTagList(activeTags);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading tags:', error);
|
|
||||||
} finally {
|
|
||||||
setLoadingTags(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
props.setSelectedData(null);
|
props.setSelectedData(null);
|
||||||
props.setActionMode('list');
|
props.setActionMode('list');
|
||||||
@@ -48,10 +27,7 @@ const DetailUnit = (props) => {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setConfirmLoading(true);
|
setConfirmLoading(true);
|
||||||
|
|
||||||
const validationRules = [
|
const validationRules = [{ field: 'unit_name', label: 'Unit Name', required: true }];
|
||||||
{ field: 'unit_name', label: 'Unit Name', required: true },
|
|
||||||
{ field: 'tag_id', label: 'Tag', required: true }, // Tambah validasi untuk tag_id
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
validateRun(formData, validationRules, (errorMessages) => {
|
validateRun(formData, validationRules, (errorMessages) => {
|
||||||
@@ -71,7 +47,6 @@ const DetailUnit = (props) => {
|
|||||||
unit_name: formData.unit_name,
|
unit_name: formData.unit_name,
|
||||||
unit_description: formData.unit_description,
|
unit_description: formData.unit_description,
|
||||||
is_active: formData.is_active,
|
is_active: formData.is_active,
|
||||||
tag_id: formData.tag_id, // Tambahkan tag_id ke payload
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const response =
|
const response =
|
||||||
@@ -115,13 +90,6 @@ const DetailUnit = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectChange = (name, value) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
[name]: value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleStatusToggle = (checked) => {
|
const handleStatusToggle = (checked) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
@@ -130,10 +98,6 @@ const DetailUnit = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.showModal) {
|
|
||||||
loadTags(); // Panggil fungsi loadTags saat modal muncul
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.selectedData) {
|
if (props.selectedData) {
|
||||||
setFormData(props.selectedData);
|
setFormData(props.selectedData);
|
||||||
} else {
|
} else {
|
||||||
@@ -227,33 +191,6 @@ const DetailUnit = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<Text strong>Tag</Text>
|
|
||||||
<Text style={{ color: 'red' }}> *</Text>
|
|
||||||
<Select
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="Pilih Tag"
|
|
||||||
value={formData.tag_id || undefined}
|
|
||||||
onChange={(value) => handleSelectChange('tag_id', value)}
|
|
||||||
disabled={props.readOnly}
|
|
||||||
loading={loadingTags}
|
|
||||||
showSearch
|
|
||||||
allowClear
|
|
||||||
optionFilterProp="children"
|
|
||||||
filterOption={(input, option) => {
|
|
||||||
const text = option.children;
|
|
||||||
if (!text) return false;
|
|
||||||
return text.toLowerCase().includes(input.toLowerCase());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tagList.map((tag) => (
|
|
||||||
<Select.Option key={tag.tag_id} value={tag.tag_id}>
|
|
||||||
{`${tag.tag_code || ''} - ${tag.tag_name || ''}`}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Unit Name</Text>
|
<Text strong>Unit Name</Text>
|
||||||
<Text style={{ color: 'red' }}> *</Text>
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
@@ -284,4 +221,4 @@ const DetailUnit = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DetailUnit;
|
export default DetailUnit;
|
||||||
|
|||||||
@@ -24,19 +24,20 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
title: 'Unit Code',
|
title: 'Unit Code',
|
||||||
dataIndex: 'unit_code',
|
dataIndex: 'unit_code',
|
||||||
key: 'unit_code',
|
key: 'unit_code',
|
||||||
width: '20%',
|
width: '10%',
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
dataIndex: 'unit_name',
|
dataIndex: 'unit_name',
|
||||||
key: 'unit_name',
|
key: 'unit_name',
|
||||||
width: '20%',
|
width: '15%',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
dataIndex: 'unit_description',
|
dataIndex: 'unit_description',
|
||||||
key: 'unit_description',
|
key: 'unit_description',
|
||||||
width: '25%',
|
width: '30%',
|
||||||
render: (text) => text || '-',
|
render: (text) => text || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -45,15 +46,19 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
key: 'is_active',
|
key: 'is_active',
|
||||||
width: '10%',
|
width: '10%',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (_, { is_active }) => {
|
render: (_, { is_active }) => (
|
||||||
const color = is_active ? 'green' : 'red';
|
<>
|
||||||
const text = is_active ? 'Active' : 'Inactive';
|
{is_active === true ? (
|
||||||
return (
|
<Tag color={'green'} key={'status'}>
|
||||||
<Tag color={color} key={'status'}>
|
Running
|
||||||
{text}
|
</Tag>
|
||||||
</Tag>
|
) : (
|
||||||
);
|
<Tag color={'red'} key={'status'}>
|
||||||
},
|
Offline
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Aksi',
|
title: 'Aksi',
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ import { getAllRole, deleteRole } from '../../../api/role';
|
|||||||
import TableList from '../../../components/Global/TableList';
|
import TableList from '../../../components/Global/TableList';
|
||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'role_id',
|
dataIndex: 'role_id',
|
||||||
@@ -46,9 +53,17 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
width: '10%',
|
width: '10%',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (_, { is_active }) => (
|
render: (_, { is_active }) => (
|
||||||
<Tag color={is_active ? 'green' : 'red'} key={'status'}>
|
<>
|
||||||
{is_active ? 'Active' : 'Inactive'}
|
{is_active === true ? (
|
||||||
</Tag>
|
<Tag color={'green'} key={'status'}>
|
||||||
|
Active
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color={'default'} key={'status'}>
|
||||||
|
Inactive
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -241,4 +256,4 @@ const ListRole = memo(function ListRole(props) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ListRole;
|
export default ListRole;
|
||||||
|
|||||||
@@ -155,6 +155,11 @@ const DetailUser = (props) => {
|
|||||||
newErrors.user_phone = 'Nomor harus format Indonesia (08xxxxxxxx atau +628xxxxxxxx)';
|
newErrors.user_phone = 'Nomor harus format Indonesia (08xxxxxxxx atau +628xxxxxxxx)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Role validation - make role required
|
||||||
|
if (!FormData.role_id) {
|
||||||
|
newErrors.role_id = 'Role wajib dipilih';
|
||||||
|
}
|
||||||
|
|
||||||
// Password validation for add mode
|
// Password validation for add mode
|
||||||
if (!FormData.user_id) {
|
if (!FormData.user_id) {
|
||||||
const passwordError = validatePassword(FormData.password);
|
const passwordError = validatePassword(FormData.password);
|
||||||
@@ -352,6 +357,14 @@ const DetailUser = (props) => {
|
|||||||
...FormData,
|
...FormData,
|
||||||
role_id: value,
|
role_id: value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear role error when user selects a role
|
||||||
|
if (errors.role_id) {
|
||||||
|
setErrors({
|
||||||
|
...errors,
|
||||||
|
role_id: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSwitchChange = (name, checked) => {
|
const handleSwitchChange = (name, checked) => {
|
||||||
@@ -365,26 +378,69 @@ const DetailUser = (props) => {
|
|||||||
const fetchRoles = async () => {
|
const fetchRoles = async () => {
|
||||||
setLoadingRoles(true);
|
setLoadingRoles(true);
|
||||||
try {
|
try {
|
||||||
// Create query params for fetching all roles without pagination limit
|
// Create query params for fetching all roles
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 100, // Get all roles
|
limit: 100,
|
||||||
search: '',
|
search: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('Fetching roles with params:', queryParams.toString());
|
||||||
const response = await getAllRole(queryParams);
|
const response = await getAllRole(queryParams);
|
||||||
console.log('Fetched roles:', response);
|
console.log('Fetched roles response:', response);
|
||||||
|
|
||||||
if (response && response.data && response.data.data) {
|
// Handle different response structures
|
||||||
setRoleList(response.data.data);
|
if (response && response.data) {
|
||||||
|
let roles = [];
|
||||||
|
|
||||||
|
if (response.data.data && Array.isArray(response.data.data)) {
|
||||||
|
roles = response.data.data;
|
||||||
|
} else if (Array.isArray(response.data)) {
|
||||||
|
roles = response.data;
|
||||||
|
} else {
|
||||||
|
// Add mock data as fallback for testing
|
||||||
|
console.warn('Unexpected role data structure, using mock data');
|
||||||
|
roles = [
|
||||||
|
{ role_id: 1, role_name: 'Admin', role_level: 1 },
|
||||||
|
{ role_id: 2, role_name: 'Manager', role_level: 2 },
|
||||||
|
{ role_id: 3, role_name: 'User', role_level: 3 },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
setRoleList(roles);
|
||||||
|
console.log('Setting role list:', roles);
|
||||||
|
} else {
|
||||||
|
// Add mock data as fallback
|
||||||
|
console.warn('No response data, using mock data');
|
||||||
|
const mockRoles = [
|
||||||
|
{ role_id: 1, role_name: 'Admin', role_level: 1 },
|
||||||
|
{ role_id: 2, role_name: 'Manager', role_level: 2 },
|
||||||
|
{ role_id: 3, role_name: 'User', role_level: 3 },
|
||||||
|
];
|
||||||
|
setRoleList(mockRoles);
|
||||||
|
console.log('Setting mock role list:', mockRoles);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching roles:', error);
|
console.error('Error fetching roles:', error);
|
||||||
NotifAlert({
|
// Add mock data as fallback on error
|
||||||
icon: 'error',
|
const mockRoles = [
|
||||||
title: 'Error',
|
{ role_id: 1, role_name: 'Admin', role_level: 1 },
|
||||||
message: 'Gagal memuat daftar role',
|
{ role_id: 2, role_name: 'Manager', role_level: 2 },
|
||||||
});
|
{ role_id: 3, role_name: 'User', role_level: 3 },
|
||||||
|
];
|
||||||
|
setRoleList(mockRoles);
|
||||||
|
console.log('Setting mock role list due to error:', mockRoles);
|
||||||
|
|
||||||
|
// Only show error notification if we don't have fallback data
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('Using mock role data due to API error');
|
||||||
|
} else {
|
||||||
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Gagal memuat daftar role, menggunakan data default',
|
||||||
|
});
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingRoles(false);
|
setLoadingRoles(false);
|
||||||
}
|
}
|
||||||
@@ -1072,6 +1128,7 @@ const DetailUser = (props) => {
|
|||||||
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Role</Text>
|
<Text strong>Role</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
<Select
|
<Select
|
||||||
value={FormData.role_id}
|
value={FormData.role_id}
|
||||||
onChange={handleSelectChange}
|
onChange={handleSelectChange}
|
||||||
@@ -1080,6 +1137,7 @@ const DetailUser = (props) => {
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder={loadingRoles ? 'Memuat role...' : 'Pilih role'}
|
placeholder={loadingRoles ? 'Memuat role...' : 'Pilih role'}
|
||||||
allowClear
|
allowClear
|
||||||
|
status={errors.role_id ? 'error' : ''}
|
||||||
>
|
>
|
||||||
{roleList.map((role) => (
|
{roleList.map((role) => (
|
||||||
<Option key={role.role_id} value={role.role_id}>
|
<Option key={role.role_id} value={role.role_id}>
|
||||||
@@ -1087,6 +1145,11 @@ const DetailUser = (props) => {
|
|||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
{errors.role_id && (
|
||||||
|
<Text style={{ color: 'red', fontSize: '12px' }}>
|
||||||
|
{errors.role_id}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ const getRoleColor = (role_name, role_level) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApprovalModal) => [
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApprovalModal) => [
|
||||||
|
{
|
||||||
|
title: 'No',
|
||||||
|
key: 'no',
|
||||||
|
width: '5%',
|
||||||
|
align: 'center',
|
||||||
|
render: (_, __, index) => index + 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'user_id',
|
dataIndex: 'user_id',
|
||||||
|
|||||||
Reference in New Issue
Block a user