diff --git a/src/api/user.jsx b/src/api/user.jsx
new file mode 100644
index 0000000..5812dd7
--- /dev/null
+++ b/src/api/user.jsx
@@ -0,0 +1,103 @@
+import { SendRequest } from '../components/Global/ApiRequest';
+
+const getAllUser = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `user?${queryParams.toString()}`,
+ });
+
+ // Parse query params to get page and limit
+ const params = Object.fromEntries(queryParams);
+ const currentPage = parseInt(params.page) || 1;
+ const currentLimit = parseInt(params.limit) || 10;
+
+ // Backend returns all data, so we need to do client-side pagination
+ const allData = response.data || [];
+ const totalData = allData.length;
+
+ // Calculate start and end index for current page
+ const startIndex = (currentPage - 1) * currentLimit;
+ const endIndex = startIndex + currentLimit;
+
+ // Slice data for current page
+ const paginatedData = allData.slice(startIndex, endIndex);
+
+ // Transform response to match TableList expected structure
+ return {
+ status: response.statusCode || 200,
+ data: {
+ data: paginatedData,
+ paging: {
+ page: currentPage,
+ limit: currentLimit,
+ total: totalData,
+ page_total: Math.ceil(totalData / currentLimit)
+ },
+ total: totalData
+ }
+ };
+};
+
+const getUserById = async (id) => {
+ const response = await SendRequest({
+ method: 'get',
+ prefix: `user/${id}`,
+ });
+ return response.data;
+};
+
+const createUser = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'post',
+ prefix: `user`,
+ params: queryParams,
+ });
+ // Return full response with statusCode
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const updateUser = async (user_id, queryParams) => {
+ const response = await SendRequest({
+ method: 'put',
+ prefix: `user/${user_id}`,
+ params: queryParams,
+ });
+ // Return full response with statusCode
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const deleteUser = async (queryParams) => {
+ const response = await SendRequest({
+ method: 'delete',
+ prefix: `user/${queryParams}`,
+ });
+ // Return full response with statusCode
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+const approveUser = async (user_id) => {
+ const response = await SendRequest({
+ method: 'put',
+ prefix: `user/${user_id}/approve`,
+ });
+ // Return full response with statusCode
+ return {
+ statusCode: response.statusCode || 200,
+ data: response.data,
+ message: response.message
+ };
+};
+
+export { getAllUser, getUserById, createUser, updateUser, deleteUser, approveUser };
\ No newline at end of file
diff --git a/src/pages/user/IndexUser.jsx b/src/pages/user/IndexUser.jsx
index 373aa47..1fb19d6 100644
--- a/src/pages/user/IndexUser.jsx
+++ b/src/pages/user/IndexUser.jsx
@@ -1,5 +1,6 @@
-import React, { memo, useEffect } from 'react';
+import React, { memo, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
+import ListUser from './component/ListUser';
import { useBreadcrumb } from '../../layout/LayoutBreadcrumb';
import { Typography } from 'antd';
@@ -9,11 +10,44 @@ const IndexUser = memo(function IndexUser() {
const navigate = useNavigate();
const { setBreadcrumbItems } = useBreadcrumb();
+ const [actionMode, setActionMode] = useState('list');
+ const [selectedData, setSelectedData] = useState(null);
+ const [readOnly, setReadOnly] = useState(false);
+ const [showModal, setShowmodal] = useState(false);
+
+ const setMode = (param) => {
+ setShowmodal(true);
+ switch (param) {
+ case 'add':
+ setReadOnly(false);
+ break;
+
+ case 'edit':
+ setReadOnly(false);
+ break;
+
+ case 'preview':
+ setReadOnly(true);
+ break;
+
+ default:
+ setShowmodal(false);
+ break;
+ }
+ setActionMode(param);
+ };
+
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
setBreadcrumbItems([
- { title: • User }
+ {
+ title: (
+
+ • User
+
+ ),
+ },
]);
} else {
navigate('/signin');
@@ -21,9 +55,15 @@ const IndexUser = memo(function IndexUser() {
}, []);
return (
-
-
User Page
-
+
+
+
);
});
diff --git a/src/pages/user/component/ListUser.jsx b/src/pages/user/component/ListUser.jsx
new file mode 100644
index 0000000..a716544
--- /dev/null
+++ b/src/pages/user/component/ListUser.jsx
@@ -0,0 +1,349 @@
+import React, { memo, useState, useEffect } from 'react';
+import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input } from 'antd';
+import {
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ EyeOutlined,
+ SearchOutlined,
+ CheckOutlined,
+ CloseOutlined,
+} from '@ant-design/icons';
+import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
+import { useNavigate } from 'react-router-dom';
+import { deleteUser, getAllUser } from '../../../api/user';
+import TableList from '../../../components/Global/TableList';
+
+const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApproveDialog) => [
+ {
+ title: 'ID',
+ dataIndex: 'user_id',
+ key: 'user_id',
+ width: '5%',
+ hidden: 'true',
+ },
+ {
+ title: 'Username',
+ dataIndex: 'username',
+ key: 'username',
+ width: '12%',
+ },
+ {
+ title: 'Nama Lengkap',
+ dataIndex: 'fullname',
+ key: 'fullname',
+ width: '15%',
+ },
+ {
+ title: 'Nomor WA',
+ dataIndex: 'whatsapp_number',
+ key: 'whatsapp_number',
+ width: '12%',
+ },
+ {
+ title: 'Level',
+ dataIndex: 'level',
+ key: 'level',
+ width: '8%',
+ align: 'center',
+ },
+ {
+ title: 'Nama Role',
+ dataIndex: 'role_name',
+ key: 'role_name',
+ width: '12%',
+ render: (_, { role_name }) => (
+ <>
+ {role_name === 'administrator' && (
+
+ Administrator
+
+ )}
+ {role_name === 'operator' && (
+
+ Operator
+
+ )}
+ {role_name === 'engineer' && (
+
+ Engineer
+
+ )}
+ {role_name === 'guest' && (
+
+ Guest
+
+ )}
+ >
+ ),
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ key: 'status',
+ width: '10%',
+ align: 'center',
+ render: (_, { status }) => (
+ <>
+ {status === 'active' && (
+
+ Active
+
+ )}
+ {status === 'pending' && (
+
+ Pending
+
+ )}
+ {status === 'inactive' && (
+
+ Inactive
+
+ )}
+ {status === 'rejected' && (
+
+ Rejected
+
+ )}
+ >
+ ),
+ },
+ {
+ title: 'Aksi',
+ key: 'aksi',
+ align: 'center',
+ width: '15%',
+ render: (_, record) => (
+
+ }
+ onClick={() => showPreviewModal(record)}
+ />
+ {record.status === 'pending' && (
+ }
+ onClick={() => showApproveDialog(record)}
+ />
+ )}
+ }
+ onClick={() => showEditModal(record)}
+ />
+ }
+ onClick={() => showDeleteDialog(record)}
+ />
+
+ ),
+ },
+];
+
+const ListUser = memo(function ListUser(props) {
+ const [showFilter, setShowFilter] = useState(false);
+ 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) {
+ if (props.actionMode == 'list') {
+ setFormDataFilter(defaultFilter);
+ doFilter();
+ }
+ } else {
+ navigate('/signin');
+ }
+ }, [props.actionMode]);
+
+ const toggleFilter = () => {
+ setFormDataFilter(defaultFilter);
+ setShowFilter((prev) => !prev);
+ };
+
+ const doFilter = () => {
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleSearch = () => {
+ setFormDataFilter({ search: searchValue });
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const handleSearchClear = () => {
+ setSearchValue('');
+ setFormDataFilter({ search: '' });
+ setTrigerFilter((prev) => !prev);
+ };
+
+ const showPreviewModal = (param) => {
+ props.setSelectedData(param);
+ props.setActionMode('preview');
+ };
+
+ const showEditModal = (param = null) => {
+ props.setSelectedData(param);
+ props.setActionMode('edit');
+ };
+
+ const showAddModal = (param = null) => {
+ props.setSelectedData(param);
+ props.setActionMode('add');
+ };
+
+ const showApproveDialog = (param) => {
+ NotifConfirmDialog({
+ icon: 'question',
+ title: 'Konfirmasi Approve User',
+ message: 'Apakah anda yakin approve user "' + param.fullname + '" ?',
+ onConfirm: () => handleApprove(param.user_id),
+ onCancel: () => props.setSelectedData(null),
+ });
+ };
+
+ const showDeleteDialog = (param) => {
+ NotifConfirmDialog({
+ icon: 'question',
+ title: 'Konfirmasi',
+ message: 'Apakah anda yakin hapus user "' + param.fullname + '" ?',
+ onConfirm: () => handleDelete(param.user_id),
+ onCancel: () => props.setSelectedData(null),
+ });
+ };
+
+ const handleApprove = async (user_id) => {
+ // TODO: Implement approve user API call
+ NotifAlert({
+ icon: 'info',
+ title: 'Info',
+ message: 'Approve user akan diimplementasikan dengan API',
+ });
+ // const response = await approveUser(user_id);
+ // if (response.statusCode == 200) {
+ // NotifAlert({
+ // icon: 'success',
+ // title: 'Berhasil',
+ // message: 'User berhasil diapprove.',
+ // });
+ // doFilter();
+ // }
+ };
+
+ const handleDelete = async (user_id) => {
+ const response = await deleteUser(user_id);
+
+ if (response.statusCode == 200) {
+ NotifAlert({
+ icon: 'success',
+ title: 'Berhasil',
+ message: 'User "' + response.data.fullname + '" berhasil dihapus.',
+ });
+ doFilter();
+ } else {
+ NotifOk({
+ icon: 'error',
+ title: 'Gagal',
+ message: 'Gagal Menghapus User',
+ });
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ {
+ const value = e.target.value;
+ setSearchValue(value);
+ // Auto search when clearing by backspace/delete
+ if (value === '') {
+ setFormDataFilter({ search: '' });
+ setTrigerFilter((prev) => !prev);
+ }
+ }}
+ onSearch={handleSearch}
+ allowClear={{
+ clearIcon: ✕,
+ }}
+ enterButton={
+ }
+ style={{
+ backgroundColor: '#23A55A',
+ borderColor: '#23A55A',
+ }}
+ >
+ Search
+
+ }
+ size="large"
+ />
+
+
+
+
+ }
+ onClick={() => showAddModal()}
+ size="large"
+ >
+ Tambah User
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+export default ListUser;