Files
cod-fe/src/pages/master/brandDevice/ViewBrandDevice.jsx

551 lines
25 KiB
JavaScript

import React, { memo, useEffect, useState } from 'react';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { Typography, Card, Row, Col, Tag, Button, Space, Descriptions, Divider, Table, Steps, Collapse, Switch, Skeleton, Spin, Modal } from 'antd';
import { ArrowLeftOutlined, EditOutlined, DeleteOutlined, FileTextOutlined, FilePdfOutlined, EyeOutlined } from '@ant-design/icons';
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
import { NotifConfirmDialog, NotifOk, NotifAlert } from '../../../components/Global/ToastNotif';
import { getBrandById, deleteBrand } from '../../../api/master-brand';
import TableList from '../../../components/Global/TableList';
const { Title, Text } = Typography;
const { Step } = Steps;
const { Panel } = Collapse;
const ViewBrandDevice = () => {
const navigate = useNavigate();
const { id } = useParams();
const location = useLocation();
const { setBreadcrumbItems } = useBreadcrumb();
const [brandData, setBrandData] = useState(null);
const [loading, setLoading] = useState(true);
const [currentStep, setCurrentStep] = useState(0);
const [errorCodesTriger, setErrorCodesTriger] = useState(0);
useEffect(() => {
const fetchBrandData = async () => {
const token = localStorage.getItem('token');
if (token) {
const savedPhase = location.state?.phase || localStorage.getItem(`brand_device_${id}_last_phase`);
if (savedPhase) {
setCurrentStep(parseInt(savedPhase));
localStorage.removeItem(`brand_device_${id}_last_phase`);
}
setBreadcrumbItems([
{ title: <Text strong style={{ fontSize: '14px' }}> Master</Text> },
{
title: <Text strong style={{ fontSize: '14px' }} onClick={() => navigate('/master/brand-device')}>Brand Device</Text>
},
{ title: <Text strong style={{ fontSize: '14px' }}>View Brand Device</Text> }
]);
try {
setLoading(true);
const response = await getBrandById(id);
if (response && response.statusCode === 200) {
setBrandData(response.data);
setErrorCodesTriger(prev => prev + 1);
} else {
NotifAlert({
icon: 'error',
title: 'Error',
message: response?.message || 'Failed to fetch brand device data',
});
}
} catch (error) {
console.error('Fetch Brand Device Error:', error);
NotifAlert({
icon: 'error',
title: 'Error',
message: error.message || 'Failed to fetch brand device data',
});
} finally {
setLoading(false);
}
} else {
navigate('/signin');
}
};
fetchBrandData();
}, [id, setBreadcrumbItems, navigate, location.state]);
// const handleEdit = () => {
// navigate(`/master/brand-device/edit/${id}`);
// };
// const handleDelete = () => {
// NotifConfirmDialog({
// icon: 'question',
// title: 'Konfirmasi Hapus',
// message: `Brand Device "${brandData?.brand_name}" akan dihapus?`,
// onConfirm: async () => {
// try {
// const response = await deleteBrand(id);
// if (response && response.statusCode === 200) {
// NotifOk({
// icon: 'success',
// title: 'Berhasil',
// message: response.message || 'Brand Device berhasil dihapus.',
// });
// navigate('/master/brand-device');
// } else {
// NotifAlert({
// icon: 'error',
// title: 'Gagal',
// message: response?.message || 'Gagal menghapus Brand Device',
// });
// }
// } catch (error) {
// console.error('Delete Brand Device Error:', error);
// NotifAlert({
// icon: 'error',
// title: 'Error',
// message: error.message || 'Gagal menghapus Brand Device',
// });
// }
// },
// onCancel: () => {},
// });
// };
// Fungsi untuk membuka file viewer di halaman baru
const handleFileView = (fileName, fileType) => {
console.log('handleFileView called with:', { fileName, fileType });
// Save current phase before navigating to file viewer
localStorage.setItem(`brand_device_${id}_last_phase`, currentStep.toString());
// Extract only the filename without folder prefix
let actualFileName = fileName;
if (fileName && fileName.includes('/')) {
const parts = fileName.split('/');
actualFileName = parts[parts.length - 1]; // Get the last part (actual filename)
}
console.log('Processed filename:', { original: fileName, actual: actualFileName });
const encodedFileName = encodeURIComponent(actualFileName);
const fileTypeParam = fileType === 'image' ? 'image' : 'pdf';
const navigationPath = `/master/brand-device/view/${id}/files/${fileTypeParam}/${encodedFileName}`;
console.log('Navigating to:', navigationPath);
navigate(navigationPath);
};
// if (loading) {
// return (
// <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
// <Spin size="large" />
// </div>
// );
// }
if (!brandData && !loading) {
return <div>Brand Device not found</div>;
}
// Error code table columns configuration
const errorCodeColumns = [
{
title: 'No',
key: 'no',
width: '5%',
align: 'center',
render: (_, __, index) => index + 1,
},
{
title: 'Error Code',
dataIndex: 'error_code',
key: 'error_code',
width: '15%',
render: (text) => text || '-',
},
{
title: 'Error Code Name',
dataIndex: 'error_code_name',
key: 'error_code_name',
width: '20%',
render: (text) => text || '-',
},
{
title: 'Description',
dataIndex: 'error_code_description',
key: 'error_code_description',
width: '25%',
render: (text) => text || '-',
},
{
title: 'Solutions',
dataIndex: 'solution',
key: 'solution',
width: '20%',
render: (solutions) => (
<div>
{solutions && solutions.length > 0 ? (
<div>
<Text type="secondary">{solutions.length} solution(s)</Text>
<div style={{ marginTop: 4 }}>
{solutions.slice(0, 2).map((sol, index) => (
<div key={index} style={{ fontSize: '12px', color: '#666' }}>
{sol.type_solution === 'text' ? (
<span> {sol.solution_name}</span>
) : (
<span> {sol.solution_name} ({sol.type_solution})</span>
)}
</div>
))}
{solutions.length > 2 && (
<div style={{ fontSize: '12px', color: '#999' }}>
...and {solutions.length - 2} more
</div>
)}
</div>
</div>
) : (
<Text type="secondary">No solutions</Text>
)}
</div>
)
},
{
title: 'Status',
dataIndex: 'is_active',
key: 'is_active',
width: '10%',
align: 'center',
render: (_, { is_active }) => (
<Tag color={is_active ? 'green' : 'red'}>
{is_active ? 'Active' : 'Inactive'}
</Tag>
),
},
{
title: 'Action',
key: 'action',
align: 'center',
width: '5%',
render: (_, record) => (
<Button
type="text"
icon={<EyeOutlined />}
onClick={() => {
// Show detailed view for this error code
Modal.info({
title: 'Error Code Details',
width: 800,
content: (
<div style={{ marginTop: 16 }}>
<Descriptions bordered column={1} size="small">
<Descriptions.Item label="Error Code">{record.error_code}</Descriptions.Item>
<Descriptions.Item label="Error Code Name">{record.error_code_name}</Descriptions.Item>
<Descriptions.Item label="Description">{record.error_code_description}</Descriptions.Item>
<Descriptions.Item label="Status">
<Tag color={record.is_active ? 'green' : 'red'}>
{record.is_active ? 'Active' : 'Inactive'}
</Tag>
</Descriptions.Item>
</Descriptions>
<Title level={5} style={{ marginTop: 24, marginBottom: 16 }}>
Solutions ({record.solution?.length || 0})
</Title>
{record.solution && record.solution.length > 0 ? (
<Row gutter={[16, 16]}>
{record.solution.map((solution) => (
<Col span={24} key={solution.brand_code_solution_id}>
<Card size="small">
<Row justify="space-between" align="middle">
<Col>
<Space>
{solution.type_solution === 'pdf' ? (
<FilePdfOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />
) : (
<FileTextOutlined style={{ color: '#1890ff', fontSize: '16px' }} />
)}
<Text strong>{solution.solution_name}</Text>
</Space>
</Col>
<Col>
<Tag color={solution.type_solution === 'pdf' ? 'red' : 'blue'}>
{solution.type_solution.toUpperCase()}
</Tag>
</Col>
</Row>
<div style={{ marginTop: 12 }}>
{solution.type_solution === 'text' ? (
<Text>{solution.text_solution}</Text>
) : (
<Space>
<Text type="secondary">File: {solution.path_document || solution.path_solution}</Text>
{solution.path_document && (
<Button
type="link"
size="small"
onClick={() => {
handleFileView(
(solution.path_document || solution.path_solution || solution.file_upload_name || solution.solution_name || 'Document')?.toString(),
solution.type_solution || 'pdf'
);
}}
>
View Document
</Button>
)}
</Space>
)}
</div>
</Card>
</Col>
))}
</Row>
) : (
<Text type="secondary">No solutions available</Text>
)}
</div>
),
});
}}
style={{ color: '#1890ff' }}
/>
),
},
];
// Mock data function for error codes
const getErrorCodesData = async () => {
const errorCodes = brandData?.error_code || [];
return {
data: errorCodes,
paging: {
current_page: 1,
current_limit: 10,
total_limit: errorCodes.length,
total_page: 1,
}
};
};
const renderStepContent = () => {
if (currentStep === 0) {
return (
<div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 16 }}>
<Text strong>Status</Text>
</div>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 24 }}>
<div style={{ width: 44, height: 24, backgroundColor: (brandData || {}).is_active ? '#23A55A' : '#bfbfbf', borderRadius: 12, marginRight: 8, position: 'relative' }}>
<div style={{
width: 20,
height: 20,
backgroundColor: 'white',
borderRadius: '50%',
position: 'absolute',
top: 2,
left: (brandData || {}).is_active ? 22 : 2,
transition: 'left 0.3s ease',
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
}}></div>
</div>
<Text>{(brandData || {}).is_active ? 'Running' : 'Offline'}</Text>
</div>
<div style={{ marginBottom: 16 }}>
<Text strong>Brand Code</Text>
</div>
<div style={{ marginBottom: 24 }}>
<div style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '6px',
backgroundColor: '#f5f5f5',
color: '#000000'
}}>
{brandData?.brand_code || (loading ? 'Loading...' : '-')}
</div>
</div>
<Row gutter={16}>
<Col span={12}>
<div style={{ marginBottom: 16 }}>
<Text strong>Brand Name</Text>
</div>
<div style={{ marginBottom: 24 }}>
<div style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '6px',
backgroundColor: 'white',
minHeight: '32px'
}}>
{brandData?.brand_name || (loading ? 'Loading...' : '-')}
</div>
</div>
</Col>
<Col span={12}>
<div style={{ marginBottom: 16 }}>
<Text strong>Manufacture</Text>
</div>
<div style={{ marginBottom: 24 }}>
<div style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '6px',
backgroundColor: 'white',
minHeight: '32px'
}}>
{brandData?.brand_manufacture || (loading ? 'Loading...' : '-')}
</div>
</div>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<div style={{ marginBottom: 16 }}>
<Text strong>Brand Type</Text>
</div>
<div style={{ marginBottom: 24 }}>
<div style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '6px',
backgroundColor: 'white',
minHeight: '32px'
}}>
{brandData?.brand_type || (loading ? 'Loading...' : '-')}
</div>
</div>
</Col>
<Col span={12}>
<div style={{ marginBottom: 16 }}>
<Text strong>Model</Text>
</div>
<div style={{ marginBottom: 24 }}>
<div style={{
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '6px',
backgroundColor: 'white',
minHeight: '32px'
}}>
{brandData?.brand_model || (loading ? 'Loading...' : '-')}
</div>
</div>
</Col>
</Row>
</div>
);
}
if (currentStep === 1) {
const errorCodesCount = loading ? 3 : (brandData?.error_code?.length || 0);
return (
<div>
<Title level={5} style={{ marginBottom: 16 }}>
Error Codes ({errorCodesCount})
</Title>
{errorCodesCount > 0 ? (
<TableList
mobile={false}
cardColor={'#42AAFF'}
header={'error_code'}
getData={getErrorCodesData}
queryParams={{}}
columns={errorCodeColumns}
triger={errorCodesTriger}
firstLoad={false}
/>
) : (
!loading && <Text type="secondary">No error codes available</Text>
)}
</div>
);
}
return null;
};
return (
<React.Fragment>
<Card>
<Row justify="space-between" align="middle">
<Col>
<Title level={4} style={{ margin: 0 }}>View Brand Device</Title>
</Col>
<Col>
<Space>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/master/brand-device')}
>
Kembali
</Button>
</Space>
</Col>
</Row>
<Divider />
<Steps current={currentStep} style={{ marginBottom: 24 }}>
<Step title="Brand Device Details" />
<Step title="Error Codes & Solutions" />
</Steps>
{/* Content area with blur overlay during loading */}
<div style={{ position: 'relative', marginTop: 24 }}>
{/* Overlay with blur effect during loading - only on content area */}
{loading && (
<div style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(255, 255, 255, 0.6)',
backdropFilter: 'blur(0.8px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 5,
borderRadius: '8px'
}}>
<Spin size="large" />
</div>
)}
<div style={{ filter: loading ? 'blur(0.5px)' : 'none', transition: 'filter 0.3s ease' }}>
{renderStepContent()}
</div>
</div>
<Divider />
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
{currentStep > 0 && (
<Button
onClick={() => setCurrentStep(currentStep - 1)}
style={{ marginRight: 8 }}
>
Kembali
</Button>
)}
{currentStep < 1 && (
<Button
type="primary"
onClick={() => setCurrentStep(currentStep + 1)}
style={{ backgroundColor: '#23a55a', borderColor: '#23a55a' }}
>
Lanjut
</Button>
)}
</div>
</Card>
</React.Fragment>
);
};
export default ViewBrandDevice;