diff --git a/src/App.jsx b/src/App.jsx
index d3f9c69..6bf096f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -15,6 +15,10 @@ import IndexTag from './pages/master/tag/IndexTag';
import IndexBrandDevice from './pages/master/brandDevice/IndexBrandDevice';
import IndexPlantSection from './pages/master/plantSection/IndexPlantSection';
import IndexStatus from './pages/master/status/IndexStatus';
+import IndexShift from './pages/master/shift/IndexShift';
+
+// Jadwal Shift
+import IndexJadwalShift from './pages/jadwalShift/IndexJadwalShift';
// History
import IndexTrending from './pages/history/trending/IndexTrending';
@@ -53,10 +57,14 @@ const App = () => {
} />
} />
} />
-
+ } />
} />
+ }>
+ } />
+
+
}>
} />
} />
diff --git a/src/api/jadwal-shift.jsx b/src/api/jadwal-shift.jsx
new file mode 100644
index 0000000..2ae403a
--- /dev/null
+++ b/src/api/jadwal-shift.jsx
@@ -0,0 +1,67 @@
+import { SendRequest } from '../components/Global/ApiRequest';
+
+const getAllJadwalShift = async (queryParams) => {
+ try {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `jadwal-shift?${queryParams.toString()}`,
+ });
+ return response;
+ } catch (error) {
+ console.error('getAllJadwalShift error:', error);
+ return {
+ status: 500,
+ data: {
+ data: [],
+ paging: {
+ page: 1,
+ limit: 10,
+ total: 0,
+ page_total: 0
+ },
+ total: 0
+ },
+ error: error.message
+ };
+ }
+};
+
+const createJadwalShift = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'post',
+ prefix: `jadwal-shift`,
+ data: queryParams,
+ });
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const updateJadwalShift = async (id, queryParams) => {
+ const response = await SendRequest({
+ method: 'put',
+ prefix: `jadwal-shift/${id}`,
+ data: queryParams,
+ });
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const deleteJadwalShift = async (id) => {
+ const response = await SendRequest({
+ method: 'delete',
+ prefix: `jadwal-shift/${id}`,
+ });
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+export { getAllJadwalShift, createJadwalShift, updateJadwalShift, deleteJadwalShift };
diff --git a/src/api/master-shift.jsx b/src/api/master-shift.jsx
new file mode 100644
index 0000000..a5a3f24
--- /dev/null
+++ b/src/api/master-shift.jsx
@@ -0,0 +1,75 @@
+import { SendRequest } from '../components/Global/ApiRequest';
+
+const getAllShift = async (queryParams) => {
+ try {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `shift?${queryParams.toString()}`,
+ });
+ return response;
+ } catch (error) {
+ console.error('getAllShift error:', error);
+ return {
+ status: 500,
+ data: {
+ data: [],
+ paging: {
+ page: 1,
+ limit: 10,
+ total: 0,
+ page_total: 0
+ },
+ total: 0
+ },
+ error: error.message
+ };
+ }
+};
+
+const getShiftById = async (id) => {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `shift/${id}`,
+ });
+ return response.data;
+};
+
+const createShift = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'post',
+ prefix: `shift`,
+ data: queryParams,
+ });
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const updateShift = async (id, queryParams) => {
+ const response = await SendRequest({
+ method: 'put',
+ prefix: `shift/${id}`,
+ data: queryParams,
+ });
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const deleteShift = async (id) => {
+ const response = await SendRequest({
+ method: 'delete',
+ prefix: `shift/${id}`,
+ });
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+export { getAllShift, getShiftById, createShift, updateShift, deleteShift };
diff --git a/src/layout/LayoutMenu.jsx b/src/layout/LayoutMenu.jsx
index 28cf0ad..97f044f 100644
--- a/src/layout/LayoutMenu.jsx
+++ b/src/layout/LayoutMenu.jsx
@@ -70,6 +70,11 @@ const allItems = [
icon: ,
label: Status,
},
+ {
+ key: 'master-shift',
+ icon: ,
+ label: Shift,
+ },
],
},
{
diff --git a/src/pages/jadwalShift/IndexJadwalShift.jsx b/src/pages/jadwalShift/IndexJadwalShift.jsx
new file mode 100644
index 0000000..36e2fc8
--- /dev/null
+++ b/src/pages/jadwalShift/IndexJadwalShift.jsx
@@ -0,0 +1,28 @@
+import { useState } from 'react';
+import ListJadwalShift from './component/ListJadwalShift';
+import DetailJadwalShift from './component/DetailJadwalShift';
+
+const IndexJadwalShift = () => {
+ const [actionMode, setActionMode] = useState('list');
+ const [selectedData, setSelectedData] = useState(null);
+
+ return (
+ <>
+ {actionMode === 'list' && (
+
+ )}
+ {(actionMode === 'add' || actionMode === 'edit') && (
+
+ )}
+ >
+ );
+};
+
+export default IndexJadwalShift;
diff --git a/src/pages/jadwalShift/component/DetailJadwalShift.jsx b/src/pages/jadwalShift/component/DetailJadwalShift.jsx
new file mode 100644
index 0000000..1e11da8
--- /dev/null
+++ b/src/pages/jadwalShift/component/DetailJadwalShift.jsx
@@ -0,0 +1,210 @@
+import { useEffect, useState } from 'react';
+import { Modal, Select, Divider, Typography, Button, ConfigProvider } from 'antd';
+import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
+import { createJadwalShift, updateJadwalShift } from '../../../api/jadwal-shift';
+import { getAllShift } from '../../../api/master-shift';
+import { getAllUser } from '../../../api/user';
+
+const { Text } = Typography;
+const { Option } = Select;
+
+const DetailJadwalShift = (props) => {
+ const [confirmLoading, setConfirmLoading] = useState(false);
+ const [shifts, setShifts] = useState([]);
+ const [users, setUsers] = useState([]);
+
+ const defaultData = {
+ id: '',
+ id_shift: null,
+ id_user: null,
+ };
+
+ const [FormData, setFormData] = useState(defaultData);
+
+ const fetchShifts = async () => {
+ const response = await getAllShift(new URLSearchParams());
+ setShifts(response.data.data);
+ };
+
+ const fetchUsers = async () => {
+ const response = await getAllUser(new URLSearchParams("limit=1000")); // Fetch all users
+ setUsers(response.data.data);
+ };
+
+ useEffect(() => {
+ fetchShifts();
+ fetchUsers();
+ }, []);
+
+ const handleCancel = () => {
+ props.setSelectedData(null);
+ props.setActionMode('list');
+ };
+
+ const handleSave = async () => {
+ setConfirmLoading(true);
+
+ if (!FormData.id_shift) {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: 'Kolom Shift Tidak Boleh Kosong',
+ });
+ setConfirmLoading(false);
+ return;
+ }
+
+ if (!FormData.id_user) {
+ NotifOk({
+ icon: 'warning',
+ title: 'Peringatan',
+ message: 'Kolom User Tidak Boleh Kosong',
+ });
+ setConfirmLoading(false);
+ return;
+ }
+
+ const payload = {
+ id_shift: FormData.id_shift,
+ id_user: FormData.id_user,
+ };
+
+ try {
+ const response = FormData.id
+ ? await updateJadwalShift(FormData.id, payload)
+ : await createJadwalShift(payload);
+
+ if (response && (response.statusCode === 200 || response.statusCode === 201)) {
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Jadwal Shift berhasil ${FormData.id ? 'diubah' : 'ditambahkan'}.`,
+ });
+
+ props.setActionMode('list');
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: response?.message || 'Terjadi kesalahan saat menyimpan data.',
+ });
+ }
+ } catch (error) {
+ console.error('Save Jadwal Shift Error:', error);
+ NotifAlert({
+ icon: 'error',
+ title: 'Error',
+ message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.',
+ });
+ }
+
+ setConfirmLoading(false);
+ };
+
+ const handleSelectChange = (name, value) => {
+ setFormData({
+ ...FormData,
+ [name]: value,
+ });
+ };
+
+ useEffect(() => {
+ if (props.selectedData) {
+ setFormData(props.selectedData);
+ } else {
+ setFormData(defaultData);
+ }
+ }, [props.actionMode, props.selectedData]);
+
+ return (
+
+
+
+
+
+
+
+ >,
+ ]}
+ >
+ {FormData && (
+
+
+ Shift
+ *
+
+
+
+ User
+ *
+
+
+
+ )}
+
+ );
+};
+
+export default DetailJadwalShift;
diff --git a/src/pages/jadwalShift/component/ListJadwalShift.jsx b/src/pages/jadwalShift/component/ListJadwalShift.jsx
new file mode 100644
index 0000000..641a33c
--- /dev/null
+++ b/src/pages/jadwalShift/component/ListJadwalShift.jsx
@@ -0,0 +1,118 @@
+import { useEffect, useState } from 'react';
+import { Button, Table, Space, Popconfirm } from 'antd';
+import { getAllJadwalShift, deleteJadwalShift } from '../../../api/jadwal-shift';
+import { NotifAlert, NotifOk } from '../../../components/Global/ToastNotif';
+
+const ListJadwalShift = (props) => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ const response = await getAllJadwalShift(new URLSearchParams());
+ if (response.data && response.data.data) {
+ setData(response.data.data);
+ } else {
+ setData([]);
+ }
+ } catch (error) {
+ console.error("Failed to fetch shift schedule data:", error);
+ setData([]); // Set data to empty array on error
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleAdd = () => {
+ props.setSelectedData(null);
+ props.setActionMode('add');
+ };
+
+ const handleEdit = (record) => {
+ props.setSelectedData(record);
+ props.setActionMode('edit');
+ };
+
+ const handleDelete = async (id) => {
+ try {
+ const response = await deleteJadwalShift(id);
+ if (response.statusCode === 200) {
+ NotifOk({ icon: 'success', title: 'Berhasil', message: 'Data jadwal shift berhasil dihapus' });
+ fetchData();
+ } else {
+ NotifAlert({ icon: 'error', title: 'Gagal', message: response.message });
+ }
+ } catch (error) {
+ NotifAlert({ icon: 'error', title: 'Error', message: error.message });
+ }
+ };
+
+ useEffect(() => {
+ if(props.actionMode === 'list') {
+ fetchData();
+ }
+ }, [props.actionMode]);
+
+ const columns = [
+ {
+ title: 'Nama Shift',
+ dataIndex: 'nama_shift',
+ key: 'nama_shift',
+ },
+ {
+ title: 'Jam Shift',
+ dataIndex: 'jam_shift',
+ key: 'jam_shift',
+ },
+ {
+ title: 'Username',
+ dataIndex: 'username',
+ key: 'username',
+ },
+ {
+ title: 'Nama Employee',
+ dataIndex: 'nama_employee',
+ key: 'nama_employee',
+ },
+ {
+ title: 'WhatsApp',
+ dataIndex: 'whatsapp',
+ key: 'whatsapp',
+ },
+ {
+ title: 'Aksi',
+ key: 'action',
+ render: (text, record) => (
+
+
+ handleDelete(record.id)}
+ okText="Yes"
+ cancelText="No"
+ >
+
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+
+ );
+};
+
+export default ListJadwalShift;
diff --git a/src/pages/master/shift/IndexShift.jsx b/src/pages/master/shift/IndexShift.jsx
new file mode 100644
index 0000000..bb67155
--- /dev/null
+++ b/src/pages/master/shift/IndexShift.jsx
@@ -0,0 +1,29 @@
+import { useState } from 'react';
+import ListShift from './component/ListShift';
+import DetailShift from './component/DetailShift';
+
+const IndexShift = () => {
+ const [actionMode, setActionMode] = useState('list');
+ const [selectedData, setSelectedData] = useState(null);
+
+ 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
new file mode 100644
index 0000000..1ca4d31
--- /dev/null
+++ b/src/pages/master/shift/component/DetailShift.jsx
@@ -0,0 +1,142 @@
+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';
+
+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
+ };
+
+ const [FormData, setFormData] = useState(defaultData);
+
+ const handleCancel = () => {
+ props.setSelectedData(null);
+ props.setActionMode('list');
+ };
+
+ const handleSave = async () => {
+ setConfirmLoading(true);
+
+ if (!FormData.nama_shift) {
+ 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' });
+ setConfirmLoading(false);
+ return;
+ }
+
+ const payload = {
+ nama_shift: FormData.nama_shift,
+ jam_shift: FormData.jam_shift,
+ status: FormData.status,
+ };
+
+ try {
+ const response = FormData.id
+ ? await updateShift(FormData.id, payload)
+ : await createShift(payload);
+
+ if (response && (response.statusCode === 200 || response.statusCode === 201)) {
+ NotifOk({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `Data Shift "${FormData.nama_shift}" berhasil ${FormData.id ? 'diubah' : 'ditambahkan'}.`,
+ });
+ props.setActionMode('list');
+ } else {
+ NotifAlert({ icon: 'error', title: 'Gagal', message: response?.message || 'Terjadi kesalahan saat menyimpan data.' });
+ }
+ } catch (error) {
+ NotifAlert({ icon: 'error', title: 'Error', message: error.message || 'Terjadi kesalahan pada server.' });
+ }
+
+ setConfirmLoading(false);
+ };
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData({ ...FormData, [name]: value });
+ };
+
+ const handleStatusToggle = (checked) => {
+ setFormData({ ...FormData, status: checked });
+ };
+
+ useEffect(() => {
+ if (props.selectedData) {
+ setFormData(props.selectedData);
+ } else {
+ setFormData(defaultData);
+ }
+ }, [props.actionMode, props.selectedData]);
+
+ return (
+ Batal,
+ !readOnly && (
+
+ ),
+ ]}
+ >
+ {FormData && (
+
+
+
Status
+
+
+ {FormData.status ? 'Active' : 'Inactive'}
+
+
+
+
+ Nama Shift
+ *
+
+
+
+ Jam Shift
+ *
+
+
+
+ )}
+
+ );
+};
+
+export default DetailShift;
\ No newline at end of file
diff --git a/src/pages/master/shift/component/ListShift.jsx b/src/pages/master/shift/component/ListShift.jsx
new file mode 100644
index 0000000..ab20b52
--- /dev/null
+++ b/src/pages/master/shift/component/ListShift.jsx
@@ -0,0 +1,181 @@
+import React, { memo, useState, useEffect } from 'react';
+import { Button, Col, Row, Input, ConfigProvider, Card, Tag } from 'antd';
+import {
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ SearchOutlined,
+ EyeOutlined,
+} from '@ant-design/icons';
+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) => {
+ const [trigerFilter, setTrigerFilter] = useState(false);
+ const defaultFilter = { search: '' };
+ const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
+ const [searchValue, setSearchValue] = useState('');
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ navigate('/signin');
+ }
+ }, [navigate]);
+
+ const doFilter = () => {
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleSearch = (value) => {
+ setFormDataFilter({ search: value });
+ doFilter();
+ };
+
+ const handleSearchClear = () => {
+ setSearchValue('');
+ setFormDataFilter(defaultFilter);
+ doFilter();
+ };
+
+ const showPreviewModal = (record) => {
+ props.setSelectedData(record);
+ props.setActionMode('preview');
+ };
+
+ const showEditModal = (record) => {
+ props.setSelectedData(record);
+ props.setActionMode('edit');
+ };
+
+ const showAddModal = () => {
+ props.setSelectedData(null);
+ props.setActionMode('add');
+ };
+
+ const showDeleteDialog = (record) => {
+ NotifConfirmDialog({
+ icon: 'question',
+ title: 'Konfirmasi',
+ message: `Apakah anda yakin ingin menghapus data shift "${record.nama_shift}"?`,
+ onConfirm: () => handleDelete(record.id),
+ });
+ };
+
+ const handleDelete = async (id) => {
+ try {
+ const response = await deleteShift(id);
+ if (response.statusCode === 200) {
+ NotifAlert({ icon: 'success', title: 'Berhasil', message: 'Data shift berhasil dihapus' });
+ doFilter();
+ } else {
+ NotifAlert({ icon: 'error', title: 'Gagal', message: response.message });
+ }
+ } catch (error) {
+ NotifAlert({ icon: 'error', title: 'Error', message: error.message });
+ }
+ };
+
+ 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)} />
+ } onClick={() => showEditModal(record)} />
+ } onClick={() => showDeleteDialog(record)} danger />
+
+ ),
+ },
+ ];
+
+ 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
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default memo(ListShift);
\ No newline at end of file