repair: view brand device, add: read only

This commit is contained in:
2025-12-12 23:02:09 +07:00
parent cf1ccb0fd0
commit 49ba00d886
12 changed files with 868 additions and 518 deletions

View File

@@ -982,7 +982,7 @@ const AddBrandDevice = () => {
borderTop: '1px solid #f0f0f0',
marginTop: '12px'
}}>
{/* Cancel Button - Only show when editing existing error code */}
{editingErrorCodeKey && (
<Button
size="large"
@@ -1010,7 +1010,7 @@ const AddBrandDevice = () => {
</Button>
)}
{/* Save Button - Always show on right */}
<div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}>
<Button
type="primary"
@@ -1123,7 +1123,7 @@ const AddBrandDevice = () => {
};
}, [isTemporaryBrand, temporaryBrandId, currentStep]);
return (
<ConfigProvider
theme={{
@@ -1142,79 +1142,79 @@ const AddBrandDevice = () => {
</Steps>
<div style={{ position: 'relative' }}>
{loading && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.6)',
backdropFilter: 'blur(0.8px)',
filter: 'blur(0.5px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
borderRadius: '8px',
}}
>
<Spin size="large" />
</div>
)}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.6)',
backdropFilter: 'blur(0.8px)',
filter: 'blur(0.5px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
borderRadius: '8px',
filter: loading ? 'blur(0.5px)' : 'none',
transition: 'filter 0.3s ease',
}}
>
<Spin size="large" />
{renderStepContent()}
</div>
)}
<div
style={{
filter: loading ? 'blur(0.5px)' : 'none',
transition: 'filter 0.3s ease',
}}
>
{renderStepContent()}
</div>
</div>
<Divider />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
<Button onClick={handleCancel}>
Cancel
</Button>
{currentStep === 1 && (
<Button
onClick={handlePrevStep}
style={{ marginLeft: 8 }}
>
Kembali ke Brand Info
<Divider />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
<Button onClick={handleCancel}>
Cancel
</Button>
)}
{currentStep === 1 && (
<Button
onClick={handlePrevStep}
style={{ marginLeft: 8 }}
>
Kembali ke Brand Info
</Button>
)}
</div>
<div>
{currentStep === 0 && (
<Button
type="primary"
onClick={handleNextStep}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Lanjut
</Button>
)}
{currentStep === 1 && (
<Button
type="primary"
onClick={handleFinish}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Selesai
</Button>
)}
</div>
</div>
<div>
{currentStep === 0 && (
<Button
type="primary"
onClick={handleNextStep}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Lanjut
</Button>
)}
{currentStep === 1 && (
<Button
type="primary"
onClick={handleFinish}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Selesai
</Button>
)}
</div>
</div>
</Card>
</ConfigProvider>
);

View File

