Untitled
unknown
plain_text
a year ago
7.7 kB
12
Indexable
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import axios from 'axios';
import { Table, Select, Button, Modal } from '@citi-icg-172888/icgds-react';
import './Datatable.css';
const chunkColumns = (columns, size) => {
const result = [];
for (let i = 0; i < columns.length; i += size) {
result.push(columns.slice(i, i + size));
}
return result;
};
const DataTable = ({ type }) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Column filter state
const [searchCriteria, setSearchCriteria] = useState([]);
const [selectedColumn, setSelectedColumn] = useState('');
// Global search
const [globalSearch, setGlobalSearch] = useState('');
// NEW: Detail modal
const [selectedRecord, setSelectedRecord] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// Real fetch:
const response = await axios.get(`${BASE_URL}/${type}`);
setData(response.data);
setError(null);
} catch (err) {
setError(`Failed to fetch ${type} data`);
setData([]);
} finally {
setLoading(false);
}
};
if (type) {
fetchData();
}
}, [type]);
const columns = useMemo(() => {
if (!data.length) return [];
const firstRecord = data[0];
const colKeys = Object.keys(firstRecord);
// If at least one column, treat [0] as “clickable”
return colKeys.map((key, idx) => {
if (idx === 0) {
// Make first column clickable
return {
title: key,
dataIndex: key,
key: key,
sorter: (a, b) => String(a[key]).localeCompare(String(b[key])),
render: (text, record) => (
<a
style={{ cursor: 'pointer' }}
onClick={() => handleRowClick(record)}
>
{text}
</a>
),
};
}
// Normal column
return {
title: key,
dataIndex: key,
key: key,
sorter: (a, b) => {
const valA = a[key];
const valB = b[key];
if (typeof valA === 'string') {
return valA.localeCompare(valB);
}
return valA - valB;
},
};
});
}, [data]);
const availableColumns = useMemo(() => {
if (!columns.length) return [];
const usedCols = searchCriteria.map((c) => c.column);
return columns.filter((col) => !usedCols.includes(col.key));
}, [columns, searchCriteria]);
const filteredData = useMemo(() => {
// 1) Global search
let result = data;
if (globalSearch.trim()) {
const gSearch = globalSearch.toLowerCase();
result = result.filter((item) =>
columns.some((col) => String(item[col.key]).toLowerCase().includes(gSearch))
);
}
// 2) Column filters
if (searchCriteria.length) {
result = result.filter((item) =>
searchCriteria.every((crit) =>
String(item[crit.column]).toLowerCase().includes(crit.searchText.toLowerCase())
)
);
}
return result;
}, [data, globalSearch, searchCriteria, columns]);
const handleAddSearchCriteria = () => {
if (!selectedColumn) return;
setSearchCriteria([...searchCriteria, { column: selectedColumn, searchText: '' }]);
setSelectedColumn('');
};
const handleRemoveSearchCriteria = (columnToRemove) => {
setSearchCriteria((prev) => prev.filter((crit) => crit.column !== columnToRemove));
};
const handleSearchTextChange = (column, value) => {
setSearchCriteria((prev) =>
prev.map((crit) => (crit.column === column ? { ...crit, searchText: value } : crit))
);
};
// When user clicks the first column
const handleRowClick = useCallback((record) => {
setSelectedRecord(record);
setIsModalOpen(true);
}, []);
const closeModal = () => {
setIsModalOpen(false);
setSelectedRecord(null);
};
const MAX_COLUMNS_PER_TABLE = 5;
const chunkedColumns = useMemo(() => chunkColumns(columns, MAX_COLUMNS_PER_TABLE), [columns]);
return (
<div className="datatable-container">
{/* FILTERS */}
<div className="filters">
{/* Global Search */}
<div className="search-add-row">
<strong>Search:</strong>
<input
type="search"
value={globalSearch}
onChange={(e) => setGlobalSearch(e.target.value)}
placeholder="Search across columns..."
style={{ width: '180px', height: '34px', marginLeft: '6px' }}
/>
</div>
{/* Column-Specific Filter */}
<div className="search-add-row">
<Select
value={selectedColumn}
onChange={(val) => setSelectedColumn(val)}
style={{ width: '200px' }}
disabled={availableColumns.length === 0}
placeholder="Filter on columns..."
>
{availableColumns.map((col) => (
<Select.Option key={col.key} value={col.key}>
{col.title}
</Select.Option>
))}
</Select>
<Button onClick={handleAddSearchCriteria} disabled={!selectedColumn}>
+
</Button>
</div>
{/* Active column filters */}
{searchCriteria.map((crit) => {
const colLabel =
columns.find((col) => col.key === crit.column)?.title || crit.column;
return (
<div key={crit.column} className="search-criteria-row">
<span className="criteria-label">{colLabel}:</span>
<input
type="search"
value={crit.searchText}
onChange={(e) => handleSearchTextChange(crit.column, e.target.value)}
className="criteria-input"
/>
<Button onClick={() => handleRemoveSearchCriteria(crit.column)}>x</Button>
</div>
);
})}
</div>
{/* ERROR */}
{error && <div className="error-message">{error}</div>}
{/* TABLE(S) */}
{chunkedColumns.map((colSet, idx) => (
<Table
key={idx}
columns={colSet}
data={filteredData}
loading={loading}
pagination={
idx === chunkedColumns.length - 1
? {
defaultPageSize: 10,
showSizeChanger: true,
showTotal: (total, range) =>
`${range[0]}-${range[1]} of ${total} items`,
}
: false
}
/>
))}
{/* DETAIL MODAL */}
{selectedRecord && (
<Modal style={{ minWidth: '90%', minHeight: '90%' }} visible={isModalOpen} onCancel={closeModal} onOk={closeModal} title="Record Details">
{/* Display all fields of selectedRecord in a vertical layout */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{Object.keys(selectedRecord).map((key) => (
<div key={key} style={{ display: 'flex', gap: '10px' }}>
<strong style={{ width: '200px' }}>{key}:</strong>
<span>{String(selectedRecord[key])}</span>
</div>
))}
</div>
</Modal>
)}
</div>
);
};
export default DataTable;Editor is loading...
Leave a Comment