template card or table in component table list

This commit is contained in:
2025-10-16 14:55:57 +07:00
parent 9bb07b1224
commit 9f46908d79
5 changed files with 183 additions and 321 deletions

View File

@@ -0,0 +1,102 @@
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 CardList = ({
data,
column,
header,
showPreviewModal,
showEditModal,
showDeleteDialog,
cardColor,
}) => {
const getCardStyle = () => {
const color = cardColor ?? '#F3EDEA'; // Orange color
return {
border: `2px solid ${color}`,
borderRadius: '8px',
textAlign: 'center', // Center text
};
};
const getTitleStyle = (color) => {
const backgroundColor = color ?? '#FCF2ED';
return {
backgroundColor,
color: '#fff',
padding: '2px 8px',
borderRadius: '4px',
display: 'inline-block', // ganti inline-block → block
width: 'fit-content', // biar lebarnya tetap menyesuaikan teks
};
};
return (
<Row gutter={[16, 16]} style={{ marginTop: '16px', justifyContent: 'left' }}>
{data.map((item) => (
<Col xs={24} sm={24} md={12} lg={8} key={item.device_id}>
<Card
title={
<div
style={{
display: 'flex',
justifyContent: 'space-between', // kiri & kanan
alignItems: 'center',
}}
>
<span style={getTitleStyle(item.color ?? cardColor)}>
{item[header]}
</span>
</div>
}
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>,
]}
>
<div style={{ textAlign: 'left' }}>
{column.map((itemCard) => (
<>
{!itemCard.hidden && !itemCard.render && (
<p>
<Text strong>{itemCard.title}:</Text>{' '}
{item[itemCard.key]}
</p>
)}
{itemCard.render && itemCard.render}
</>
))}
</div>
</Card>
</Col>
))}
</Row>
);
};
export default CardList;

View File

