Compare commits
13 Commits
46e922a33d
...
lavoce
| Author | SHA1 | Date | |
|---|---|---|---|
| e02aa92c64 | |||
| 276a75f13f | |||
| 51e2fa46bf | |||
| c22000437b | |||
| 1f5678f435 | |||
| d7747b6f83 | |||
| cabf0e4ce1 | |||
| 00c2a583f4 | |||
| bde507b85d | |||
| f5b81a6250 | |||
| 2fcf79f1aa | |||
| 4c5f6b7e5c | |||
| 3fe5fbef31 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,7 +6,6 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
*.config
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<system.webServer>
|
<system.webServer>
|
||||||
<rewrite>
|
<rewrite>
|
||||||
<rules>
|
<rules>
|
||||||
<rule name="CallOfDuty">
|
<rule name="reactViteSypiu">
|
||||||
<match url=".*" />
|
<match url=".*" />
|
||||||
<conditions logicalGrouping="MatchAll">
|
<conditions logicalGrouping="MatchAll">
|
||||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||||
|
|||||||
@@ -91,6 +91,25 @@ const searchData = async (queryParams) => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reminder Monthly
|
||||||
|
const reminderMonthly = async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `notifikasi-wa/reminder-monthly/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reminderCustom= async (id, queryParams) => {
|
||||||
|
const response = await SendRequest({
|
||||||
|
method: 'post',
|
||||||
|
prefix: `notifikasi-wa/reminder-custom/${id}`,
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAllNotification,
|
getAllNotification,
|
||||||
getNotificationById,
|
getNotificationById,
|
||||||
@@ -102,4 +121,6 @@ export {
|
|||||||
resendChatByUser,
|
resendChatByUser,
|
||||||
resendChatAllUser,
|
resendChatAllUser,
|
||||||
searchData,
|
searchData,
|
||||||
|
reminderMonthly,
|
||||||
|
reminderCustom,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,19 @@ const NotifOk = ({ icon, title, message }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NotifSmall = ({ icon, title, message }) => {
|
||||||
|
Swal.fire({
|
||||||
|
toast: true,
|
||||||
|
position: 'top-end',
|
||||||
|
icon: icon,
|
||||||
|
title: title,
|
||||||
|
text: message,
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 2000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const NotifConfirmDialog = ({
|
const NotifConfirmDialog = ({
|
||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
@@ -67,4 +80,4 @@ const QuestionConfirmSubmit = ({ icon, title, message, onConfirm, onCancel }) =>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { NotifAlert, NotifOk, NotifConfirmDialog, QuestionConfirmSubmit };
|
export { NotifAlert, NotifOk, NotifSmall, NotifConfirmDialog, QuestionConfirmSubmit };
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
|
email: '',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +35,22 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
[name]: value,
|
[name]: value,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'email') {
|
||||||
|
const email = value.trim();
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: email,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
email:
|
||||||
|
email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
|
||||||
|
? 'Format email tidak valid'
|
||||||
|
: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +68,7 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
const validationRules = [
|
const validationRules = [
|
||||||
{ field: 'name', label: 'Contact Name', required: true },
|
{ field: 'name', label: 'Contact Name', required: true },
|
||||||
{ field: 'phone', label: 'Phone', required: true },
|
{ field: 'phone', label: 'Phone', required: true },
|
||||||
|
{ field: 'email', label: 'Email', required: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -84,10 +102,23 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validasi Email
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (formData.email && !emailRegex.test(formData.email.trim())) {
|
||||||
|
NotifOk({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Peringatan',
|
||||||
|
message: 'Format email tidak valid.',
|
||||||
|
});
|
||||||
|
setConfirmLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contactData = {
|
const contactData = {
|
||||||
contact_name: formData.name,
|
contact_name: formData.name,
|
||||||
contact_phone: formData.phone.replace(/[\s\-\(\)]/g, ''), // Clean phone number
|
contact_phone: formData.phone.replace(/[\s\-\(\)]/g, ''), // Clean phone number
|
||||||
|
email: formData.email?.trim(),
|
||||||
is_active: formData.is_active,
|
is_active: formData.is_active,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,6 +165,7 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
setFormData({
|
setFormData({
|
||||||
name: props.selectedData.contact_name || props.selectedData.name,
|
name: props.selectedData.contact_name || props.selectedData.name,
|
||||||
phone: props.selectedData.contact_phone || props.selectedData.phone,
|
phone: props.selectedData.contact_phone || props.selectedData.phone,
|
||||||
|
email: props.selectedData.contact_email || props.selectedData.email,
|
||||||
is_active:
|
is_active:
|
||||||
props.selectedData.is_active || props.selectedData.status === 'active',
|
props.selectedData.is_active || props.selectedData.status === 'active',
|
||||||
});
|
});
|
||||||
@@ -141,6 +173,7 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
setFormData({
|
setFormData({
|
||||||
name: '',
|
name: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
|
email: '',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -249,6 +282,19 @@ const DetailContact = memo(function DetailContact(props) {
|
|||||||
style={{ color: formData.is_active ? '#000000' : '#ff4d4f' }}
|
style={{ color: formData.is_active ? '#000000' : '#ff4d4f' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Email</Text>
|
||||||
|
{/* <Text style={{ color: 'red' }}> *</Text> */}
|
||||||
|
<Input
|
||||||
|
name="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Enter Email Number"
|
||||||
|
readOnly={props.readOnly}
|
||||||
|
maxLength={64}
|
||||||
|
style={{ color: formData.is_active ? '#000000' : '#ff4d4f' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/* Contact Type */}
|
{/* Contact Type */}
|
||||||
{/* <div style={{ marginBottom: 12 }}>
|
{/* <div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Contact Type</Text>
|
<Text strong>Contact Type</Text>
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk, NotifSmall } from '../../../../components/Global/ToastNotif';
|
||||||
import { createDevice, updateDevice } from '../../../../api/master-device';
|
import { createDevice, updateDevice } from '../../../../api/master-device';
|
||||||
|
import { reminderMonthly, reminderCustom } from '../../../../api/notification';
|
||||||
import { getAllBrands } from '../../../../api/master-brand';
|
import { getAllBrands } from '../../../../api/master-brand';
|
||||||
import { validateRun } from '../../../../Utils/validate';
|
import { validateRun } from '../../../../Utils/validate';
|
||||||
import { toApiDateFormatter } from '../../../../components/Global/Formatter';
|
import { toApiDateFormatter } from '../../../../components/Global/Formatter';
|
||||||
@@ -35,8 +36,12 @@ const DetailDevice = (props) => {
|
|||||||
device_location: '',
|
device_location: '',
|
||||||
device_description: '',
|
device_description: '',
|
||||||
ip_address: '',
|
ip_address: '',
|
||||||
|
listen_channel: null,
|
||||||
reminder_at: null,
|
reminder_at: null,
|
||||||
listen_channel: '',
|
reminder_at_monthly: null,
|
||||||
|
start_date: null,
|
||||||
|
end_date: null,
|
||||||
|
period: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [formData, setFormData] = useState(defaultData);
|
const [formData, setFormData] = useState(defaultData);
|
||||||
@@ -82,6 +87,9 @@ const DetailDevice = (props) => {
|
|||||||
listen_channel: formData.listen_channel,
|
listen_channel: formData.listen_channel,
|
||||||
listen_channel_reminder: formData.listen_channel_reminder,
|
listen_channel_reminder: formData.listen_channel_reminder,
|
||||||
reminder_at: formData.reminder_at,
|
reminder_at: formData.reminder_at,
|
||||||
|
reminder_at_monthly: formData.period !== 'custom' ? formData.reminder_at_monthly : null,
|
||||||
|
start_date: formData.start_date,
|
||||||
|
end_date: formData.end_date,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(payload);
|
console.log(payload);
|
||||||
@@ -90,6 +98,13 @@ const DetailDevice = (props) => {
|
|||||||
? await updateDevice(formData.device_id, payload)
|
? await updateDevice(formData.device_id, payload)
|
||||||
: await createDevice(payload);
|
: await createDevice(payload);
|
||||||
|
|
||||||
|
const response2 = formData.period !== 'custom'
|
||||||
|
? await updateDevice(formData.device_id, { reminder_at_monthly: formData.reminder_at_monthly,})
|
||||||
|
: await updateDevice(formData.device_id, {
|
||||||
|
start_date: formData.start_date,
|
||||||
|
end_date: formData.end_date,
|
||||||
|
});
|
||||||
|
|
||||||
// Check if response is successful
|
// Check if response is successful
|
||||||
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
|
||||||
const deviceName = response.data?.device_name || formData.device_name;
|
const deviceName = response.data?.device_name || formData.device_name;
|
||||||
@@ -110,6 +125,25 @@ const DetailDevice = (props) => {
|
|||||||
message: response?.message || 'Terjadi kesalahan saat menyimpan data.',
|
message: response?.message || 'Terjadi kesalahan saat menyimpan data.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if response Reminder At(Monthly) or Period are successful
|
||||||
|
const tipe = formData.period !== 'custom' ? 'Reminder At(Monthly)' : 'Period';
|
||||||
|
if (response2 && (response2.statusCode === 200 || response2.statusCode === 201)) {
|
||||||
|
NotifSmall({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: `${tipe} berhasil disimpan`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
NotifSmall({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message:
|
||||||
|
response?.message ||
|
||||||
|
`Terjadi kesalahan saat menyimpan ${tipe}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Save Device Error:', error);
|
console.error('Save Device Error:', error);
|
||||||
NotifAlert({
|
NotifAlert({
|
||||||
@@ -172,6 +206,56 @@ const DetailDevice = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formData.start_date && formData.end_date) {
|
||||||
|
const start = dayjs(formData.start_date);
|
||||||
|
const end = dayjs(formData.end_date);
|
||||||
|
|
||||||
|
let periodValue = 'custom';
|
||||||
|
|
||||||
|
if (end.isSame(start.add(1, 'month').subtract(1, 'day'), 'day')) {
|
||||||
|
periodValue = '1';
|
||||||
|
} else if (end.isSame(start.add(3, 'month').subtract(1, 'day'), 'day')) {
|
||||||
|
periodValue = '3';
|
||||||
|
} else if (end.isSame(start.add(6, 'month').subtract(1, 'day'), 'day')) {
|
||||||
|
periodValue = '6';
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
period: periodValue,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [formData.start_date, formData.end_date]);
|
||||||
|
|
||||||
|
const handleMonthly = (field, value) => {
|
||||||
|
setFormData((prev) => {
|
||||||
|
const data = {
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value !== 'custom') {
|
||||||
|
const months = Number(value);
|
||||||
|
|
||||||
|
const startDate = dayjs().startOf('day');
|
||||||
|
const endDate = startDate
|
||||||
|
.add(months, 'month')
|
||||||
|
.subtract(1, 'day')
|
||||||
|
.endOf('day');
|
||||||
|
|
||||||
|
data.start_date = startDate.toISOString();
|
||||||
|
data.end_date = endDate.toISOString();
|
||||||
|
data.reminder_at_monthly = null;
|
||||||
|
} else {
|
||||||
|
data.start_date = null;
|
||||||
|
data.end_date = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.showModal && (props.actionMode === 'add' || props.actionMode === 'edit')) {
|
if (props.showModal && (props.actionMode === 'add' || props.actionMode === 'edit')) {
|
||||||
fetchBrands();
|
fetchBrands();
|
||||||
@@ -381,7 +465,7 @@ const DetailDevice = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Reminder At</Text>
|
<Text strong>Reminder At(Yearly)</Text>
|
||||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={
|
value={
|
||||||
@@ -413,6 +497,92 @@ const DetailDevice = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Period</Text>
|
||||||
|
<Text style={{ color: 'red' }}> *</Text>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
value={formData.period}
|
||||||
|
onChange={(value) => handleMonthly('period', value)}
|
||||||
|
placeholder="Select Period"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Select.Option value="1">1 Monthly</Select.Option>
|
||||||
|
<Select.Option value="3">3 Monthly</Select.Option>
|
||||||
|
<Select.Option value="6">6 Monthly</Select.Option>
|
||||||
|
<Select.Option value="custom">Custom</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
{formData.period === 'custom' && (
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
||||||
|
<DatePicker
|
||||||
|
placeholder="Start Date"
|
||||||
|
value={formData.start_date ? dayjs(formData.start_date) : null}
|
||||||
|
onChange={(date) =>
|
||||||
|
handleDateChange('start_date', date ? date.toISOString() : null)
|
||||||
|
}
|
||||||
|
disabledDate={(current) =>
|
||||||
|
formData.end_date &&
|
||||||
|
current &&
|
||||||
|
current.isAfter(dayjs(formData.end_date), 'day')
|
||||||
|
}
|
||||||
|
format="DD-MM-YYYY HH:mm"
|
||||||
|
showTime={{ format: 'HH:mm' }}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
placeholder="End Date"
|
||||||
|
value={formData.end_date ? dayjs(formData.end_date) : null}
|
||||||
|
onChange={(date) =>
|
||||||
|
handleDateChange('end_date', date ? date.toISOString() : null)
|
||||||
|
}
|
||||||
|
disabledDate={(current) =>
|
||||||
|
formData.start_date &&
|
||||||
|
current &&
|
||||||
|
current.isBefore(dayjs(formData.start_date), 'day')
|
||||||
|
}
|
||||||
|
format="DD-MM-YYYY HH:mm"
|
||||||
|
showTime={{ format: 'HH:mm' }}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>Reminder At(Monthly)</Text>
|
||||||
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||||
|
<DatePicker
|
||||||
|
disabled={formData.period === 'custom'}
|
||||||
|
value={
|
||||||
|
formData.reminder_at_monthly
|
||||||
|
? dayjs(formData.reminder_at_monthly) // ✅ langsung parse ISO
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
onChange={(date) =>
|
||||||
|
handleDateChange(
|
||||||
|
'reminder_at_monthly',
|
||||||
|
date ? date.toISOString() : null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
format="DD-MM-YYYY HH:mm"
|
||||||
|
showTime={{ format: 'HH:mm' }}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={formData.period === 'custom'}
|
||||||
|
danger
|
||||||
|
onClick={() =>
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
reminder_at_monthly: null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<Text strong>Device Description</Text>
|
<Text strong>Device Description</Text>
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ const DetailSparepart = (props) => {
|
|||||||
const [previewTitle, setPreviewTitle] = useState('');
|
const [previewTitle, setPreviewTitle] = useState('');
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
|
const optionsMonthly = [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
value: 1,
|
||||||
|
text: '1 Month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
value: 3,
|
||||||
|
text: '3 Month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 6,
|
||||||
|
value: 6,
|
||||||
|
text: '6 Month',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const optionsYearly = [
|
const optionsYearly = [
|
||||||
{
|
{
|
||||||
key: 1,
|
key: 1,
|
||||||
@@ -70,6 +88,10 @@ const DetailSparepart = (props) => {
|
|||||||
text: '6 Year',
|
text: '6 Year',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const [checkedListM, setCheckedListM] = useState([]);
|
||||||
|
const [checkAllM, setCheckAllM] = useState(false);
|
||||||
|
const [indeterminateM, setIndeterminateM] = useState(false);
|
||||||
|
|
||||||
const [checkedList, setCheckedList] = useState([]);
|
const [checkedList, setCheckedList] = useState([]);
|
||||||
const [checkAll, setCheckAll] = useState(false);
|
const [checkAll, setCheckAll] = useState(false);
|
||||||
const [indeterminate, setIndeterminate] = useState(false);
|
const [indeterminate, setIndeterminate] = useState(false);
|
||||||
@@ -276,6 +298,7 @@ const DetailSparepart = (props) => {
|
|||||||
|
|
||||||
payload.sparepart_number = formData.sparepart_number;
|
payload.sparepart_number = formData.sparepart_number;
|
||||||
payload.sparepart_yearly = checkedList;
|
payload.sparepart_yearly = checkedList;
|
||||||
|
payload.sparepart_monthly = checkedListM;
|
||||||
|
|
||||||
// console.log('Sending payload:', payload);
|
// console.log('Sending payload:', payload);
|
||||||
|
|
||||||
@@ -323,6 +346,21 @@ const DetailSparepart = (props) => {
|
|||||||
setFormData({ ...formData, [name]: value });
|
setFormData({ ...formData, [name]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeCheckboxM = (list) => {
|
||||||
|
setCheckedListM(list);
|
||||||
|
|
||||||
|
setIndeterminateM(!!list.length && list.length < optionsMonthly.length);
|
||||||
|
setCheckAllM(list.length === optionsMonthly.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCheckAllChangeM = (e) => {
|
||||||
|
const checked = e.target.checked;
|
||||||
|
|
||||||
|
setCheckedListM(checked ? optionsMonthly.map((item) => item.value) : []);
|
||||||
|
setIndeterminateM(false);
|
||||||
|
setCheckAllM(checked);
|
||||||
|
};
|
||||||
|
|
||||||
const onChangeCheckbox = (list) => {
|
const onChangeCheckbox = (list) => {
|
||||||
setCheckedList(list);
|
setCheckedList(list);
|
||||||
|
|
||||||
@@ -374,10 +412,16 @@ const DetailSparepart = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
setFileList([]);
|
setFileList([]);
|
||||||
}
|
}
|
||||||
|
setCheckedListM(JSON.parse(props.selectedData.sparepart_monthly));
|
||||||
setCheckedList(JSON.parse(props.selectedData.sparepart_yearly));
|
setCheckedList(JSON.parse(props.selectedData.sparepart_yearly));
|
||||||
} else {
|
} else {
|
||||||
setFormData(defaultData);
|
setFormData(defaultData);
|
||||||
setFileList([]);
|
setFileList([]);
|
||||||
|
|
||||||
|
setIndeterminateM(false);
|
||||||
|
setCheckedListM([]);
|
||||||
|
setCheckAllM(false);
|
||||||
|
|
||||||
setIndeterminate(false);
|
setIndeterminate(false);
|
||||||
setCheckedList([]);
|
setCheckedList([]);
|
||||||
setCheckAll(false);
|
setCheckAll(false);
|
||||||
@@ -639,6 +683,8 @@ const DetailSparepart = (props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{/* Yearly */}
|
||||||
<Row style={{ marginTop: 16 }}>
|
<Row style={{ marginTop: 16 }}>
|
||||||
<Col>
|
<Col>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -669,6 +715,38 @@ const DetailSparepart = (props) => {
|
|||||||
</Checkbox.Group>
|
</Checkbox.Group>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
{/* Monthly */}
|
||||||
|
<Row style={{ marginTop: 16 }}>
|
||||||
|
<Col>
|
||||||
|
<Checkbox
|
||||||
|
indeterminate={indeterminateM}
|
||||||
|
onChange={onCheckAllChangeM}
|
||||||
|
checked={checkAllM}
|
||||||
|
>
|
||||||
|
{checkAllM ? 'Unselect All Months' : 'Select All Months'}
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{ marginTop: 10 }}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Checkbox.Group
|
||||||
|
value={checkedListM}
|
||||||
|
onChange={onChangeCheckboxM}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{optionsMonthly.map((item) => (
|
||||||
|
<Checkbox key={item.key} value={item.value}>
|
||||||
|
{item.text}
|
||||||
|
</Checkbox>
|
||||||
|
))}
|
||||||
|
</Checkbox.Group>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ const transformNotificationData = (apiData) => {
|
|||||||
brand_name: apiData.brand_name,
|
brand_name: apiData.brand_name,
|
||||||
},
|
},
|
||||||
users: apiData.users || [],
|
users: apiData.users || [],
|
||||||
|
is_reminder: apiData.is_reminder || false,
|
||||||
|
spareparts_reminder: apiData.spareparts_reminder || [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -353,7 +355,7 @@ const NotificationDetailTab = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography.Title level={4} style={{ margin: 0, color: '#262626' }}>
|
<Typography.Title level={4} style={{ margin: 0, color: '#262626' }}>
|
||||||
Error Notification Detail
|
Notification Detail
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -361,59 +363,61 @@ const NotificationDetailTab = (props) => {
|
|||||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||||
<Row gutter={[8, 8]}>
|
<Row gutter={[8, 8]}>
|
||||||
{/* Kolom Kiri: Data Kompresor */}
|
{/* Kolom Kiri: Data Kompresor */}
|
||||||
<Col xs={24} lg={8}>
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
style={{ height: '100%', borderColor: '#d4380d' }}
|
|
||||||
bodyStyle={{ padding: '16px' }}
|
|
||||||
>
|
|
||||||
<Space
|
|
||||||
direction="vertical"
|
|
||||||
size="large"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
<Row gutter={16} align="middle">
|
|
||||||
<Col>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '32px',
|
|
||||||
height: '32px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
backgroundColor: '#d4380d',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: '18px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseOutlined />
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col>
|
|
||||||
<Text>{notification.title}</Text>
|
|
||||||
<div style={{ marginTop: '2px' }}>
|
|
||||||
<Text strong style={{ fontSize: '16px' }}>
|
|
||||||
Error Code {notification.issue}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<div>
|
|
||||||
<Text strong>Plant Subsection</Text>
|
|
||||||
<div>{notification.location}</div>
|
|
||||||
<Text
|
|
||||||
strong
|
|
||||||
style={{ display: 'block', marginTop: '8px' }}
|
|
||||||
>
|
|
||||||
Date & Time
|
|
||||||
</Text>
|
|
||||||
<div>{notification.timestamp}</div>
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
|
{notification.is_reminder == false && (
|
||||||
|
<Col xs={24} lg={8}>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{ height: '100%', borderColor: '#d4380d' }}
|
||||||
|
bodyStyle={{ padding: '16px' }}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="large"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Row gutter={16} align="middle">
|
||||||
|
<Col>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#d4380d',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '18px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseOutlined />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Text>{notification.title}</Text>
|
||||||
|
<div style={{ marginTop: '2px' }}>
|
||||||
|
<Text strong style={{ fontSize: '16px' }}>
|
||||||
|
Error Code {notification.issue}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div>
|
||||||
|
<Text strong>Plant Subsection</Text>
|
||||||
|
<div>{notification.location}</div>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{ display: 'block', marginTop: '8px' }}
|
||||||
|
>
|
||||||
|
Date & Time
|
||||||
|
</Text>
|
||||||
|
<div>{notification.timestamp}</div>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
{/* Kolom Tengah: Informasi Teknis */}
|
{/* Kolom Tengah: Informasi Teknis */}
|
||||||
<Col xs={24} lg={8}>
|
<Col xs={24} lg={8}>
|
||||||
<Card
|
<Card
|
||||||
@@ -458,6 +462,159 @@ const NotificationDetailTab = (props) => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
{notification.is_reminder == true && (
|
||||||
|
<Col xs={24} md={8}>
|
||||||
|
<div>
|
||||||
|
<Card hoverable bodyStyle={{ padding: '12px' }}>
|
||||||
|
<Space>
|
||||||
|
<ToolOutlined
|
||||||
|
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{ fontSize: '16px', color: '#262626' }}
|
||||||
|
>
|
||||||
|
Spare Part Reminder Maintenance
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="small"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{notification.spareparts_reminder &&
|
||||||
|
notification.spareparts_reminder.length > 0 ? (
|
||||||
|
notification.spareparts_reminder.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',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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',
|
||||||
|
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',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tidak ada spare parts terkait
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Kolom Kanan: User History */}
|
{/* Kolom Kanan: User History */}
|
||||||
<Col xs={24} lg={8}>
|
<Col xs={24} lg={8}>
|
||||||
<Card title="User History" size="small" style={{ height: '100%' }}>
|
<Card title="User History" size="small" style={{ height: '100%' }}>
|
||||||
@@ -545,10 +702,7 @@ const NotificationDetailTab = (props) => {
|
|||||||
<Row gutter={[8, 8]}>
|
<Row gutter={[8, 8]}>
|
||||||
<Col xs={24} md={8}>
|
<Col xs={24} md={8}>
|
||||||
<div>
|
<div>
|
||||||
<Card
|
<Card hoverable bodyStyle={{ padding: '12px' }}>
|
||||||
hoverable
|
|
||||||
bodyStyle={{ padding: '12px'}}
|
|
||||||
>
|
|
||||||
<Space>
|
<Space>
|
||||||
<BookOutlined
|
<BookOutlined
|
||||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||||
@@ -700,10 +854,7 @@ const NotificationDetailTab = (props) => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={8}>
|
<Col xs={24} md={8}>
|
||||||
<div>
|
<div>
|
||||||
<Card
|
<Card hoverable bodyStyle={{ padding: '12px' }}>
|
||||||
hoverable
|
|
||||||
bodyStyle={{ padding: '12px'}}
|
|
||||||
>
|
|
||||||
<Space>
|
<Space>
|
||||||
<ToolOutlined
|
<ToolOutlined
|
||||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||||
@@ -828,7 +979,7 @@ const NotificationDetailTab = (props) => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={8}>
|
<Col xs={24} md={8}>
|
||||||
<div>
|
<div>
|
||||||
<Card bodyStyle={{ padding: '12px'}}>
|
<Card bodyStyle={{ padding: '12px' }}>
|
||||||
<Space>
|
<Space>
|
||||||
<HistoryOutlined
|
<HistoryOutlined
|
||||||
style={{ fontSize: '16px', color: '#1890ff' }}
|
style={{ fontSize: '16px', color: '#1890ff' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user