enhance search and pagination functionality to ListNotification component

This commit is contained in:
2025-11-20 19:45:45 +07:00
parent 73b5cd6e97
commit f304a28493

View File

@@ -12,6 +12,8 @@ import {
Modal, Modal,
Tag, Tag,
message, message,
Spin,
Pagination,
} from 'antd'; } from 'antd';
import { import {
CloseCircleFilled, CloseCircleFilled,
@@ -33,6 +35,7 @@ import {
FilePdfOutlined, FilePdfOutlined,
PlusOutlined, PlusOutlined,
ExclamationCircleOutlined, ExclamationCircleOutlined,
SearchOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { useNavigate, Link as RouterLink } from 'react-router-dom'; import { useNavigate, Link as RouterLink } from 'react-router-dom';
import { getAllNotification } from '../../../api/notification'; import { getAllNotification } from '../../../api/notification';
@@ -129,25 +132,61 @@ const ListNotification = memo(function ListNotification(props) {
const [notifications, setNotifications] = useState([]); const [notifications, setNotifications] = useState([]);
const [activeTab, setActiveTab] = useState('all'); const [activeTab, setActiveTab] = useState('all');
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [searchValue, setSearchValue] = useState('');
const [loading, setLoading] = useState(false);
const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null
const [isAddingLog, setIsAddingLog] = useState(false); const [isAddingLog, setIsAddingLog] = useState(false);
const [selectedNotification, setSelectedNotification] = useState(null); const [selectedNotification, setSelectedNotification] = useState(null);
const [pagination, setPagination] = useState({
current_page: 1,
current_limit: 10,
total_limit: 0,
total_page: 1,
});
const navigate = useNavigate(); const navigate = useNavigate();
// Fetch notifications from API // Fetch notifications from API
const fetchNotifications = async () => { const fetchNotifications = async () => {
setLoading(true);
try { try {
const response = await getAllNotification(); const response = await getAllNotification();
if (response && response.data) { if (response && response.data) {
const transformedData = transformNotificationData(response.data); const transformedData = transformNotificationData(response.data);
setNotifications(transformedData); setNotifications(transformedData);
// Update pagination with mock data (since API doesn't provide pagination info)
const totalItems = transformedData.length;
setPagination((prev) => ({
...prev,
total_limit: totalItems,
total_page: Math.ceil(totalItems / prev.current_limit),
}));
} }
} catch (error) { } catch (error) {
console.error('Error fetching notifications:', error); console.error('Error fetching notifications:', error);
setNotifications([]); setNotifications([]);
} finally {
setTimeout(() => {
setLoading(false);
}, 500);
} }
}; };
const handlePaginationChange = (page, pageSize) => {
setPagination((prev) => ({
...prev,
current_page: page,
current_limit: pageSize,
}));
};
// Get paginated notifications
const getPaginatedNotifications = () => {
const startIndex = (pagination.current_page - 1) * pagination.current_limit;
const endIndex = startIndex + pagination.current_limit;
return filteredNotifications.slice(startIndex, endIndex);
};
useEffect(() => { useEffect(() => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (!token) { if (!token) {
@@ -200,6 +239,15 @@ const ListNotification = memo(function ListNotification(props) {
); );
}; };
const handleSearch = () => {
setSearchTerm(searchValue);
};
const handleSearchClear = () => {
setSearchValue('');
setSearchTerm('');
};
const filteredNotifications = notifications const filteredNotifications = notifications
.filter((n) => { .filter((n) => {
const matchesTab = const matchesTab =
@@ -210,8 +258,8 @@ const ListNotification = memo(function ListNotification(props) {
}) })
.filter((n) => { .filter((n) => {
if (!searchTerm) return true; if (!searchTerm) return true;
const searchableText = // Search by title and error code name
`${n.title} ${n.issue} ${n.description} ${n.location} ${n.details}`.toLowerCase(); const searchableText = `${n.title} ${n.issue}`.toLowerCase();
return searchableText.includes(searchTerm.toLowerCase()); return searchableText.includes(searchTerm.toLowerCase());
}); });
@@ -230,14 +278,17 @@ const ListNotification = memo(function ListNotification(props) {
transition: 'all 0.3s', transition: 'all 0.3s',
}); });
const renderDeviceNotifications = () => ( const renderDeviceNotifications = () => {
<Space direction="vertical" size="middle" style={{ display: 'flex' }}> const paginatedNotifications = getPaginatedNotifications();
{filteredNotifications.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px 0', color: '#8c8c8c' }}> return (
Tidak ada notifikasi <Space direction="vertical" size="middle" style={{ display: 'flex' }}>
</div> {filteredNotifications.length === 0 ? (
) : ( <div style={{ textAlign: 'center', padding: '40px 0', color: '#8c8c8c' }}>
filteredNotifications.map((notification) => { Tidak ada notifikasi
</div>
) : (
paginatedNotifications.map((notification) => {
const { IconComponent, color, bgColor } = getIconAndColor(notification.type); const { IconComponent, color, bgColor } = getIconAndColor(notification.type);
return ( return (
<Card <Card
@@ -333,7 +384,10 @@ const ListNotification = memo(function ListNotification(props) {
</Space> </Space>
<Space> <Space>
<LinkOutlined /> <LinkOutlined />
<AntdLink href={notification.link} target="_blank"> <AntdLink
href={notification.link}
target="_blank"
>
{notification.link} {notification.link}
</AntdLink> </AntdLink>
<Button <Button
@@ -381,14 +435,20 @@ const ListNotification = memo(function ListNotification(props) {
}} }}
/> />
<RouterLink <RouterLink
to={`/detail-notification/${notification.id.split('-')[1]}`} to={`/detail-notification/${
notification.id.split('-')[1]
}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<Button <Button
type="text" type="text"
icon={<EyeOutlined style={{ color: '#1890ff' }} />} icon={
<EyeOutlined
style={{ color: '#1890ff' }}
/>
}
title="Details" title="Details"
style={{ style={{
border: '1px solid #1890ff', border: '1px solid #1890ff',
@@ -422,8 +482,9 @@ const ListNotification = memo(function ListNotification(props) {
); );
}) })
)} )}
</Space> </Space>
); );
};
const renderUserHistory = () => ( const renderUserHistory = () => (
<> <>
@@ -635,9 +696,7 @@ const ListNotification = memo(function ListNotification(props) {
<Text type="secondary" style={{ fontSize: '12px' }}> <Text type="secondary" style={{ fontSize: '12px' }}>
Treshold Treshold
</Text> </Text>
<div style={{ fontWeight: 500 }}> <div style={{ fontWeight: 500 }}>N/A</div>
N/A
</div>
</Col> </Col>
</Row> </Row>
</div> </div>
@@ -1042,23 +1101,23 @@ const ListNotification = memo(function ListNotification(props) {
</Space> </Space>
</Card> </Card>
{logHistoryData.map((log) => ( {logHistoryData.map((log) => (
<Card <Card
key={log.id} key={log.id}
size="small" size="small"
bodyStyle={{ padding: '8px 12px' }} bodyStyle={{ padding: '8px 12px' }}
>
<Paragraph
style={{ fontSize: '12px', margin: 0 }}
ellipsis={{ rows: 2 }}
> >
<Paragraph <Text strong>{log.addedBy.name}:</Text>{' '}
style={{ fontSize: '12px', margin: 0 }} {log.description}
ellipsis={{ rows: 2 }} </Paragraph>
> <Text type="secondary" style={{ fontSize: '11px' }}>
<Text strong>{log.addedBy.name}:</Text>{' '} {log.timestamp}
{log.description} </Text>
</Paragraph> </Card>
<Text type="secondary" style={{ fontSize: '11px' }}> ))}
{log.timestamp}
</Text>
</Card>
))}
</Space> </Space>
</Card> </Card>
</Col> </Col>
@@ -1087,17 +1146,35 @@ const ListNotification = memo(function ListNotification(props) {
Riwayat notifikasi yang dikirim ke engineer Riwayat notifikasi yang dikirim ke engineer
</p> </p>
<Row <Row justify="space-between" align="middle" gutter={[8, 8]}>
justify="space-between" <Col xs={24} sm={24} md={12} lg={12}>
align="middle"
style={{ marginBottom: '24px' }}
>
<Col>
<Input.Search <Input.Search
placeholder="Search notifications..." placeholder="Search by notification name or error code name..."
onSearch={setSearchTerm} value={searchValue}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => {
style={{ width: 300 }} const value = e.target.value;
setSearchValue(value);
if (value === '') {
handleSearchClear();
}
}}
onSearch={handleSearch}
allowClear={{
clearIcon: <span onClick={handleSearchClear}></span>,
}}
enterButton={
<Button
type="primary"
icon={<SearchOutlined />}
style={{
backgroundColor: '#23A55A',
borderColor: '#23A55A',
}}
>
Search
</Button>
}
size="large"
/> />
</Col> </Col>
</Row> </Row>
@@ -1135,7 +1212,29 @@ const ListNotification = memo(function ListNotification(props) {
</div> </div>
</div> </div>
{renderDeviceNotifications()} <Spin spinning={loading}>
{renderDeviceNotifications()}
</Spin>
{/* PAGINATION */}
<Row justify="space-between" align="middle" style={{ marginTop: '16px' }}>
<Col>
<div>
Menampilkan {pagination.current_limit} data halaman{' '}
{pagination.current_page} dari total {pagination.total_limit} data
</div>
</Col>
<Col>
<Pagination
showSizeChanger
onChange={handlePaginationChange}
onShowSizeChange={handlePaginationChange}
current={pagination.current_page}
pageSize={pagination.current_limit}
total={pagination.total_limit}
/>
</Col>
</Row>
</Col> </Col>
</Row> </Row>
</Card> </Card>