@@ -328,13 +328,13 @@ const EditBrandDevice = () => {
}
}, [currentStep, id]);
// Auto refresh error codes when returning from add/edit error code
useEffect(() => {
if (location.state?.refreshErrorCodes) {
// Trigger refresh of error codes list
setTrigerFilter(prev => !prev);
// Clear the state to prevent infinite refresh
const state = { ...location.state };
delete state.refreshErrorCodes;
navigate(location.pathname + location.search, {
@@ -345,7 +345,7 @@ const EditBrandDevice = () => {
}, [location, navigate]);
const addErrorCode = (newErrorCode) => {
// Generate unique tempId with timestamp and random number
const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const errorCodeWithId = {
...newErrorCode,
@@ -566,13 +566,13 @@ const EditBrandDevice = () => {
message: editingErrorCodeKey ? 'Error code berhasil diupdate!' : 'Error code berhasil ditambahkan!',
});
// Clear temp error codes after successful save to prevent duplication
setTempErrorCodes(prev => prev.filter(ec => {
// Keep existing error codes that weren't just saved
if (ec.status === 'existing' || ec.tempId.startsWith('existing_')) {
return true;
}
// Remove the newly saved error code to prevent duplication
return ec.tempId !== editingErrorCodeKey;
}));
@@ -1182,7 +1182,7 @@ const EditBrandDevice = () => {
borderTop: '1px solid #f0f0f0',
marginTop: '12px'
}}>
{/* Cancel Button - Only show when editing existing error code */}
{editingErrorCodeKey && (
<Button
size="large"
@@ -1210,7 +1210,7 @@ const EditBrandDevice = () => {
</Button>
)}
{/* Save Button - Always show on right */}
<div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}>
<Button
type="primary"
@@ -1266,45 +1266,45 @@ const EditBrandDevice = () => {
</Steps>
{renderStepContent()}
<Divider />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
{currentStep === 1 && (
<Button
onClick={() => navigate(`/master/brand-device/edit/${id}?tab=brand`)}
style={{ marginLeft: 8 }}
>
Back to Brand Info
</Button>
)}
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
{currentStep === 1 && (
<Button
onClick={() => navigate(`/master/brand-device/edit/${id}?tab=brand`)}
style={{ marginLeft: 8 }}
>
Back to Brand Info
</Button>
)}
</div>
<div>
{currentStep === 0 && (
<Button
type="primary"
onClick={handleNextStep}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Error Code
</Button>
)}
{currentStep === 1 && (
<Button
type="primary"
onClick={handleCancel}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Selesai
</Button>
)}
</div>
</div>
<div>
{currentStep === 0 && (
<Button
type="primary"
onClick={handleNextStep}
loading={confirmLoading}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Error Code
</Button>
)}
{currentStep === 1 && (
<Button
type="primary"
onClick={handleCancel}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Selesai
</Button>
)}
</div>
</div>
</Card>
</ConfigProvider>
);

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@ import { ArrowLeftOutlined, FilePdfOutlined, FileImageOutlined, DownloadOutlined
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
import { getBrandById } from '../../../api/master-brand';
import {
downloadFile,
getFile,
getFileUrl,
getFolderFromFileType,
downloadFile,
getFile,
getFileUrl,
getFolderFromFileType,
} from '../../../api/file-uploads';
const { Title } = Typography;
@@ -412,7 +412,7 @@ const ViewFilePage = () => {
</Space>
</div>
{/* File type indicator */}
<div style={{ marginBottom: '16px' }}>
<div style={{
display: 'inline-block',
@@ -429,7 +429,7 @@ const ViewFilePage = () => {
</div>
<div style={{ position: 'relative' }}>
{/* Overlay with blur effect during loading */}
{loading && (
<div style={{
position: 'absolute',

View File

@@ -8,6 +8,7 @@ const BrandForm = ({
onValuesChange,
isEdit = false,
brandInfo = null,
readOnly = false,
}) => {
const isActive = Form.useWatch('is_active', form) ?? true;
@@ -38,6 +39,7 @@ const BrandForm = ({
<Form.Item name="is_active" valuePropName="checked" noStyle>
<Switch
style={{ backgroundColor: isActive ? '#23A55A' : '#bfbfbf' }}
disabled={readOnly}
/>
</Form.Item>
<Text style={{ marginLeft: 8 }}>
@@ -61,18 +63,18 @@ const BrandForm = ({
<Form.Item
label="Brand Name"
name="brand_name"
rules={[{ required: true, message: 'Brand Name wajib diisi!' }]}
rules={[{ required: !readOnly, message: 'Brand Name wajib diisi!' }]}
>
<Input />
<Input disabled={readOnly} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="Manufacturer"
name="brand_manufacture"
rules={[{ required: true, message: 'Manufacturer wajib diisi!' }]}
rules={[{ required: !readOnly, message: 'Manufacturer wajib diisi!' }]}
>
<Input placeholder="Enter Manufacturer" />
<Input placeholder="Enter Manufacturer" disabled={readOnly} />
</Form.Item>
</Col>
</Row>
@@ -80,12 +82,12 @@ const BrandForm = ({
<Row gutter={16}>
<Col span={12}>
<Form.Item label="Brand Type" name="brand_type">
<Input placeholder="Enter Brand Type (Optional)" />
<Input placeholder="Enter Brand Type (Optional)" disabled={readOnly} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="Model" name="brand_model">
<Input placeholder="Enter Model (Optional)" />
<Input placeholder="Enter Model (Optional)" disabled={readOnly} />
</Form.Item>
</Col>
</Row>

View File

@@ -140,7 +140,7 @@ const CustomSparepartCard = ({
onClick={handleCardClick}
>
<div style={{ display: 'flex', height: '100%' }}>
{/* Image Section */}
<div style={{
width: size === 'small' ? '90px' : '110px',
flexShrink: 0,
@@ -192,7 +192,7 @@ const CustomSparepartCard = ({
/>
</div>
{/* Selection Indicator */}
{isSelected && (
<div
style={{
@@ -215,7 +215,7 @@ const CustomSparepartCard = ({
)}
</div>
{/* Content Section */}
<div style={{
flex: 1,
padding: size === 'small' ? '12px' : '16px',
@@ -242,9 +242,9 @@ const CustomSparepartCard = ({
{sparepart.sparepart_name || sparepart.name || 'Unnamed'}
</Title>
{/* Stock and Quantity Information */}
<div style={{ marginBottom: size === 'small' ? '6px' : '8px' }}>
{/* Stock Status Tag */}
<div style={{ marginBottom: '4px' }}>
<Tag
color={sparepart.sparepart_stok === 'Available' ? 'green' : 'red'}
@@ -259,7 +259,7 @@ const CustomSparepartCard = ({
</Tag>
</div>
{/* Quantity */}
<div style={{ display: 'flex', alignItems: 'center' }}>
<Text
style={{
@@ -330,7 +330,7 @@ const CustomSparepartCard = ({
</div>
</Card>
{/* Preview Modal */}
<Modal
title="Sparepart Details"
open={previewModalVisible}
@@ -374,7 +374,7 @@ const CustomSparepartCard = ({
/>
</div>
{/* Item Type Tag */}
{sparepart.sparepart_item_type && (
<div style={{ marginBottom: '12px' }}>
<Tag color="blue" style={{ fontSize: '14px', padding: '4px 12px' }}>
@@ -383,7 +383,7 @@ const CustomSparepartCard = ({
</div>
)}
{/* Stock Status and Quantity */}
<div style={{
textAlign: 'left',
background: '#f8f9fa',
@@ -412,12 +412,12 @@ const CustomSparepartCard = ({
<Col span={14}>
<div>
{/* Sparepart Name */}
<Title level={3} style={{ marginBottom: '20px', color: '#262626' }}>
{sparepart.sparepart_name || 'Unnamed'}
</Title>
{/* Basic Information */}
<div style={{ marginBottom: '24px' }}>
<Row gutter={[16, 12]}>
<Col span={24}>
@@ -480,7 +480,7 @@ const CustomSparepartCard = ({
</Row>
</div>
{/* Timeline Information */}
{sparepart.created_at && (
<div>
<Row gutter={16}>

View File

@@ -232,15 +232,16 @@ const ErrorCodeForm = ({
<Form.Item
label="Error Name"
name="error_code_name"
rules={[{ required: true, message: 'Error name wajib diisi!' }]}
rules={[{ required: !isErrorCodeFormReadOnly, message: 'Error name wajib diisi!' }]}
>
<Input placeholder="Enter error name" />
<Input placeholder="Enter error name" disabled={isErrorCodeFormReadOnly} />
</Form.Item>
<Form.Item label="Description" name="error_code_description">
<Input.TextArea
placeholder="Enter error description"
rows={3}
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item>
@@ -260,7 +261,9 @@ const ErrorCodeForm = ({
height: '40px',
border: '1px solid #d9d9d9',
borderRadius: 4,
cursor: isErrorCodeFormReadOnly ? 'not-allowed' : 'pointer',
}}
disabled={isErrorCodeFormReadOnly}
/>
</Form.Item>

View File

@@ -147,7 +147,7 @@ const FileUploadHandler = ({
}
}
if (actualPath) {
let fileObject;
@@ -394,9 +394,7 @@ const FileUploadHandler = ({
</Upload>
)}
{/* renderExistingFile() is disabled because SolutionField.jsx already handles file card rendering */}
{/* This prevents duplicate card rendering */}
{/* {renderExistingFile()} */}
{showPreview && (
<Modal

View File

@@ -142,7 +142,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) {
title: 'Konfirmasi',
message: 'Apakah anda yakin hapus data "' + param.brand_name + '" ?',
onConfirm: () => handleDelete(param.brand_id, param.brand_name),
onCancel: () => {},
onCancel: () => { },
});
};
@@ -156,7 +156,7 @@ const ListBrandDevice = memo(function ListBrandDevice(props) {
title: 'Berhasil',
message: `Brand ${brand_name} deleted successfully.`,
});
doFilter(); // Refresh data
doFilter();
} else {
NotifAlert({
icon: 'error',

View File

@@ -14,7 +14,9 @@ const ListErrorCode = ({
searchText,
onSearchChange,
onSearch,
onSearchClear
onSearchClear,
isReadOnly = false,
errorCodes: propErrorCodes = null
}) => {
const [errorCodes, setErrorCodes] = useState([]);
const [loading, setLoading] = useState(false);
@@ -25,7 +27,7 @@ const ListErrorCode = ({
total_page: 0,
});
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 15; // Fixed limit 15 items per page
const pageSize = 15;
const queryParams = useMemo(() => {
const params = new URLSearchParams();
@@ -79,8 +81,15 @@ const ListErrorCode = ({
};
useEffect(() => {
fetchErrorCodes();
}, [brandId, queryParams, tempErrorCodes, trigerFilter]);
if (isReadOnly && propErrorCodes) {
setErrorCodes(propErrorCodes);
setLoading(false);
} else {
fetchErrorCodes();
}
}, [brandId, queryParams, tempErrorCodes, trigerFilter, isReadOnly, propErrorCodes]);
const handlePrevious = () => {
if (pagination.current_page > 1) {
@@ -117,7 +126,7 @@ const ListErrorCode = ({
title: 'Hapus Error Code',
message: `Apakah Anda yakin ingin menghapus error code ${item.error_code}?`,
onConfirm: () => performDelete(item),
onCancel: () => {},
onCancel: () => { },
confirmButtonText: 'Hapus'
});
}
@@ -125,7 +134,7 @@ const ListErrorCode = ({
const performDelete = async (item) => {
try {
// Additional validation
if (!item.error_code_id || item.error_code_id === 'undefined') {
NotifAlert({
icon: 'error',
@@ -144,7 +153,7 @@ const ListErrorCode = ({
return;
}
const response = await deleteErrorCode(item.brand_id, item.error_code_id);
const response = await deleteErrorCode(item.brand_id, item.error_code_id);
if (response && response.statusCode === 200) {
NotifOk({

View File

@@ -30,28 +30,15 @@ const SolutionFieldNew = ({
const fileUpload = Form.useWatch(['solution_items', fieldKey, 'fileUpload'], form);
const file = Form.useWatch(['solution_items', fieldKey, 'file'], form);
const getSolutionData = () => {
try {
return form.getFieldValue(['solution_items', fieldKey]) || {};
} catch (error) {
return {};
}
};
const nameValue = Form.useWatch(['solution_items', fieldKey, 'name'], form);
const typeValue = Form.useWatch(['solution_items', fieldKey, 'type'], form);
const textValue = Form.useWatch(['solution_items', fieldKey, 'text'], form);
const fileNameValue = Form.useWatch(['solution_items', fieldKey, 'fileName'], form);
const statusValue = Form.useWatch(['solution_items', fieldKey, 'status'], form) ?? true;
const pathSolution = Form.useWatch(['solution_items', fieldKey, 'path_solution'], form);
const [deleteCounter, setDeleteCounter] = useState(0);
// Reset internal state when form is reset to initial values
React.useEffect(() => {
// Check if form is in initial/reset state (name is empty)
if (!nameValue || nameValue === '') {
setCurrentFile(null);
setIsDeleted(false);

View File

@@ -115,7 +115,7 @@ const SparepartSelect = ({
return (
<>
{/* Fixed Search Section */}
{!isReadOnly && (
<div style={{
marginBottom: 16,
@@ -156,7 +156,7 @@ const SparepartSelect = ({
</div>
)}
{/* Scrollable Selected Items Section */}
<div>
{selectedSpareparts.length > 0 ? (
<div>