@@ -1,5 +1,5 @@
import React, { memo, useState, useEffect, useRef } from 'react';
import { Table, Pagination, Row, Col, Card, Grid, Button, Typography, Tag } from 'antd';
import { Table, Pagination, Row, Col, Card, Grid, Button, Typography, Tag, Segmented } from 'antd';
import {
PlusOutlined,
FilterOutlined,
@@ -8,57 +8,15 @@ import {
EyeOutlined,
SearchOutlined,
FilePdfOutlined,
AppstoreOutlined,
TableOutlined,
} from '@ant-design/icons';
import { setFilterData } from './DataFilter';
import CardDevice from '../../pages/master/device/component/CardDevice';
import CardList from './CardList';
const { Text } = Typography;
const defCard = {
r1: {
style: { fontWeight: 'bold', fontSize: 13 },
type: 'primary',
color: '',
text: 'Cold Work Permit',
name: '',
},
r2: {
style: { marginLeft: 8, fontSize: 13 },
type: 'primary',
color: 'success',
text: 'Pengajuan',
name: '',
},
r3: {
style: { fontSize: 12 },
type: 'secondary',
color: '',
text: 'No. IVR/20250203/XXV/III',
name: '',
},
r4: {
style: { fontSize: 12 },
type: 'primary',
color: '',
text: '3 Feb 2025',
name: '',
},
r5: {
style: { fontSize: 12 },
type: 'primary',
color: '',
text: 'Lokasi Gudang Robang',
name: '',
},
r6: {
style: { fontSize: 12 },
type: 'primary',
color: '',
text: 'maka tambahkan user tersebut dalam user_partner dengan partner baru yang ditambahkan diatas',
name: '',
},
action: (e) => {},
};
const TableList = memo(function TableList({
getData,
queryParams,
@@ -66,6 +24,11 @@ const TableList = memo(function TableList({
triger,
mobile,
rowSelection = null,
header = 'name',
showPreviewModal,
showEditModal,
showDeleteDialog,
cardColor,
}) {
const [gridLoading, setGridLoading] = useState(false);
@@ -82,6 +45,8 @@ const TableList = memo(function TableList({
total: 0,
});
const [viewMode, setViewMode] = useState('card');
const { useBreakpoint } = Grid;
useEffect(() => {
@@ -110,16 +75,16 @@ const TableList = memo(function TableList({
if (resData.status == 200) {
setPagingResponse({
totalData: resData.data.total,
perPage: resData.data.paging.page_total,
totalPage: resData.data.paging.limit,
totalData: resData.paging.total_limit,
perPage: resData.paging.page_total,
totalPage: resData.paging.total_page,
});
setPagination((prev) => ({
...prev,
current: resData.data.paging.page,
limit: resData.data.paging.limit,
total: resData.data.paging.total,
current: resData.paging.current_page,
limit: resData.paging.current_limit,
total: resData.paging.total_limit,
}));
}
};
@@ -139,124 +104,42 @@ const TableList = memo(function TableList({
return (
<div>
{isMobile && mobile ? (
<Row gutter={24}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{data.map((item) => (
<Card
key={item.id}
title={
(mobile.r1 || mobile.r2) && (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{mobile.r1 && (
<span style={mobile.r1.style ?? {}}>
{item[mobile.r1.name] ?? mobile.r1.text ?? ''}
</span>
)}
{mobile.r2 && (
<Tag
color={mobile.r2.color ?? ''}
style={mobile.r2.style ?? {}}
>
{item[mobile.r2.name] ?? mobile.r2.text ?? ''}
</Tag>
)}
</div>
)
}
style={{ width: '100%' }}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{mobile.r3 && mobile.r4 && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
justifyContent: 'space-between',
}}
>
<Text
type={mobile.r3 ? mobile.r3.type ?? 'primary' : ''}
style={mobile.r3 ? mobile.r3.style ?? {} : {}}
>
{item[mobile.r3 ? mobile.r3.name : ''] ?? ''}
</Text>
<Text style={mobile.r4 ? mobile.r4.style ?? {} : {}}>
{item[mobile.r4 ? mobile.r4.name : ''] ?? ''}
</Text>
</div>
)}
{mobile.r5 && (
<Text
type={mobile.r5 ? mobile.r5.type ?? 'secondary' : ''}
style={mobile.r5 ? mobile.r5.style ?? {} : {}}
>
{item[mobile.r5 ? mobile.r5.name : ''] ?? ''}
</Text>
)}
</div>
{mobile.r6 && (
<div>
<Text
type={mobile.r6 ? mobile.r6.type ?? 'primary' : ''}
style={mobile.r6 ? mobile.r6.style ?? {} : {}}
>
{item[mobile.r6 ? mobile.r6.name : ''] ?? ''}
</Text>
</div>
)}
<div
style={{
marginTop: 16,
borderTop: '1px solid #f0f0f0',
paddingTop: 8,
textAlign: 'right',
}}
>
<Button
type="primary"
size="small"
shape="round"
icon={<EyeOutlined />}
onClick={(e) => {
mobile.action(item);
}}
>
Detail
</Button>
</div>
</Card>
))}
</div>
</Row>
<Segmented
options={[
{ value: 'card', icon: <AppstoreOutlined /> },
{ value: 'table', icon: <TableOutlined /> },
]}
value={viewMode}
onChange={setViewMode}
/>
{(isMobile && mobile) || viewMode === 'card' ? (
<CardList
cardColor={cardColor}
data={data}
column={columns}
header={header}
showPreviewModal={showPreviewModal}
showEditModal={showEditModal}
showDeleteDialog={showDeleteDialog}
/>
) : (
<Row gutter={24}>
{/* TABLE */}
<Table
rowSelection={rowSelection || null}
columns={columns}
dataSource={data.map((item, index) => ({ ...item, key: index }))}
pagination={false}
loading={gridLoading}
scroll={{
y: 520,
}}
scroll={{ y: 520 }}
/>
</Row>
)}
{/* PAGINATION */}
<Col xs={24} style={{ marginTop: '16px' }}>
<Row justify="space-between" align="middle">
<Col>
<div>
Menampilkan {pagingResponse.totalPage} Data dari{' '}
{pagingResponse.perPage} Halaman
Menampilkan {pagingResponse.totalPage} Data dari {pagingResponse.perPage}{' '}
Halaman
</div>
</Col>
<Col>
@@ -270,9 +153,6 @@ const TableList = memo(function TableList({
/>
</Col>
</Row>
</Col>
</Row>
)}
</div>
);
});

View File

@@ -137,7 +137,6 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
];
const ListBrandDevice = memo(function ListBrandDevice(props) {
const [showFilter, setShowFilter] = useState(false);
const [trigerFilter, setTrigerFilter] = useState(false);
const [brandDeviceData, setBrandDeviceData] = useState(initialBrandDeviceData);
@@ -208,11 +207,6 @@ const ListBrandDevice = memo(function ListBrandDevice(props) {
}
}, [props.actionMode, brandDeviceData]);
const toggleFilter = () => {
setFormDataFilter(defaultFilter);
setShowFilter((prev) => !prev);
};
const doFilter = () => {
setTrigerFilter((prev) => !prev);
};
@@ -370,6 +364,12 @@ const ListBrandDevice = memo(function ListBrandDevice(props) {
</Col>
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
<TableList
mobile
cardColor={'#42AAFF'}
header={'tag_name'}
showPreviewModal={showPreviewModal}
showEditModal={showEditModal}
showDeleteDialog={showDeleteDialog}
getData={getAllBrandDevice}
queryParams={formDataFilter}
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}

View File

@@ -1,15 +1,5 @@
import React, { memo, useState, useEffect } from 'react';
import {
Space,
Tag,
ConfigProvider,
Button,
Row,
Col,
Card,
Input,
Segmented,
} from 'antd';
import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input, Segmented } from 'antd';
import {
PlusOutlined,
EditOutlined,
@@ -23,11 +13,6 @@ import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../../components/
import { useNavigate } from 'react-router-dom';
import { deleteDevice, getAllDevice } from '../../../../api/master-device';
import TableList from '../../../../components/Global/TableList';
import CardDevice from './CardDevice';
import { getFilterData } from '../../../../components/Global/DataFilter';
import ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
import logoPiEnergi from '../../../../assets/images/logo/pi-energi.png';
const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
{
@@ -113,10 +98,7 @@ const columns = (showPreviewModal, showEditModal, showDeleteDialog) => [
];
const ListDevice = memo(function ListDevice(props) {
const [showFilter, setShowFilter] = useState(false);
const [trigerFilter, setTrigerFilter] = useState(false);
const [viewMode, setViewMode] = useState('card');
const [deviceData, setDeviceData] = useState([]);
const defaultFilter = { criteria: '' };
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
@@ -124,15 +106,6 @@ const ListDevice = memo(function ListDevice(props) {
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(() => {
const token = localStorage.getItem('token');
if (token) {
@@ -145,15 +118,6 @@ const ListDevice = memo(function ListDevice(props) {
}
}, [props.actionMode]);
useEffect(() => {
fetchData();
}, [trigerFilter]);
const toggleFilter = () => {
setFormDataFilter(defaultFilter);
setShowFilter((prev) => !prev);
};
const doFilter = () => {
setTrigerFilter((prev) => !prev);
};
@@ -213,85 +177,6 @@ const ListDevice = memo(function ListDevice(props) {
}
};
const generatePdf = () => {
props.setActionMode('generatepdf');
};
const exportExcel = async () => {
const data = getFilterData();
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Data APD');
let rowCursor = 1;
if (logoPiEnergi) {
const response = await fetch(logoPiEnergi);
const blob = await response.blob();
const buffer = await blob.arrayBuffer();
const imageId = workbook.addImage({
buffer,
extension: 'png',
});
sheet.addImage(imageId, {
tl: { col: 0.2, row: 0.8 },
ext: { width: 163, height: 80 },
});
sheet.getRow(5).height = 15;
rowCursor = 3;
}
const titleCell = sheet.getCell(`C${rowCursor}`);
titleCell.value = 'Data APD';
titleCell.font = { size: 20, bold: true, color: { argb: 'FF00AEEF' } };
titleCell.alignment = { vertical: 'middle', horizontal: 'center' };
sheet.mergeCells(`C${rowCursor}:F${rowCursor}`);
const headers = [
'ID APD',
'Nama APD',
'Deskripsi',
'Jenis Permit Default',
'Aktif',
'Dibuat',
'Diubah',
];
sheet.addRow(headers);
const headerRow = sheet.getRow(6);
headerRow.font = { bold: true, size: 12 };
headerRow.eachCell((cell) => {
cell.alignment = {
horizontal: 'center',
vertical: 'middle',
};
});
data.forEach((item) => {
sheet.addRow([
item.id_apd,
item.nama_apd,
item.deskripsi_apd ?? '',
item.jenis_permit_default ?? '',
item.is_active ? 'Ya' : 'Tidak',
new Date(item.created_at).toLocaleString(),
new Date(item.updated_at).toLocaleString(),
]);
});
sheet.columns.forEach((col) => {
let maxLength = 10;
col.eachCell({ includeEmpty: true }, (cell) => {
const len = cell.value?.toString().length || 0;
if (len > maxLength) maxLength = len;
});
col.width = maxLength + 2;
});
const buffer = await workbook.xlsx.writeBuffer();
saveAs(new Blob([buffer]), 'Data_APD.xlsx');
};
return (
<React.Fragment>
<Card>
@@ -331,14 +216,6 @@ const ListDevice = memo(function ListDevice(props) {
</Col>
<Col>
<Space wrap size="small">
<Segmented
options={[
{ value: 'card', icon: <AppstoreOutlined /> },
{ value: 'table', icon: <TableOutlined /> },
]}
value={viewMode}
onChange={setViewMode}
/>
<ConfigProvider
theme={{
token: { colorBgContainer: '#E9F6EF' },
@@ -366,21 +243,18 @@ const ListDevice = memo(function ListDevice(props) {
</Row>
</Col>
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
{viewMode === 'card' ? (
<CardDevice
data={deviceData}
<TableList
mobile
cardColor={'#42AAFF'}
header={'device_name'}
showPreviewModal={showPreviewModal}
showEditModal={showEditModal}
showDeleteDialog={showDeleteDialog}
/>
) : (
<TableList
getData={getAllDevice}
queryParams={formDataFilter}
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}
triger={trigerFilter}
/>
)}
</Col>
</Row>
</Card>

View File

@@ -274,6 +274,12 @@ const ListTag = memo(function ListTag(props) {
</Col>
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
<TableList
mobile
cardColor={'#42AAFF'}
header={'tag_name'}
showPreviewModal={showPreviewModal}
showEditModal={showEditModal}
showDeleteDialog={showDeleteDialog}
getData={getAllTag}
queryParams={formDataFilter}
columns={columns(showPreviewModal, showEditModal, showDeleteDialog)}