Plant Sub Section Name
*
{
);
};
-export default DetailPlantSection;
\ No newline at end of file
+export default DetailPlantSection;
diff --git a/src/pages/master/shift/IndexShift.jsx b/src/pages/master/shift/IndexShift.jsx
index 4e860d3..78eb228 100644
--- a/src/pages/master/shift/IndexShift.jsx
+++ b/src/pages/master/shift/IndexShift.jsx
@@ -1,83 +1,76 @@
-import { useState, useEffect } from 'react';
+import React, { memo, useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
import ListShift from './component/ListShift';
import DetailShift from './component/DetailShift';
-import { getAllShift } from '../../../api/master-shift';
+import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb';
+import { Typography } from 'antd';
+
+const { Text } = Typography;
+
+const IndexShift = memo(function IndexShift() {
+ const navigate = useNavigate();
+ const { setBreadcrumbItems } = useBreadcrumb();
-const IndexShift = () => {
const [actionMode, setActionMode] = useState('list');
const [selectedData, setSelectedData] = useState(null);
- const [shiftData, setShiftData] = useState([]);
- const [loading, setLoading] = useState(false);
+ const [readOnly, setReadOnly] = useState(false);
+ const [showModal, setShowModal] = useState(false);
- const fetchData = async () => {
- setLoading(true);
- try {
- const localData = localStorage.getItem('shiftData');
- if (localData) {
- setShiftData(JSON.parse(localData));
- } else {
- const response = await getAllShift();
- if (response.data) {
- setShiftData(response.data.data);
- localStorage.setItem('shiftData', JSON.stringify(response.data.data));
- }
- }
- } catch (error) {
- console.error("Error fetching shift data:", error);
+ const setMode = (param) => {
+ setActionMode(param);
+ switch (param) {
+ case 'add':
+ setReadOnly(false);
+ setShowModal(true);
+ break;
+
+ case 'edit':
+ setReadOnly(false);
+ setShowModal(true);
+ break;
+
+ case 'preview':
+ setReadOnly(true);
+ setShowModal(true);
+ break;
+
+ default:
+ setShowModal(false);
+ break;
}
- setLoading(false);
};
useEffect(() => {
- fetchData();
+ const token = localStorage.getItem('token');
+ if (token) {
+ setBreadcrumbItems([
+ { title:
• Master },
+ { title:
Shift }
+ ]);
+ } else {
+ navigate('/signin');
+ }
}, []);
- const handleAddShift = (newShift) => {
- const newData = { ...newShift, id: Date.now() }; // Simulate adding an ID
- const updatedData = [newData, ...shiftData];
- setShiftData(updatedData);
- localStorage.setItem('shiftData', JSON.stringify(updatedData));
- setActionMode('list');
- };
-
- const handleUpdateShift = (updatedShift) => {
- const updatedData = shiftData.map(shift => shift.id === updatedShift.id ? updatedShift : shift);
- setShiftData(updatedData);
- localStorage.setItem('shiftData', JSON.stringify(updatedData));
- setActionMode('list');
- };
-
- const handleDeleteShift = (id) => {
- const updatedData = shiftData.filter(shift => shift.id !== id);
- setShiftData(updatedData);
- localStorage.setItem('shiftData', JSON.stringify(updatedData));
- };
-
-
return (
- <>
- {actionMode === 'list' && (
-
- )}
- {(actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') && (
-
- )}
- >
+
+
+
+
);
-};
+});
export default IndexShift;
diff --git a/src/pages/master/shift/component/DetailShift.jsx b/src/pages/master/shift/component/DetailShift.jsx
index d0b354b..86d1b8d 100644
--- a/src/pages/master/shift/component/DetailShift.jsx
+++ b/src/pages/master/shift/component/DetailShift.jsx
@@ -2,18 +2,22 @@ import { useEffect, useState } from 'react';
import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider } from 'antd';
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
import { createShift, updateShift } from '../../../../api/master-shift';
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+
+dayjs.extend(utc);
const { Text } = Typography;
const DetailShift = (props) => {
const [confirmLoading, setConfirmLoading] = useState(false);
- const readOnly = props.actionMode === 'preview';
const defaultData = {
- id: '',
- nama_shift: '',
- jam_shift: '',
- status: true, // default to active
+ shift_id: '',
+ shift_name: '',
+ start_time: '',
+ end_time: '',
+ is_active: true,
};
const [FormData, setFormData] = useState(defaultData);
@@ -26,68 +30,225 @@ const DetailShift = (props) => {
const handleSave = async () => {
setConfirmLoading(true);
- if (!FormData.nama_shift) {
- NotifOk({ icon: 'warning', title: 'Peringatan', message: 'Kolom Nama Shift Tidak Boleh Kosong' });
+ // Validasi required fields
+ if (!FormData.shift_name || FormData.shift_name.trim() === '') {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: 'Kolom Nama Shift Tidak Boleh Kosong',
+ });
setConfirmLoading(false);
return;
}
- if (!FormData.jam_shift) {
- NotifOk({ icon: 'warning', title: 'Peringatan', message: 'Kolom Jam Shift Tidak Boleh Kosong' });
+ if (!FormData.start_time || FormData.start_time.trim() === '') {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: 'Kolom Jam Mulai Tidak Boleh Kosong',
+ });
setConfirmLoading(false);
return;
}
- const payload = {
- nama_shift: FormData.nama_shift,
- jam_shift: FormData.jam_shift,
- status: FormData.status,
- };
+ if (!FormData.end_time || FormData.end_time.trim() === '') {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: 'Kolom Jam Selesai Tidak Boleh Kosong',
+ });
+ setConfirmLoading(false);
+ return;
+ }
+
+ // Validate time format
+ const timePattern = /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/;
+ if (!timePattern.test(FormData.start_time)) {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message:
+ 'Format Jam Mulai tidak valid. Gunakan format HH:mm atau HH:mm:ss (contoh: 08:00)',
+ });
+ setConfirmLoading(false);
+ return;
+ }
+
+ if (!timePattern.test(FormData.end_time)) {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message:
+ 'Format Jam Selesai tidak valid. Gunakan format HH:mm atau HH:mm:ss (contoh: 17:00)',
+ });
+ setConfirmLoading(false);
+ return;
+ }
try {
- if (FormData.id) {
- props.onUpdate(payload);
+ if (FormData.shift_id) {
+ // Update existing shift
+ const payload = {
+ shift_name: FormData.shift_name,
+ start_time: FormData.start_time,
+ end_time: FormData.end_time,
+ is_active: FormData.is_active,
+ };
+
+ const response = await updateShift(FormData.shift_id, payload);
+ console.log('updateShift response:', response);
+
+ if (response.statusCode === 200) {
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Shift "${FormData.shift_name}" berhasil diubah.`,
+ });
+ props.setActionMode('list');
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response.message || 'Gagal mengubah data Shift.',
+ });
+ }
} else {
- props.onAdd(payload);
+ // Create new shift
+ const payload = {
+ shift_name: FormData.shift_name,
+ start_time: FormData.start_time,
+ end_time: FormData.end_time,
+ is_active: FormData.is_active,
+ };
+
+ const response = await createShift(payload);
+ console.log('createShift response:', response);
+
+ if (response.statusCode === 200 || response.statusCode === 201) {
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Shift "${FormData.shift_name}" berhasil ditambahkan.`,
+ });
+ props.setActionMode('list');
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response.message || 'Gagal menambahkan data Shift.',
+ });
+ }
}
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: `Data Shift "${payload.nama_shift}" berhasil ${FormData.id ? 'diubah' : 'ditambahkan'}.`,
- });
} catch (error) {
console.error('Save Shift Error:', error);
NotifAlert({
icon: 'error',
title: 'Error',
- message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.',
+ message: error.message || 'Terjadi kesalahan saat menyimpan data.',
});
}
setConfirmLoading(false);
};
- const handleInputChange = (e) => {
- const { name, value } = e.target;
- setFormData({ ...FormData, [name]: value });
+ // Helper function to format time input
+ const formatTimeInput = (value) => {
+ if (!value) return value;
+
+ // Remove any whitespace
+ value = value.trim();
+
+ // If user inputs single digit hour like "8:00", convert to "08:00"
+ const timeRegex = /^(\d{1,2}):(\d{2})(:\d{2})?$/;
+ const match = value.match(timeRegex);
+
+ if (match) {
+ const hours = match[1].padStart(2, '0');
+ const minutes = match[2];
+ const seconds = match[3] || '';
+ return `${hours}:${minutes}${seconds}`;
+ }
+
+ return value;
};
- const handleStatusToggle = (checked) => {
- setFormData({ ...FormData, status: checked });
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+
+ // Just set the value without formatting during typing
+ setFormData({
+ ...FormData,
+ [name]: value,
+ });
+ };
+
+ // Format time when user leaves the input field (onBlur)
+ const handleTimeBlur = (e) => {
+ const { name, value } = e.target;
+
+ if (name === 'start_time' || name === 'end_time') {
+ const formattedValue = formatTimeInput(value);
+ setFormData({
+ ...FormData,
+ [name]: formattedValue,
+ });
+ }
+ };
+
+ const handleStatusToggle = (isChecked) => {
+ setFormData({
+ ...FormData,
+ is_active: isChecked,
+ });
+ };
+
+ // Helper function to extract time from ISO timestamp using dayjs
+ const extractTime = (timeString) => {
+ if (!timeString) return '';
+
+ // If it's ISO timestamp like "1970-01-01T08:00:00.000Z"
+ if (timeString.includes('T')) {
+ return dayjs.utc(timeString).format('HH:mm');
+ }
+
+ // If it's already in HH:mm:ss format, remove seconds
+ if (timeString.includes(':')) {
+ const parts = timeString.split(':');
+ return `${parts[0]}:${parts[1]}`;
+ }
+
+ return timeString;
};
useEffect(() => {
- if (props.selectedData) {
- setFormData(props.selectedData);
- } else {
- setFormData(defaultData);
+ const token = localStorage.getItem('token');
+ if (token) {
+ if (props.selectedData != null) {
+ // Only set fields that are in defaultData
+ const filteredData = {
+ shift_id: props.selectedData.shift_id || '',
+ shift_name: props.selectedData.shift_name || '',
+ start_time: extractTime(props.selectedData.start_time) || '',
+ end_time: extractTime(props.selectedData.end_time) || '',
+ is_active: props.selectedData.is_active ?? true,
+ };
+ setFormData(filteredData);
+ } else {
+ setFormData(defaultData);
+ }
}
- }, [props.actionMode, props.selectedData]);
+ }, [props.showModal]);
return (
@@ -100,7 +261,6 @@ const DetailShift = (props) => {
defaultColor: '#23A55A',
defaultBorderColor: '#23A55A',
defaultHoverColor: '#23A55A',
- defaultHoverBorderColor: '#23A55A',
},
},
}}
@@ -123,7 +283,7 @@ const DetailShift = (props) => {
},
}}
>
- {!readOnly && (
+ {!props.readOnly && (
Simpan
@@ -134,7 +294,8 @@ const DetailShift = (props) => {
>
{FormData && (
-
+ {/* Status Toggle */}
+
Status
@@ -147,42 +308,66 @@ const DetailShift = (props) => {
>
- {FormData.status === true ? 'Active' : 'Inactive'}
+ {FormData.is_active === true ? 'Active' : 'Inactive'}
-
Nama Shift
*
- Jam Shift
+ Jam Mulai
*
+
+ Contoh: 08:00 atau 08:00:00
+
+
+
+ Jam Selesai
+ *
+
+
+ Contoh: 17:00 atau 17:00:00
+
)}
@@ -190,4 +375,4 @@ const DetailShift = (props) => {
);
};
-export default DetailShift;
\ No newline at end of file
+export default DetailShift;
diff --git a/src/pages/master/shift/component/ListShift.jsx b/src/pages/master/shift/component/ListShift.jsx
index 303c76f..1a2e07e 100644
--- a/src/pages/master/shift/component/ListShift.jsx
+++ b/src/pages/master/shift/component/ListShift.jsx
@@ -1,183 +1,305 @@
import React, { memo, useState, useEffect } from 'react';
-import { Button, Col, Row, Input, ConfigProvider, Card, Tag, Table, Space } from 'antd';
+import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input } from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
- SearchOutlined,
EyeOutlined,
- SyncOutlined,
+ SearchOutlined,
} from '@ant-design/icons';
-import { NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
+import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
import { useNavigate } from 'react-router-dom';
+import TableList from '../../../../components/Global/TableList';
+import { getAllShift, deleteShift } from '../../../../api/master-shift';
-const ListShift = (props) => {
+// Helper function to extract time from ISO timestamp
+const extractTime = (timeString) => {
+ if (!timeString) return '-';
+
+ // If it's ISO timestamp like "1970-01-01T08:00:00.000Z"
+ if (timeString.includes('T')) {
+ const date = new Date(timeString);
+ const hours = String(date.getUTCHours()).padStart(2, '0');
+ const minutes = String(date.getUTCMinutes()).padStart(2, '0');
+ return `${hours}:${minutes}`;
+ }
+
+ // If it's already in HH:mm or HH:mm:ss format
+ if (timeString.includes(':')) {
+ const parts = timeString.split(':');
+ return `${parts[0]}:${parts[1]}`; // Return HH:mm only
+ }
+
+ return timeString;
+};
+
+const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
+ {
+ title: 'No',
+ key: 'no',
+ width: '5%',
+ align: 'center',
+ render: (_, __, index) => index + 1,
+ },
+ {
+ title: 'Nama Shift',
+ dataIndex: 'shift_name',
+ key: 'shift_name',
+ width: '20%',
+ },
+ {
+ title: 'Jam Mulai',
+ dataIndex: 'start_time',
+ key: 'start_time',
+ width: '15%',
+ render: (time) => extractTime(time),
+ },
+ {
+ title: 'Jam Selesai',
+ dataIndex: 'end_time',
+ key: 'end_time',
+ width: '15%',
+ render: (time) => extractTime(time),
+ },
+ {
+ title: 'Status',
+ dataIndex: 'is_active',
+ key: 'is_active',
+ width: '10%',
+ align: 'center',
+ render: (_, { is_active }) => {
+ const color = is_active ? 'green' : 'red';
+ const text = is_active ? 'Active' : 'Inactive';
+ return (
+
+ {text}
+
+ );
+ },
+ },
+ {
+ title: 'Aksi',
+ key: 'aksi',
+ align: 'center',
+ width: '20%',
+ render: (_, record) => (
+
+ }
+ onClick={() => showPreviewModal(record)}
+ style={{
+ color: '#1890ff',
+ borderColor: '#1890ff',
+ }}
+ />
+ }
+ onClick={() => showEditModal(record)}
+ style={{
+ color: '#faad14',
+ borderColor: '#faad14',
+ }}
+ />
+ }
+ onClick={() => showDeleteDialog(record)}
+ style={{
+ borderColor: '#ff4d4f',
+ }}
+ />
+
+ ),
+ },
+];
+
+const ListShift = memo(function ListShift(props) {
+ const [trigerFilter, setTrigerFilter] = useState(false);
+
+ const defaultFilter = {
+ criteria: '',
+ };
+ const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
const [searchValue, setSearchValue] = useState('');
+
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('token');
- if (!token) {
+ if (token) {
+ if (props.actionMode == 'list') {
+ doFilter();
+ }
+ } else {
navigate('/signin');
}
- }, [navigate]);
+ }, [props.actionMode]);
- const handleSearch = (value) => {
- // This will be handled by the parent component if server-side search is needed
- console.log('Search value:', value);
+ const doFilter = () => {
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleSearch = () => {
+ setFormDataFilter((prev) => ({ ...prev, criteria: searchValue }));
+ doFilter();
};
const handleSearchClear = () => {
setSearchValue('');
- // This will be handled by the parent component if server-side search is needed
+ setFormDataFilter((prev) => ({ ...prev, criteria: '' }));
+ doFilter();
};
- const showPreviewModal = (record) => {
- props.setSelectedData(record);
+ const showPreviewModal = (param) => {
+ props.setSelectedData(param);
props.setActionMode('preview');
};
- const showEditModal = (record) => {
- props.setSelectedData(record);
+ const showEditModal = (param = null) => {
+ props.setSelectedData(param);
props.setActionMode('edit');
};
- const showAddModal = () => {
- props.setSelectedData(null);
+ const showAddModal = (param = null) => {
+ props.setSelectedData(param);
props.setActionMode('add');
};
- const showDeleteDialog = (record) => {
+ const showDeleteDialog = (param) => {
NotifConfirmDialog({
icon: 'question',
title: 'Konfirmasi',
- message: `Apakah anda yakin ingin menghapus data shift "${record.nama_shift}"?`,
- onConfirm: () => props.onDelete(record.id),
+ message: `Apakah anda yakin hapus data "${param.shift_name}" ?`,
+ onConfirm: () => handleDelete(param),
+ onCancel: () => props.setSelectedData(null),
});
};
- const columns = [
- {
- title: 'No',
- key: 'no',
- width: '5%',
- align: 'center',
- render: (_, __, index) => index + 1,
- },
- {
- title: 'Nama Shift',
- dataIndex: 'nama_shift',
- key: 'nama_shift',
- },
- {
- title: 'Jam Shift',
- dataIndex: 'jam_shift',
- key: 'jam_shift',
- },
- {
- title: 'Status',
- dataIndex: 'status',
- key: 'status',
- align: 'center',
- render: (status) => (
-
- {status ? 'Active' : 'Inactive'}
-
- ),
- },
- {
- title: 'Aksi',
- key: 'action',
- align: 'center',
- width: '15%',
- render: (_, record) => (
-
- }
- onClick={() => showPreviewModal(record)}
- style={{
- color: '#1890ff',
- borderColor: '#1890ff',
- }}
- />
- }
- onClick={() => showEditModal(record)}
- style={{
- color: '#faad14',
- borderColor: '#faad14',
- }}
- />
- }
- onClick={() => showDeleteDialog(record)}
- style={{
- borderColor: '#ff4d4f',
- }}
- />
-
- ),
- },
- ];
+ const handleDelete = async (param) => {
+ try {
+ const response = await deleteShift(param.shift_id);
+ console.log('deleteShift response:', response);
- const filteredData = props.shiftData.filter(item =>
- item.nama_shift.toLowerCase().includes(searchValue.toLowerCase())
- );
+ if (response.statusCode === 200) {
+ NotifAlert({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Shift "${param.shift_name}" berhasil dihapus.`,
+ });
+ // Refresh table
+ doFilter();
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response.message || 'Gagal menghapus data Shift.',
+ });
+ }
+ } catch (error) {
+ console.error('Delete Shift Error:', error);
+ NotifAlert({
+ icon: 'error',
+ title: 'Error',
+ message: error.message || 'Terjadi kesalahan saat menghapus data.',
+ });
+ }
+ };
+
+ // Function untuk dipanggil dari DetailShift setelah create/update
+ const refreshData = () => {
+ doFilter();
+ };
+
+ // Pass refresh function to props
+ if (props.setRefreshData) {
+ props.setRefreshData(refreshData);
+ }
return (
-
-
-
- setSearchValue(e.target.value)}
- onSearch={handleSearch}
- allowClear={{
- clearIcon: x ,
- }}
- enterButton={ } style={{ backgroundColor: '#23A55A', borderColor: '#23A55A' }}>Cari}
- size="large"
- />
-
-
-
- }
- onClick={showAddModal}
- size="large"
- >
- Tambah Shift
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {
+ const value = e.target.value;
+ setSearchValue(value);
+ // Auto search when clearing by backspace/delete
+ if (value === '') {
+ handleSearchClear();
+ }
+ }}
+ onSearch={handleSearch}
+ allowClear
+ onClear={handleSearchClear}
+ enterButton={
+ }
+ style={{
+ backgroundColor: '#23A55A',
+ borderColor: '#23A55A',
+ }}
+ >
+ Search
+
+ }
+ size="large"
+ />
+
+
+
+
+ }
+ onClick={() => showAddModal()}
+ size="large"
+ >
+ Tambah Data
+
+
+
+
+
+
+
+
+
+
+
+
);
-};
+});
-export default memo(ListShift);
\ No newline at end of file
+export default ListShift;
\ No newline at end of file
diff --git a/src/pages/master/status/IndexStatus.jsx b/src/pages/master/status/IndexStatus.jsx
index 86e1421..61cc608 100644
--- a/src/pages/master/status/IndexStatus.jsx
+++ b/src/pages/master/status/IndexStatus.jsx
@@ -5,87 +5,23 @@ import { Typography } from 'antd';
import ListStatus from './component/ListStatus';
import DetailStatus from './component/DetailStatus';
-import { NotifConfirmDialog, NotifAlert } from '../../../components/Global/ToastNotif';
-
const { Text } = Typography;
-// Mock Data
-const initialData = [
- {
- key: '3',
- statusCode: 3,
- statusName: 'Done',
- description: 'Indicates that the process is complete.',
- },
- {
- key: '1',
- statusCode: 1,
- statusName: 'Warning',
- description: 'Indicates a warning condition.',
- },
- {
- key: '2',
- statusCode: 2,
- statusName: 'Alarm',
- description: 'Indicates an alarm condition.',
- },
- {
- key: '4',
- statusCode: 4,
- statusName: 'Critical',
- description: 'Indicates a critical condition.',
- },
-];
-
const IndexStatus = memo(function IndexStatus() {
const navigate = useNavigate();
const { setBreadcrumbItems } = useBreadcrumb();
- const [data, setData] = useState(initialData);
const [actionMode, setActionMode] = useState('list');
const [selectedData, setSelectedData] = useState(null);
const [isModalVisible, setIsModalVisible] = useState(false);
const [readOnly, setReadOnly] = useState(false);
- // Mock API function
- const getAllStatus = async (params) => {
- const { page = 1, limit = 10, search = '' } = Object.fromEntries(params.entries());
-
- let filteredData = data;
- if (search) {
- filteredData = data.filter(item =>
- item.statusName.toLowerCase().includes(search.toLowerCase())
- );
- }
-
- const start = (page - 1) * limit;
- const end = start + limit;
- const paginatedData = filteredData.slice(start, end);
-
- return new Promise(resolve => {
- setTimeout(() => {
- resolve({
- status: 200,
- data: {
- data: paginatedData,
- total: filteredData.length,
- paging: {
- page: parseInt(page),
- limit: parseInt(limit),
- total: filteredData.length,
- },
- },
- });
- }, 500);
- });
- };
-
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
setBreadcrumbItems([
- { title: • Master },
- { title: Status }
+ { title: • Master },
+ { title: Status }
]);
} else {
navigate('/signin');
@@ -101,67 +37,24 @@ const IndexStatus = memo(function IndexStatus() {
}
}, [actionMode]);
- const handleDataSaved = (values) => {
- let newData = [...data];
- if (values.key) { // Editing
- const index = newData.findIndex((item) => values.key === item.key);
- if (index > -1) {
- newData.splice(index, 1, values);
- }
- } else { // Adding
- const newKey = (Math.max(...data.map(item => parseInt(item.key))) + 1).toString();
- newData = [{ key: newKey, ...values }, ...newData];
- }
- setData(newData);
- };
-
- const handleEdit = (record) => {
- setSelectedData(record);
- setActionMode('edit');
- };
-
- const handlePreview = (record) => {
- setSelectedData(record);
- setActionMode('preview');
- };
-
- const handleDelete = (record) => {
- NotifConfirmDialog({
- icon: 'question',
- title: 'Konfirmasi',
- message: `Apakah anda yakin ingin menghapus status "${record.statusName}"?`,
- onConfirm: () => {
- const newData = data.filter((item) => item.key !== record.key);
- setData(newData);
- NotifAlert({
- icon: 'success',
- title: 'Berhasil',
- message: `Status "${record.statusName}" berhasil dihapus.`,
- });
- },
- });
- };
-
return (
-
+ {actionMode === 'list' &&
+
+ }
);
});
-export default IndexStatus;
\ No newline at end of file
+export default IndexStatus;
diff --git a/src/pages/master/status/component/DetailStatus.jsx b/src/pages/master/status/component/DetailStatus.jsx
index a5a8274..85d3410 100644
--- a/src/pages/master/status/component/DetailStatus.jsx
+++ b/src/pages/master/status/component/DetailStatus.jsx
@@ -1,66 +1,113 @@
import React, { useEffect, useState } from 'react';
-import { Modal, Input, Divider, Typography, Button, ConfigProvider, InputNumber, Form } from 'antd';
+import { Modal, Input, Divider, Typography, Button, ConfigProvider, InputNumber, Switch } from 'antd';
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
+import { validateRun } from '../../../../Utils/validate';
+import { createStatus, updateStatus } from '../../../../api/master-status';
const { Text } = Typography;
const { TextArea } = Input;
const DetailStatus = (props) => {
- const [form] = Form.useForm();
const [confirmLoading, setConfirmLoading] = useState(false);
const defaultData = {
- key: '',
- statusCode: '',
- statusName: '',
- description: '',
+ status_id: '',
+ status_number: null,
+ status_name: '',
+ status_color: '',
+ status_description: '',
+ is_active: true,
};
- const [FormData, setFormData] = useState(defaultData);
+ const [formData, setFormData] = useState(defaultData);
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData({ ...formData, [name]: value });
+ };
+
+ const handleInputNumberChange = (value) => {
+ setFormData({ ...formData, status_number: value });
+ };
+
+ const handleStatusToggle = (checked) => {
+ setFormData({ ...formData, is_active: checked });
+ };
const handleCancel = () => {
props.setSelectedData(null);
props.setActionMode('list');
- form.resetFields();
};
const handleSave = async () => {
- try {
- const values = await form.validateFields();
- setConfirmLoading(true);
+ setConfirmLoading(true);
+ const validationRules = [
+ { field: 'status_number', label: 'Status Number', required: true },
+ { field: 'status_name', label: 'Status Name', required: true },
+ { field: 'status_color', label: 'Status Color', required: true },
+ { field: 'status_description', label: 'Description', required: true },
+ ];
+
+ if (
+ validateRun(formData, validationRules, (errorMessages) => {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: errorMessages,
+ });
+ setConfirmLoading(false);
+ })
+ ) {
+ return;
+ }
+
+ try {
const payload = {
- key: FormData.key,
- ...values,
+ status_number: formData.status_number,
+ status_name: formData.status_name,
+ status_color: formData.status_color,
+ status_description: formData.status_description,
+ is_active: formData.is_active,
};
- props.onDataSaved(payload);
-
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: `Data Status "${payload.statusName}" berhasil ${
- payload.key ? 'diubah' : 'ditambahkan'
- }.`,
- });
+ const response = formData.status_id
+ ? await updateStatus(formData.status_id, payload)
+ : await createStatus(payload);
+ if (response && (response.statusCode === 200 || response.statusCode === 201)) {
+ const action = formData.status_id ? 'diubah' : 'ditambahkan';
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Status "${payload.status_name}" berhasil ${action}.`,
+ });
+ props.setActionMode('list');
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response?.message || 'Gagal menyimpan data.',
+ });
+ }
+ } catch (error) {
+ NotifAlert({
+ icon: 'error',
+ title: 'Error',
+ message: error.message || 'Terjadi kesalahan pada server.',
+ });
+ } finally {
setConfirmLoading(false);
- props.setActionMode('list');
- form.resetFields();
- } catch (errorInfo) {
- console.log('Failed:', errorInfo);
}
};
useEffect(() => {
if (props.selectedData) {
- setFormData(props.selectedData);
- form.setFieldsValue(props.selectedData);
+ setFormData({ ...defaultData, ...props.selectedData });
} else {
setFormData(defaultData);
- form.resetFields();
}
- }, [props.showModal, props.selectedData, form]);
+ }, [props.showModal, props.selectedData]);
return (
{
footer={
!props.readOnly && (
-
- Batal
-
-
-
- Simpan
-
-
+ Batal
+
+ Simpan
+
)
}
>
- Status Code}
- rules={[{ required: true, message: 'Silakan masukkan kode status!' }]}
- >
-
+ Status
+
+
-
-
Status Name}
- rules={[{ required: true, message: 'Silakan masukkan nama status!' }]}
- >
-
-
- Description}
- rules={[{ required: true, message: 'Silakan masukkan deskripsi!' }]}
- >
-
-
-
+ {formData.is_active ? 'Active' : 'Inactive'}
+
+
+