lavoce #8

Merged
bragaz_rexita merged 5 commits from lavoce into main 2025-10-25 09:19:36 +00:00
3 changed files with 84 additions and 103 deletions
Showing only changes of commit 2abed31bde - Show all commits

View File

@@ -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;

View File

@@ -3,6 +3,7 @@ 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;
@@ -40,7 +41,7 @@ const DetailPlantSection = (props) => {
console.log(`📝 Input change: ${name} = ${value}`);
if (name) {
setFormData(prev => ({
setFormData((prev) => ({
...prev,
[name]: value,
}));
@@ -73,7 +74,7 @@ const DetailPlantSection = (props) => {
return;
try {
console.log("💾 Current formData before save:", formData);
console.log('💾 Current formData before save:', formData);
const payload = {
plant_sub_section_name: formData.plant_sub_section_name,
@@ -82,7 +83,7 @@ const DetailPlantSection = (props) => {
is_active: formData.is_active,
};
console.log("📤 Payload to be sent:", payload);
console.log('📤 Payload to be sent:', payload);
const response =
props.actionMode === 'edit'
@@ -125,17 +126,17 @@ const DetailPlantSection = (props) => {
};
useEffect(() => {
console.log("🔄 Modal state changed:", {
console.log('🔄 Modal state changed:', {
showModal: props.showModal,
actionMode: props.actionMode,
selectedData: props.selectedData
selectedData: props.selectedData,
});
if (props.selectedData) {
console.log("📋 Setting form data from selectedData:", props.selectedData);
console.log('📋 Setting form data from selectedData:', props.selectedData);
setFormData(props.selectedData);
} else {
console.log("📋 Resetting to default data");
console.log('📋 Resetting to default data');
setFormData(defaultData);
}
}, [props.showModal, props.selectedData, props.actionMode]);
@@ -212,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 || ''}
@@ -249,12 +250,13 @@ const DetailPlantSection = (props) => {
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Description</Text>
<Input
<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>

View File

@@ -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>
)}