lavoce #3
@@ -6,9 +6,75 @@ const getAllShift = async (queryParams) => {
|
||||
method: 'get',
|
||||
prefix: `shift?${queryParams.toString()}`,
|
||||
});
|
||||
return response;
|
||||
console.log('getAllShift response:', response);
|
||||
console.log('Query params:', queryParams.toString());
|
||||
|
||||
// Check if response has error
|
||||
if (response.error) {
|
||||
console.error('getAllShift error response:', response);
|
||||
return {
|
||||
status: response.statusCode || 500,
|
||||
data: {
|
||||
data: [],
|
||||
paging: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
page_total: 0
|
||||
},
|
||||
total: 0
|
||||
},
|
||||
error: response.message
|
||||
};
|
||||
}
|
||||
|
||||
// Check if backend returns paginated data
|
||||
if (response.paging) {
|
||||
const totalData = response.data?.[0]?.total_data || response.rows || response.data?.length || 0;
|
||||
|
||||
return {
|
||||
status: response.statusCode || 200,
|
||||
data: {
|
||||
data: response.data || [],
|
||||
paging: {
|
||||
page: response.paging.current_page || 1,
|
||||
limit: response.paging.current_limit || 10,
|
||||
total: totalData,
|
||||
page_total: response.paging.total_page || Math.ceil(totalData / (response.paging.current_limit || 10))
|
||||
},
|
||||
total: totalData
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback: If backend returns all data without pagination
|
||||
const params = Object.fromEntries(queryParams);
|
||||
const currentPage = parseInt(params.page) || 1;
|
||||
const currentLimit = parseInt(params.limit) || 10;
|
||||
|
||||
const allData = response.data || [];
|
||||
const totalData = allData.length;
|
||||
|
||||
// Client-side pagination
|
||||
const startIndex = (currentPage - 1) * currentLimit;
|
||||
const endIndex = startIndex + currentLimit;
|
||||
const paginatedData = allData.slice(startIndex, endIndex);
|
||||
|
||||
return {
|
||||
status: response.statusCode || 200,
|
||||
data: {
|
||||
data: paginatedData,
|
||||
paging: {
|
||||
page: currentPage,
|
||||
limit: currentLimit,
|
||||
total: totalData,
|
||||
page_total: Math.ceil(totalData / currentLimit)
|
||||
},
|
||||
total: totalData
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('getAllShift error:', error);
|
||||
console.error('getAllShift catch error:', error);
|
||||
return {
|
||||
status: 500,
|
||||
data: {
|
||||
@@ -38,12 +104,27 @@ const createShift = async (queryParams) => {
|
||||
const response = await SendRequest({
|
||||
method: 'post',
|
||||
prefix: `shift`,
|
||||
data: queryParams,
|
||||
params: queryParams,
|
||||
});
|
||||
console.log('createShift full response:', response);
|
||||
console.log('createShift payload sent:', queryParams);
|
||||
|
||||
// Check if response has error flag
|
||||
if (response.error) {
|
||||
return {
|
||||
statusCode: response.statusCode || 500,
|
||||
data: null,
|
||||
message: response.message || 'Request failed',
|
||||
rows: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Backend returns: { statusCode, message, rows, data: [shift_object] }
|
||||
return {
|
||||
statusCode: response.statusCode || 200,
|
||||
data: response.data,
|
||||
message: response.message
|
||||
data: response.data?.[0] || response.data,
|
||||
message: response.message,
|
||||
rows: response.rows
|
||||
};
|
||||
};
|
||||
|
||||
@@ -51,12 +132,27 @@ const updateShift = async (id, queryParams) => {
|
||||
const response = await SendRequest({
|
||||
method: 'put',
|
||||
prefix: `shift/${id}`,
|
||||
data: queryParams,
|
||||
params: queryParams,
|
||||
});
|
||||
console.log('updateShift full response:', response);
|
||||
console.log('updateShift payload sent:', queryParams);
|
||||
|
||||
// Check if response has error flag
|
||||
if (response.error) {
|
||||
return {
|
||||
statusCode: response.statusCode || 500,
|
||||
data: null,
|
||||
message: response.message || 'Request failed',
|
||||
rows: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Backend returns: { statusCode, message, rows, data: [shift_object] }
|
||||
return {
|
||||
statusCode: response.statusCode || 200,
|
||||
data: response.data,
|
||||
message: response.message
|
||||
data: response.data?.[0] || response.data,
|
||||
message: response.message,
|
||||
rows: response.rows
|
||||
};
|
||||
};
|
||||
|
||||
@@ -65,10 +161,24 @@ const deleteShift = async (id) => {
|
||||
method: 'delete',
|
||||
prefix: `shift/${id}`,
|
||||
});
|
||||
console.log('deleteShift full response:', response);
|
||||
|
||||
// Check if response has error flag
|
||||
if (response.error) {
|
||||
return {
|
||||
statusCode: response.statusCode || 500,
|
||||
data: null,
|
||||
message: response.message || 'Request failed',
|
||||
rows: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Backend returns: { statusCode, message, rows: null, data: true }
|
||||
return {
|
||||
statusCode: response.statusCode || 200,
|
||||
data: response.data,
|
||||
message: response.message
|
||||
message: response.message,
|
||||
rows: response.rows
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -10,68 +10,42 @@ import {
|
||||
import { NotifAlert, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TableList from '../../../components/Global/TableList';
|
||||
|
||||
// --- DUMMY DATA (Initial State) --- //
|
||||
const initialDummyData = [
|
||||
{
|
||||
id: 1,
|
||||
nama_shift: 'Shift Pagi',
|
||||
jam_masuk: '07:00',
|
||||
jam_pulang: '15:00',
|
||||
username: 'd.sanjaya',
|
||||
nama_employee: 'Dede Sanjaya',
|
||||
whatsapp: '081234567890'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nama_shift: 'Shift Siang',
|
||||
jam_masuk: '15:00',
|
||||
jam_pulang: '23:00',
|
||||
username: 'a.wijaya',
|
||||
nama_employee: 'Andi Wijaya',
|
||||
whatsapp: '081234567891'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
nama_shift: 'Shift Malam',
|
||||
jam_masuk: '23:00',
|
||||
jam_pulang: '07:00',
|
||||
username: 'b.cahya',
|
||||
nama_employee: 'Budi Cahya',
|
||||
whatsapp: '081234567892'
|
||||
},
|
||||
];
|
||||
import { getAllJadwalShift, deleteJadwalShift } from '../../../api/jadwal-shift';
|
||||
|
||||
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
{
|
||||
title: 'Nama Karyawan',
|
||||
dataIndex: 'nama_employee',
|
||||
key: 'nama_employee',
|
||||
},
|
||||
{
|
||||
title: 'Username',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
title: 'Tanggal Jadwal',
|
||||
dataIndex: 'schedule_date',
|
||||
key: 'schedule_date',
|
||||
render: (date) => date ? new Date(date).toLocaleDateString('id-ID') : '-',
|
||||
},
|
||||
{
|
||||
title: 'Nama Shift',
|
||||
dataIndex: 'nama_shift',
|
||||
key: 'nama_shift',
|
||||
dataIndex: 'shift_name',
|
||||
key: 'shift_name',
|
||||
render: (text) => text || '-',
|
||||
},
|
||||
{
|
||||
title: 'Jam Masuk',
|
||||
dataIndex: 'jam_masuk',
|
||||
key: 'jam_masuk',
|
||||
dataIndex: 'start_time',
|
||||
key: 'start_time',
|
||||
render: (time) => time || '-',
|
||||
},
|
||||
{
|
||||
title: 'Jam Pulang',
|
||||
dataIndex: 'jam_pulang',
|
||||
key: 'jam_pulang',
|
||||
dataIndex: 'end_time',
|
||||
key: 'end_time',
|
||||
render: (time) => time || '-',
|
||||
},
|
||||
{
|
||||
title: 'Whatsapp',
|
||||
dataIndex: 'whatsapp',
|
||||
key: 'whatsapp',
|
||||
title: 'Status',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
render: (isActive) => (
|
||||
<Tag color={isActive ? 'green' : 'red'}>
|
||||
{isActive ? 'Aktif' : 'Tidak Aktif'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
@@ -101,39 +75,42 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||
];
|
||||
|
||||
const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||
const [dataSource, setDataSource] = useState(initialDummyData);
|
||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||
const defaultFilter = { criteria: '' };
|
||||
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
// --- DUMMY API --- //
|
||||
const getDummyData = (queryParams) => {
|
||||
return new Promise((resolve) => {
|
||||
const { criteria } = queryParams;
|
||||
let data = dataSource;
|
||||
if (criteria) {
|
||||
data = dataSource.filter(item =>
|
||||
item.nama_employee.toLowerCase().includes(criteria.toLowerCase()) ||
|
||||
item.username.toLowerCase().includes(criteria.toLowerCase())
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
status: 200,
|
||||
data: {
|
||||
data: data,
|
||||
paging: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: data.length,
|
||||
page_total: 1
|
||||
}
|
||||
const getData = async (queryParams) => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: queryParams.page || 1,
|
||||
limit: queryParams.limit || 10,
|
||||
criteria: queryParams.criteria || ''
|
||||
});
|
||||
|
||||
const response = await getAllJadwalShift(params);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching jadwal shift:', error);
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: 'Gagal mengambil data jadwal shift.',
|
||||
});
|
||||
return {
|
||||
status: 500,
|
||||
data: {
|
||||
data: [],
|
||||
paging: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
page_total: 0
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -146,7 +123,7 @@ const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||
} else {
|
||||
navigate('/signin');
|
||||
}
|
||||
}, [props.actionMode, dataSource]); // Added dataSource to dependency array
|
||||
}, [props.actionMode]);
|
||||
|
||||
const doFilter = () => {
|
||||
setTrigerFilter((prev) => !prev);
|
||||
@@ -179,23 +156,41 @@ const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||
};
|
||||
|
||||
const showDeleteDialog = (param) => {
|
||||
const dateStr = param.schedule_date ? new Date(param.schedule_date).toLocaleDateString('id-ID') : 'tanggal tidak diketahui';
|
||||
NotifConfirmDialog({
|
||||
icon: 'question',
|
||||
title: 'Konfirmasi Hapus',
|
||||
message: `Jadwal shift untuk "${param.nama_employee}" akan dihapus?`,
|
||||
onConfirm: () => handleDelete(param.id),
|
||||
message: `Jadwal shift tanggal ${dateStr} akan dihapus?`,
|
||||
onConfirm: () => handleDelete(param.schedule_id),
|
||||
onCancel: () => props.setSelectedData(null),
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (id) => {
|
||||
setDataSource(prevData => prevData.filter(item => item.id !== id));
|
||||
NotifAlert({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: 'Data Jadwal Shift berhasil dihapus.',
|
||||
});
|
||||
doFilter(); // Trigger a re-fetch from the new state
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
const response = await deleteJadwalShift(id);
|
||||
if (response.statusCode === 200) {
|
||||
NotifAlert({
|
||||
icon: 'success',
|
||||
title: 'Berhasil',
|
||||
message: 'Data Jadwal Shift berhasil dihapus.',
|
||||
});
|
||||
doFilter();
|
||||
} else {
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: response.message || 'Gagal menghapus data jadwal shift.',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting jadwal shift:', error);
|
||||
NotifAlert({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
message: 'Terjadi kesalahan saat menghapus data.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -206,7 +201,7 @@ const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<Input.Search
|
||||
placeholder="Cari berdasarkan nama atau username..."
|
||||
placeholder="Cari jadwal shift..."
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
@@ -263,11 +258,11 @@ const ListJadwalShift = memo(function ListJadwalShift(props) {
|
||||
<TableList
|
||||
mobile
|
||||
cardColor={'#42AAFF'}
|
||||
header={'nama_employee'}
|
||||
header={'schedule_date'}
|
||||
showPreviewModal={showPreviewModal}
|
||||
showEditModal={showEditModal}
|
||||
showDeleteDialog={showDeleteDialog}
|
||||
getData={getDummyData}
|
||||
getData={getData}
|
||||
queryParams={formDataFilter}
|
||||
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||
triger={trigerFilter}
|
||||
|
||||
@@ -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: <Text strong style={{ fontSize: '14px' }}>• Master</Text> },
|
||||
{ title: <Text strong style={{ fontSize: '14px' }}>Shift</Text> }
|
||||
]);
|
||||
} 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' && (
|
||||
<ListShift
|
||||
setActionMode={setActionMode}
|
||||
setSelectedData={setSelectedData}
|
||||
shiftData={shiftData}
|
||||
loading={loading}
|
||||
onDelete={handleDeleteShift}
|
||||
fetchData={fetchData}
|
||||
/>
|
||||
)}
|
||||
{(actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') && (
|
||||
<DetailShift
|
||||
actionMode={actionMode}
|
||||
selectedData={selectedData}
|
||||
setActionMode={setActionMode}
|
||||
setSelectedData={setSelectedData}
|
||||
onAdd={handleAddShift}
|
||||
onUpdate={handleUpdateShift}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<React.Fragment>
|
||||
<ListShift
|
||||
actionMode={actionMode}
|
||||
setActionMode={setMode}
|
||||
selectedData={selectedData}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
<DetailShift
|
||||
setActionMode={setMode}
|
||||
selectedData={selectedData}
|
||||
setSelectedData={setSelectedData}
|
||||
readOnly={readOnly}
|
||||
showModal={showModal}
|
||||
actionMode={actionMode}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default IndexShift;
|
||||
|
||||
@@ -7,13 +7,13 @@ 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 +26,228 @@ 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
|
||||
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: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 (
|
||||
<Modal
|
||||
title={`${props.actionMode === 'add' ? 'Tambah' : props.actionMode === 'preview' ? 'Preview' : 'Edit'} Shift`}
|
||||
open={props.actionMode !== 'list'}
|
||||
title={`${
|
||||
props.actionMode === 'add'
|
||||
? 'Tambah'
|
||||
: props.actionMode === 'preview'
|
||||
? 'Preview'
|
||||
: 'Edit'
|
||||
} Shift`}
|
||||
open={props.showModal}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<>
|
||||
@@ -100,7 +260,6 @@ const DetailShift = (props) => {
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
@@ -123,7 +282,7 @@ const DetailShift = (props) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{!readOnly && (
|
||||
{!props.readOnly && (
|
||||
<Button loading={confirmLoading} onClick={handleSave}>
|
||||
Simpan
|
||||
</Button>
|
||||
@@ -134,7 +293,8 @@ const DetailShift = (props) => {
|
||||
>
|
||||
{FormData && (
|
||||
<div>
|
||||
<div>
|
||||
{/* Status Toggle */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div>
|
||||
<Text strong>Status</Text>
|
||||
</div>
|
||||
@@ -147,42 +307,66 @@ const DetailShift = (props) => {
|
||||
>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<Switch
|
||||
disabled={readOnly}
|
||||
disabled={props.readOnly}
|
||||
style={{
|
||||
backgroundColor:
|
||||
FormData.status === true ? '#23A55A' : '#bfbfbf',
|
||||
FormData.is_active === true ? '#23A55A' : '#bfbfbf',
|
||||
}}
|
||||
checked={FormData.status === true}
|
||||
checked={FormData.is_active === true}
|
||||
onChange={handleStatusToggle}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Text>{FormData.status === true ? 'Active' : 'Inactive'}</Text>
|
||||
<Text>{FormData.is_active === true ? 'Active' : 'Inactive'}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Nama Shift</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="nama_shift"
|
||||
value={FormData.nama_shift}
|
||||
name="shift_name"
|
||||
value={FormData.shift_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan Nama Shift"
|
||||
readOnly={readOnly}
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Jam Shift</Text>
|
||||
<Text strong>Jam Mulai</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="jam_shift"
|
||||
value={FormData.jam_shift}
|
||||
name="start_time"
|
||||
value={FormData.start_time}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Contoh: 08:00 - 17:00"
|
||||
readOnly={readOnly}
|
||||
placeholder="Masukkan Jam Mulai"
|
||||
readOnly={props.readOnly}
|
||||
maxLength={8}
|
||||
/>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}
|
||||
>
|
||||
Contoh: 08:00 atau 08:00:00
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong>Jam Selesai</Text>
|
||||
<Text style={{ color: 'red' }}> *</Text>
|
||||
<Input
|
||||
name="end_time"
|
||||
value={FormData.end_time}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan Jam Selesai"
|
||||
readOnly={props.readOnly}
|
||||
maxLength={8}
|
||||
/>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}
|
||||
>
|
||||
Contoh: 17:00 atau 17:00:00
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -190,4 +374,4 @@ const DetailShift = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailShift;
|
||||
export default DetailShift;
|
||||
|
||||
@@ -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 (
|
||||
<Tag color={color} key={'status'}>
|
||||
{text}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
key: 'aksi',
|
||||
align: 'center',
|
||||
width: '20%',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => showPreviewModal(record)}
|
||||
style={{
|
||||
color: '#1890ff',
|
||||
borderColor: '#1890ff',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showEditModal(record)}
|
||||
style={{
|
||||
color: '#faad14',
|
||||
borderColor: '#faad14',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => showDeleteDialog(record)}
|
||||
style={{
|
||||
borderColor: '#ff4d4f',
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
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) => (
|
||||
<Tag color={status ? 'green' : 'red'}>
|
||||
{status ? 'Active' : 'Inactive'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Aksi',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: '15%',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => showPreviewModal(record)}
|
||||
style={{
|
||||
color: '#1890ff',
|
||||
borderColor: '#1890ff',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showEditModal(record)}
|
||||
style={{
|
||||
color: '#faad14',
|
||||
borderColor: '#faad14',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => showDeleteDialog(record)}
|
||||
style={{
|
||||
borderColor: '#ff4d4f',
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
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 (
|
||||
<Card>
|
||||
<Row justify="space-between" align="middle" gutter={[16, 16]}>
|
||||
<Col xs={24} sm={12} md={10} lg={8}>
|
||||
<Input.Search
|
||||
placeholder="Cari nama shift..."
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
onSearch={handleSearch}
|
||||
allowClear={{
|
||||
clearIcon: <span onClick={handleSearchClear}>x</span>,
|
||||
}}
|
||||
enterButton={<Button type="primary" icon={<SearchOutlined />} style={{ backgroundColor: '#23A55A', borderColor: '#23A55A' }}>Cari</Button>}
|
||||
size="large"
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={showAddModal}
|
||||
size="large"
|
||||
>
|
||||
Tambah Shift
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginTop: 16 }}>
|
||||
<Col span={24}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredData}
|
||||
loading={props.loading}
|
||||
rowKey="id"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
<React.Fragment>
|
||||
<Card>
|
||||
<Row>
|
||||
<Col xs={24}>
|
||||
<Row justify="space-between" align="middle" gutter={[8, 8]}>
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<Input.Search
|
||||
placeholder="Search shift by name..."
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSearchValue(value);
|
||||
// Auto search when clearing by backspace/delete
|
||||
if (value === '') {
|
||||
handleSearchClear();
|
||||
}
|
||||
}}
|
||||
onSearch={handleSearch}
|
||||
allowClear
|
||||
onClear={handleSearchClear}
|
||||
enterButton={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
style={{
|
||||
backgroundColor: '#23A55A',
|
||||
borderColor: '#23A55A',
|
||||
}}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
}
|
||||
size="large"
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Space wrap size="small">
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#E9F6EF' },
|
||||
components: {
|
||||
Button: {
|
||||
defaultBg: 'white',
|
||||
defaultColor: '#23A55A',
|
||||
defaultBorderColor: '#23A55A',
|
||||
defaultHoverColor: '#23A55A',
|
||||
defaultHoverBorderColor: '#23A55A',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => showAddModal()}
|
||||
size="large"
|
||||
>
|
||||
Tambah Data
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
||||
<TableList
|
||||
mobile
|
||||
cardColor={'#42AAFF'}
|
||||
header={'shift_name'}
|
||||
showPreviewModal={showPreviewModal}
|
||||
showEditModal={showEditModal}
|
||||
showDeleteDialog={showDeleteDialog}
|
||||
getData={getAllShift}
|
||||
queryParams={formDataFilter}
|
||||
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||
triger={trigerFilter}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(ListShift);
|
||||
export default ListShift;
|
||||
Reference in New Issue
Block a user