Compare commits
23 Commits
98057beb0f
...
lavoce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0fe3aaca1 | ||
|
|
e8da716e8f | ||
|
|
0ffcf4c3c0 | ||
|
|
c2163cec5e | ||
|
|
d5866ceae4 | ||
| 6fdb259246 | |||
| 0aad43c751 | |||
| d988d47e30 | |||
|
|
e08eaaa43e | ||
|
|
f6ca54f5b4 | ||
|
|
a9b8053bd8 | ||
| 600c101c68 | |||
|
|
14a6884f43 | ||
|
|
8e151ffe0b | ||
| 8f64843613 | |||
|
|
fe8f6d1002 | ||
|
|
5281e288a9 | ||
|
|
4ed05cc640 | ||
|
|
14e97fead2 | ||
|
|
0935d7c9f5 | ||
|
|
3266641f81 | ||
|
|
739c55c0bc | ||
|
|
5b4485d20d |
@@ -46,6 +46,15 @@ const getNotificationLogByNotificationId = async (notificationId) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// update is_read status
|
||||
const updateIsRead = async (notificationId) => {
|
||||
const response = await SendRequest({
|
||||
method: 'put',
|
||||
prefix: `notification/${notificationId}`,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Resend notification to specific user
|
||||
const resendNotificationToUser = async (notificationId, userId) => {
|
||||
const response = await SendRequest({
|
||||
@@ -55,11 +64,42 @@ const resendNotificationToUser = async (notificationId, userId) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Resend Chat by User
|
||||
const resendChatByUser = async (notificationId, userPhone) => {
|
||||
const response = await SendRequest({
|
||||
method: 'post',
|
||||
prefix: `notification-user/resend/${notificationId}/${userPhone}`,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Resend Chat All User
|
||||
const resendChatAllUser = async (notificationId) => {
|
||||
const response = await SendRequest({
|
||||
method: 'post',
|
||||
prefix: `notification/resend/${notificationId}`,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Searching
|
||||
const searchData = async (queryParams) => {
|
||||
const response = await SendRequest({
|
||||
method: 'get',
|
||||
prefix: `notification?criteria=${queryParams}`,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export {
|
||||
getAllNotification,
|
||||
getNotificationById,
|
||||
getNotificationDetail,
|
||||
createNotificationLog,
|
||||
getNotificationLogByNotificationId,
|
||||
updateIsRead,
|
||||
resendNotificationToUser,
|
||||
resendChatByUser,
|
||||
resendChatAllUser,
|
||||
searchData,
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function RedirectWa() {
|
||||
|
||||
console.log('tes', response);
|
||||
|
||||
const tokenResult = JSON.stringify(response.data?.accessToken);
|
||||
const tokenResult = JSON.stringify(response.data?.data?.accessToken);
|
||||
|
||||
sessionStorage.setItem('token_redirect', tokenResult);
|
||||
response.data.auth = true;
|
||||
|
||||
@@ -13,13 +13,18 @@ import {
|
||||
Space,
|
||||
ConfigProvider,
|
||||
} from 'antd';
|
||||
import {
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||
import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
|
||||
import { getBrandById, createBrand, createErrorCode, getErrorCodesByBrandId, updateErrorCode, deleteErrorCode, deleteBrand } from '../../../api/master-brand';
|
||||
import {
|
||||
getBrandById,
|
||||
createBrand,
|
||||
createErrorCode,
|
||||
getErrorCodesByBrandId,
|
||||
updateErrorCode,
|
||||
deleteErrorCode,
|
||||
deleteBrand,
|
||||
} from '../../../api/master-brand';
|
||||
import BrandForm from './component/BrandForm';
|
||||
import ErrorCodeForm from './component/ErrorCodeForm';
|
||||
import SolutionForm from './component/SolutionForm';
|
||||
@@ -42,7 +47,9 @@ const AddBrandDevice = () => {
|
||||
const [selectedSparepartIds, setSelectedSparepartIds] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const tab = searchParams.get('tab');
|
||||
const [currentStep, setCurrentStep] = useState(tab === 'error-codes' ? 1 : (location.state?.phase || 0));
|
||||
const [currentStep, setCurrentStep] = useState(
|
||||
tab === 'error-codes' ? 1 : location.state?.phase || 0
|
||||
);
|
||||
const [editingErrorCodeKey, setEditingErrorCodeKey] = useState(null);
|
||||
const [isErrorCodeFormReadOnly, setIsErrorCodeFormReadOnly] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
@@ -68,7 +75,7 @@ const AddBrandDevice = () => {
|
||||
const values = solutionForm.getFieldsValue(true);
|
||||
const solutions = [];
|
||||
|
||||
solutionFields.forEach(fieldKey => {
|
||||
solutionFields.forEach((fieldKey) => {
|
||||
let solution = null;
|
||||
|
||||
if (values.solution_items && values.solution_items[fieldKey]) {
|
||||
@@ -85,9 +92,16 @@ const AddBrandDevice = () => {
|
||||
if (solutionType === 'text') {
|
||||
isValid = solution.text && solution.text.trim() !== '';
|
||||
} else if (solutionType === 'file') {
|
||||
const hasPathSolution = solution.path_solution && solution.path_solution.trim() !== '';
|
||||
const hasFileUpload = (solution.fileUpload && typeof solution.fileUpload === 'object' && Object.keys(solution.fileUpload).length > 0);
|
||||
const hasFile = (solution.file && typeof solution.file === 'object' && Object.keys(solution.file).length > 0);
|
||||
const hasPathSolution =
|
||||
solution.path_solution && solution.path_solution.trim() !== '';
|
||||
const hasFileUpload =
|
||||
solution.fileUpload &&
|
||||
typeof solution.fileUpload === 'object' &&
|
||||
Object.keys(solution.fileUpload).length > 0;
|
||||
const hasFile =
|
||||
solution.file &&
|
||||
typeof solution.file === 'object' &&
|
||||
Object.keys(solution.file).length > 0;
|
||||
isValid = hasPathSolution || hasFileUpload || hasFile;
|
||||
}
|
||||
|
||||
@@ -118,9 +132,9 @@ const AddBrandDevice = () => {
|
||||
text: '',
|
||||
status: true,
|
||||
file: null,
|
||||
fileUpload: null
|
||||
}
|
||||
}
|
||||
fileUpload: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
@@ -128,7 +142,6 @@ const AddBrandDevice = () => {
|
||||
};
|
||||
|
||||
const setSolutionsForExistingRecord = (solutions, targetForm) => {
|
||||
|
||||
if (!targetForm || !solutions || solutions.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -153,11 +166,14 @@ const AddBrandDevice = () => {
|
||||
fileObject = {
|
||||
uploadPath: solution.path_solution || solution.path_document,
|
||||
path_solution: solution.path_solution || solution.path_document,
|
||||
name: solution.file_upload_name || (solution.path_solution || solution.path_document).split('/').pop() || 'File',
|
||||
name:
|
||||
solution.file_upload_name ||
|
||||
(solution.path_solution || solution.path_document).split('/').pop() ||
|
||||
'File',
|
||||
type_solution: solution.type_solution,
|
||||
isExisting: true,
|
||||
size: 0,
|
||||
url: solution.path_solution || solution.path_document
|
||||
url: solution.path_solution || solution.path_document,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,7 +186,7 @@ const AddBrandDevice = () => {
|
||||
file: fileObject,
|
||||
fileUpload: fileObject,
|
||||
path_solution: solution.path_solution || solution.path_document || null,
|
||||
fileName: solution.file_upload_name || null
|
||||
fileName: solution.file_upload_name || null,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -180,28 +196,35 @@ const AddBrandDevice = () => {
|
||||
|
||||
setSolutionStatuses(newSolutionStatuses);
|
||||
|
||||
|
||||
targetForm.resetFields();
|
||||
|
||||
setTimeout(() => {
|
||||
targetForm.setFieldsValue({
|
||||
solution_items: solutionItems
|
||||
solution_items: solutionItems,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
Object.keys(solutionItems).forEach(key => {
|
||||
Object.keys(solutionItems).forEach((key) => {
|
||||
const solution = solutionItems[key];
|
||||
targetForm.setFieldValue(['solution_items', key, 'name'], solution.name);
|
||||
targetForm.setFieldValue(['solution_items', key, 'type'], solution.type);
|
||||
targetForm.setFieldValue(['solution_items', key, 'text'], solution.text);
|
||||
targetForm.setFieldValue(['solution_items', key, 'file'], solution.file);
|
||||
targetForm.setFieldValue(['solution_items', key, 'fileUpload'], solution.fileUpload);
|
||||
targetForm.setFieldValue(
|
||||
['solution_items', key, 'fileUpload'],
|
||||
solution.fileUpload
|
||||
);
|
||||
targetForm.setFieldValue(['solution_items', key, 'status'], solution.status);
|
||||
targetForm.setFieldValue(['solution_items', key, 'path_solution'], solution.path_solution);
|
||||
targetForm.setFieldValue(['solution_items', key, 'fileName'], solution.fileName);
|
||||
targetForm.setFieldValue(
|
||||
['solution_items', key, 'path_solution'],
|
||||
solution.path_solution
|
||||
);
|
||||
targetForm.setFieldValue(
|
||||
['solution_items', key, 'fileName'],
|
||||
solution.fileName
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
const finalValues = targetForm.getFieldsValue();
|
||||
}, 100);
|
||||
}, 100);
|
||||
@@ -209,14 +232,14 @@ const AddBrandDevice = () => {
|
||||
|
||||
const handleAddSolutionField = () => {
|
||||
const newKey = Math.max(...solutionFields, 0) + 1;
|
||||
setSolutionFields(prev => [...prev, newKey]);
|
||||
setSolutionTypes(prev => ({ ...prev, [newKey]: 'text' }));
|
||||
setSolutionStatuses(prev => ({ ...prev, [newKey]: true }));
|
||||
setSolutionFields((prev) => [...prev, newKey]);
|
||||
setSolutionTypes((prev) => ({ ...prev, [newKey]: 'text' }));
|
||||
setSolutionStatuses((prev) => ({ ...prev, [newKey]: true }));
|
||||
};
|
||||
|
||||
const handleRemoveSolutionField = (fieldKey) => {
|
||||
if (solutionFields.length > 1) {
|
||||
setSolutionFields(prev => prev.filter(key => key !== fieldKey));
|
||||
setSolutionFields((prev) => prev.filter((key) => key !== fieldKey));
|
||||
const newTypes = { ...solutionTypes };
|
||||
const newStatuses = { ...solutionStatuses };
|
||||
delete newTypes[fieldKey];
|
||||
@@ -233,7 +256,7 @@ const AddBrandDevice = () => {
|
||||
};
|
||||
|
||||
const handleSolutionTypeChange = (fieldKey, type) => {
|
||||
setSolutionTypes(prev => ({ ...prev, [fieldKey]: type }));
|
||||
setSolutionTypes((prev) => ({ ...prev, [fieldKey]: type }));
|
||||
|
||||
if (type === 'file') {
|
||||
solutionForm.setFieldValue(['solution_items', fieldKey, 'text'], '');
|
||||
@@ -246,7 +269,7 @@ const AddBrandDevice = () => {
|
||||
};
|
||||
|
||||
const handleSolutionStatusChange = (fieldKey, status) => {
|
||||
setSolutionStatuses(prev => ({ ...prev, [fieldKey]: status }));
|
||||
setSolutionStatuses((prev) => ({ ...prev, [fieldKey]: status }));
|
||||
};
|
||||
|
||||
const handleNextStep = async () => {
|
||||
@@ -259,7 +282,7 @@ const AddBrandDevice = () => {
|
||||
brand_type: brandValues.brand_type || '',
|
||||
brand_manufacture: brandValues.brand_manufacture || '',
|
||||
brand_model: brandValues.brand_model || '',
|
||||
is_active: brandValues.is_active !== undefined ? brandValues.is_active : true
|
||||
is_active: brandValues.is_active !== undefined ? brandValues.is_active : true,
|
||||
};
|
||||
|
||||
const response = await createBrand(brandApiData);
|
||||
@@ -268,7 +291,7 @@ const AddBrandDevice = () => {
|
||||
const newBrandInfo = {
|
||||
...brandValues,
|
||||
brand_id: response.data.brand_id,
|
||||
brand_code: response.data.brand_code
|
||||
brand_code: response.data.brand_code,
|
||||
};
|
||||
setBrandInfo(newBrandInfo);
|
||||
setTemporaryBrandId(response.data.brand_id);
|
||||
@@ -307,8 +330,7 @@ const AddBrandDevice = () => {
|
||||
if (isTemporaryBrand && temporaryBrandId) {
|
||||
try {
|
||||
await deleteBrand(temporaryBrandId);
|
||||
} catch (error) {
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
navigate('/master/brand-device');
|
||||
};
|
||||
@@ -360,8 +382,6 @@ const AddBrandDevice = () => {
|
||||
setTrigerFilter((prev) => !prev);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const resetErrorCodeForm = () => {
|
||||
errorCodeForm.resetFields();
|
||||
errorCodeForm.setFieldsValue({
|
||||
@@ -391,16 +411,16 @@ const AddBrandDevice = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!solutionData || solutionData.length === 0) {
|
||||
NotifAlert({
|
||||
icon: 'warning',
|
||||
title: 'Perhatian',
|
||||
message: 'Setiap error code harus memiliki minimal 1 solution!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// if (!solutionData || solutionData.length === 0) {
|
||||
// NotifAlert({
|
||||
// icon: 'warning',
|
||||
// title: 'Perhatian',
|
||||
// message: 'Setiap error code harus memiliki minimal 1 solution!',
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
const formattedSolutions = solutionData.map(solution => {
|
||||
const formattedSolutions = solutionData.map((solution) => {
|
||||
const solutionType = solution.type || 'text';
|
||||
|
||||
let typeSolution = solutionType === 'text' ? 'text' : 'image';
|
||||
@@ -422,7 +442,11 @@ const AddBrandDevice = () => {
|
||||
} else {
|
||||
formattedSolution.text_solution = '';
|
||||
|
||||
formattedSolution.path_solution = solution.path_solution || solution.file?.uploadPath || solution.fileUpload?.uploadPath || '';
|
||||
formattedSolution.path_solution =
|
||||
solution.path_solution ||
|
||||
solution.file?.uploadPath ||
|
||||
solution.fileUpload?.uploadPath ||
|
||||
'';
|
||||
}
|
||||
|
||||
if (formattedSolution.brand_code_solution_id) {
|
||||
@@ -440,7 +464,7 @@ const AddBrandDevice = () => {
|
||||
path_icon: errorCodeIcon?.uploadPath || '',
|
||||
is_active: errorCodeValues.status === undefined ? true : errorCodeValues.status,
|
||||
solution: formattedSolutions,
|
||||
spareparts: selectedSparepartIds || []
|
||||
spareparts: selectedSparepartIds || [],
|
||||
};
|
||||
|
||||
let response;
|
||||
@@ -456,11 +480,13 @@ const AddBrandDevice = () => {
|
||||
NotifOk({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: editingErrorCodeKey ? 'Error code berhasil diupdate!' : 'Error code berhasil ditambahkan!',
|
||||
message: editingErrorCodeKey
|
||||
? 'Error code berhasil diupdate!'
|
||||
: 'Error code berhasil ditambahkan!',
|
||||
});
|
||||
|
||||
resetErrorCodeForm();
|
||||
setTrigerFilter(prev => !prev);
|
||||
setTrigerFilter((prev) => !prev);
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
@@ -479,12 +505,10 @@ const AddBrandDevice = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleErrorCodeIconRemove = () => {
|
||||
setErrorCodeIcon(null);
|
||||
};
|
||||
|
||||
|
||||
const handleFinish = async () => {
|
||||
setConfirmLoading(true);
|
||||
try {
|
||||
@@ -506,10 +530,10 @@ const AddBrandDevice = () => {
|
||||
const response = await getErrorCodesByBrandId(brandInfo.brand_id, queryParams);
|
||||
|
||||
if (response && response.statusCode === 200 && response.data) {
|
||||
const freshErrorCodes = response.data.map(ec => ({
|
||||
const freshErrorCodes = response.data.map((ec) => ({
|
||||
...ec,
|
||||
tempId: `existing_${ec.error_code_id}`,
|
||||
status: 'existing'
|
||||
status: 'existing',
|
||||
}));
|
||||
setApiErrorCodes(freshErrorCodes);
|
||||
|
||||
@@ -517,7 +541,8 @@ const AddBrandDevice = () => {
|
||||
NotifAlert({
|
||||
icon: 'warning',
|
||||
title: 'Perhatian',
|
||||
message: 'Harap tambahkan minimal 1 error code sebelum menyelesaikan.',
|
||||
message:
|
||||
'Harap tambahkan minimal 1 error code sebelum menyelesaikan.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -526,7 +551,8 @@ const AddBrandDevice = () => {
|
||||
NotifAlert({
|
||||
icon: 'warning',
|
||||
title: 'Perhatian',
|
||||
message: 'Harap tambahkan minimal 1 error code sebelum menyelesaikan.',
|
||||
message:
|
||||
'Harap tambahkan minimal 1 error code sebelum menyelesaikan.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -591,11 +617,7 @@ const AddBrandDevice = () => {
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
)}
|
||||
<BrandForm
|
||||
form={brandForm}
|
||||
isEdit={false}
|
||||
brandInfo={brandInfo}
|
||||
/>
|
||||
<BrandForm form={brandForm} isEdit={false} brandInfo={brandInfo} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -635,31 +657,39 @@ const AddBrandDevice = () => {
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={16} lg={16}>
|
||||
<div style={{
|
||||
paddingLeft: '12px'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: '12px',
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
title={
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%'
|
||||
}}>
|
||||
<span style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '#262626',
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<span style={{
|
||||
width: '4px',
|
||||
height: '20px',
|
||||
backgroundColor: '#23A55A',
|
||||
borderRadius: '2px'
|
||||
}}></span>
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
color: '#262626',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: '4px',
|
||||
height: '20px',
|
||||
backgroundColor: '#23A55A',
|
||||
borderRadius: '2px',
|
||||
}}
|
||||
></span>
|
||||
Error Code Form
|
||||
</span>
|
||||
<Button
|
||||
@@ -675,43 +705,51 @@ const AddBrandDevice = () => {
|
||||
padding: '0 24px',
|
||||
fontWeight: '500',
|
||||
boxShadow: '0 2px 4px rgba(35, 165, 90, 0.2)',
|
||||
transition: 'all 0.3s ease'
|
||||
transition: 'all 0.3s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.boxShadow = '0 4px 8px rgba(35, 165, 90, 0.3)';
|
||||
e.target.style.boxShadow =
|
||||
'0 4px 8px rgba(35, 165, 90, 0.3)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.target.style.boxShadow = '0 2px 4px rgba(35, 165, 90, 0.2)';
|
||||
e.target.style.boxShadow =
|
||||
'0 2px 4px rgba(35, 165, 90, 0.2)';
|
||||
}}
|
||||
>
|
||||
{editingErrorCodeKey ? 'Update Error Code' : 'Save Error Code'}
|
||||
{editingErrorCodeKey
|
||||
? 'Update Error Code'
|
||||
: 'Save Error Code'}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
width: '100%',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
||||
borderRadius: '12px'
|
||||
borderRadius: '12px',
|
||||
}}
|
||||
styles={{
|
||||
body: { padding: '16px 24px 12px 24px' },
|
||||
header: {
|
||||
padding: '16px 24px',
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
backgroundColor: '#fafafa'
|
||||
}
|
||||
backgroundColor: '#fafafa',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<div style={{
|
||||
padding: '16px',
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#ffffff',
|
||||
marginBottom: '0',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
||||
}}>
|
||||
<div
|
||||
style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#ffffff',
|
||||
marginBottom: '0',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
|
||||
}}
|
||||
>
|
||||
<ErrorCodeForm
|
||||
errorCodeForm={errorCodeForm}
|
||||
isErrorCodeFormReadOnly={isErrorCodeFormReadOnly}
|
||||
@@ -724,29 +762,42 @@ const AddBrandDevice = () => {
|
||||
|
||||
<Row gutter={[20, 0]} style={{ marginTop: '0' }}>
|
||||
<Col xs={24} md={12} lg={12}>
|
||||
<div style={{
|
||||
padding: '16px',
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#ffffff',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '12px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '1px solid #f5f5f5'
|
||||
}}>
|
||||
<div style={{
|
||||
width: '3px',
|
||||
height: '16px',
|
||||
backgroundColor: '#1890ff',
|
||||
borderRadius: '2px'
|
||||
}}></div>
|
||||
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#ffffff',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '12px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '1px solid #f5f5f5',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '3px',
|
||||
height: '16px',
|
||||
backgroundColor: '#1890ff',
|
||||
borderRadius: '2px',
|
||||
}}
|
||||
></div>
|
||||
<h4
|
||||
style={{
|
||||
margin: 0,
|
||||
color: '#262626',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
}}
|
||||
>
|
||||
Solution
|
||||
</h4>
|
||||
</div>
|
||||
@@ -756,14 +807,23 @@ const AddBrandDevice = () => {
|
||||
solutionTypes={solutionTypes}
|
||||
solutionStatuses={solutionStatuses}
|
||||
onAddSolutionField={handleAddSolutionField}
|
||||
onRemoveSolutionField={handleRemoveSolutionField}
|
||||
onRemoveSolutionField={
|
||||
handleRemoveSolutionField
|
||||
}
|
||||
onSolutionTypeChange={handleSolutionTypeChange}
|
||||
onSolutionStatusChange={handleSolutionStatusChange}
|
||||
onSolutionFileUpload={(fileData) => {
|
||||
}}
|
||||
onSolutionStatusChange={
|
||||
handleSolutionStatusChange
|
||||
}
|
||||
onSolutionFileUpload={(fileData) => {}}
|
||||
onFileView={(fileData) => {
|
||||
if (fileData && (fileData.url || fileData.uploadPath)) {
|
||||
window.open(fileData.url || fileData.uploadPath, '_blank');
|
||||
if (
|
||||
fileData &&
|
||||
(fileData.url || fileData.uploadPath)
|
||||
) {
|
||||
window.open(
|
||||
fileData.url || fileData.uploadPath,
|
||||
'_blank'
|
||||
);
|
||||
}
|
||||
}}
|
||||
isReadOnly={false}
|
||||
@@ -772,40 +832,55 @@ const AddBrandDevice = () => {
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} md={12} lg={12}>
|
||||
<div style={{
|
||||
padding: '16px',
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#ffffff',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '12px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '1px solid #f5f5f5'
|
||||
}}>
|
||||
<div style={{
|
||||
width: '3px',
|
||||
height: '16px',
|
||||
backgroundColor: '#faad14',
|
||||
borderRadius: '2px'
|
||||
}}></div>
|
||||
<h4 style={{ margin: 0, color: '#262626', fontSize: '14px', fontWeight: '600' }}>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
border: '1px solid #f0f0f0',
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#ffffff',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginBottom: '12px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '1px solid #f5f5f5',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '3px',
|
||||
height: '16px',
|
||||
backgroundColor: '#faad14',
|
||||
borderRadius: '2px',
|
||||
}}
|
||||
></div>
|
||||
<h4
|
||||
style={{
|
||||
margin: 0,
|
||||
color: '#262626',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
}}
|
||||
>
|
||||
Sparepart Selection
|
||||
</h4>
|
||||
</div>
|
||||
<div style={{
|
||||
maxHeight: '45vh',
|
||||
overflow: 'auto',
|
||||
border: '1px solid #e8e8e8',
|
||||
borderRadius: '8px',
|
||||
padding: '12px',
|
||||
backgroundColor: '#fafafa'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
maxHeight: '45vh',
|
||||
overflow: 'auto',
|
||||
border: '1px solid #e8e8e8',
|
||||
borderRadius: '8px',
|
||||
padding: '12px',
|
||||
backgroundColor: '#fafafa',
|
||||
}}
|
||||
>
|
||||
<SparepartSelect
|
||||
selectedSparepartIds={selectedSparepartIds}
|
||||
onSparepartChange={setSelectedSparepartIds}
|
||||
@@ -816,15 +891,16 @@ const AddBrandDevice = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '16px 0 0 0',
|
||||
borderTop: '1px solid #f0f0f0',
|
||||
marginTop: '12px'
|
||||
}}>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '16px 0 0 0',
|
||||
borderTop: '1px solid #f0f0f0',
|
||||
marginTop: '12px',
|
||||
}}
|
||||
>
|
||||
{editingErrorCodeKey && (
|
||||
<Button
|
||||
size="large"
|
||||
@@ -837,7 +913,7 @@ const AddBrandDevice = () => {
|
||||
height: '40px',
|
||||
padding: '0 24px',
|
||||
fontWeight: '500',
|
||||
transition: 'all 0.3s ease'
|
||||
transition: 'all 0.3s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.borderColor = '#ff4d4f';
|
||||
@@ -871,7 +947,7 @@ const AddBrandDevice = () => {
|
||||
|
||||
setBreadcrumbItems([
|
||||
{
|
||||
title: <span style={{ fontSize: '14px', fontWeight: 'bold' }}>• Master</span>
|
||||
title: <span style={{ fontSize: '14px', fontWeight: 'bold' }}>• Master</span>,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
@@ -895,12 +971,11 @@ const AddBrandDevice = () => {
|
||||
if (location.state?.fromFileViewer && location.state.phase !== undefined) {
|
||||
setCurrentStep(location.state.phase);
|
||||
}
|
||||
|
||||
}, [setBreadcrumbItems, navigate, searchParams, location.state]);
|
||||
|
||||
useEffect(() => {
|
||||
if (brandInfo.brand_id && currentStep === 1) {
|
||||
setTrigerFilter(prev => !prev);
|
||||
setTrigerFilter((prev) => !prev);
|
||||
}
|
||||
}, [brandInfo.brand_id, currentStep]);
|
||||
|
||||
@@ -913,8 +988,7 @@ const AddBrandDevice = () => {
|
||||
const errorCodes = response.data || [];
|
||||
setApiErrorCodes(errorCodes);
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
};
|
||||
fetchErrorCodes();
|
||||
@@ -925,8 +999,7 @@ const AddBrandDevice = () => {
|
||||
if (isTemporaryBrand && temporaryBrandId && currentStep === 0) {
|
||||
try {
|
||||
await deleteBrand(temporaryBrandId);
|
||||
} catch (error) {
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -937,7 +1010,6 @@ const AddBrandDevice = () => {
|
||||
};
|
||||
}, [isTemporaryBrand, temporaryBrandId, currentStep]);
|
||||
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
@@ -988,14 +1060,9 @@ const AddBrandDevice = () => {
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<Button onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleCancel}>Cancel</Button>
|
||||
{currentStep === 1 && (
|
||||
<Button
|
||||
onClick={handlePrevStep}
|
||||
style={{ marginLeft: 8 }}
|
||||
>
|
||||
<Button onClick={handlePrevStep} style={{ marginLeft: 8 }}>
|
||||
Kembali ke Brand Info
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -479,14 +479,14 @@ const EditBrandDevice = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!solutionData || solutionData.length === 0) {
|
||||
NotifAlert({
|
||||
icon: 'warning',
|
||||
title: 'Perhatian',
|
||||
message: 'Setiap error code harus memiliki minimal 1 solution!',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// if (!solutionData || solutionData.length === 0) {
|
||||
// NotifAlert({
|
||||
// icon: 'warning',
|
||||
// title: 'Perhatian',
|
||||
// message: 'Setiap error code harus memiliki minimal 1 solution!',
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
const formattedSolutions = solutionData.map(solution => {
|
||||
const solutionType = solution.type || 'text';
|
||||
|
||||
@@ -42,16 +42,30 @@ import {
|
||||
getAllNotification,
|
||||
getNotificationLogByNotificationId,
|
||||
getNotificationDetail,
|
||||
resendChatByUser,
|
||||
resendChatAllUser,
|
||||
searchData,
|
||||
} from '../../../api/notification';
|
||||
|
||||
const { Text, Paragraph, Link: AntdLink } = Typography;
|
||||
|
||||
const OpenMail = ({ size = 22, color = 'black' }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 640"
|
||||
width={size}
|
||||
height={size}
|
||||
fill={color}
|
||||
>
|
||||
<path d="M576 480C576 515.3 547.5 544 512.1 544L128 544C92.6 544 64 515.3 64 480L64 228C64.1 212.5 71.8 198 84.5 189.2L270 61.3C300.1 40.6 339.8 40.6 369.9 61.3L555.5 189.2C568.3 198 575.9 212.5 576 228L576 480zM128 496L512.1 496C520.9 496 528 488.9 528 480L528 288.3L373.2 405.7C341.8 429.6 298.3 429.6 266.8 405.7L112 288.3L112 480C112 488.9 119.2 496 128 496zM527.6 228.4L342.7 100.8C329 91.4 311 91.4 297.3 100.8L112.4 228.4L295.8 367.5C310.1 378.3 329.9 378.3 344.2 367.5L527.6 228.4z" />
|
||||
</svg>
|
||||
);
|
||||
// Transform API response to component format
|
||||
const transformNotificationData = (apiData) => {
|
||||
return apiData.map((item, index) => ({
|
||||
id: `notification-${item.notification_error_id}-${index}`, // Unique key prefix with array index
|
||||
type: item.is_read ? 'resolved' : item.is_delivered ? 'warning' : 'critical',
|
||||
title: item.error_code_name || 'Unknown Error',
|
||||
color: item.error_code_color || 'Black',
|
||||
issue: item.error_code || item.error_code_name || 'Unknown Error',
|
||||
description: `${item.error_code} - ${item.error_code_name || ''}`,
|
||||
timestamp: item.created_at
|
||||
@@ -65,6 +79,7 @@ const transformNotificationData = (apiData) => {
|
||||
: 'N/A',
|
||||
location: item.plant_sub_section_name || item.device_location || 'Location not specified',
|
||||
details: item.device_name || '-',
|
||||
errId: item.notification_error_id || 0,
|
||||
link: `/verification-sparepart/${item.notification_error_id}`, // Dummy URL untuk verifikasi spare part
|
||||
subsection: item.plant_sub_section_name || 'N/A',
|
||||
isRead: item.is_read,
|
||||
@@ -178,13 +193,13 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
const getIconAndColor = (type) => {
|
||||
switch (type) {
|
||||
case 'critical':
|
||||
return { IconComponent: CloseCircleFilled, color: '#ff4d4f', bgColor: '#fff1f0' };
|
||||
return { IconComponent: MailOutlined, color: '#faad14', bgColor: '#fff1f0' };
|
||||
case 'warning':
|
||||
return { IconComponent: WarningFilled, color: '#faad14', bgColor: '#fffbe6' };
|
||||
return { IconComponent: MailOutlined, color: '#1890ff', bgColor: '#fffbe6' };
|
||||
case 'resolved':
|
||||
return { IconComponent: CheckCircleFilled, color: '#52c41a', bgColor: '#f6ffed' };
|
||||
return { IconComponent: MailOutlined, color: '#52c41a', bgColor: '#f6ffed' };
|
||||
default:
|
||||
return { IconComponent: InfoCircleFilled, color: '#1890ff', bgColor: '#e6f7ff' };
|
||||
return { IconComponent: MailOutlined, color: '#1890ff', bgColor: '#e6f7ff' };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -195,9 +210,9 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
content: `Are you sure you want to resend the notification for "${notification.title}"?`,
|
||||
okText: 'Resend',
|
||||
cancelText: 'Cancel',
|
||||
onOk() {
|
||||
async onOk() {
|
||||
console.log('Resending notification:', notification.id);
|
||||
|
||||
await resendChatAllUser(notification.errId);
|
||||
message.success(
|
||||
`Notification for "${notification.title}" has been resent successfully.`
|
||||
);
|
||||
@@ -216,13 +231,49 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
);
|
||||
};
|
||||
|
||||
const fetchSearch = async (data) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await searchData(data);
|
||||
if (response && response.data) {
|
||||
const transformedData = transformNotificationData(response.data);
|
||||
setNotifications(transformedData);
|
||||
|
||||
// Update pagination with API response or calculate from data
|
||||
if (response.paging) {
|
||||
setPagination({
|
||||
current_page: response.paging.current_page || page,
|
||||
current_limit: response.paging.current_limit || limit,
|
||||
total_limit: response.paging.total_limit || transformedData.length,
|
||||
total_page:
|
||||
response.paging.total_page || Math.ceil(transformedData.length / limit),
|
||||
});
|
||||
} else {
|
||||
// Fallback: calculate pagination from data
|
||||
const totalItems = transformedData.length;
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current_page: page,
|
||||
current_limit: limit,
|
||||
total_limit: totalItems,
|
||||
total_page: Math.ceil(totalItems / limit),
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
setSearchTerm(searchValue);
|
||||
fetchSearch(searchValue);
|
||||
};
|
||||
|
||||
const handleSearchClear = () => {
|
||||
setSearchValue('');
|
||||
setSearchTerm('');
|
||||
fetchSearch('');
|
||||
};
|
||||
|
||||
const getUnreadCount = () => notifications.filter((n) => !n.isRead).length;
|
||||
@@ -285,14 +336,16 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
name: user.contact_name,
|
||||
phone: user.contact_phone,
|
||||
status: user.is_send ? 'Delivered' : 'Pending',
|
||||
timestamp: user.created_at
|
||||
? new Date(user.created_at).toLocaleString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}) + ' WIB'
|
||||
timestamp: user.updated_at
|
||||
? new Date(user.updated_at)
|
||||
.toLocaleString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
.replace('.', ':') + ' WIB'
|
||||
: 'N/A',
|
||||
}));
|
||||
setUserHistoryData(transformedUsers);
|
||||
@@ -363,7 +416,11 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<IconComponent style={{ fontSize: '22px' }} />
|
||||
{notification.type === 'resolved' ? (
|
||||
<OpenMail size={28.5} color={color} />
|
||||
) : (
|
||||
<IconComponent style={{ fontSize: '22px' }} />
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Row align="top">
|
||||
@@ -378,8 +435,12 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
<div>
|
||||
<Text strong>{notification.title}</Text>
|
||||
<div style={{ marginTop: '4px' }}>
|
||||
<Text style={{ color }}>
|
||||
{notification.issue}
|
||||
<Text
|
||||
style={{
|
||||
color: notification.color,
|
||||
}}
|
||||
>
|
||||
Error Code {notification.issue}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
@@ -605,10 +666,8 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<SendOutlined />}
|
||||
onClick={() => {
|
||||
message.info(
|
||||
'Resend feature is not available yet. This feature is still under development.'
|
||||
);
|
||||
onClick={async () => {
|
||||
await resendChatByUser(user.id, user.phone);
|
||||
}}
|
||||
>
|
||||
Resend
|
||||
@@ -694,8 +753,8 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
{/* Kolom Kanan: Card */}
|
||||
<Col flex="auto">
|
||||
<Card size="small" style={{ borderColor: '#91d5ff' }}>
|
||||
<Row gutter={[16, 8]} align="middle">
|
||||
<Col xs={24} md={12}>
|
||||
<Row gutter={[16, 8]} align="top">
|
||||
<Col xs={24} md={10}>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Space>
|
||||
<ClockCircleOutlined />
|
||||
@@ -706,13 +765,14 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
Added at {log.timestamp}
|
||||
</Text>
|
||||
</Space>
|
||||
|
||||
<div>
|
||||
<Text strong>{log.addedBy.name}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text strong>
|
||||
Added by: {log.addedBy.name}
|
||||
</Text>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: '8px',
|
||||
border: '1px solid #52c41a',
|
||||
color: '#52c41a',
|
||||
padding: '2px 6px',
|
||||
@@ -725,7 +785,8 @@ const ListNotification = memo(function ListNotification(props) {
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
<Col xs={24} md={14}>
|
||||
<Text strong>Description:</Text>
|
||||
<Paragraph
|
||||
style={{
|
||||
color: '#595959',
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
message,
|
||||
Avatar,
|
||||
Tag,
|
||||
Badge,
|
||||
Divider,
|
||||
} from 'antd';
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
@@ -33,12 +35,16 @@ import {
|
||||
CheckCircleOutlined,
|
||||
SyncOutlined,
|
||||
SendOutlined,
|
||||
MobileOutlined,
|
||||
ClockCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
getNotificationDetail,
|
||||
createNotificationLog,
|
||||
getNotificationLogByNotificationId,
|
||||
updateIsRead,
|
||||
resendNotificationToUser,
|
||||
resendChatByUser,
|
||||
} from '../../api/notification';
|
||||
|
||||
const { Content } = Layout;
|
||||
@@ -107,8 +113,19 @@ const getUsersFromNotification = (notification) => {
|
||||
id: user.notification_error_user_id.toString(),
|
||||
name: user.contact_name,
|
||||
phone: user.contact_phone,
|
||||
status: user.is_send ? 'sent' : 'pending',
|
||||
status: user.is_send ? 'Delivered' : 'Pending',
|
||||
loading: user.loading || false,
|
||||
timestamp: user.updated_at
|
||||
? new Date(user.updated_at)
|
||||
.toLocaleString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
.replace('.', ':') + ' WIB'
|
||||
: 'N/A',
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -241,6 +258,9 @@ const NotificationDetailTab = (props) => {
|
||||
|
||||
// Fetch log history
|
||||
fetchLogHistory(notificationId);
|
||||
|
||||
// Fetch using the actual API
|
||||
const resUpdate = await updateIsRead(notificationId);
|
||||
} else {
|
||||
throw new Error('Notification not found');
|
||||
}
|
||||
@@ -374,7 +394,7 @@ const NotificationDetailTab = (props) => {
|
||||
<Text>{notification.title}</Text>
|
||||
<div style={{ marginTop: '2px' }}>
|
||||
<Text strong style={{ fontSize: '16px' }}>
|
||||
{notification.issue}
|
||||
Error Code {notification.issue}
|
||||
</Text>
|
||||
</div>
|
||||
</Col>
|
||||
@@ -462,101 +482,56 @@ const NotificationDetailTab = (props) => {
|
||||
<Row align="middle" justify="space-between">
|
||||
<Col>
|
||||
<Space align="center">
|
||||
<Avatar
|
||||
size="large"
|
||||
icon={<UserOutlined />}
|
||||
<Text strong>{user.name}</Text>
|
||||
<Text>|</Text>
|
||||
<Text>
|
||||
<MobileOutlined /> {user.phone}
|
||||
</Text>
|
||||
<Text>|</Text>
|
||||
<Badge
|
||||
status={
|
||||
user.status === 'Delivered'
|
||||
? 'success'
|
||||
: 'default'
|
||||
}
|
||||
text={user.status}
|
||||
/>
|
||||
<div>
|
||||
<Text strong>{user.name}</Text>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
<PhoneOutlined
|
||||
style={{
|
||||
color: '#8c8c8c',
|
||||
}}
|
||||
/>
|
||||
<Text type="secondary">
|
||||
{user.phone}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Space align="center">
|
||||
{user.status === 'Delivered' ? (
|
||||
<CheckCircleFilled
|
||||
style={{ color: '#52c41a' }}
|
||||
/>
|
||||
) : (
|
||||
<ClockCircleOutlined
|
||||
style={{ color: '#faad14' }}
|
||||
/>
|
||||
)}
|
||||
<Text type="secondary">
|
||||
{user.status === 'Delivered'
|
||||
? 'Success Delivered at'
|
||||
: 'Status '}{' '}
|
||||
{user.timestamp}
|
||||
</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col>
|
||||
<Space align="center" size="large">
|
||||
{getStatusTag(user.status)}
|
||||
<Col>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
icon={<SendOutlined />}
|
||||
size="small"
|
||||
loading={user.loading}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const userId = parseInt(user.id);
|
||||
try {
|
||||
// Update user status to show loading
|
||||
const updatedUsers = notification.users.map(u =>
|
||||
u.notification_error_user_id === userId
|
||||
? { ...u, loading: true }
|
||||
: u
|
||||
);
|
||||
setNotification({
|
||||
...notification,
|
||||
users: updatedUsers
|
||||
});
|
||||
|
||||
// Call the resend API
|
||||
const response = await resendNotificationToUser(
|
||||
notification.notification_error_id,
|
||||
userId
|
||||
);
|
||||
|
||||
if (response && response.statusCode === 200) {
|
||||
message.success(`Notification resent to ${user.name}`);
|
||||
|
||||
// Update user status
|
||||
const updatedUsersAfterSuccess = notification.users.map(u =>
|
||||
u.notification_error_user_id === userId
|
||||
? {
|
||||
...u,
|
||||
is_send: true,
|
||||
status: 'sent',
|
||||
loading: false
|
||||
}
|
||||
: { ...u, loading: false }
|
||||
);
|
||||
setNotification({
|
||||
...notification,
|
||||
users: updatedUsersAfterSuccess
|
||||
});
|
||||
} else {
|
||||
throw new Error(response?.message || 'Failed to resend notification');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error resending notification:', error);
|
||||
message.error(error.message || 'Failed to resend notification');
|
||||
|
||||
// Reset loading state
|
||||
const resetUsers = notification.users.map(u =>
|
||||
u.notification_error_user_id === userId
|
||||
? { ...u, loading: false }
|
||||
: u
|
||||
);
|
||||
setNotification({
|
||||
...notification,
|
||||
users: resetUsers
|
||||
});
|
||||
}
|
||||
onClick={async () => {
|
||||
await resendChatByUser(
|
||||
user.id,
|
||||
user.phone
|
||||
);
|
||||
}}
|
||||
>
|
||||
Resend
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
@@ -567,393 +542,407 @@ const NotificationDetailTab = (props) => {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[8, 8]} style={{ marginBottom: 'px' }}>
|
||||
<Row gutter={[8, 8]}>
|
||||
<Col xs={24} md={8}>
|
||||
<Card
|
||||
hoverable
|
||||
bodyStyle={{ padding: '12px', textAlign: 'center' }}
|
||||
>
|
||||
<Space>
|
||||
<BookOutlined
|
||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||
/>
|
||||
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
|
||||
Handling Guideline
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card
|
||||
hoverable
|
||||
bodyStyle={{ padding: '12px', textAlign: 'center' }}
|
||||
>
|
||||
<Space>
|
||||
<ToolOutlined
|
||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||
/>
|
||||
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
|
||||
Spare Part
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card bodyStyle={{ padding: '12px', textAlign: 'center' }}>
|
||||
<Space>
|
||||
<HistoryOutlined
|
||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||
/>
|
||||
<Text strong style={{ fontSize: '16px', color: '#262626' }}>
|
||||
Log Activity
|
||||
</Text>
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={[8, 8]} style={{ marginTop: '-12px' }}>
|
||||
<Col xs={24} md={8}>
|
||||
<Card
|
||||
size="small"
|
||||
title="Guideline Documents"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
<div>
|
||||
<Card
|
||||
hoverable
|
||||
bodyStyle={{ padding: '12px'}}
|
||||
>
|
||||
{notification.error_code?.solution &&
|
||||
notification.error_code.solution.length > 0 ? (
|
||||
<>
|
||||
{notification.error_code.solution
|
||||
.filter((sol) => sol.is_active) // Hanya tampilkan solusi yang aktif
|
||||
.map((sol, index) => (
|
||||
<div
|
||||
key={
|
||||
sol.brand_code_solution_id || index
|
||||
}
|
||||
>
|
||||
{sol.path_document ? (
|
||||
<Card
|
||||
size="small"
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
hoverable
|
||||
extra={
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
}}
|
||||
>
|
||||
PDF
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent:
|
||||
'space-between',
|
||||
alignItems: 'center',
|
||||
<Space>
|
||||
<BookOutlined
|
||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||
/>
|
||||
<Text
|
||||
strong
|
||||
style={{ fontSize: '16px', color: '#262626' }}
|
||||
>
|
||||
Handling Guideline
|
||||
</Text>
|
||||
</Space>
|
||||
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{notification.error_code?.solution &&
|
||||
notification.error_code.solution.length > 0 ? (
|
||||
<>
|
||||
{notification.error_code.solution
|
||||
.filter((sol) => sol.is_active) // Hanya tampilkan solusi yang aktif
|
||||
.map((sol, index) => (
|
||||
<div
|
||||
key={
|
||||
sol.brand_code_solution_id ||
|
||||
index
|
||||
}
|
||||
>
|
||||
{sol.path_document ? (
|
||||
<Card
|
||||
size="small"
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
hoverable
|
||||
extra={
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize:
|
||||
'12px',
|
||||
color: '#262626',
|
||||
'10px',
|
||||
}}
|
||||
>
|
||||
<FilePdfOutlined
|
||||
style={{
|
||||
marginRight:
|
||||
'8px',
|
||||
}}
|
||||
/>{' '}
|
||||
{sol.file_upload_name ||
|
||||
'Solution Document.pdf'}
|
||||
PDF
|
||||
</Text>
|
||||
<Link
|
||||
href={sol.path_document.replace(
|
||||
'/detail-notification/pdf/',
|
||||
'/notification-detail/pdf/'
|
||||
)}
|
||||
target="_blank"
|
||||
style={{
|
||||
fontSize:
|
||||
'12px',
|
||||
display:
|
||||
'block',
|
||||
}}
|
||||
>
|
||||
lihat disini
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
{sol.type_solution === 'text' &&
|
||||
sol.text_solution ? (
|
||||
<Card
|
||||
size="small"
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
extra={
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
}}
|
||||
>
|
||||
{sol.type_solution.toUpperCase()}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Text strong>
|
||||
{sol.solution_name}:
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '4px',
|
||||
display: 'flex',
|
||||
justifyContent:
|
||||
'space-between',
|
||||
alignItems:
|
||||
'center',
|
||||
}}
|
||||
>
|
||||
{sol.text_solution}
|
||||
<div>
|
||||
<Text
|
||||
style={{
|
||||
fontSize:
|
||||
'12px',
|
||||
color: '#262626',
|
||||
}}
|
||||
>
|
||||
<FilePdfOutlined
|
||||
style={{
|
||||
marginRight:
|
||||
'8px',
|
||||
}}
|
||||
/>{' '}
|
||||
{sol.file_upload_name ||
|
||||
'Solution Document.pdf'}
|
||||
</Text>
|
||||
<Link
|
||||
href={sol.path_document.replace(
|
||||
'/detail-notification/pdf/',
|
||||
'/notification-detail/pdf/'
|
||||
)}
|
||||
target="_blank"
|
||||
style={{
|
||||
fontSize:
|
||||
'12px',
|
||||
display:
|
||||
'block',
|
||||
}}
|
||||
>
|
||||
lihat disini
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
color: '#8c8c8c',
|
||||
}}
|
||||
>
|
||||
Tidak ada dokumen solusi tersedia
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Card>
|
||||
) : null}
|
||||
{sol.type_solution === 'text' &&
|
||||
sol.text_solution ? (
|
||||
<Card
|
||||
size="small"
|
||||
title={
|
||||
<Text strong>
|
||||
{sol.solution_name}:
|
||||
</Text>
|
||||
}
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
extra={
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize:
|
||||
'10px',
|
||||
}}
|
||||
>
|
||||
{sol.type_solution.toUpperCase()}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
marginTop:
|
||||
'4px',
|
||||
}}
|
||||
>
|
||||
{sol.text_solution}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
color: '#8c8c8c',
|
||||
}}
|
||||
>
|
||||
Tidak ada dokumen solusi tersedia
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card
|
||||
size="small"
|
||||
title="Required Spare Parts"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
<div>
|
||||
<Card
|
||||
hoverable
|
||||
bodyStyle={{ padding: '12px'}}
|
||||
>
|
||||
{notification.spareparts &&
|
||||
notification.spareparts.length > 0 ? (
|
||||
notification.spareparts.map((sparepart, index) => (
|
||||
<Card
|
||||
size="small"
|
||||
key={index}
|
||||
bodyStyle={{ padding: '12px' }}
|
||||
hoverable
|
||||
>
|
||||
<Row gutter={16} align="top">
|
||||
<Col
|
||||
span={7}
|
||||
style={{ textAlign: 'center' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '60px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
<Space>
|
||||
<ToolOutlined
|
||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||
/>
|
||||
<Text
|
||||
strong
|
||||
style={{ fontSize: '16px', color: '#262626' }}
|
||||
>
|
||||
Spare Part
|
||||
</Text>
|
||||
</Space>
|
||||
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{notification.spareparts &&
|
||||
notification.spareparts.length > 0 ? (
|
||||
notification.spareparts.map((sparepart, index) => (
|
||||
<Card
|
||||
size="small"
|
||||
key={index}
|
||||
bodyStyle={{ padding: '12px' }}
|
||||
hoverable
|
||||
>
|
||||
<Row gutter={16} align="top">
|
||||
<Col
|
||||
span={7}
|
||||
style={{ textAlign: 'center' }}
|
||||
>
|
||||
<ToolOutlined
|
||||
style={{
|
||||
fontSize: '24px',
|
||||
color: '#bfbfbf',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color:
|
||||
sparepart.sparepart_stok ===
|
||||
'Available' ||
|
||||
sparepart.sparepart_stok ===
|
||||
'available'
|
||||
? '#52c41a'
|
||||
: '#ff4d4f',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{sparepart.sparepart_stok}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col span={17}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size={4}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Text strong>
|
||||
{sparepart.sparepart_name}
|
||||
</Text>
|
||||
<Paragraph
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
margin: 0,
|
||||
color: '#595959',
|
||||
}}
|
||||
>
|
||||
{sparepart.sparepart_description ||
|
||||
'Deskripsi tidak tersedia'}
|
||||
</Paragraph>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #d9d9d9',
|
||||
width: '100%',
|
||||
height: '60px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '11px',
|
||||
color: '#8c8c8c',
|
||||
marginTop: '8px',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
Kode: {sparepart.sparepart_code}{' '}
|
||||
| Qty: {sparepart.sparepart_qty}{' '}
|
||||
| Unit:{' '}
|
||||
{sparepart.sparepart_unit}
|
||||
<ToolOutlined
|
||||
style={{
|
||||
fontSize: '24px',
|
||||
color: '#bfbfbf',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
color: '#8c8c8c',
|
||||
}}
|
||||
>
|
||||
Tidak ada spare parts terkait
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card size="small" style={{ height: '100%' }}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Card
|
||||
size="small"
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
backgroundColor: isAddingLog ? '#fafafa' : '#fff',
|
||||
}}
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
style={{ width: '100%' }}
|
||||
size="small"
|
||||
>
|
||||
{isAddingLog && (
|
||||
<>
|
||||
<Text strong style={{ fontSize: '12px' }}>
|
||||
Add New Log / Update Progress
|
||||
</Text>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
placeholder="Tuliskan update penanganan di sini..."
|
||||
value={newLogDescription}
|
||||
onChange={(e) =>
|
||||
setNewLogDescription(e.target.value)
|
||||
}
|
||||
disabled={submitLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
type={isAddingLog ? 'primary' : 'dashed'}
|
||||
size="small"
|
||||
block
|
||||
icon={
|
||||
submitLoading ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
!isAddingLog && <PlusOutlined />
|
||||
)
|
||||
}
|
||||
onClick={
|
||||
isAddingLog
|
||||
? handleSubmitLog
|
||||
: () => setIsAddingLog(true)
|
||||
}
|
||||
loading={submitLoading}
|
||||
disabled={submitLoading}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color:
|
||||
sparepart.sparepart_stok ===
|
||||
'Available' ||
|
||||
sparepart.sparepart_stok ===
|
||||
'available'
|
||||
? '#52c41a'
|
||||
: '#ff4d4f',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{sparepart.sparepart_stok}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col span={17}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size={4}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Text strong>
|
||||
{sparepart.sparepart_name}
|
||||
</Text>
|
||||
<Paragraph
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
margin: 0,
|
||||
color: '#595959',
|
||||
}}
|
||||
>
|
||||
{sparepart.sparepart_description ||
|
||||
'Deskripsi tidak tersedia'}
|
||||
</Paragraph>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '11px',
|
||||
color: '#8c8c8c',
|
||||
marginTop: '8px',
|
||||
}}
|
||||
>
|
||||
Kode:{' '}
|
||||
{sparepart.sparepart_code} |
|
||||
Qty:{' '}
|
||||
{sparepart.sparepart_qty} |
|
||||
Unit:{' '}
|
||||
{sparepart.sparepart_unit}
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
color: '#8c8c8c',
|
||||
}}
|
||||
>
|
||||
{isAddingLog ? 'Submit Log' : 'Add Log'}
|
||||
</Button>
|
||||
{isAddingLog && (
|
||||
<Button
|
||||
size="small"
|
||||
block
|
||||
onClick={() => {
|
||||
setIsAddingLog(false);
|
||||
setNewLogDescription('');
|
||||
}}
|
||||
disabled={submitLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
{logHistoryData.map((log) => (
|
||||
Tidak ada spare parts terkait
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<div>
|
||||
<Card bodyStyle={{ padding: '12px'}}>
|
||||
<Space>
|
||||
<HistoryOutlined
|
||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||
/>
|
||||
<Text
|
||||
strong
|
||||
style={{ fontSize: '16px', color: '#262626' }}
|
||||
>
|
||||
Log Activity
|
||||
</Text>
|
||||
</Space>
|
||||
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Card
|
||||
key={log.id}
|
||||
size="small"
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
backgroundColor: isAddingLog
|
||||
? '#fafafa'
|
||||
: '#fff',
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{ fontSize: '12px', margin: 0 }}
|
||||
ellipsis={{ rows: 2 }}
|
||||
<Space
|
||||
direction="vertical"
|
||||
style={{ width: '100%' }}
|
||||
size="small"
|
||||
>
|
||||
<Text strong>{log.addedBy.name}:</Text>{' '}
|
||||
{log.description}
|
||||
</Paragraph>
|
||||
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||
{log.timestamp}
|
||||
</Text>
|
||||
{isAddingLog && (
|
||||
<>
|
||||
<Text
|
||||
strong
|
||||
style={{ fontSize: '12px' }}
|
||||
>
|
||||
Add New Log / Update Progress
|
||||
</Text>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
placeholder="Tuliskan update penanganan di sini..."
|
||||
value={newLogDescription}
|
||||
onChange={(e) =>
|
||||
setNewLogDescription(
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
disabled={submitLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
type={isAddingLog ? 'primary' : 'dashed'}
|
||||
size="small"
|
||||
block
|
||||
icon={
|
||||
submitLoading ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
!isAddingLog && <PlusOutlined />
|
||||
)
|
||||
}
|
||||
onClick={
|
||||
isAddingLog
|
||||
? handleSubmitLog
|
||||
: () => setIsAddingLog(true)
|
||||
}
|
||||
loading={submitLoading}
|
||||
disabled={submitLoading}
|
||||
>
|
||||
{isAddingLog ? 'Submit Log' : 'Add Log'}
|
||||
</Button>
|
||||
{isAddingLog && (
|
||||
<Button
|
||||
size="small"
|
||||
block
|
||||
onClick={() => {
|
||||
setIsAddingLog(false);
|
||||
setNewLogDescription('');
|
||||
}}
|
||||
disabled={submitLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
{logHistoryData.map((log) => (
|
||||
<Card
|
||||
key={log.id}
|
||||
size="small"
|
||||
bodyStyle={{
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{ fontSize: '12px', margin: 0 }}
|
||||
// ellipsis={{ rows: 2 }}
|
||||
>
|
||||
<Text strong>{log.addedBy.name}:</Text>{' '}
|
||||
{log.description}
|
||||
</Paragraph>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{ fontSize: '11px' }}
|
||||
>
|
||||
{log.timestamp}
|
||||
</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
|
||||
Reference in New Issue
Block a user