Compare commits

..

49 Commits

Author SHA1 Message Date
c92e1ef0ec Merge pull request 'lavoce' (#44) from lavoce into main
Reviewed-on: #44
2026-04-23 04:23:08 +00:00
ce55126bf8 Merge pull request 'add space' (#43) from lavoce into main
Reviewed-on: #43
2026-04-14 07:25:48 +00:00
11ac6ad9ba Merge pull request 'lavoce' (#42) from lavoce into main
Reviewed-on: #42
2026-04-10 05:35:38 +00:00
d02d7d87da Merge pull request 'Repair image svg' (#41) from lavoce into main
Reviewed-on: #41
2026-02-12 05:32:33 +00:00
255c1e64c7 Merge pull request 'lavoce' (#40) from lavoce into main
Reviewed-on: #40
2026-01-20 04:32:42 +00:00
90b882daf3 Merge pull request 'fix(color): change design & color for icon notification' (#39) from lavoce into main
Reviewed-on: #39
2026-01-13 05:39:38 +00:00
cc6a52ccbf Merge pull request 'lavoce' (#38) from lavoce into main
Reviewed-on: #38
2026-01-12 04:29:21 +00:00
03d5646565 Merge pull request 'lavoce' (#37) from lavoce into main
Reviewed-on: #37
2026-01-08 07:44:22 +00:00
4d8af01316 Merge pull request 'fixing typo detail notification user' (#36) from lavoce into main
Reviewed-on: #36
2026-01-08 05:38:53 +00:00
8f32f29c03 Merge pull request 'lavoce' (#35) from lavoce into main
Reviewed-on: #35
2026-01-08 04:52:16 +00:00
5a7d64a05b Merge pull request 'fix: resize add log card' (#34) from lavoce into main
Reviewed-on: #34
2026-01-07 03:33:48 +00:00
e74b802a60 Merge pull request 'lavoce' (#33) from lavoce into main
Reviewed-on: #33
2026-01-06 11:30:27 +00:00
f436865b7f Merge pull request 'lavoce' (#32) from lavoce into main
Reviewed-on: #32
2026-01-05 03:41:39 +00:00
48437c3c50 Merge pull request 'lavoce' (#30) from lavoce into main
Reviewed-on: #30
2025-12-31 03:20:51 +00:00
a33c9b3b92 Merge pull request 'lavoce' (#29) from lavoce into main
Reviewed-on: #29
2025-12-23 05:22:06 +00:00
58d1f5c7ab Merge pull request 'Repair and replace svg' (#28) from lavoce into main
Reviewed-on: #28
2025-12-22 09:38:05 +00:00
c10a5bf62d Merge pull request 'lavoce' (#27) from lavoce into main
Reviewed-on: #27
2025-12-22 09:28:34 +00:00
6cc5042956 Merge pull request 'repair' (#26) from lavoce into main
Reviewed-on: #26
2025-11-28 07:17:06 +00:00
5079f8d316 Merge pull request 'lavoce' (#25) from lavoce into main
Reviewed-on: #25
2025-11-28 07:12:41 +00:00
7073390de7 Merge pull request 'lavoce' (#24) from lavoce into main
Reviewed-on: #24
2025-11-28 05:50:36 +00:00
4226a24e79 Merge pull request 'lavoce' (#23) from lavoce into main
Reviewed-on: #23
2025-11-28 05:10:26 +00:00
038009433f Merge pull request 'lavoce' (#22) from lavoce into main
Reviewed-on: #22
2025-11-25 03:50:54 +00:00
64ba51b17c Merge pull request 'lavoce' (#21) from lavoce into main
Reviewed-on: #21
2025-11-20 03:44:05 +00:00
47d0638a42 Merge pull request 'lavoce' (#20) from lavoce into main
Reviewed-on: #20
2025-11-19 01:05:19 +00:00
d8c5f3ed44 Merge pull request 'lavoce' (#19) from lavoce into main
Reviewed-on: #19
2025-11-18 00:26:36 +00:00
affd9146bb Merge pull request 'lavoce' (#18) from lavoce into main
Reviewed-on: #18
2025-11-17 08:27:39 +00:00
4022b3f8f4 Merge pull request 'lavoce' (#17) from lavoce into main
Reviewed-on: #17
2025-11-10 07:57:17 +00:00
446a4e2b95 Merge pull request 'lavoce' (#16) from lavoce into main
Reviewed-on: #16
2025-11-10 06:33:35 +00:00
83a475c708 Merge pull request 'Repair beautiful apps' (#15) from lavoce into main
Reviewed-on: #15
2025-11-04 10:01:47 +00:00
ab1c510a77 Merge pull request 'Repair bg-signin' (#14) from lavoce into main
Reviewed-on: #14
2025-11-04 08:48:59 +00:00
59859c6d18 Update public/web.config 2025-11-04 07:03:57 +00:00
2bd27937dc Update public/web.config 2025-11-04 07:02:55 +00:00
1058c660d6 Update public/web.config 2025-11-04 07:01:10 +00:00
35b2167791 Update public/web.config 2025-11-04 06:53:05 +00:00
ec676983d0 Update .gitignore 2025-11-04 06:51:39 +00:00
c07c5f8235 Update .gitignore 2025-11-04 06:47:31 +00:00
b32ad97034 Merge pull request 'Add side bar menus mobile mode' (#13) from lavoce into main
Reviewed-on: #13
2025-11-04 06:42:09 +00:00
76244f6f6e Merge pull request 'lavoce' (#12) from lavoce into main
Reviewed-on: #12
2025-11-04 06:23:15 +00:00
0a128cbb3c Merge pull request 'lavoce' (#11) from lavoce into main
Reviewed-on: #11
2025-10-28 09:47:36 +00:00
bd4ab26680 Merge pull request 'lavoce' (#10) from lavoce into main
Reviewed-on: #10
2025-10-28 04:48:54 +00:00
3e728a1ff5 Merge pull request 'lavoce' (#9) from lavoce into main
Reviewed-on: #9
2025-10-27 03:48:58 +00:00
9db143972e Merge pull request 'lavoce' (#8) from lavoce into main
Reviewed-on: #8
2025-10-25 09:19:36 +00:00
029ea269a7 Merge pull request 'lavoce' (#7) from lavoce into main
Reviewed-on: #7
2025-10-24 05:43:32 +00:00
4cdaa042da Merge pull request 'lavoce' (#6) from lavoce into main
Reviewed-on: #6
2025-10-23 07:28:11 +00:00
56af2a16c0 Merge pull request 'update file svg to src assets' (#5) from lavoce into main
Reviewed-on: #5
2025-10-23 04:52:13 +00:00
deadf2ffb4 Merge pull request 'lavoce' (#4) from lavoce into main
Reviewed-on: #4
2025-10-23 04:27:57 +00:00
4da80c7089 Merge pull request 'lavoce' (#3) from lavoce into main
Reviewed-on: #3
2025-10-22 05:59:57 +00:00
56e3ce78a6 Merge pull request 'lavoce' (#2) from lavoce into main
Reviewed-on: #2
2025-10-20 04:06:02 +00:00
7c2a019dd2 Merge pull request 'lavoce' (#1) from lavoce into main
Reviewed-on: #1
2025-09-17 08:39:35 +00:00
11 changed files with 88 additions and 804 deletions

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
*.config
node_modules
dist

View File

@@ -3,7 +3,7 @@
<system.webServer>
<rewrite>
<rules>
<rule name="reactViteSypiu">
<rule name="CallOfDuty">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />

View File

@@ -91,25 +91,6 @@ const searchData = async (queryParams) => {
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 {
getAllNotification,
getNotificationById,
@@ -121,6 +102,4 @@ export {
resendChatByUser,
resendChatAllUser,
searchData,
reminderMonthly,
reminderCustom,
};

View File

@@ -1,52 +0,0 @@
import { Input } from "antd";
import { useEffect, useState, useRef } from "react";
const InputNumber = ({
name,
value = 0,
onChange,
...props
}) => {
const [inputValue, setInputValue] = useState(
value !== null && value !== undefined ? value.toString() : ''
);
useEffect(() => {
const newValue = value !== null && value !== undefined ? value.toString() : '';
setInputValue(newValue);
}, [value]);
const handleChange = (e) => {
const rawValue = e.target.value;
if (/^\d*$/.test(rawValue)) {
setInputValue(rawValue);
const numValue = rawValue === '' ? 0 : parseInt(rawValue, 10);
// Kirim event object sintetis yang konsisten dengan handler biasa
onChange?.({
target: {
name: name,
value: numValue
}
});
}
};
const handleFocus = (e) => {
// Pilih semua teks saat fokus (opsional)
e.target.select();
};
return (
<Input
{...props}
name={name} // Teruskan name ke input
value={inputValue}
onChange={handleChange}
onFocus={handleFocus}
style={{ textAlign: 'left' }}
/>
);
};
export { InputNumber };

View File

@@ -103,12 +103,14 @@ const handleBoolean = (svg, el, value) => {
const els2 = svg.querySelectorAll(`#${id2}`);
if (!els1 || !els2) return;
// balik arah khusus untuk airdryerA
if (dryerIds.slice(0, 2).includes(el.id)) value = !value;
els1.forEach(el => {
el.style.fill = value ? 'rgb(216,216,216)' : 'rgb(255, 204, 63)';
el.style.fill = value ? 'rgb(255, 204, 63)' : 'rgb(216,216,216)';
});
els2.forEach(el => {
el.style.fill = value ? 'rgb(255, 204, 63)' : 'rgb(216,216,216)';
el.style.fill = value ? 'rgb(216,216,216)' : 'rgb(255, 204, 63)';
});
};

View File

@@ -20,19 +20,6 @@ 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 = ({
icon,
title,
@@ -80,4 +67,4 @@ const QuestionConfirmSubmit = ({ icon, title, message, onConfirm, onCancel }) =>
});
};
export { NotifAlert, NotifOk, NotifSmall, NotifConfirmDialog, QuestionConfirmSubmit };
export { NotifAlert, NotifOk, NotifConfirmDialog, QuestionConfirmSubmit };

View File

@@ -13,7 +13,6 @@ const DetailContact = memo(function DetailContact(props) {
id: '',
name: '',
phone: '',
email: '',
is_active: true,
};
@@ -35,22 +34,6 @@ const DetailContact = memo(function DetailContact(props) {
[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'
: '',
}));
}
};
@@ -68,7 +51,6 @@ const DetailContact = memo(function DetailContact(props) {
const validationRules = [
{ field: 'name', label: 'Contact Name', required: true },
{ field: 'phone', label: 'Phone', required: true },
{ field: 'email', label: 'Email', required: false },
];
if (
@@ -102,23 +84,10 @@ const DetailContact = memo(function DetailContact(props) {
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 {
const contactData = {
contact_name: formData.name,
contact_phone: formData.phone.replace(/[\s\-\(\)]/g, ''), // Clean phone number
email: formData.email?.trim(),
is_active: formData.is_active,
};
@@ -165,7 +134,6 @@ const DetailContact = memo(function DetailContact(props) {
setFormData({
name: props.selectedData.contact_name || props.selectedData.name,
phone: props.selectedData.contact_phone || props.selectedData.phone,
email: props.selectedData.contact_email || props.selectedData.email,
is_active:
props.selectedData.is_active || props.selectedData.status === 'active',
});
@@ -173,7 +141,6 @@ const DetailContact = memo(function DetailContact(props) {
setFormData({
name: '',
phone: '',
email: '',
is_active: true,
});
}
@@ -282,19 +249,6 @@ const DetailContact = memo(function DetailContact(props) {
style={{ color: formData.is_active ? '#000000' : '#ff4d4f' }}
/>
</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 */}
{/* <div style={{ marginBottom: 12 }}>
<Text strong>Contact Type</Text>

View File

@@ -1,22 +1,9 @@
import React, { useEffect, useState } from 'react';
import {
Modal,
Input,
Divider,
Typography,
Switch,
Button,
ConfigProvider,
Select,
DatePicker,
} from 'antd';
import { NotifAlert, NotifOk, NotifSmall } from '../../../../components/Global/ToastNotif';
import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, Select } from 'antd';
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
import { createDevice, updateDevice } from '../../../../api/master-device';
import { reminderMonthly, reminderCustom } from '../../../../api/notification';
import { getAllBrands } from '../../../../api/master-brand';
import { validateRun } from '../../../../Utils/validate';
import { toApiDateFormatter } from '../../../../components/Global/Formatter';
import dayjs from 'dayjs';
const { Text } = Typography;
const { TextArea } = Input;
@@ -36,12 +23,7 @@ const DetailDevice = (props) => {
device_location: '',
device_description: '',
ip_address: '',
listen_channel: null,
reminder_at: null,
reminder_at_monthly: null,
start_date: null,
end_date: null,
period: null,
listen_channel: '',
};
const [formData, setFormData] = useState(defaultData);
@@ -85,26 +67,12 @@ const DetailDevice = (props) => {
ip_address: formData.ip_address,
brand_id: formData.brand_id,
listen_channel: formData.listen_channel,
listen_channel_reminder: formData.listen_channel_reminder,
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);
const response = formData.device_id
? await updateDevice(formData.device_id, 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
if (response && (response.statusCode === 200 || response.statusCode === 201)) {
const deviceName = response.data?.device_name || formData.device_name;
@@ -125,25 +93,6 @@ const DetailDevice = (props) => {
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) {
console.error('Save Device Error:', error);
NotifAlert({
@@ -179,13 +128,6 @@ const DetailDevice = (props) => {
});
};
const handleDateChange = (field, value) => {
setFormData({
...formData,
[field]: value,
});
};
// Fungsi untuk mengambil daftar brand
const fetchBrands = async () => {
setLoadingBrands(true);
@@ -206,56 +148,6 @@ 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(() => {
if (props.showModal && (props.actionMode === 'add' || props.actionMode === 'edit')) {
fetchBrands();
@@ -277,8 +169,8 @@ const DetailDevice = (props) => {
props.actionMode === 'add'
? 'Tambah'
: props.actionMode === 'preview'
? 'Preview'
: 'Edit'
? 'Preview'
: 'Edit'
} Device`}
open={props.showModal}
// open={true}
@@ -326,10 +218,9 @@ const DetailDevice = (props) => {
</React.Fragment>,
]}
>
<Divider />
{formData && (
<div>
<div style={{ marginBottom: 12 }}>
<div>
<div>
<Text strong>Device Status</Text>
</div>
@@ -356,7 +247,7 @@ const DetailDevice = (props) => {
</div>
</div>
</div>
{/* <Divider style={{ margin: '12px 0' }} /> */}
<Divider style={{ margin: '12px 0' }} />
<div hidden>
<Text strong>Device ID</Text>
<Input
@@ -439,149 +330,15 @@ const DetailDevice = (props) => {
readOnly={props.readOnly}
/>
</div>
<div style={{ marginBottom: 12 }}></div>
<div style={{ marginBottom: 12 }}></div>
<div style={{ display: 'flex', gap: 16 }}>
<div style={{ flex: 1 }}>
{' '}
<Text strong>Channel Error</Text>
<Input
name="listen_channel"
value={formData.listen_channel}
onChange={handleInputChange}
placeholder="Enter Listen Channel"
readOnly={props.readOnly}
/>
</div>
<div style={{ flex: 1 }}>
<Text strong>Channel Reminder</Text>
<Input
name="listen_channel_reminder"
value={formData.listen_channel_reminder}
onChange={handleInputChange}
placeholder="Enter Listen Channel Reminder"
readOnly={props.readOnly}
/>
</div>
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Reminder At(Yearly)</Text>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<DatePicker
value={
formData.reminder_at
? dayjs(formData.reminder_at) // ✅ langsung parse ISO
: null
}
onChange={(date) =>
handleDateChange(
'reminder_at',
date ? date.toISOString() : null
)
}
format="DD-MM-YYYY HH:mm"
showTime={{ format: 'HH:mm' }}
style={{ flex: 1 }}
/>
<Button
danger
onClick={() =>
setFormData((prev) => ({
...prev,
reminder_at: null,
}))
}
>
Clear
</Button>
</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>
<Text strong>Listen Channel</Text>
<Input
name="listen_channel"
value={formData.listen_channel}
onChange={handleInputChange}
placeholder="Enter Listen Channel"
readOnly={props.readOnly}
/>
</div>
<div style={{ marginBottom: 12 }}>
<Text strong>Device Description</Text>

View File

@@ -11,7 +11,6 @@ import {
Row,
Col,
Image,
Checkbox,
} from 'antd';
import { PlusOutlined, EyeOutlined, DeleteOutlined } from '@ant-design/icons';
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
@@ -38,68 +37,9 @@ const DetailSparepart = (props) => {
const [previewTitle, setPreviewTitle] = useState('');
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 = [
{
key: 1,
value: 1,
text: '1 Year',
},
{
key: 2,
value: 2,
text: '2 Year',
},
{
key: 3,
value: 3,
text: '3 Year',
},
{
key: 4,
value: 4,
text: '4 Year',
},
{
key: 5,
value: 5,
text: '5 Year',
},
{
key: 6,
value: 6,
text: '6 Year',
},
];
const [checkedListM, setCheckedListM] = useState([]);
const [checkAllM, setCheckAllM] = useState(false);
const [indeterminateM, setIndeterminateM] = useState(false);
const [checkedList, setCheckedList] = useState([]);
const [checkAll, setCheckAll] = useState(false);
const [indeterminate, setIndeterminate] = useState(false);
const defaultData = {
sparepart_id: '',
sparepart_name: '',
sparepart_number: '',
sparepart_description: '',
sparepart_model: '',
sparepart_item_type: null,
@@ -296,10 +236,6 @@ const DetailSparepart = (props) => {
payload.sparepart_foto = imageUrl;
}
payload.sparepart_number = formData.sparepart_number;
payload.sparepart_yearly = checkedList;
payload.sparepart_monthly = checkedListM;
// console.log('Sending payload:', payload);
const response = formData.sparepart_id
@@ -346,36 +282,6 @@ const DetailSparepart = (props) => {
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) => {
setCheckedList(list);
setIndeterminate(!!list.length && list.length < optionsYearly.length);
setCheckAll(list.length === optionsYearly.length);
};
const onCheckAllChange = (e) => {
const checked = e.target.checked;
setCheckedList(checked ? optionsYearly.map((item) => item.value) : []);
setIndeterminate(false);
setCheckAll(checked);
};
useEffect(() => {
if (props.selectedData) {
setFormData(props.selectedData);
@@ -412,19 +318,9 @@ const DetailSparepart = (props) => {
} else {
setFileList([]);
}
setCheckedListM(JSON.parse(props.selectedData.sparepart_monthly));
setCheckedList(JSON.parse(props.selectedData.sparepart_yearly));
} else {
setFormData(defaultData);
setFileList([]);
setIndeterminateM(false);
setCheckedListM([]);
setCheckAllM(false);
setIndeterminate(false);
setCheckedList([]);
setCheckAll(false);
}
}, [props.showModal, props.selectedData, props.actionMode]);
@@ -437,13 +333,12 @@ const DetailSparepart = (props) => {
return (
<Modal
width="40rem"
title={`${
props.actionMode === 'add'
? 'Tambah'
: props.actionMode === 'preview'
? 'Preview'
: 'Edit'
? 'Preview'
: 'Edit'
} Sparepart`}
open={props.showModal}
onCancel={handleCancel}
@@ -568,7 +463,7 @@ const DetailSparepart = (props) => {
<div
style={{
width: '180px', // Fixed width for square
height: '240px',
height: '180px',
border: '1px dashed #d9d9d9',
borderRadius: '8px',
display: 'flex',
@@ -590,17 +485,6 @@ const DetailSparepart = (props) => {
{/* Kolom untuk field lainnya */}
<Col span={14}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Text strong>Part Number</Text>
<Text style={{ color: 'red' }}> *</Text>
<Input
name="sparepart_number"
value={formData.sparepart_number}
onChange={handleInputChange}
placeholder="Enter Sparepart Number"
readOnly={props.readOnly}
/>
</Col>
<Col span={24}>
<Text strong>Sparepart Name</Text>
<Text style={{ color: 'red' }}> *</Text>
@@ -676,78 +560,7 @@ const DetailSparepart = (props) => {
/>
</Col>
</Row>
<ConfigProvider
theme={{
token: {
colorPrimary: '#23A55A',
},
}}
>
{/* Yearly */}
<Row style={{ marginTop: 16 }}>
<Col>
<Checkbox
indeterminate={indeterminate}
onChange={onCheckAllChange}
checked={checkAll}
>
{checkAll ? 'Unselect All Years' : 'Select All Years'}
</Checkbox>
</Col>
</Row>
<Row style={{ marginTop: 10 }}>
<Col span={24}>
<Checkbox.Group
value={checkedList}
onChange={onChangeCheckbox}
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
}}
>
{optionsYearly.map((item) => (
<Checkbox key={item.key} value={item.value}>
{item.text}
</Checkbox>
))}
</Checkbox.Group>
</Col>
</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>
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
<Col span={24}>
<Text strong>Description</Text>

View File

@@ -36,12 +36,6 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
key: 'sparepart_name',
width: '20%',
},
{
title: 'Sparepart Number',
dataIndex: 'sparepart_number',
key: 'sparepart_number',
width: '20%',
},
{
title: 'Description',
dataIndex: 'sparepart_description',
@@ -283,8 +277,8 @@ const ListSparepart = memo(function ListSparepart(props) {
onStockUpdate={doFilter}
onGetData={(data) => {
if(data && data.length > 0) {
// console.log('Sample sparepart data from API:', data[0]);
// console.log('Available fields:', Object.keys(data[0] || {}));
console.log('Sample sparepart data from API:', data[0]);
console.log('Available fields:', Object.keys(data[0] || {}));
}
}} // Log untuk debugging field-field yang tersedia
/>

View File

@@ -102,8 +102,6 @@ const transformNotificationData = (apiData) => {
brand_name: apiData.brand_name,
},
users: apiData.users || [],
is_reminder: apiData.is_reminder || false,
spareparts_reminder: apiData.spareparts_reminder || [],
};
};
@@ -355,7 +353,7 @@ const NotificationDetailTab = (props) => {
}}
>
<Typography.Title level={4} style={{ margin: 0, color: '#262626' }}>
Notification Detail
Error Notification Detail
</Typography.Title>
</div>
</div>
@@ -363,61 +361,59 @@ const NotificationDetailTab = (props) => {
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Row gutter={[8, 8]}>
{/* Kolom Kiri: Data Kompresor */}
{notification.is_reminder == false && (
<Col xs={24} lg={8}>
<Card
size="small"
style={{ height: '100%', borderColor: '#d4380d' }}
bodyStyle={{ padding: '16px' }}
<Col xs={24} lg={8}>
<Card
size="small"
style={{ height: '100%', borderColor: '#d4380d' }}
bodyStyle={{ padding: '16px' }}
>
<Space
direction="vertical"
size="large"
style={{ width: '100%' }}
>
<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' }}
<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',
}}
>
Date & Time
</Text>
<div>{notification.timestamp}</div>
</div>
</Space>
</Card>
</Col>
)}
<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 */}
<Col xs={24} lg={8}>
<Card
@@ -462,159 +458,6 @@ const NotificationDetailTab = (props) => {
</Card>
</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 */}
<Col xs={24} lg={8}>
<Card title="User History" size="small" style={{ height: '100%' }}>
@@ -702,7 +545,10 @@ const NotificationDetailTab = (props) => {
<Row gutter={[8, 8]}>
<Col xs={24} md={8}>
<div>
<Card hoverable bodyStyle={{ padding: '12px' }}>
<Card
hoverable
bodyStyle={{ padding: '12px'}}
>
<Space>
<BookOutlined
style={{ fontSize: '16px', color: '#1890ff' }}
@@ -854,7 +700,10 @@ const NotificationDetailTab = (props) => {
</Col>
<Col xs={24} md={8}>
<div>
<Card hoverable bodyStyle={{ padding: '12px' }}>
<Card
hoverable
bodyStyle={{ padding: '12px'}}
>
<Space>
<ToolOutlined
style={{ fontSize: '16px', color: '#1890ff' }}
@@ -979,7 +828,7 @@ const NotificationDetailTab = (props) => {
</Col>
<Col xs={24} md={8}>
<div>
<Card bodyStyle={{ padding: '12px' }}>
<Card bodyStyle={{ padding: '12px'}}>
<Space>
<HistoryOutlined
style={{ fontSize: '16px', color: '#1890ff' }}