feat: implement card view for device listing with actions for preview, edit, and delete
This commit is contained in:
84
src/pages/master/device/component/CardDevice.jsx
Normal file
84
src/pages/master/device/component/CardDevice.jsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, Button, Row, Col, Typography, Space, Tag } from 'antd';
|
||||||
|
import { EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
const CardDevice = ({ data, showPreviewModal, showEditModal, showDeleteDialog }) => {
|
||||||
|
const getCardStyle = () => {
|
||||||
|
const color = '#FF8C42'; // Orange color
|
||||||
|
return {
|
||||||
|
border: `2px solid ${color}`,
|
||||||
|
borderRadius: '8px',
|
||||||
|
textAlign: 'center' // Center text
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTitleStyle = () => {
|
||||||
|
const backgroundColor = '#FF8C42'; // Orange color
|
||||||
|
return {
|
||||||
|
backgroundColor,
|
||||||
|
color: '#fff',
|
||||||
|
padding: '2px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
display: 'inline-block',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={[16, 16]} style={{ marginTop: '16px', justifyContent: 'center' }}>
|
||||||
|
{data.map((item) => (
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6} key={item.device_id}>
|
||||||
|
<Card
|
||||||
|
title={
|
||||||
|
<span style={getTitleStyle()}>
|
||||||
|
{item.device_name}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
style={getCardStyle()}
|
||||||
|
actions={[
|
||||||
|
<Space size="middle" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ color: '#1890ff' }}
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => showPreviewModal(item)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ color: '#faad14' }}
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => showEditModal(item)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => showDeleteDialog(item)}
|
||||||
|
/>
|
||||||
|
</Space>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<Text strong>Code:</Text> {item.device_code}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Text strong>Location:</Text> {item.device_location}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Text strong>IP Address:</Text> {item.ip_address}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Text strong>Status:</Text>{' '}
|
||||||
|
<Tag color={item.device_status ? 'green' : 'red'}>
|
||||||
|
{item.device_status ? 'Running' : 'Offline'}
|
||||||
|
</Tag>
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardDevice;
|
||||||
@@ -7,27 +7,23 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
Card,
|
Card,
|
||||||
Divider,
|
|
||||||
Form,
|
|
||||||
Input,
|
Input,
|
||||||
Dropdown,
|
Segmented,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
FilterOutlined,
|
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EyeOutlined,
|
EyeOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
FilePdfOutlined,
|
AppstoreOutlined,
|
||||||
FileExcelOutlined,
|
TableOutlined,
|
||||||
EllipsisOutlined,
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../../components/Global/ToastNotif';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { deleteApd, getAllApd } from '../../../../api/master-apd';
|
|
||||||
import { deleteDevice, getAllDevice } from '../../../../api/master-device';
|
import { deleteDevice, getAllDevice } from '../../../../api/master-device';
|
||||||
import TableList from '../../../../components/Global/TableList';
|
import TableList from '../../../../components/Global/TableList';
|
||||||
|
import CardDevice from './CardDevice';
|
||||||
import { getFilterData } from '../../../../components/Global/DataFilter';
|
import { getFilterData } from '../../../../components/Global/DataFilter';
|
||||||
import ExcelJS from 'exceljs';
|
import ExcelJS from 'exceljs';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
@@ -119,6 +115,8 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
|
|||||||
const ListDevice = memo(function ListDevice(props) {
|
const ListDevice = memo(function ListDevice(props) {
|
||||||
const [showFilter, setShowFilter] = useState(false);
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
const [trigerFilter, setTrigerFilter] = useState(false);
|
const [trigerFilter, setTrigerFilter] = useState(false);
|
||||||
|
const [viewMode, setViewMode] = useState('card');
|
||||||
|
const [deviceData, setDeviceData] = useState([]);
|
||||||
|
|
||||||
const defaultFilter = { criteria: '' };
|
const defaultFilter = { criteria: '' };
|
||||||
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
||||||
@@ -126,10 +124,19 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getAllDevice(formDataFilter);
|
||||||
|
setDeviceData(response.data.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch device data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
if (props.actionMode == 'list') {
|
if (props.actionMode === 'list') {
|
||||||
setFormDataFilter(defaultFilter);
|
setFormDataFilter(defaultFilter);
|
||||||
doFilter();
|
doFilter();
|
||||||
}
|
}
|
||||||
@@ -138,6 +145,10 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
}
|
}
|
||||||
}, [props.actionMode]);
|
}, [props.actionMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [trigerFilter]);
|
||||||
|
|
||||||
const toggleFilter = () => {
|
const toggleFilter = () => {
|
||||||
setFormDataFilter(defaultFilter);
|
setFormDataFilter(defaultFilter);
|
||||||
setShowFilter((prev) => !prev);
|
setShowFilter((prev) => !prev);
|
||||||
@@ -186,8 +197,7 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
const handleDelete = async (device_id) => {
|
const handleDelete = async (device_id) => {
|
||||||
const response = await deleteDevice(device_id);
|
const response = await deleteDevice(device_id);
|
||||||
|
|
||||||
// Backend returns: { statusCode: 200, message: "Device deleted successfully", rows: null, data: true }
|
if (response.statusCode === 200 && response.data === true) {
|
||||||
if (response.statusCode == 200 && response.data === true) {
|
|
||||||
NotifAlert({
|
NotifAlert({
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
title: 'Berhasil',
|
title: 'Berhasil',
|
||||||
@@ -213,7 +223,6 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
const sheet = workbook.addWorksheet('Data APD');
|
const sheet = workbook.addWorksheet('Data APD');
|
||||||
let rowCursor = 1;
|
let rowCursor = 1;
|
||||||
// Kop Logo PIE
|
|
||||||
if (logoPiEnergi) {
|
if (logoPiEnergi) {
|
||||||
const response = await fetch(logoPiEnergi);
|
const response = await fetch(logoPiEnergi);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
@@ -224,24 +233,21 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
extension: 'png',
|
extension: 'png',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tempatkan gambar di pojok atas
|
|
||||||
sheet.addImage(imageId, {
|
sheet.addImage(imageId, {
|
||||||
tl: { col: 0.2, row: 0.8 },
|
tl: { col: 0.2, row: 0.8 },
|
||||||
ext: { width: 163, height: 80 },
|
ext: { width: 163, height: 80 },
|
||||||
});
|
});
|
||||||
|
|
||||||
sheet.getRow(5).height = 15; // biar ada jarak ke tabel
|
sheet.getRow(5).height = 15;
|
||||||
rowCursor = 3;
|
rowCursor = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tambah Judul
|
|
||||||
const titleCell = sheet.getCell(`C${rowCursor}`);
|
const titleCell = sheet.getCell(`C${rowCursor}`);
|
||||||
titleCell.value = 'Data APD';
|
titleCell.value = 'Data APD';
|
||||||
titleCell.font = { size: 20, bold: true, color: { argb: 'FF00AEEF' } };
|
titleCell.font = { size: 20, bold: true, color: { argb: 'FF00AEEF' } };
|
||||||
titleCell.alignment = { vertical: 'middle', horizontal: 'center' };
|
titleCell.alignment = { vertical: 'middle', horizontal: 'center' };
|
||||||
sheet.mergeCells(`C${rowCursor}:F${rowCursor}`);
|
sheet.mergeCells(`C${rowCursor}:F${rowCursor}`);
|
||||||
|
|
||||||
// Header tabel
|
|
||||||
const headers = [
|
const headers = [
|
||||||
'ID APD',
|
'ID APD',
|
||||||
'Nama APD',
|
'Nama APD',
|
||||||
@@ -256,12 +262,11 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
headerRow.font = { bold: true, size: 12 };
|
headerRow.font = { bold: true, size: 12 };
|
||||||
headerRow.eachCell((cell) => {
|
headerRow.eachCell((cell) => {
|
||||||
cell.alignment = {
|
cell.alignment = {
|
||||||
horizontal: 'center', // rata tengah kiri-kanan
|
horizontal: 'center',
|
||||||
vertical: 'middle', // rata tengah atas-bawah
|
vertical: 'middle',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tambahkan data
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
sheet.addRow([
|
sheet.addRow([
|
||||||
item.id_apd,
|
item.id_apd,
|
||||||
@@ -274,7 +279,6 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto width
|
|
||||||
sheet.columns.forEach((col) => {
|
sheet.columns.forEach((col) => {
|
||||||
let maxLength = 10;
|
let maxLength = 10;
|
||||||
col.eachCell({ includeEmpty: true }, (cell) => {
|
col.eachCell({ includeEmpty: true }, (cell) => {
|
||||||
@@ -284,7 +288,6 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
col.width = maxLength + 2;
|
col.width = maxLength + 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Export
|
|
||||||
const buffer = await workbook.xlsx.writeBuffer();
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
saveAs(new Blob([buffer]), 'Data_APD.xlsx');
|
saveAs(new Blob([buffer]), 'Data_APD.xlsx');
|
||||||
};
|
};
|
||||||
@@ -302,7 +305,6 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setSearchValue(value);
|
setSearchValue(value);
|
||||||
// Auto search when clearing by backspace/delete
|
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
setFormDataFilter({ criteria: '' });
|
setFormDataFilter({ criteria: '' });
|
||||||
setTrigerFilter((prev) => !prev);
|
setTrigerFilter((prev) => !prev);
|
||||||
@@ -329,6 +331,14 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Space wrap size="small">
|
<Space wrap size="small">
|
||||||
|
<Segmented
|
||||||
|
options={[
|
||||||
|
{ value: 'card', icon: <AppstoreOutlined /> },
|
||||||
|
{ value: 'table', icon: <TableOutlined /> },
|
||||||
|
]}
|
||||||
|
value={viewMode}
|
||||||
|
onChange={setViewMode}
|
||||||
|
/>
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
theme={{
|
theme={{
|
||||||
token: { colorBgContainer: '#E9F6EF' },
|
token: { colorBgContainer: '#E9F6EF' },
|
||||||
@@ -356,12 +366,21 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
||||||
|
{viewMode === 'card' ? (
|
||||||
|
<CardDevice
|
||||||
|
data={deviceData}
|
||||||
|
showPreviewModal={showPreviewModal}
|
||||||
|
showEditModal={showEditModal}
|
||||||
|
showDeleteDialog={showDeleteDialog}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<TableList
|
<TableList
|
||||||
getData={getAllDevice}
|
getData={getAllDevice}
|
||||||
queryParams={formDataFilter}
|
queryParams={formDataFilter}
|
||||||
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
|
||||||
triger={trigerFilter}
|
triger={trigerFilter}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user