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', borderTop: '1px solid #f0f0f0',
marginTop: '12px' marginTop: '12px'
}}> }}>
{/* Cancel Button - Only show when editing existing error code */}
{editingErrorCodeKey && ( {editingErrorCodeKey && (
<Button <Button
size="large" size="large"
@@ -1010,7 +1010,7 @@ const AddBrandDevice = () => {
</Button> </Button>
)} )}
{/* Save Button - Always show on right */}
<div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}> <div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}>
<Button <Button
type="primary" type="primary"
@@ -1123,7 +1123,7 @@ const AddBrandDevice = () => {
}; };
}, [isTemporaryBrand, temporaryBrandId, currentStep]); }, [isTemporaryBrand, temporaryBrandId, currentStep]);
return ( return (
<ConfigProvider <ConfigProvider
theme={{ theme={{
@@ -1142,79 +1142,79 @@ const AddBrandDevice = () => {
</Steps> </Steps>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
{loading && ( {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 <div
style={{ style={{
position: 'absolute', filter: loading ? 'blur(0.5px)' : 'none',
top: 0, transition: 'filter 0.3s ease',
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" /> {renderStepContent()}
</div> </div>
)}
<div
style={{
filter: loading ? 'blur(0.5px)' : 'none',
transition: 'filter 0.3s ease',
}}
>
{renderStepContent()}
</div> </div>
</div> <Divider />
<Divider /> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div>
<div> <Button onClick={handleCancel}>
<Button onClick={handleCancel}> Cancel
Cancel
</Button>
{currentStep === 1 && (
<Button
onClick={handlePrevStep}
style={{ marginLeft: 8 }}
>
Kembali ke Brand Info
</Button> </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>
<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> </Card>
</ConfigProvider> </ConfigProvider>
); );

View File

@@ -328,13 +328,13 @@ const EditBrandDevice = () => {
} }
}, [currentStep, id]); }, [currentStep, id]);
// Auto refresh error codes when returning from add/edit error code
useEffect(() => { useEffect(() => {
if (location.state?.refreshErrorCodes) { if (location.state?.refreshErrorCodes) {
// Trigger refresh of error codes list
setTrigerFilter(prev => !prev); setTrigerFilter(prev => !prev);
// Clear the state to prevent infinite refresh
const state = { ...location.state }; const state = { ...location.state };
delete state.refreshErrorCodes; delete state.refreshErrorCodes;
navigate(location.pathname + location.search, { navigate(location.pathname + location.search, {
@@ -345,7 +345,7 @@ const EditBrandDevice = () => {
}, [location, navigate]); }, [location, navigate]);
const addErrorCode = (newErrorCode) => { const addErrorCode = (newErrorCode) => {
// Generate unique tempId with timestamp and random number
const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const uniqueId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const errorCodeWithId = { const errorCodeWithId = {
...newErrorCode, ...newErrorCode,
@@ -566,13 +566,13 @@ const EditBrandDevice = () => {
message: editingErrorCodeKey ? 'Error code berhasil diupdate!' : 'Error code berhasil ditambahkan!', 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 => { setTempErrorCodes(prev => prev.filter(ec => {
// Keep existing error codes that weren't just saved
if (ec.status === 'existing' || ec.tempId.startsWith('existing_')) { if (ec.status === 'existing' || ec.tempId.startsWith('existing_')) {
return true; return true;
} }
// Remove the newly saved error code to prevent duplication
return ec.tempId !== editingErrorCodeKey; return ec.tempId !== editingErrorCodeKey;
})); }));
@@ -1182,7 +1182,7 @@ const EditBrandDevice = () => {
borderTop: '1px solid #f0f0f0', borderTop: '1px solid #f0f0f0',
marginTop: '12px' marginTop: '12px'
}}> }}>
{/* Cancel Button - Only show when editing existing error code */}
{editingErrorCodeKey && ( {editingErrorCodeKey && (
<Button <Button
size="large" size="large"
@@ -1210,7 +1210,7 @@ const EditBrandDevice = () => {
</Button> </Button>
)} )}
{/* Save Button - Always show on right */}
<div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}> <div style={{ marginLeft: editingErrorCodeKey ? '0' : 'auto' }}>
<Button <Button
type="primary" type="primary"
@@ -1266,45 +1266,45 @@ const EditBrandDevice = () => {
</Steps> </Steps>
{renderStepContent()} {renderStepContent()}
<Divider /> <Divider />
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div> <div>
{currentStep === 1 && ( {currentStep === 1 && (
<Button <Button
onClick={() => navigate(`/master/brand-device/edit/${id}?tab=brand`)} onClick={() => navigate(`/master/brand-device/edit/${id}?tab=brand`)}
style={{ marginLeft: 8 }} style={{ marginLeft: 8 }}
> >
Back to Brand Info Back to Brand Info
</Button> </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>
<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> </Card>
</ConfigProvider> </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 { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
import { getBrandById } from '../../../api/master-brand'; import { getBrandById } from '../../../api/master-brand';
import { import {
downloadFile, downloadFile,
getFile, getFile,
getFileUrl, getFileUrl,
getFolderFromFileType, getFolderFromFileType,
} from '../../../api/file-uploads'; } from '../../../api/file-uploads';
const { Title } = Typography; const { Title } = Typography;
@@ -412,7 +412,7 @@ const ViewFilePage = () => {
</Space> </Space>
</div> </div>
{/* File type indicator */}
<div style={{ marginBottom: '16px' }}> <div style={{ marginBottom: '16px' }}>
<div style={{ <div style={{
display: 'inline-block', display: 'inline-block',
@@ -429,7 +429,7 @@ const ViewFilePage = () => {
</div> </div>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
{/* Overlay with blur effect during loading */}
{loading && ( {loading && (
<div style={{ <div style={{
position: 'absolute', position: 'absolute',

View File

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

View File

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

View File

@@ -232,15 +232,16 @@ const ErrorCodeForm = ({
<Form.Item <Form.Item
label="Error Name" label="Error Name"
name="error_code_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>
<Form.Item label="Description" name="error_code_description"> <Form.Item label="Description" name="error_code_description">
<Input.TextArea <Input.TextArea
placeholder="Enter error description" placeholder="Enter error description"
rows={3} rows={3}
disabled={isErrorCodeFormReadOnly}
/> />
</Form.Item> </Form.Item>
@@ -260,7 +261,9 @@ const ErrorCodeForm = ({
height: '40px', height: '40px',
border: '1px solid #d9d9d9', border: '1px solid #d9d9d9',
borderRadius: 4, borderRadius: 4,
cursor: isErrorCodeFormReadOnly ? 'not-allowed' : 'pointer',
}} }}
disabled={isErrorCodeFormReadOnly}
/> />
</Form.Item> </Form.Item>

View File

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

View File

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

View File

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

View File

@@ -30,28 +30,15 @@ const SolutionFieldNew = ({
const fileUpload = Form.useWatch(['solution_items', fieldKey, 'fileUpload'], form); const fileUpload = Form.useWatch(['solution_items', fieldKey, 'fileUpload'], form);
const file = Form.useWatch(['solution_items', fieldKey, 'file'], 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 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 fileNameValue = Form.useWatch(['solution_items', fieldKey, 'fileName'], form);
const statusValue = Form.useWatch(['solution_items', fieldKey, 'status'], form) ?? true; const statusValue = Form.useWatch(['solution_items', fieldKey, 'status'], form) ?? true;
const pathSolution = Form.useWatch(['solution_items', fieldKey, 'path_solution'], form); const pathSolution = Form.useWatch(['solution_items', fieldKey, 'path_solution'], form);
const [deleteCounter, setDeleteCounter] = useState(0); const [deleteCounter, setDeleteCounter] = useState(0);
// Reset internal state when form is reset to initial values
React.useEffect(() => { React.useEffect(() => {
// Check if form is in initial/reset state (name is empty)
if (!nameValue || nameValue === '') { if (!nameValue || nameValue === '') {
setCurrentFile(null); setCurrentFile(null);
setIsDeleted(false); setIsDeleted(false);

View File

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