@@ -15,7 +15,6 @@ 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';
|
||||
|
||||
@@ -41,6 +40,7 @@ import SvgAirDryerB from './pages/home/SvgAirDryerB';
|
||||
import SvgAirDryerC from './pages/home/SvgAirDryerC';
|
||||
import IndexHistoryAlarm from './pages/history/alarm/IndexHistoryAlarm';
|
||||
import IndexHistoryEvent from './pages/history/event/IndexHistoryEvent';
|
||||
import IndexPlantSubSection from './pages/master/plantSubSection/IndexPlantSubSection';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
@@ -74,7 +74,7 @@ const App = () => {
|
||||
<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="plant-sub-section" element={<IndexPlantSubSection />} />
|
||||
<Route path="shift" element={<IndexShift />} />
|
||||
<Route path="status" element={<IndexStatus />} />
|
||||
</Route>
|
||||
|
||||
@@ -93,9 +93,9 @@ const allItems = [
|
||||
label: 'Master',
|
||||
children: [
|
||||
{
|
||||
key: 'master-plant-section',
|
||||
key: 'master-plant-sub-section',
|
||||
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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'device_id',
|
||||
@@ -27,6 +34,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
dataIndex: 'device_code',
|
||||
key: 'device_code',
|
||||
width: '10%',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
title: 'Device Name',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { memo, useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ListPlantSection from './component/ListPlantSection';
|
||||
import DetailPlantSection from './component/DetailPlantSection';
|
||||
import ListPlantSection from './component/ListPlantSubSection';
|
||||
import DetailPlantSection from './component/DetailPlantSubSection';
|
||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const IndexPlantSection = memo(function IndexPlantSection() {
|
||||
const IndexPlantSubSection = memo(function IndexPlantSubSection() {
|
||||
const navigate = useNavigate();
|
||||
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 { createPlantSection, updatePlantSection } from '../../../../api/master-plant-section';
|
||||
import { validateRun } from '../../../../Utils/validate';
|
||||
import TextArea from 'antd/es/input/TextArea';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const DetailPlantSection = (props) => {
|
||||
const DetailPlantSubSection = (props) => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const defaultData = {
|
||||
sub_section_id: '',
|
||||
sub_section_code: '',
|
||||
sub_section_name: '',
|
||||
plant_sub_section_id: '',
|
||||
plant_sub_section_code: '',
|
||||
plant_sub_section_name: '',
|
||||
table_name_value: '', // Fix field name
|
||||
plant_sub_section_description: '',
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const [formData, setFormData] = useState(defaultData);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
// Handle different input types
|
||||
let 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 = () => {
|
||||
@@ -36,7 +58,7 @@ const DetailPlantSection = (props) => {
|
||||
|
||||
// Daftar aturan validasi
|
||||
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 (
|
||||
@@ -52,14 +74,20 @@ const DetailPlantSection = (props) => {
|
||||
return;
|
||||
|
||||
try {
|
||||
console.log('💾 Current formData before save:', formData);
|
||||
|
||||
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,
|
||||
sub_section_name: formData.sub_section_name,
|
||||
};
|
||||
|
||||
console.log('📤 Payload to be sent:', payload);
|
||||
|
||||
const response =
|
||||
props.actionMode === 'edit'
|
||||
? await updatePlantSection(formData.sub_section_id, payload)
|
||||
? await updatePlantSection(formData.plant_sub_section_id, payload)
|
||||
: await createPlantSection(payload);
|
||||
|
||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||
@@ -98,9 +126,17 @@ const DetailPlantSection = (props) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🔄 Modal state changed:', {
|
||||
showModal: props.showModal,
|
||||
actionMode: props.actionMode,
|
||||
selectedData: props.selectedData,
|
||||
});
|
||||
|
||||
if (props.selectedData) {
|
||||
console.log('📋 Setting form data from selectedData:', props.selectedData);
|
||||
setFormData(props.selectedData);
|
||||
} else {
|
||||
console.log('📋 Resetting to default data');
|
||||
setFormData(defaultData);
|
||||
}
|
||||
}, [props.showModal, props.selectedData, props.actionMode]);
|
||||
@@ -177,7 +213,7 @@ const DetailPlantSection = (props) => {
|
||||
|
||||
{/* Plant Section Code - Auto Increment & Read Only */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Plant Section Code</Text>
|
||||
<Text strong>Plant Sub Section Code</Text>
|
||||
<Input
|
||||
name="sub_section_code"
|
||||
value={formData.sub_section_code || ''}
|
||||
@@ -195,17 +231,38 @@ const DetailPlantSection = (props) => {
|
||||
<Text strong>Plant Sub Section Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="sub_section_name"
|
||||
value={formData.sub_section_name}
|
||||
name="plant_sub_section_name"
|
||||
value={formData.plant_sub_section_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Plant Sub Section Name"
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailPlantSection;
|
||||
export default DetailPlantSubSection;
|
||||
@@ -14,27 +14,51 @@ import TableList from '../../../../components/Global/TableList';
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
{
|
||||
title: 'Section Code',
|
||||
dataIndex: 'sub_section_code',
|
||||
key: 'sub_section_code',
|
||||
width: '20%',
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
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',
|
||||
dataIndex: 'sub_section_name',
|
||||
key: 'sub_section_name',
|
||||
width: '40%',
|
||||
dataIndex: 'plant_sub_section_name',
|
||||
key: 'plant_sub_section_name',
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'plant_sub_section_description',
|
||||
key: 'plant_sub_section_description',
|
||||
width: '30%',
|
||||
render: (text) => text || '-',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
width: '15%',
|
||||
width: '10%',
|
||||
align: 'center',
|
||||
render: (status) => (
|
||||
<Tag color={status ? 'green' : 'red'}>
|
||||
{status ? 'Active' : 'Inactive'}
|
||||
</Tag>
|
||||
render: (_, { is_active }) => (
|
||||
<>
|
||||
{is_active === true ? (
|
||||
<Tag color={'green'} key={'status'}>
|
||||
Running
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag color={'red'} key={'status'}>
|
||||
Offline
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -46,29 +70,32 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
style={{ borderColor: '#1890ff' }}
|
||||
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => showPreviewModal(record)}
|
||||
style={{ color: '#1890ff', borderColor: '#1890ff' }}
|
||||
title="View"
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
style={{ borderColor: '#faad14' }}
|
||||
icon={<EditOutlined style={{ color: '#faad14' }} />}
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showEditModal(record)}
|
||||
style={{ color: '#faad14', borderColor: '#faad14' }}
|
||||
title="Edit"
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
style={{ borderColor: 'red' }}
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => showDeleteDialog(record)}
|
||||
style={{ borderColor: '#ff4d4f' }}
|
||||
title="Delete"
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const ListPlantSection = memo(function ListPlantSection(props) {
|
||||
const ListPlantSubSection = memo(function ListPlantSubSection(props) {
|
||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||
const defaultFilter = { criteria: '' };
|
||||
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||
@@ -121,8 +148,8 @@ const ListPlantSection = memo(function ListPlantSection(props) {
|
||||
NotifConfirmDialog({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi Hapus',
|
||||
message: 'Plant Section "' + param.sub_section_name + '" akan dihapus?',
|
||||
onConfirm: () => handleDelete(param.sub_section_id),
|
||||
message: `Plant Sub Section "${param.plant_sub_section_name}" akan dihapus?`,
|
||||
onConfirm: () => handleDelete(param.plant_sub_section_id),
|
||||
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) => [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'Shift Name',
|
||||
dataIndex: 'shift_name',
|
||||
@@ -56,8 +63,18 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
key: 'is_active',
|
||||
width: '15%',
|
||||
align: 'center',
|
||||
render: (status) => (
|
||||
<Tag color={status ? 'green' : 'red'}>{status ? 'Active' : 'Inactive'}</Tag>
|
||||
render: (_, { is_active }) => (
|
||||
<>
|
||||
{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>
|
||||
</Col>
|
||||
</Row>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Status Color</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<ColorPicker
|
||||
value={formData.status_color || '#000000'}
|
||||
onChange={handleColorChange}
|
||||
disabled={props.readOnly}
|
||||
showText={(color) => `color hex: ${color.toHexString()}`}
|
||||
allowClear={false}
|
||||
format="hex"
|
||||
size="large"
|
||||
style={{ width: '100%' }}
|
||||
presets={[
|
||||
{
|
||||
label: 'Recommended',
|
||||
colors: [
|
||||
'#EF4444', // Merah
|
||||
'#3B82F6', // Biru
|
||||
'#10B981', // Hijau
|
||||
'#F59E0B', // Kuning
|
||||
'#8B5CF6', // Ungu
|
||||
'#EC4899', // Pink
|
||||
'#F97316', // Orange
|
||||
'#14B8A6', // Teal
|
||||
'#6B7280', // Gray
|
||||
'#000000', // Black
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Status Color</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<ColorPicker
|
||||
value={formData.status_color || '#000000'}
|
||||
onChange={handleColorChange}
|
||||
disabled={props.readOnly}
|
||||
showText={(color) => `color hex: ${color.toHexString()}`}
|
||||
allowClear={false}
|
||||
format="hex"
|
||||
style={{ width: '100%' }}
|
||||
presets={[
|
||||
{
|
||||
label: 'Recommended',
|
||||
colors: [
|
||||
'#EF4444', // Merah
|
||||
'#3B82F6', // Biru
|
||||
'#10B981', // Hijau
|
||||
'#F59E0B', // Kuning
|
||||
'#8B5CF6', // Ungu
|
||||
'#EC4899', // Pink
|
||||
'#F97316', // Orange
|
||||
'#14B8A6', // Teal
|
||||
'#6B7280', // Gray
|
||||
'#000000', // Black
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Description</Text>
|
||||
<TextArea
|
||||
|
||||
@@ -13,8 +13,29 @@ import { deleteStatus, getAllStatuss } from '../../../../api/master-status';
|
||||
import TableList from '../../../../components/Global/TableList';
|
||||
|
||||
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: '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',
|
||||
dataIndex: 'status_description',
|
||||
|
||||
@@ -34,9 +34,9 @@ const DetailTag = (props) => {
|
||||
lim_high: '',
|
||||
lim_high_crash: '',
|
||||
device_id: null,
|
||||
description: '',
|
||||
tag_description: '',
|
||||
|
||||
sub_section_id: null,
|
||||
plant_sub_section_id: null,
|
||||
};
|
||||
|
||||
const [formData, setformData] = useState(defaultData);
|
||||
@@ -68,7 +68,7 @@ const DetailTag = (props) => {
|
||||
return;
|
||||
|
||||
// Validasi format number untuk tag_number
|
||||
const tagNumberInt = parseInt(formData.tag_number);
|
||||
const tagNumberInt = Number(formData.tag_number);
|
||||
if (isNaN(tagNumberInt)) {
|
||||
NotifOk({
|
||||
icon: 'warning',
|
||||
@@ -88,7 +88,7 @@ const DetailTag = (props) => {
|
||||
const existingTags = response.data.data;
|
||||
|
||||
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;
|
||||
return isSameNumber && isDifferentTag;
|
||||
});
|
||||
@@ -131,7 +131,7 @@ const DetailTag = (props) => {
|
||||
// Prepare payload berdasarkan backend validation schema
|
||||
const payload = {
|
||||
tag_name: formData.tag_name.trim(),
|
||||
tag_number: parseInt(formData.tag_number),
|
||||
tag_number: Number(formData.tag_number),
|
||||
is_active: formData.is_active,
|
||||
is_alarm: formData.is_alarm,
|
||||
is_report: formData.is_report,
|
||||
@@ -148,25 +148,34 @@ const DetailTag = (props) => {
|
||||
payload.unit = formData.unit.trim();
|
||||
}
|
||||
|
||||
// Add device_id - backend requires this field even if null
|
||||
payload.device_id = formData.device_id ? parseInt(formData.device_id) : null;
|
||||
// Add tag_description only if it has a value
|
||||
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
|
||||
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) {
|
||||
payload.lim_low = parseFloat(formData.lim_low);
|
||||
payload.lim_low = Number(formData.lim_low);
|
||||
}
|
||||
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) {
|
||||
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
|
||||
payload.sub_section_id = formData.sub_section_id ? parseInt(formData.sub_section_id) : null;
|
||||
// Add plant_sub_section_id only if it has a value
|
||||
if (formData.plant_sub_section_id) {
|
||||
payload.plant_sub_section_id = Number(formData.plant_sub_section_id);
|
||||
}
|
||||
|
||||
try {
|
||||
const response =
|
||||
@@ -424,37 +433,47 @@ const DetailTag = (props) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Alarm Checkbox */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text strong>Alarm</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<Checkbox
|
||||
disabled={props.readOnly}
|
||||
checked={formData.is_alarm === true}
|
||||
onChange={handleAlarmToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Report Checkbox */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text strong>Report</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<Checkbox
|
||||
disabled={props.readOnly}
|
||||
checked={formData.is_report === true}
|
||||
onChange={handleReportToggle}
|
||||
/>
|
||||
</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
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
{/* Alarm Checkbox */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text strong>Alarm</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<Checkbox
|
||||
disabled={props.readOnly}
|
||||
checked={formData.is_alarm === true}
|
||||
onChange={handleAlarmToggle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Report Checkbox */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text strong>Report</Text>
|
||||
<div style={{ marginTop: '8px' }}>
|
||||
<Checkbox
|
||||
disabled={props.readOnly}
|
||||
checked={formData.is_report === true}
|
||||
onChange={handleReportToggle}
|
||||
/>
|
||||
</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>
|
||||
@@ -510,9 +529,9 @@ const DetailTag = (props) => {
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Select Plant Sub Section"
|
||||
value={formData.sub_section_id || undefined}
|
||||
value={formData.plant_sub_section_id || undefined}
|
||||
onChange={(value) =>
|
||||
handleSelectChange('sub_section_id', value)
|
||||
handleSelectChange('plant_sub_section_id', value)
|
||||
}
|
||||
disabled={props.readOnly}
|
||||
loading={loadingPlantSubSections}
|
||||
@@ -527,10 +546,10 @@ const DetailTag = (props) => {
|
||||
>
|
||||
{plantSubSectionList.map((section) => (
|
||||
<Select.Option
|
||||
key={section.sub_section_id}
|
||||
value={section.sub_section_id}
|
||||
key={section.plant_sub_section_id}
|
||||
value={section.plant_sub_section_id}
|
||||
>
|
||||
{section.sub_section_name || ''}
|
||||
{section.plant_sub_section_name || ''}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
@@ -630,14 +649,14 @@ const DetailTag = (props) => {
|
||||
gap: '12px',
|
||||
}}
|
||||
>
|
||||
{/* Limit Low Crash */}
|
||||
{/* Limit Low Low */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text strong>Limit Low Crash</Text>
|
||||
<Text strong>Limit Low Low</Text>
|
||||
<Input
|
||||
name="lim_low_crash"
|
||||
value={formData.lim_low_crash}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Limit Low Crash"
|
||||
placeholder="Enter Limit Low Low"
|
||||
readOnly={props.readOnly}
|
||||
type="number"
|
||||
step="any"
|
||||
@@ -669,14 +688,14 @@ const DetailTag = (props) => {
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
{/* Limit High Crash */}
|
||||
{/* Limit High High */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text strong>Limit High Crash</Text>
|
||||
<Text strong>Limit High High</Text>
|
||||
<Input
|
||||
name="lim_high_crash"
|
||||
value={formData.lim_high_crash}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Limit High Crash"
|
||||
placeholder="Enter Limit High High"
|
||||
readOnly={props.readOnly}
|
||||
type="number"
|
||||
step="any"
|
||||
@@ -688,8 +707,8 @@ const DetailTag = (props) => {
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Description</Text>
|
||||
<Input.TextArea
|
||||
name="description"
|
||||
value={formData.description}
|
||||
name="tag_description"
|
||||
value={formData.tag_description}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Description (Optional)"
|
||||
readOnly={props.readOnly}
|
||||
|
||||
@@ -13,6 +13,13 @@ import TableList from '../../../../components/Global/TableList';
|
||||
import { getAllTag, deleteTag } from '../../../../api/master-tag';
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'tag_id',
|
||||
@@ -25,12 +32,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
dataIndex: 'tag_code',
|
||||
key: 'tag_code',
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
title: 'Tag Name',
|
||||
dataIndex: 'tag_name',
|
||||
key: 'tag_name',
|
||||
width: '15%',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
title: 'Tag Number',
|
||||
@@ -40,10 +42,17 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'Data Type',
|
||||
title: 'Tag Name',
|
||||
dataIndex: 'tag_name',
|
||||
key: 'tag_name',
|
||||
width: '20%',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Type',
|
||||
dataIndex: 'data_type',
|
||||
key: 'data_type',
|
||||
width: '10%',
|
||||
width: '8%',
|
||||
render: (text) => text || '-',
|
||||
},
|
||||
{
|
||||
@@ -55,9 +64,9 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
},
|
||||
{
|
||||
title: 'Sub Section',
|
||||
dataIndex: 'sub_section_name',
|
||||
key: 'sub_section_name',
|
||||
width: '12%',
|
||||
dataIndex: 'plant_sub_section_name',
|
||||
key: 'plant_sub_section_name',
|
||||
width: '10%',
|
||||
render: (text) => text || '-',
|
||||
},
|
||||
{
|
||||
@@ -66,22 +75,27 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
key: 'device_name',
|
||||
width: '12%',
|
||||
render: (text) => text || '-',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
width: '8%',
|
||||
width: '5%',
|
||||
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>
|
||||
);
|
||||
},
|
||||
render: (_, { is_active }) => (
|
||||
<>
|
||||
{is_active === true ? (
|
||||
<Tag color={'green'} key={'status'}>
|
||||
Running
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag color={'red'} key={'status'}>
|
||||
Offline
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
|
||||
@@ -2,15 +2,12 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider, Select } from 'antd';
|
||||
import { NotifOk } from '../../../../components/Global/ToastNotif';
|
||||
import { createUnit, updateUnit } from '../../../../api/master-unit';
|
||||
import { getAllTag } from '../../../../api/master-tag'; // Import API untuk Tag
|
||||
import { validateRun } from '../../../../Utils/validate';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const DetailUnit = (props) => {
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [tagList, setTagList] = useState([]);
|
||||
const [loadingTags, setLoadingTags] = useState(false);
|
||||
|
||||
const defaultData = {
|
||||
unit_id: '',
|
||||
@@ -18,28 +15,10 @@ const DetailUnit = (props) => {
|
||||
unit_name: '',
|
||||
unit_description: '',
|
||||
is_active: true,
|
||||
tag_id: null, // Tambahkan tag_id
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
props.setSelectedData(null);
|
||||
props.setActionMode('list');
|
||||
@@ -48,10 +27,7 @@ const DetailUnit = (props) => {
|
||||
const handleSave = async () => {
|
||||
setConfirmLoading(true);
|
||||
|
||||
const validationRules = [
|
||||
{ field: 'unit_name', label: 'Unit Name', required: true },
|
||||
{ field: 'tag_id', label: 'Tag', required: true }, // Tambah validasi untuk tag_id
|
||||
];
|
||||
const validationRules = [{ field: 'unit_name', label: 'Unit Name', required: true }];
|
||||
|
||||
if (
|
||||
validateRun(formData, validationRules, (errorMessages) => {
|
||||
@@ -71,7 +47,6 @@ const DetailUnit = (props) => {
|
||||
unit_name: formData.unit_name,
|
||||
unit_description: formData.unit_description,
|
||||
is_active: formData.is_active,
|
||||
tag_id: formData.tag_id, // Tambahkan tag_id ke payload
|
||||
};
|
||||
|
||||
const response =
|
||||
@@ -115,13 +90,6 @@ const DetailUnit = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectChange = (name, value) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleStatusToggle = (checked) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
@@ -130,10 +98,6 @@ const DetailUnit = (props) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.showModal) {
|
||||
loadTags(); // Panggil fungsi loadTags saat modal muncul
|
||||
}
|
||||
|
||||
if (props.selectedData) {
|
||||
setFormData(props.selectedData);
|
||||
} else {
|
||||
@@ -227,33 +191,6 @@ const DetailUnit = (props) => {
|
||||
/>
|
||||
</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 }}>
|
||||
<Text strong>Unit Name</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
|
||||
@@ -24,19 +24,20 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
title: 'Unit Code',
|
||||
dataIndex: 'unit_code',
|
||||
key: 'unit_code',
|
||||
width: '20%',
|
||||
width: '10%',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'unit_name',
|
||||
key: 'unit_name',
|
||||
width: '20%',
|
||||
width: '15%',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'unit_description',
|
||||
key: 'unit_description',
|
||||
width: '25%',
|
||||
width: '30%',
|
||||
render: (text) => text || '-',
|
||||
},
|
||||
{
|
||||
@@ -45,15 +46,19 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
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>
|
||||
);
|
||||
},
|
||||
render: (_, { is_active }) => (
|
||||
<>
|
||||
{is_active === true ? (
|
||||
<Tag color={'green'} key={'status'}>
|
||||
Running
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag color={'red'} key={'status'}>
|
||||
Offline
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
|
||||
@@ -13,6 +13,13 @@ import { getAllRole, deleteRole } from '../../../api/role';
|
||||
import TableList from '../../../components/Global/TableList';
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'role_id',
|
||||
@@ -46,9 +53,17 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
width: '10%',
|
||||
align: 'center',
|
||||
render: (_, { is_active }) => (
|
||||
<Tag color={is_active ? 'green' : 'red'} key={'status'}>
|
||||
{is_active ? 'Active' : 'Inactive'}
|
||||
</Tag>
|
||||
<>
|
||||
{is_active === true ? (
|
||||
<Tag color={'green'} key={'status'}>
|
||||
Active
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag color={'default'} key={'status'}>
|
||||
Inactive
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -155,6 +155,11 @@ const DetailUser = (props) => {
|
||||
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
|
||||
if (!FormData.user_id) {
|
||||
const passwordError = validatePassword(FormData.password);
|
||||
@@ -352,6 +357,14 @@ const DetailUser = (props) => {
|
||||
...FormData,
|
||||
role_id: value,
|
||||
});
|
||||
|
||||
// Clear role error when user selects a role
|
||||
if (errors.role_id) {
|
||||
setErrors({
|
||||
...errors,
|
||||
role_id: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwitchChange = (name, checked) => {
|
||||
@@ -365,26 +378,69 @@ const DetailUser = (props) => {
|
||||
const fetchRoles = async () => {
|
||||
setLoadingRoles(true);
|
||||
try {
|
||||
// Create query params for fetching all roles without pagination limit
|
||||
// Create query params for fetching all roles
|
||||
const queryParams = new URLSearchParams({
|
||||
page: 1,
|
||||
limit: 100, // Get all roles
|
||||
limit: 100,
|
||||
search: '',
|
||||
});
|
||||
|
||||
console.log('Fetching roles with params:', queryParams.toString());
|
||||
const response = await getAllRole(queryParams);
|
||||
console.log('Fetched roles:', response);
|
||||
console.log('Fetched roles response:', response);
|
||||
|
||||
if (response && response.data && response.data.data) {
|
||||
setRoleList(response.data.data);
|
||||
// Handle different response structures
|
||||
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) {
|
||||
console.error('Error fetching roles:', error);
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: 'Gagal memuat daftar role',
|
||||
});
|
||||
// Add mock data as fallback on error
|
||||
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 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 {
|
||||
setLoadingRoles(false);
|
||||
}
|
||||
@@ -1072,6 +1128,7 @@ const DetailUser = (props) => {
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Role</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Select
|
||||
value={FormData.role_id}
|
||||
onChange={handleSelectChange}
|
||||
@@ -1080,6 +1137,7 @@ const DetailUser = (props) => {
|
||||
style={{ width: '100%' }}
|
||||
placeholder={loadingRoles ? 'Memuat role...' : 'Pilih role'}
|
||||
allowClear
|
||||
status={errors.role_id ? 'error' : ''}
|
||||
>
|
||||
{roleList.map((role) => (
|
||||
<Option key={role.role_id} value={role.role_id}>
|
||||
@@ -1087,6 +1145,11 @@ const DetailUser = (props) => {
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
{errors.role_id && (
|
||||
<Text style={{ color: 'red', fontSize: '12px' }}>
|
||||
{errors.role_id}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -51,6 +51,13 @@ const getRoleColor = (role_name, role_level) => {
|
||||
};
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApprovalModal) => [
|
||||
{
|
||||
title: 'No',
|
||||
key: 'no',
|
||||
width: '5%',
|
||||
align: 'center',
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'user_id',
|
||||
|
||||
Reference in New Issue
Block a user