Untitled
unknown
plain_text
3 months ago
7.7 kB
9
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