fixing card list master status
This commit is contained in:
@@ -54,41 +54,40 @@ const CardList = ({
|
|||||||
}
|
}
|
||||||
style={getCardStyle()}
|
style={getCardStyle()}
|
||||||
actions={[
|
actions={[
|
||||||
<Space
|
<EyeOutlined
|
||||||
size="middle"
|
|
||||||
style={{ display: 'flex', justifyContent: 'center' }}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
style={{ color: '#1890ff' }}
|
style={{ color: '#1890ff' }}
|
||||||
icon={<EyeOutlined />}
|
key="preview"
|
||||||
onClick={() => showPreviewModal(item)}
|
onClick={() => showPreviewModal(item)}
|
||||||
/>
|
/>,
|
||||||
<Button
|
<EditOutlined
|
||||||
type="text"
|
|
||||||
style={{ color: '#faad14' }}
|
style={{ color: '#faad14' }}
|
||||||
icon={<EditOutlined />}
|
key="edit"
|
||||||
onClick={() => showEditModal(item)}
|
onClick={() => showEditModal(item)}
|
||||||
/>
|
/>,
|
||||||
<Button
|
<DeleteOutlined
|
||||||
type="text"
|
style={{ color: '#ff1818' }}
|
||||||
danger
|
key="delete"
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={() => showDeleteDialog(item)}
|
onClick={() => showDeleteDialog(item)}
|
||||||
/>
|
/>,
|
||||||
</Space>,
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
{column.map((itemCard, index) => (
|
{column.map((itemCard, index) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
{!itemCard.hidden && itemCard.title !== 'No' && itemCard.title !== 'Aksi' && (
|
{!itemCard.hidden &&
|
||||||
|
itemCard.title !== 'No' &&
|
||||||
|
itemCard.title !== 'Aksi' && (
|
||||||
<p style={{ margin: '8px 0' }}>
|
<p style={{ margin: '8px 0' }}>
|
||||||
<Text strong>{itemCard.title}:</Text>{' '}
|
<Text strong>{itemCard.title}:</Text>{' '}
|
||||||
{itemCard.render
|
{itemCard.render
|
||||||
? itemCard.render(item[itemCard.dataIndex], item, index)
|
? itemCard.render(
|
||||||
: item[itemCard.dataIndex] || item[itemCard.key] || '-'
|
item[itemCard.dataIndex],
|
||||||
}
|
item,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
: item[itemCard.dataIndex] ||
|
||||||
|
item[itemCard.key] ||
|
||||||
|
'-'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -13,46 +13,74 @@ const IndexStatus = memo(function IndexStatus() {
|
|||||||
|
|
||||||
const [actionMode, setActionMode] = useState('list');
|
const [actionMode, setActionMode] = useState('list');
|
||||||
const [selectedData, setSelectedData] = useState(null);
|
const [selectedData, setSelectedData] = useState(null);
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
||||||
const [readOnly, setReadOnly] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
setBreadcrumbItems([
|
setBreadcrumbItems([
|
||||||
{ title: <Text strong>• Master</Text> },
|
{
|
||||||
{ title: <Text strong>Status</Text> }
|
title: (
|
||||||
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
|
• Master
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<Text strong style={{ fontSize: '14px' }}>
|
||||||
|
Status
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
navigate('/signin');
|
navigate('/signin');
|
||||||
}
|
}
|
||||||
}, [navigate, setBreadcrumbItems]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (actionMode === 'add' || actionMode === 'edit' || actionMode === 'preview') {
|
|
||||||
setIsModalVisible(true);
|
|
||||||
setReadOnly(actionMode === 'preview');
|
|
||||||
} else {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
}
|
|
||||||
}, [actionMode]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{actionMode === 'list' &&
|
{actionMode === 'list' && (
|
||||||
<ListStatus
|
<ListStatus
|
||||||
setActionMode={setActionMode}
|
|
||||||
setSelectedData={setSelectedData}
|
|
||||||
actionMode={actionMode}
|
actionMode={actionMode}
|
||||||
/>
|
setActionMode={setMode}
|
||||||
}
|
|
||||||
<DetailStatus
|
|
||||||
showModal={isModalVisible}
|
|
||||||
setActionMode={setActionMode}
|
|
||||||
selectedData={selectedData}
|
selectedData={selectedData}
|
||||||
setSelectedData={setSelectedData}
|
setSelectedData={setSelectedData}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
<DetailStatus
|
||||||
|
setActionMode={setMode}
|
||||||
|
selectedData={selectedData}
|
||||||
|
setSelectedData={setSelectedData}
|
||||||
|
readOnly={readOnly}
|
||||||
|
showModal={showModal}
|
||||||
|
actionMode={actionMode}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,68 +1,89 @@
|
|||||||
import React, { memo, useState, useEffect } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import { Space, ConfigProvider, Button, Row, Col, Card, Input, Segmented, Table, Pagination } from 'antd';
|
import { Space, ConfigProvider, Button, Row, Col, Card, Input } from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EyeOutlined,
|
EyeOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
AppstoreOutlined,
|
|
||||||
TableOutlined,
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { deleteStatus, getAllStatuss } from '../../../../api/master-status';
|
import { deleteStatus, getAllStatuss } from '../../../../api/master-status';
|
||||||
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
|
||||||
|
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
||||||
|
{ title: 'Number', dataIndex: 'status_number', key: 'status_number', width: '15%' },
|
||||||
|
{ title: 'Name', dataIndex: 'status_name', key: 'status_name', width: '25%' },
|
||||||
|
{
|
||||||
|
title: 'Description',
|
||||||
|
dataIndex: 'status_description',
|
||||||
|
key: 'status_description',
|
||||||
|
width: '40%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Aksi',
|
||||||
|
key: 'aksi',
|
||||||
|
align: 'center',
|
||||||
|
width: '20%',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ borderColor: '#1890ff' }}
|
||||||
|
icon={<EyeOutlined style={{ color: '#1890ff' }} />}
|
||||||
|
onClick={() => showPreviewModal(record)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ borderColor: '#faad14' }}
|
||||||
|
icon={<EditOutlined style={{ color: '#faad14' }} />}
|
||||||
|
onClick={() => showEditModal(record)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
style={{ borderColor: 'red' }}
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => showDeleteDialog(record)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const ListStatus = memo(function ListStatus(props) {
|
const ListStatus = memo(function ListStatus(props) {
|
||||||
const [data, setData] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [viewMode, setViewMode] = useState('card');
|
|
||||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
const defaultFilter = { criteria: '' };
|
||||||
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const fetchData = async (page = 1, pageSize = 10) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('page', page);
|
|
||||||
params.append('limit', pageSize);
|
|
||||||
if (searchValue) {
|
|
||||||
params.append('search', searchValue);
|
|
||||||
}
|
|
||||||
const response = await getAllStatuss(params);
|
|
||||||
setData(response.data || []);
|
|
||||||
setPagination(prev => ({ ...prev, total: response.paging?.total || 0, current: page, pageSize: pageSize }));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch status data:", error);
|
|
||||||
setData([]);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (!token) {
|
if (token) {
|
||||||
navigate('/signin');
|
if (props.actionMode === 'list') {
|
||||||
return;
|
setFormDataFilter(defaultFilter);
|
||||||
|
doFilter();
|
||||||
}
|
}
|
||||||
fetchData(pagination.current, pagination.pageSize);
|
} else {
|
||||||
}, [props.actionMode, trigerFilter, navigate]);
|
navigate('/signin');
|
||||||
|
}
|
||||||
|
}, [props.actionMode]);
|
||||||
|
|
||||||
const doFilter = () => {
|
const doFilter = () => {
|
||||||
setTrigerFilter(prev => !prev);
|
setTrigerFilter((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = () => {
|
||||||
setSearchValue(value);
|
setFormDataFilter({ criteria: searchValue });
|
||||||
setPagination(prev => ({ ...prev, current: 1 })); // Reset to first page on search
|
setTrigerFilter((prev) => !prev);
|
||||||
doFilter();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePaginationChange = (page, pageSize) => {
|
const handleSearchClear = () => {
|
||||||
fetchData(page, pageSize);
|
setSearchValue('');
|
||||||
|
setFormDataFilter({ criteria: '' });
|
||||||
|
setTrigerFilter((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPreviewModal = (record) => {
|
const showPreviewModal = (record) => {
|
||||||
@@ -93,40 +114,24 @@ const ListStatus = memo(function ListStatus(props) {
|
|||||||
try {
|
try {
|
||||||
const response = await deleteStatus(status_id);
|
const response = await deleteStatus(status_id);
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
NotifAlert({ icon: 'success', title: 'Berhasil', message: 'Data Status berhasil dihapus.' });
|
NotifAlert({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
message: 'Data Status berhasil dihapus.',
|
||||||
|
});
|
||||||
doFilter();
|
doFilter();
|
||||||
} else {
|
} else {
|
||||||
NotifAlert({ icon: 'error', title: 'Gagal', message: response?.message || 'Gagal Menghapus Data' });
|
NotifAlert({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
message: response?.message || 'Gagal Menghapus Data',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
NotifAlert({ icon: 'error', title: 'Error', message: error.message });
|
NotifAlert({ icon: 'error', title: 'Error', message: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{ title: 'Number', dataIndex: 'status_number', key: 'status_number', width: '15%' },
|
|
||||||
{ title: 'Name', dataIndex: 'status_name', key: 'status_name', width: '25%' },
|
|
||||||
{ title: 'Description', dataIndex: 'status_description', key: 'status_description', width: '40%' },
|
|
||||||
{
|
|
||||||
title: 'Aksi', key: 'aksi', align: 'center', width: '20%',
|
|
||||||
render: (_, record) => (
|
|
||||||
<Space>
|
|
||||||
<Button type="text" icon={<EyeOutlined />} onClick={() => showPreviewModal(record)} />
|
|
||||||
<Button type="text" icon={<EditOutlined />} onClick={() => showEditModal(record)} />
|
|
||||||
<Button danger type="text" icon={<DeleteOutlined />} onClick={() => showDeleteDialog(record)} />
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getCardStyle = (color) => {
|
|
||||||
return { border: `2px solid ${color || '#d9d9d9'}`, height: '100%' };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTitleStyle = (color) => {
|
|
||||||
return { backgroundColor: color || 'transparent', color: '#fff', padding: '2px 8px', borderRadius: '4px', display: 'inline-block' };
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Row justify="space-between" align="middle" gutter={[16, 16]}>
|
<Row justify="space-between" align="middle" gutter={[16, 16]}>
|
||||||
@@ -134,7 +139,16 @@ const ListStatus = memo(function ListStatus(props) {
|
|||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder="Search by status name..."
|
placeholder="Search by status name..."
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
allowClear
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setSearchValue(value);
|
||||||
|
if (value === '') {
|
||||||
|
handleSearchClear();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
allowClear={{
|
||||||
|
clearIcon: <span onClick={handleSearchClear}>✕</span>,
|
||||||
|
}}
|
||||||
enterButton={
|
enterButton={
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -167,57 +181,22 @@ const ListStatus = memo(function ListStatus(props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row style={{ marginTop: 16 }}>
|
<Col xs={24} style={{ marginTop: '16px' }}>
|
||||||
<Col>
|
<TableList
|
||||||
<Segmented
|
mobile
|
||||||
options={[{ value: 'card', icon: <AppstoreOutlined /> }, { value: 'table', icon: <TableOutlined /> }]}
|
cardColor={'#42AAFF'}
|
||||||
value={viewMode}
|
header={'status_name'}
|
||||||
onChange={setViewMode}
|
showPreviewModal={showPreviewModal}
|
||||||
|
showEditModal={showEditModal}
|
||||||
|
showDeleteDialog={showDeleteDialog}
|
||||||
|
getData={getAllStatuss}
|
||||||
|
queryParams={formDataFilter}
|
||||||
|
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||||
|
triger={trigerFilter}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<div style={{ marginTop: 24 }}>
|
|
||||||
{viewMode === 'card' ? (
|
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{data.map(item => (
|
|
||||||
<Col xs={24} sm={12} md={8} lg={6} key={item.status_id}>
|
|
||||||
<Card
|
|
||||||
title={<span style={getTitleStyle(item.status_color)}>{item.status_name}</span>}
|
|
||||||
style={getCardStyle(item.status_color)}
|
|
||||||
actions={[
|
|
||||||
<EyeOutlined key="preview" onClick={() => showPreviewModal(item)} />,
|
|
||||||
<EditOutlined key="edit" onClick={() => showEditModal(item)} />,
|
|
||||||
<DeleteOutlined key="delete" onClick={() => showDeleteDialog(item)} />,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<p><b>Number:</b> {item.status_number}</p>
|
|
||||||
<p><b>Description:</b> {item.status_description}</p>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
dataSource={data.map(item => ({ ...item, key: item.status_id }))}
|
|
||||||
pagination={false}
|
|
||||||
loading={loading}
|
|
||||||
/>
|
|
||||||
<Pagination
|
|
||||||
style={{ marginTop: 16, textAlign: 'right' }}
|
|
||||||
current={pagination.current}
|
|
||||||
pageSize={pagination.pageSize}
|
|
||||||
total={pagination.total}
|
|
||||||
onChange={handlePaginationChange}
|
|
||||||
showSizeChanger
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user