diff --git a/src/pages/master/shift/component/DetailShift.jsx b/src/pages/master/shift/component/DetailShift.jsx index d4df499..ab2d3bc 100644 --- a/src/pages/master/shift/component/DetailShift.jsx +++ b/src/pages/master/shift/component/DetailShift.jsx @@ -1,11 +1,22 @@ import React, { useEffect, useState } from 'react'; -import { Modal, Input, Typography, Switch, Button, ConfigProvider, Divider, TimePicker, Space } from 'antd'; +import { + Modal, + Input, + Typography, + Switch, + Button, + ConfigProvider, + Divider, + TimePicker, + Space, +} from 'antd'; import { NotifOk } from '../../../../components/Global/ToastNotif'; +import { createShift, updateShift } from '../../../../api/master-shift'; +import { validateRun } from '../../../../Utils/validate'; import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; -// Mock API calls for demonstration -const createShift = async (payload) => ({ statusCode: 201, data: { ...payload, shift_id: Date.now() } }); -const updateShift = async (id, payload) => ({ statusCode: 200, data: { ...payload, shift_id: id } }); +dayjs.extend(utc); const { Text } = Typography; const timeFormat = 'HH:mm'; @@ -16,13 +27,21 @@ const DetailShift = (props) => { const defaultData = { shift_id: '', shift_name: '', - start_time: '08:00', - end_time: '16:00', + start_time: '', + end_time: '', is_active: true, }; const [formData, setFormData] = useState(defaultData); + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + const handleCancel = () => { props.setSelectedData(null); props.setActionMode('list'); @@ -31,125 +50,264 @@ const DetailShift = (props) => { const handleSave = async () => { setConfirmLoading(true); - if (!formData.shift_name) { - NotifOk({ icon: 'warning', title: 'Peringatan', message: 'Nama Shift wajib diisi.' }); + // Daftar aturan validasi + const validationRules = [ + { field: 'shift_name', label: 'Shift Name', required: true }, + { field: 'start_time', label: 'Start Time', required: true }, + { field: 'end_time', label: 'End Time', required: true }, + ]; + + if ( + validateRun(formData, validationRules, (errorMessages) => { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: errorMessages, + }); + setConfirmLoading(false); + }) + ) + return; + + // Validasi format waktu + if (!formData.start_time || !formData.end_time) { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Waktu Mulai dan Waktu Selesai wajib diisi.', + }); setConfirmLoading(false); return; } try { + // Pastikan format waktu HH:mm sesuai validasi BE (regex: /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/) + const formatTimeForAPI = (timeValue) => { + if (!timeValue) return ''; + + // Jika sudah dalam format HH:mm, return langsung + if (typeof timeValue === 'string' && timeValue.match(/^\d{2}:\d{2}$/)) { + return timeValue; + } + + // Parse dengan dayjs dan format ke HH:mm (string murni, bukan Date object) + const time = dayjs(timeValue, 'HH:mm', true); // strict mode + if (time.isValid()) { + return time.format('HH:mm'); // Return string "08:00" bukan Date object + } + + // Fallback: coba parse sebagai ISO date dan ambil jam/menitnya (gunakan UTC) + const isoTime = dayjs.utc(timeValue); + if (isoTime.isValid()) { + return isoTime.format('HH:mm'); + } + + return ''; + }; + const payload = { shift_name: formData.shift_name, - start_time: formData.start_time, - end_time: formData.end_time, + start_time: formatTimeForAPI(formData.start_time), + end_time: formatTimeForAPI(formData.end_time), is_active: formData.is_active, }; + console.log('Payload yang dikirim:', payload); + console.log('Type start_time:', typeof payload.start_time, payload.start_time); + console.log('Type end_time:', typeof payload.end_time, payload.end_time); + const response = props.actionMode === 'edit' ? await updateShift(formData.shift_id, payload) : await createShift(payload); if (response && (response.statusCode === 200 || response.statusCode === 201)) { - NotifOk({ icon: 'success', title: 'Berhasil', message: `Data Shift berhasil disimpan.` }); + const action = props.actionMode === 'edit' ? 'diubah' : 'ditambahkan'; + + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Data Shift berhasil ${action}.`, + }); + props.setActionMode('list'); } else { - NotifOk({ icon: 'error', title: 'Gagal', message: response?.message || 'Gagal menyimpan data.' }); + NotifOk({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Terjadi kesalahan saat menyimpan data.', + }); } } catch (error) { - NotifOk({ icon: 'error', title: 'Error', message: error.message || 'Terjadi kesalahan server.' }); + NotifOk({ + icon: 'error', + title: 'Error', + message: error.message || 'Terjadi kesalahan pada server.', + }); } finally { setConfirmLoading(false); } }; - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData({ ...formData, [name]: value }); + const handleTimeChange = (time, _, field) => { + // Pastikan format HH:mm yang konsisten sesuai validasi BE + const formattedTime = time && time.isValid() ? time.format('HH:mm') : ''; + setFormData({ + ...formData, + [field]: formattedTime, + }); }; - const handleTimeChange = (time, timeString, field) => { - setFormData({ ...formData, [field]: timeString }); + const handleStatusToggle = (checked) => { + setFormData({ + ...formData, + is_active: checked, + }); }; useEffect(() => { if (props.selectedData) { - setFormData(props.selectedData); + // Konversi waktu dari berbagai format ke HH:mm menggunakan dayjs + const convertTimeToString = (timeValue) => { + if (!timeValue) return ''; + + // Jika sudah dalam format HH:mm, return langsung + if (typeof timeValue === 'string' && timeValue.match(/^\d{2}:\d{2}$/)) { + return timeValue; + } + + // Jika dalam format ISO (1970-01-01T08:00:00.000Z), extract jam:menit dalam UTC + const time = dayjs.utc(timeValue); + if (time.isValid()) { + return time.format('HH:mm'); + } + + return ''; + }; + + setFormData({ + ...props.selectedData, + start_time: convertTimeToString(props.selectedData.start_time), + end_time: convertTimeToString(props.selectedData.end_time), + }); } else { setFormData(defaultData); } - }, [props.showModal, props.selectedData]); - - const modalTitle = `${props.actionMode === 'add' ? 'Tambah' : props.actionMode === 'preview' ? 'Preview' : 'Edit'} Shift`; + }, [props.showModal, props.selectedData, props.actionMode]); return ( - - {!props.readOnly && ( - - )} - , + + + + + + {!props.readOnly && ( + + )} + + , ]} > -
+ {formData && (
- Status -
- setFormData({ ...formData, is_active: checked })} +
+
+ Status +
+
+
+ +
+
+ {formData.is_active ? 'Active' : 'Inactive'} +
+
+
+ + +
+ Shift Name + * + - {formData.is_active ? 'Active' : 'Inactive'} +
+ +
+ Shift Time + * + + + handleTimeChange(time, timeString, 'start_time') + } + style={{ width: '50%' }} + placeholder="Start Time " + disabled={props.readOnly} + /> + + handleTimeChange(time, timeString, 'end_time') + } + style={{ width: '50%' }} + placeholder="End Time " + disabled={props.readOnly} + /> +
- - -
- Nama Shift - * - -
- -
- Waktu Shift - * - - handleTimeChange(time, timeString, 'start_time')} - style={{ width: '50%' }} - placeholder="Waktu Mulai" - disabled={props.readOnly} - /> - handleTimeChange(time, time-string, 'end_time')} - style={{ width: '50%' }} - placeholder="Waktu Selesai" - disabled={props.readOnly} - /> - -
-
+ )} ); }; -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 9c8b278..c810568 100644 --- a/src/pages/master/shift/component/ListShift.jsx +++ b/src/pages/master/shift/component/ListShift.jsx @@ -9,19 +9,23 @@ import { } from '@ant-design/icons'; import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif'; import { useNavigate } from 'react-router-dom'; +import { deleteShift, getAllShift } from '../../../../api/master-shift'; import TableList from '../../../../components/Global/TableList'; -// import { getAllShift, deleteShift } from '../../../../api/master-shift'; // <-- API needs to be created +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; -// Mock API calls for demonstration -const getAllShift = async () => ({ - data: [ - { shift_id: 1, shift_name: 'Pagi', start_time: '08:00', end_time: '16:00', is_active: true }, - { shift_id: 2, shift_name: 'Sore', start_time: '16:00', end_time: '00:00', is_active: true }, - { shift_id: 3, shift_name: 'Malam', start_time: '00:00', end_time: '08:00', is_active: false }, - ], - statusCode: 200, -}); -const deleteShift = async (id) => ({ statusCode: 200, message: 'Data berhasil dihapus' }); +dayjs.extend(utc); + +// Helper function untuk convert ISO time ke HH:mm +const formatTime = (timeValue) => { + if (!timeValue) return '-'; + if (typeof timeValue === 'string' && timeValue.match(/^\d{2}:\d{2}$/)) { + return timeValue; + } + // UTC untuk menghindari timezone conversion + const time = dayjs.utc(timeValue); + return time.isValid() ? time.format('HH:mm') : '-'; +}; const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ { @@ -29,7 +33,6 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ dataIndex: 'shift_name', key: 'shift_name', width: '30%', - render: (text, record, index) => `${index + 1}. ${text}`, }, { title: 'Start Time', @@ -37,6 +40,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ key: 'start_time', width: '15%', align: 'center', + render: (time) => formatTime(time), }, { title: 'End Time', @@ -44,6 +48,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ key: 'end_time', width: '15%', align: 'center', + render: (time) => formatTime(time), }, { title: 'Status', @@ -51,15 +56,9 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ key: 'is_active', width: '15%', align: 'center', - render: (_, { is_active }) => { - const color = is_active ? 'green' : 'red'; - const text = is_active ? 'Active' : 'Inactive'; - return ( - - {text} - - ); - }, + render: (status) => ( + {status ? 'Active' : 'Inactive'} + ), }, { title: 'Aksi', @@ -68,9 +67,25 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ width: '25%', render: (_, record) => ( - - } - size="large" - /> - - - - + + + + + + { + const value = e.target.value; + setSearchValue(value); + if (value === '') { + handleSearchClear(); + } }} - > - - - - - - - - - - - + onSearch={handleSearch} + allowClear={{ + clearIcon: , + }} + enterButton={ + + } + size="large" + /> + + + + + + + + + + + + + + + + ); }); -export default ListShift; \ No newline at end of file +export default ListShift;