diff --git a/src/api/sparepart.jsx b/src/api/sparepart.jsx new file mode 100644 index 0000000..0435d13 --- /dev/null +++ b/src/api/sparepart.jsx @@ -0,0 +1,50 @@ +import { SendRequest } from '../components/Global/ApiRequest'; + +const getAllSparepart = async (queryParams) => { + const response = await SendRequest({ + method: 'get', + prefix: `sparepart?${queryParams.toString()}`, + }); + + return response.data; +}; + +const getSparepartById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `sparepart/${id}`, + }); + + return response.data; +}; + +const createSparepart = async (queryParams) => { + const response = await SendRequest({ + method: 'post', + prefix: `sparepart`, + params: queryParams, + }); + + return response.data; +}; + +const updateSparepart = async (id, queryParams) => { + const response = await SendRequest({ + method: 'put', + prefix: `sparepart/${id}`, + params: queryParams, + }); + + return response.data; +}; + +const deleteSparepart = async (id) => { + const response = await SendRequest({ + method: 'delete', + prefix: `sparepart/${id}`, + }); + + return response.data; +}; + +export { getAllSparepart, getSparepartById, createSparepart, updateSparepart, deleteSparepart }; \ No newline at end of file diff --git a/src/pages/master/sparepart/IndexSparepart.jsx b/src/pages/master/sparepart/IndexSparepart.jsx index c6e55fe..334a661 100644 --- a/src/pages/master/sparepart/IndexSparepart.jsx +++ b/src/pages/master/sparepart/IndexSparepart.jsx @@ -1,379 +1,75 @@ -import React, { memo, useEffect, useState } from 'react'; +import React, { memo, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Button, Col, Row, Space, Input, ConfigProvider, Card, Tag, Spin, Table, Modal } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, EyeOutlined } from '@ant-design/icons'; -import { NotifAlert, NotifConfirmDialog, NotifOk } from '../../../components/Global/ToastNotif'; import { useBreadcrumb } from '../../../layout/LayoutBreadcrumb'; import { Typography } from 'antd'; +import ListSparepart from './component/ListSparepart'; +import DetailSparepart from './component/DetailSparepart'; -const { Text, Title } = Typography; - -// Mock data untuk sparepart -const mockSpareparts = [ - { - sparepart_id: 1, - sparepart_name: 'Compressor Oil', - sparepart_code: 'SP-COMP-001', - sparepart_description: 'Oil for industrial compressor maintenance', - brand_name: 'Sullair', - model: '750H', - type: 'consumable', - is_active: true, - created_date: '2024-01-15', - updated_date: '2024-11-20' - }, - { - sparepart_id: 2, - sparepart_name: 'Air Filter Element', - sparepart_code: 'SP-AF-002', - sparepart_description: 'High efficiency air filter for compressor', - brand_name: 'Ingersoll Rand', - model: 'X7', - type: 'filter', - is_active: true, - created_date: '2024-02-20', - updated_date: '2024-10-15' - }, - { - sparepart_id: 3, - sparepart_name: 'Oil Filter', - sparepart_code: 'SP-OF-003', - sparepart_description: 'Oil filtration system component', - brand_name: 'Atlas Copco', - model: 'GA 55', - type: 'filter', - is_active: false, - created_date: '2024-03-10', - updated_date: '2024-09-05' - }, - { - sparepart_id: 4, - sparepart_name: 'Cooling Fan Motor', - sparepart_code: 'SP-CFM-004', - sparepart_description: 'Motor for cooling system in compressor', - brand_name: 'Kobelco', - model: 'SRE-110', - type: 'electrical', - is_active: true, - created_date: '2024-04-05', - updated_date: '2024-11-10' - }, - { - sparepart_id: 5, - sparepart_name: 'Coupling Elastomer', - sparepart_code: 'SP-CE-005', - sparepart_description: 'Elastomer coupling for drive system', - brand_name: 'Kaesar', - model: 'Comprex', - type: 'mechanical', - is_active: true, - created_date: '2024-05-12', - updated_date: '2024-11-18' - } -]; +const { Text } = Typography; const IndexSparepart = memo(function IndexSparepart() { - const navigate = useNavigate(); - const { setBreadcrumbItems } = useBreadcrumb(); - const [spareparts, setSpareparts] = useState([]); - const [filteredSpareparts, setFilteredSpareparts] = useState([]); - const [loading, setLoading] = useState(true); - const [searchValue, setSearchValue] = useState(''); + const navigate = useNavigate(); + const { setBreadcrumbItems } = useBreadcrumb(); - useEffect(() => { - const token = localStorage.getItem('token'); - if (token) { - // Simulasi loading data - setTimeout(() => { - setSpareparts(mockSpareparts); - setFilteredSpareparts(mockSpareparts); - setLoading(false); - }, 1000); + const [actionMode, setActionMode] = useState('list'); + const [selectedData, setSelectedData] = useState(null); + const [readOnly, setReadOnly] = useState(false); + const [showModal, setShowmodal] = useState(false); - setBreadcrumbItems([ - { title: • Master }, - { title: Sparepart } - ]); - } else { - navigate('/signin'); - } - }, [navigate, setBreadcrumbItems]); + const setMode = (param) => { + setShowmodal(true); + switch (param) { + case 'add': + setReadOnly(false); + break; - // Fungsi untuk pencarian - const handleSearch = (value) => { - if (!value) { - setFilteredSpareparts(spareparts); - } else { - const filtered = spareparts.filter(item => - item.sparepart_name.toLowerCase().includes(value.toLowerCase()) || - item.sparepart_code.toLowerCase().includes(value.toLowerCase()) || - item.brand_name.toLowerCase().includes(value.toLowerCase()) || - item.model.toLowerCase().includes(value.toLowerCase()) - ); - setFilteredSpareparts(filtered); - } - }; + case 'edit': + setReadOnly(false); + break; - const handleSearchChange = (e) => { - const value = e.target.value; - setSearchValue(value); - handleSearch(value); - }; + case 'preview': + setReadOnly(true); + break; - const handleSearchClear = () => { - setSearchValue(''); - setFilteredSpareparts(spareparts); - }; + default: + setShowmodal(false); + break; + } + setActionMode(param); + }; - const handleAddSparepart = () => { - // Navigasi ke halaman tambah sparepart (akan dibuat nanti) - NotifAlert({ - icon: 'info', - title: 'Info', - message: 'Fitur tambah sparepart akan segera dibuat', - }); - }; + useEffect(() => { + const token = localStorage.getItem('token'); + if (token) { + setBreadcrumbItems([ + { title: • Master }, + { title: Sparepart } + ]); + } else { + navigate('/signin'); + } + }, []); - const handleEditSparepart = (record) => { - // Navigasi ke halaman edit sparepart (akan dibuat nanti) - NotifAlert({ - icon: 'info', - title: 'Info', - message: `Edit sparepart: ${record.sparepart_name}`, - }); - }; - - const handleViewSparepart = (record) => { - // Tampilkan detail sparepart (akan dibuat nanti) - NotifOk({ - icon: 'success', - title: 'Info', - message: `View sparepart: ${record.sparepart_name}`, - }); - }; - - const handleDeleteSparepart = (record) => { - NotifConfirmDialog({ - icon: 'question', - title: 'Konfirmasi', - message: 'Apakah anda yakin hapus sparepart "' + record.sparepart_name + '" ?', - onConfirm: () => { - const updatedSpareparts = spareparts.filter(item => item.sparepart_id !== record.sparepart_id); - setSpareparts(updatedSpareparts); - setFilteredSpareparts(updatedSpareparts); - NotifOk({ - icon: 'success', - title: 'Berhasil', - message: `Sparepart ${record.sparepart_name} deleted successfully.`, - }); - }, - onCancel: () => {}, - }); - }; - - // Kolom-kolom tabel - const columns = [ - { - title: 'No', - key: 'no', - width: '5%', - align: 'center', - render: (_, __, index) => index + 1, - }, - { - title: 'Sparepart Code', - dataIndex: 'sparepart_code', - key: 'sparepart_code', - width: '15%', - }, - { - title: 'Sparepart Name', - dataIndex: 'sparepart_name', - key: 'sparepart_name', - width: '20%', - }, - { - title: 'Description', - dataIndex: 'sparepart_description', - key: 'sparepart_description', - width: '20%', - render: (text) => text || '-', - }, - { - title: 'Brand', - dataIndex: 'brand_name', - key: 'brand_name', - width: '12%', - }, - { - title: 'Model', - dataIndex: 'model', - key: 'model', - width: '10%', - }, - { - title: 'Type', - dataIndex: 'type', - key: 'type', - width: '10%', - render: (text) => ( - - {text?.toUpperCase()} - - ), - }, - { - title: 'Status', - dataIndex: 'is_active', - key: 'is_active', - width: '8%', - align: 'center', - render: (_, { is_active }) => ( - <> - {is_active === true ? ( - - Active - - ) : ( - - Inactive - - )} - > - ), - }, - { - title: 'Action', - key: 'action', - align: 'center', - width: '15%', - render: (_, record) => ( - - } - onClick={() => handleViewSparepart(record)} - style={{ - color: '#1890ff', - borderColor: '#1890ff', - }} - size="small" - /> - } - onClick={() => handleEditSparepart(record)} - style={{ - color: '#faad14', - borderColor: '#faad14', - }} - size="small" - /> - } - onClick={() => handleDeleteSparepart(record)} - style={{ - borderColor: '#ff4d4f', - }} - size="small" - /> - - ), - }, - ]; - - return ( - - - - - - - ✕, - }} - enterButton={ - } - style={{ - backgroundColor: '#23A55A', - borderColor: '#23A55A', - }} - > - Search - - } - size="large" - /> - - - - - } - onClick={handleAddSparepart} - size="large" - > - Add Sparepart - - - - - - - - {loading ? ( - - - - ) : ( - `${range[0]}-${range[1]} of ${total} items`, - }} - scroll={{ x: 1000 }} - size="middle" - /> - )} - - - - - ); + return ( + + + + + ); }); export default IndexSparepart; \ No newline at end of file diff --git a/src/pages/master/sparepart/component/DetailSparepart.jsx b/src/pages/master/sparepart/component/DetailSparepart.jsx new file mode 100644 index 0000000..7dd4e38 --- /dev/null +++ b/src/pages/master/sparepart/component/DetailSparepart.jsx @@ -0,0 +1,289 @@ +import React, { useState, useEffect } from 'react'; +import { Modal, Input, Divider, Typography, Switch, Button, ConfigProvider, message } from 'antd'; +import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; +import { createSparepart, updateSparepart } from '../../../../api/sparepart'; +import { validateRun } from '../../../../Utils/validate'; + +const { Text } = Typography; +const { TextArea } = Input; + +const DetailSparepart = (props) => { + const [confirmLoading, setConfirmLoading] = useState(false); + + const defaultData = { + sparepart_id: '', + sparepart_name: '', + sparepart_description: '', + sparepart_model: '', + sparepart_item_type: '', + sparepart_unit: '', + sparepart_merk: '', + sparepart_stok: '', + }; + + const [formData, setFormData] = useState(defaultData); + + const handleCancel = () => { + props.setSelectedData(null); + props.setActionMode('list'); + }; + + const handleSave = async () => { + setConfirmLoading(true); + + // Daftar aturan validasi + const validationRules = [ + { field: 'sparepart_name', label: 'Sparepart Name', required: true }, + { field: 'sparepart_model', label: 'Sparepart Model', required: true }, + { field: 'sparepart_unit', label: 'Sparepart Unit', required: true }, + { field: 'sparepart_merk', label: 'Sparepart Merk', required: true }, + { field: 'sparepart_stok', label: 'Sparepart Stok', required: true }, + ]; + + if ( + validateRun(formData, validationRules, (errorMessages) => { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: errorMessages, + }); + setConfirmLoading(false); + }) + ) + return; + + try { + const payload = { + sparepart_name: formData.sparepart_name, + sparepart_description: formData.sparepart_description, + sparepart_model: formData.sparepart_model, + sparepart_item_type: formData.sparepart_item_type, + sparepart_unit: formData.sparepart_unit, + sparepart_merk: formData.sparepart_merk, + sparepart_stok: formData.sparepart_stok, + }; + + const response = formData.sparepart_id + ? await updateSparepart(formData.sparepart_id, payload) + : await createSparepart(payload); + + // Check if response is successful + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + const sparepartName = response.data?.sparepart_name || formData.sparepart_name; + + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Data Sparepart "${sparepartName}" berhasil ${ + formData.sparepart_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 Sparepart Error:', error); + NotifAlert({ + icon: 'error', + title: 'Error', + message: error.message || 'Terjadi kesalahan pada server. Coba lagi nanti.', + }); + } + + setConfirmLoading(false); + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleFieldChange = (name, value) => { + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleStatusToggle = (event) => { + const isChecked = event; + setFormData({ + ...formData, + is_active: isChecked ? true : false, + }); + }; + + useEffect(() => { + if (props.selectedData) { + setFormData(props.selectedData); + } else { + setFormData(defaultData); + } + }, [props.showModal, props.selectedData, props.actionMode]); + + return ( + + + Batal + + + {!props.readOnly && ( + + Simpan + + )} + + , + ]} + > + {formData && ( + + + Sparepart ID + + + + + Sparepart Name + * + + + + + Sparepart Description + + + + + Sparepart Model + * + + + + + Sparepart Item Type + + + + + Sparepart Unit + * + + + + + Sparepart Merk + * + + + + + Sparepart Stok + * + + + + )} + + ); +}; + +export default DetailSparepart; \ No newline at end of file diff --git a/src/pages/master/sparepart/component/ListSparepart.jsx b/src/pages/master/sparepart/component/ListSparepart.jsx new file mode 100644 index 0000000..dc7d410 --- /dev/null +++ b/src/pages/master/sparepart/component/ListSparepart.jsx @@ -0,0 +1,276 @@ +import React, { memo, useState, useEffect } from 'react'; +import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input, Segmented } from 'antd'; +import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + EyeOutlined, + SearchOutlined, + AppstoreOutlined, + TableOutlined, +} from '@ant-design/icons'; +import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../../components/Global/ToastNotif'; +import { useNavigate } from 'react-router-dom'; +import { deleteSparepart, getAllSparepart } from '../../../../api/sparepart'; +import TableList from '../../../../components/Global/TableList'; + +const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [ + { + title: 'No', + key: 'no', + width: '5%', + align: 'center', + render: (_, __, index) => index + 1, + }, + { + title: 'ID', + dataIndex: 'sparepart_id', + key: 'sparepart_id', + width: '5%', + hidden: 'true', + }, + { + title: 'Sparepart Name', + dataIndex: 'sparepart_name', + key: 'sparepart_name', + width: '20%', + }, + { + title: 'Description', + dataIndex: 'sparepart_description', + key: 'sparepart_description', + width: '20%', + render: (sparepart_description) => sparepart_description || '-' + }, + { + title: 'Model', + dataIndex: 'sparepart_model', + key: 'sparepart_model', + width: '10%', + render: (sparepart_model) => sparepart_model || '-' + }, + { + title: 'Item Type', + dataIndex: 'sparepart_item_type', + key: 'sparepart_item_type', + width: '10%', + render: (sparepart_item_type) => sparepart_item_type || '-' + }, + { + title: 'Unit', + dataIndex: 'sparepart_unit', + key: 'sparepart_unit', + width: '8%', + render: (sparepart_unit) => sparepart_unit || '-' + }, + { + title: 'Merk', + dataIndex: 'sparepart_merk', + key: 'sparepart_merk', + width: '12%', + render: (sparepart_merk) => sparepart_merk || '-' + }, + { + title: 'Stock', + dataIndex: 'sparepart_stok', + key: 'sparepart_stok', + width: '8%', + render: (sparepart_stok) => sparepart_stok || '0' + }, + { + title: 'Action', + key: 'aksi', + align: 'center', + width: '12%', + render: (_, record) => ( + + } + onClick={() => showPreviewModal(record)} + /> + } + onClick={() => showEditModal(record)} + /> + } + onClick={() => showDeleteDialog(record)} + /> + + ), + }, +]; + +const ListSparepart = memo(function ListSparepart(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 (props.actionMode === 'list') { + setFormDataFilter(defaultFilter); + doFilter(); + } + } else { + navigate('/signin'); + } + }, [props.actionMode]); + + const doFilter = () => { + setTrigerFilter((prev) => !prev); + }; + + const handleSearch = () => { + setFormDataFilter({ criteria: searchValue }); + setTrigerFilter((prev) => !prev); + }; + + const handleSearchClear = () => { + setSearchValue(''); + setFormDataFilter({ criteria: '' }); + 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 showDeleteDialog = (param) => { + NotifConfirmDialog({ + icon: 'question', + title: 'Konfirmasi Hapus', + message: 'Sparepart "' + param.sparepart_name + '" akan dihapus?', + onConfirm: () => handleDelete(param.sparepart_id), + onCancel: () => props.setSelectedData(null), + }); + }; + + const handleDelete = async (sparepart_id) => { + const response = await deleteSparepart(sparepart_id); + + if (response.statusCode === 200 && response.data === true) { + NotifAlert({ + icon: 'success', + title: 'Berhasil', + message: response.message || 'Data Sparepart berhasil dihapus.', + }); + doFilter(); + } else { + NotifOk({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Gagal Menghapus Data Sparepart', + }); + } + }; + + return ( + + + + + + + { + const value = e.target.value; + setSearchValue(value); + if (value === '') { + setFormDataFilter({ criteria: '' }); + setTrigerFilter((prev) => !prev); + } + }} + onSearch={handleSearch} + allowClear={{ + clearIcon: ✕, + }} + enterButton={ + } + style={{ + backgroundColor: '#23A55A', + borderColor: '#23A55A', + }} + > + Search + + } + size="large" + /> + + + + + } + onClick={() => showAddModal()} + size="large" + > + Add data + + + + + + + + + + + + + ); +}); + +export default ListSparepart; \ No newline at end of file