Untitled

 avatar
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