lavoce #2

Merged
yogiedigital merged 118 commits from lavoce into main 2025-10-20 04:06:02 +00:00
2 changed files with 131 additions and 28 deletions
Showing only changes of commit 9fc1c7cb40 - Show all commits

View 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;

View File

@@ -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' }}>
<TableList {viewMode === 'card' ? (
getData={getAllDevice} <CardDevice
queryParams={formDataFilter} data={deviceData}
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)} showPreviewModal={showPreviewModal}
triger={trigerFilter} showEditModal={showEditModal}
/> showDeleteDialog={showDeleteDialog}
/>
) : (
<TableList
getData={getAllDevice}
queryParams={formDataFilter}
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
triger={trigerFilter}
/>
)}
</Col> </Col>
</Row> </Row>
</Card> </Card>