diff --git a/libs/shared/lib/vis/simple_table_pagination/components/Pagination.tsx b/libs/shared/lib/vis/simple_table_pagination/components/Pagination.tsx new file mode 100644 index 0000000000000000000000000000000000000000..796461dfcffc4a98072d746a14b814ff1ba17e7d --- /dev/null +++ b/libs/shared/lib/vis/simple_table_pagination/components/Pagination.tsx @@ -0,0 +1,53 @@ +import React , { useRef } from 'react'; +import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; +import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; + +interface PaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + itemsPerPageInput: number; + numItemsArrayReal: number; + totalItems : number; +} + +const Pagination: React.FC<PaginationProps> = ({ currentPage, + totalPages, + onPageChange, + itemsPerPageInput, + numItemsArrayReal, + totalItems }) => { + + const pageNumbers = Array.from({ length: totalPages }, (_, index) => index + 1); + + const firstItem = (currentPage - 1) * itemsPerPageInput + 1; + const lastItem = Math.min(currentPage * itemsPerPageInput, totalPages); + + const goToPreviousPage = () => { + if (currentPage > 1) { + onPageChange(currentPage - 1); + } + }; + + const goToNextPage = () => { + if (currentPage < totalPages) { + onPageChange(currentPage + 1); + } + }; + + return ( + <div className="table-pagination"> + <span className="inline-block m-2 max-w-32 min-w-32"> + {`${firstItem}-${numItemsArrayReal} of ${totalItems}`} + </span> + <button className = "m-1 border-4 border-solid border-neutral p-2 w-11 text-primary" onClick={goToPreviousPage} disabled={currentPage === 1}> + <ArrowBackIosIcon /> + </button> + <button className = "m-1 border-4 border-solid border-neutral p-2 w-11 text-primary" onClick={goToNextPage} disabled={currentPage === totalPages}> + <ArrowForwardIosIcon /> + </button> + </div> + ); +}; + +export default Pagination; \ No newline at end of file diff --git a/libs/shared/lib/vis/simple_table_pagination/components/Table.tsx b/libs/shared/lib/vis/simple_table_pagination/components/Table.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ad6b7a0ed3e036ba4218861b30e0405aa9d3875f --- /dev/null +++ b/libs/shared/lib/vis/simple_table_pagination/components/Table.tsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from 'react'; +import Pagination from './Pagination'; + +interface TableProps { + data: any[]; + itemsPerPage: number; +} + +const Table: React.FC<TableProps> = ({ data, itemsPerPage }) => { + + const originalData = [...data]; + + const [sortedData, setSortedData] = useState<any[]>(data); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [sortColumn, setSortColumn] = useState<string | null>(null); + const [currentPage, setCurrentPage] = useState<number>(1); + + const keys = Object.keys(data[0]); + const totalPages = Math.ceil(sortedData.length / itemsPerPage); + + useEffect(() => { + if (sortColumn !== null) { + const sorted = [...data].sort((a, b) => { + if (sortOrder === 'asc') { + return a[sortColumn] < b[sortColumn] ? -1 : 1; + } else { + return a[sortColumn] > b[sortColumn] ? -1 : 1; + } + }); + setSortedData(sorted); + } + }, [sortOrder, data, sortColumn]); + + useEffect(() => { + setCurrentPage(1); // Reset to the first page when sorting or itemsPerPage changes + }, [sortColumn, sortOrder, itemsPerPage]); + + const onPageChange = (page: number) => { + setCurrentPage(page); + }; + + const toggleSort = (column: string) => { + if (sortColumn === column) { + if (sortOrder === 'asc') { + setSortOrder('desc'); + setSortedData([...sortedData].reverse()); // Reverse the sorted data + } else if (sortOrder === 'desc') { + setSortColumn(null); + setSortOrder('asc'); + setSortedData(originalData); // Reset to the original order + } + } else { + setSortColumn(column); + setSortOrder('asc'); + const sorted = [...originalData].sort((a, b) => { + return a[column] < b[column] ? -1 : 1; + }); + setSortedData(sorted); + } + }; + + // Calculate the startIndex and endIndex based on currentPage and itemsPerPage + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentData = sortedData.slice(startIndex, endIndex); + + function isString(value: any): boolean { + return typeof value === 'string'; + } + + return ( + <div className = "text-center font-inter text-primary"> + <table className="text-center my-2 mx-auto table-fixed w-11/12" > + <thead className="thead"> + <tr className="bg-white text-center p-0 pl-2 border-0 h-2 font-weight: 700"> + {keys.map((item, index) => ( + <th + className="th" + key={index} + onClick={() => toggleSort(item)} + > + {item}{' '} + {sortColumn === item && ( + <span>{sortOrder === 'asc' ? '▲' : '▼'}</span> + ) + } + </th> + ))} + </tr> + </thead> + <tbody className="border-l-2 border-t-2 border-r-2 border-b-2 border-white w-20"> + {currentData.map((obj, index) => ( + <tr key={index} className={index % 2 === 0 ? "bg-secondary" : "bg-base-100"}> + {keys.map((item, index) => ( + <td className={`${isString(obj[item]) ? 'text-left' : 'text-center'} px-1 py-0.5 border border-white m-0 overflow-x-hidden truncate`} + key={index} + > + {obj[item]} + </td> + )) + } + </tr> + ))} + </tbody> + </table> + + <Pagination + currentPage={currentPage} + totalPages={totalPages} + onPageChange={onPageChange} + itemsPerPageInput = {itemsPerPage} + numItemsArrayReal = {startIndex+currentData.length} + totalItems={sortedData.length} + /> + + + </div> + ); +}; + +export default Table; diff --git a/libs/shared/lib/vis/simple_table_pagination/simpleTable.tsx b/libs/shared/lib/vis/simple_table_pagination/simpleTable.tsx new file mode 100644 index 0000000000000000000000000000000000000000..847a6895c4edc4f501312f4f571e7efb66e69305 --- /dev/null +++ b/libs/shared/lib/vis/simple_table_pagination/simpleTable.tsx @@ -0,0 +1,25 @@ +import { useGraphQueryResult } from '../../data-access/store'; +import React, { useRef } from 'react'; +import Table from './components/Table'; + +export const SimpleTableVis = React.memo(() => { + + const ref = useRef<HTMLDivElement>(null); + + const graphQueryResult = useGraphQueryResult(); + + const nodes = graphQueryResult.nodes; + const attributesArray = nodes.map((node) => node.attributes); + + return ( + <> + <div className="h-full w-full overflow-hidden" ref={ref}> + {attributesArray.length > 0 && <Table data={attributesArray} itemsPerPage ={10} />} + </div> + </> + ); +}); + +SimpleTableVis.displayName = 'SimepleTableVis'; + +export default SimpleTableVis; diff --git a/libs/shared/lib/vis/simple_table_pagination/simpleTablevis.stories.tsx b/libs/shared/lib/vis/simple_table_pagination/simpleTablevis.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..780170c14863712464874d08c5a87738b7db0220 --- /dev/null +++ b/libs/shared/lib/vis/simple_table_pagination/simpleTablevis.stories.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { SimpleTableVis } from './simpleTable'; + +import { assignNewGraphQueryResult, graphQueryResultSlice, resetGraphQueryResults, store } from '../../data-access/store'; +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; +import { big2ndChamberQueryResult, smallFlightsQueryResults, mockLargeQueryResults, bigMockQueryResults } from '../../mock-data'; + +const Component: Meta<typeof SimpleTableVis> = { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Components/Visualizations/SimpleTableVis', + component: SimpleTableVis, + decorators: [ + (story) => ( + <Provider store={Mockstore}> + <div + style={{ + width: '100%', + height: '100vh', + }} + > + {story()} + </div> + </Provider> + ), + ], +}; + +const Mockstore = configureStore({ + reducer: { + graphQueryResult: graphQueryResultSlice.reducer, + }, +}); + +export const TestWithBig2ndChamber = { + args: { loading: false }, + play: async () => { + const dispatch = Mockstore.dispatch; + dispatch(assignNewGraphQueryResult({ queryID: '1', result: { type: 'nodelink', payload: big2ndChamberQueryResult } })); + }, +}; + +export default Component;