blob: 300f7d328fc6ffd1e01fefe63ec9721181cb5e75 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CSSProperties, useRef } from "react";
import {
Paper,
Table as MuiTable,
TableHead,
TableCell,
TableBody,
TableRow,
TableContainer,
Checkbox,
Stack,
CheckboxProps,
IconButton,
Collapse,
Typography,
Divider
} from "@mui/material";
import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
SortingState,
useReactTable
} from "@tanstack/react-table";
import { FC, useEffect, useMemo, useState } from "react";
import TableFilter from "./TableFilters";
import ArrowUpwardOutlinedIcon from "@mui/icons-material/ArrowUpwardOutlined";
import ArrowDownwardOutlinedIcon from "@mui/icons-material/ArrowDownwardOutlined";
import SwapVertOutlinedIcon from "@mui/icons-material/SwapVertOutlined";
import { useLocation, useSearchParams } from "react-router-dom";
import { isEmpty } from "../../utils/Utils";
import {
DndContext,
KeyboardSensor,
MouseSensor,
TouchSensor,
closestCenter,
type DragEndEvent,
useSensor,
useSensors
} from "@dnd-kit/core";
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
import {
arrayMove,
SortableContext,
horizontalListSortingStrategy
} from "@dnd-kit/sortable";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { TableProps } from "../../models/tableLayoutType";
import ChevronRightOutlinedIcon from "@mui/icons-material/ChevronRightOutlined";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import { CustomButton } from "../muiComponents";
import AddOutlinedIcon from "@mui/icons-material/AddOutlined";
import TableRowsLoader from "./TableLoader";
import AddTag from "@views/Classification/AddTag";
import FilterQuery from "@components/FilterQuery";
import TablePagination from "./TablePagination";
interface IndeterminateCheckboxProps extends Omit<CheckboxProps, "ref"> {
indeterminate?: boolean;
className?: string;
}
const IndeterminateCheckbox: FC<IndeterminateCheckboxProps> = ({
indeterminate,
className = "",
...rest
}) => {
const ref = useRef<HTMLInputElement>(null!);
useEffect(() => {
if (typeof indeterminate === "boolean") {
if (ref.current != null) {
ref.current.indeterminate = !rest.checked && indeterminate;
}
}
}, [ref, indeterminate]);
return (
<Checkbox
sx={{
color: "rgb(140, 140, 140)",
"&.Mui-checked": {
color: "rgb(25, 118, 210)"
}
}}
{...rest}
color="primary"
inputRef={ref}
className={className + " cursor-pointer"}
size="small"
/>
);
};
const Row = ({
row,
handleRow,
showRowSelection,
expandRow,
onClickRow,
columnOrder,
auditTableDetails
}: any) => {
const [openModule, setOpenModule] = useState(false);
const { Component = {}, componentProps = {} } = auditTableDetails || {};
return (
<>
<TableRow hover key={row.id} onClick={handleRow}>
{showRowSelection && (
<TableCell padding="checkbox" sx={{ width: "2%" }}>
<IndeterminateCheckbox
{...{
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler()
}}
/>
</TableCell>
)}
{expandRow && (
<TableCell padding="checkbox">
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpenModule(!openModule)}
>
{openModule ? (
<KeyboardArrowUpIcon color="success" />
) : (
<ChevronRightOutlinedIcon color="success" />
)}
</IconButton>
</TableCell>
)}
{row.getVisibleCells().map((cell: { id: null | undefined }) => (
<SortableContext
key={cell.id}
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
<DragAlongCell
key={cell.id}
cell={cell}
onClickRow={onClickRow}
row={row}
/>
</SortableContext>
))}
</TableRow>
{expandRow && (
<TableRow hover onClick={handleRow}>
<TableCell style={{ padding: 0 }} colSpan={10}>
<Collapse in={openModule} timeout="auto" unmountOnExit>
<Stack
className="properties-container"
key={row.index}
direction="column"
margin={0}
padding={2}
>
<Component componentProps={componentProps} row={row} />
</Stack>
</Collapse>
</TableCell>
</TableRow>
)}
</>
);
};
const DraggableTableHeader = ({ header }: { header: any }) => {
const { attributes, isDragging, listeners, setNodeRef, transform } =
useSortable({
id: header.column.id
});
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: "relative",
transform: CSS.Translate.toString(transform),
transition: "width transform 0.2s ease-in-out",
whiteSpace: "nowrap",
width:
header.column.columnDef?.width != undefined
? header.column.columnDef?.width
: header.column.getSize(),
zIndex: isDragging ? 1 : 0
};
return (
<TableCell
colSpan={header.colSpan}
key={header.id}
className="text-white text-sm font-cambon table-header-cell flex-1"
ref={setNodeRef}
style={style}
>
{header.isPlaceholder ? null : (
<div
className={
header.column.getCanSort() ? "cursor-pointer select-none" : ""
}
style={{
display: "flex",
alignItems: "center",
gap: "0.125rem"
}}
onClick={header.column.getToggleSortingHandler()}
title={
header.column.getCanSort()
? header.column.getNextSortingOrder() === "asc"
? "Sort ascending"
: header.column.getNextSortingOrder() === "desc"
? "Sort descending"
: "Clear sort"
: undefined
}
>
<span
style={{
display: "inline-block",
lineHeight: "20px"
}}
{...attributes}
{...listeners}
>
{" "}
{flexRender(header.column.columnDef.header, header.getContext())}
</span>
{{
asc: (
<ArrowUpwardOutlinedIcon
style={{
fontSize: "1rem",
display: "block"
}}
/>
),
desc: (
<ArrowDownwardOutlinedIcon
style={{
fontSize: "1rem",
display: "block"
}}
/>
),
false: header.column.getCanSort() &&
header.column.getIsSorted() == false && (
<SwapVertOutlinedIcon
style={{
fontSize: "1rem",
display: "block"
}}
/>
)
}[header.column.getIsSorted() as string] ?? null}{" "}
</div>
)}
</TableCell>
);
};
const DragAlongCell = ({
cell,
onClickRow,
row
}: {
cell: any;
onClickRow: any;
row: any;
}) => {
const { isDragging, setNodeRef, transform } = useSortable({
id: cell.column.id
});
const style: CSSProperties = {
opacity: isDragging ? 0.8 : 1,
position: "relative",
transform: CSS.Translate.toString(transform),
transition: "width transform 0.2s ease-in-out",
width:
cell.column.columnDef.width != undefined
? cell.column.columnDef.width
: cell.column.getSize(),
zIndex: isDragging ? 1 : 0,
padding: "8px",
fontSize: "14px !important"
};
return (
<TableCell
onClick={() => onClickRow?.(cell, row)}
key={cell.id}
sx={{ padding: "8px", fontSize: "14px !important" }}
className="text-[#2E353A] text-base font-graphik table-body-cell"
style={style}
ref={setNodeRef}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
);
};
const TableLayout: FC<TableProps> = ({
fetchData,
data,
columns,
isFetching,
defaultColumnVisibility,
pageCount,
totalCount,
onClickRow,
emptyText,
defaultColumnParams,
handleRow,
columnVisibility: isColumnVisible,
refreshTable,
defaultSortCol,
clientSideSorting,
columnSort,
showRowSelection,
tableFilters,
expandRow,
auditTableDetails,
assignFilters,
queryBuilder,
allTableFilters,
columnVisibilityParams,
showPagination,
setUpdateTable,
isfilterQuery,
isClientSidePagination,
isEmptyData,
setIsEmptyData,
showGoToPage
}) => {
let defaultHideColumns = { ...defaultColumnVisibility };
const location = useLocation();
const memoizedData = useMemo(() => data, [data]);
const memoizedColumns = useMemo(() => columns, [columns]);
const [searchParams] = useSearchParams();
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 25
});
const [goToPageVal, setGoToPageVal] = useState<any>("");
const [rowSelection, setRowSelection] = useState({});
const [sorting, setSorting] = useState<SortingState>(
!isEmpty(defaultSortCol) ? defaultSortCol : []
);
const [columnOrder, setColumnOrder] = useState<any>(() =>
!isEmpty(memoizedColumns)
? memoizedColumns.map((c: any) => c.accessorKey)
: []
);
const [columnVisibility, setColumnVisibility] = useState(defaultHideColumns);
const [tagModal, setTagModal] = useState<boolean>(false);
let currentParams = searchParams;
let typeParam = searchParams.get("type");
const params: any = {};
currentParams.forEach((value, key) => {
params[key] = value;
});
const {
getHeaderGroups,
getRowModel,
setPageIndex,
getPageCount,
nextPage,
previousPage,
setPageSize,
resetSorting,
resetRowSelection,
getIsAllRowsSelected,
getIsSomeRowsSelected,
getToggleAllRowsSelectedHandler,
getAllLeafColumns,
getSelectedRowModel
} = useReactTable({
data: memoizedData,
columns: memoizedColumns,
enableRowSelection: true,
manualSorting: !clientSideSorting && true,
enableSortingRemoval: false,
enableSorting: columnSort,
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
onSortingChange: setSorting,
onColumnOrderChange: setColumnOrder,
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: isClientSidePagination
? getPaginationRowModel()
: undefined,
onPaginationChange: setPagination,
state: {
columnVisibility: columnVisibilityParams
? defaultHideColumns
: columnVisibility,
sorting,
pagination,
rowSelection,
columnOrder
},
debugTable: true,
manualPagination: !isClientSidePagination && true,
pageCount,
autoResetPageIndex: false
});
useEffect(() => {
if (typeof fetchData === "function") {
fetchData({
pagination,
sorting
});
}
}, [
fetchData,
pagination.pageIndex,
pagination.pageSize,
clientSideSorting ? null : sorting
]);
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (active && over && active.id !== over.id) {
setColumnOrder((columnOrder: any) => {
const oldIndex = columnOrder.indexOf(active.id as string);
const newIndex = columnOrder.indexOf(over.id as string);
return arrayMove(columnOrder, oldIndex, newIndex);
});
}
}
const sensors = useSensors(
useSensor(MouseSensor, {}),
useSensor(TouchSensor, {}),
useSensor(KeyboardSensor, {})
);
const [, setSearchParams] = useSearchParams();
useEffect(() => {
resetSorting(true);
resetRowSelection(true);
setRowSelection({});
setSorting(!isEmpty(defaultSortCol) ? defaultSortCol : []);
}, [typeParam, defaultSortCol, setSearchParams]);
const handleCloseTagModal = () => {
setTagModal(false);
};
const selectedRow = !isEmpty(getSelectedRowModel()?.rows)
? getSelectedRowModel()?.rows?.map((obj) => {
return obj.original;
})
: [];
return (
<>
<Stack gap="0.5rem">
{tableFilters && (
<Paper
className="table-paper checkbox-table"
variant="outlined"
sx={{
boxShadow: "none !important",
padding: "12px 16px",
minHeight: "55px",
backgroundColor: "rgba(255,255,255,0.6)",
borderRadius: "4px"
}}
>
<Stack gap="0.75rem">
{tableFilters && (
<TableFilter
getAllColumns={getAllLeafColumns}
defaultColumnParams={defaultColumnParams}
columnVisibility={isColumnVisible}
columnVisibilityParams={columnVisibilityParams}
refreshTable={refreshTable}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
queryBuilder={queryBuilder}
allTableFilters={allTableFilters}
setUpdateTable={setUpdateTable}
getSelectedRowModel={getSelectedRowModel}
memoizedData={memoizedData}
/>
)}
{isfilterQuery && <Divider />}
{isfilterQuery &&
(location.pathname == "/search/searchResult" ||
location.pathname ==
"/relationship/relationshipSearchresult") && (
<Stack
flexWrap="wrap"
direction="row"
gap="0.25rem"
alignItems="center"
>
<FilterQuery value={params} />
</Stack>
)}
{assignFilters && (
<Stack
direction="row"
spacing={1}
position="absolute"
top={0}
left={0}
>
{!isEmpty(rowSelection) && (
<>
{assignFilters.term && (
<CustomButton
variant="outlined"
color="success"
classes="table-filter-btn"
size="small"
onClick={() => {
setRowSelection({});
}}
startIcon={<AddOutlinedIcon />}
>
Term
</CustomButton>
)}
{assignFilters.classifications && (
<CustomButton
variant="outlined"
color="success"
classes="table-filter-btn"
size="small"
onClick={() => {
setTagModal(true);
}}
startIcon={<AddOutlinedIcon />}
>
Classification
</CustomButton>
)}
</>
)}
</Stack>
)}
</Stack>
</Paper>
)}
<Paper
className="table-paper checkbox-table"
variant="outlined"
sx={{ boxShadow: "none !important" }}
>
<TableContainer>
<DndContext
collisionDetection={closestCenter}
modifiers={[restrictToHorizontalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
>
<MuiTable size="small" className="table">
{!isFetching && (
<TableHead>
{getHeaderGroups().map((headerGroup) => (
<TableRow
hover
key={headerGroup.id}
className="table-header-row"
>
{showRowSelection && (
<TableCell padding="checkbox">
<IndeterminateCheckbox
{...{
checked: getIsAllRowsSelected(),
indeterminate: getIsSomeRowsSelected(),
onChange: getToggleAllRowsSelectedHandler()
}}
/>
</TableCell>
)}
{expandRow && (
<TableCell sx={{ width: "2%" }} padding="checkbox" />
)}
<SortableContext
items={columnOrder}
strategy={horizontalListSortingStrategy}
>
{" "}
{headerGroup.headers.map((header) =>
header.isPlaceholder ? null : (
<DraggableTableHeader
key={header.id}
header={header}
/>
)
)}
</SortableContext>
</TableRow>
))}
</TableHead>
)}
<TableBody>
{isFetching ? (
<TableRowsLoader rowsNum={10} />
) : memoizedData.length === 0 && isFetching == false ? (
<TableRow>
<TableCell colSpan={columns.length + 1}>
<Stack textAlign="center">
<Typography fontWeight="600" color="text.secondary">
{emptyText}
</Typography>
</Stack>
</TableCell>
</TableRow>
) : (
getRowModel()?.rows.map((row) => (
<Row
key={row.id}
row={row}
handleRow={handleRow}
showRowSelection={showRowSelection}
expandRow={expandRow}
onClickRow={onClickRow}
columnOrder={columnOrder}
auditTableDetails={auditTableDetails}
/>
))
)}
</TableBody>
</MuiTable>
</DndContext>
</TableContainer>
{showPagination && (
<TablePagination
isServerSide={!isClientSidePagination}
getPageCount={getPageCount}
setPageIndex={setPageIndex}
setPageSize={setPageSize}
nextPage={nextPage}
previousPage={previousPage}
getRowModel={getRowModel}
pagination={pagination}
setRowSelection={setRowSelection}
memoizedData={memoizedData}
isFirstPage={pagination.pageIndex === 0}
setPagination={setPagination}
goToPageVal={goToPageVal}
setGoToPageVal={setGoToPageVal}
isEmptyData={isEmptyData}
setIsEmptyData={setIsEmptyData}
showGoToPage={showGoToPage}
totalCount={totalCount}
/>
)}
</Paper>
</Stack>
{tagModal && (
<div style={{ position: "absolute" }}>
<AddTag
open={tagModal}
isAdd={true}
entityData={selectedRow}
onClose={handleCloseTagModal}
setUpdateTable={setUpdateTable}
setRowSelection={setRowSelection}
/>
</div>
)}
</>
);
};
export { TableLayout, IndeterminateCheckbox };