Added missing ui stuff
This commit is contained in:
parent
d2d8b0f12d
commit
a763f3ec5f
147
apps/ui/web/src/app/features/table/expandableTable.tsx
Normal file
147
apps/ui/web/src/app/features/table/expandableTable.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { Box, TableContainer, Table, TableHead, TableRow, TableCell, Typography, TableBody, useTheme } from "@mui/material";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { TablePropetyConfig, TableCellCustomizer, TableRowActionEvents } from "./table";
|
||||||
|
import IconArrowUp from '@mui/icons-material/ArrowUpward';
|
||||||
|
import IconArrowDown from '@mui/icons-material/ArrowDownward';
|
||||||
|
|
||||||
|
export interface ExpandableItem<T> {
|
||||||
|
tag: string;
|
||||||
|
expandElement: JSX.Element | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExpandableRender<T> = (item: T) => ExpandableItem<T> | null;
|
||||||
|
|
||||||
|
|
||||||
|
export default function ExpandableTable<T>({ items, columns, cellCustomizer: customizer, expandableRender, onRowClickedEvent }: { items: Array<T>, columns: Array<TablePropetyConfig>, cellCustomizer?: TableCellCustomizer<T>, expandableRender: ExpandableRender<T>, onRowClickedEvent?: TableRowActionEvents<T> }) {
|
||||||
|
const muiTheme = useTheme();
|
||||||
|
|
||||||
|
const [order, setOrder] = useState<'asc' | 'desc'>('asc');
|
||||||
|
const [orderBy, setOrderBy] = useState<string>('');
|
||||||
|
const [selectedRow, setSelectedRow] = useState<T | null>(null);
|
||||||
|
|
||||||
|
const tableRowSingleClicked = (row: T | null) => {
|
||||||
|
if (row === selectedRow) {
|
||||||
|
setSelectedRow(null);
|
||||||
|
} else {
|
||||||
|
setSelectedRow(row);
|
||||||
|
if (row && onRowClickedEvent) {
|
||||||
|
onRowClickedEvent.click(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tableRowDoubleClicked = (row: T | null) => {
|
||||||
|
setSelectedRow(row);
|
||||||
|
if (row && onRowClickedEvent) {
|
||||||
|
onRowClickedEvent.doubleClick(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableRowContextMenu = (e: React.MouseEvent<HTMLTableRowElement, MouseEvent> , row: T | null) => {
|
||||||
|
if (row && onRowClickedEvent && onRowClickedEvent.contextMenu) {
|
||||||
|
e.preventDefault()
|
||||||
|
onRowClickedEvent.contextMenu(row, e.pageX, e.pageY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSort = (property: string) => {
|
||||||
|
const isAsc = orderBy === property && order === 'asc';
|
||||||
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
|
setOrderBy(property);
|
||||||
|
};
|
||||||
|
|
||||||
|
const compareValues = (a: any, b: any, orderBy: string) => {
|
||||||
|
if (typeof a[orderBy] === 'string') {
|
||||||
|
return a[orderBy].localeCompare(b[orderBy]);
|
||||||
|
} else if (typeof a[orderBy] === 'number') {
|
||||||
|
return a[orderBy] - b[orderBy];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedData = items.slice().sort((a, b) => {
|
||||||
|
if (order === 'asc') {
|
||||||
|
return compareValues(a, b, orderBy);
|
||||||
|
} else {
|
||||||
|
return compareValues(b, a, orderBy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleSort(columns[0].accessor)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column", // Bruk column-fleksretning
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}>
|
||||||
|
<TableContainer sx={{
|
||||||
|
flex: 1,
|
||||||
|
overflowY: "auto",
|
||||||
|
position: "relative", // Legg til denne linjen for å justere layout
|
||||||
|
maxHeight: "100%" // Legg til denne linjen for å begrense høyden
|
||||||
|
}}>
|
||||||
|
<Table>
|
||||||
|
<TableHead sx={{
|
||||||
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
backgroundColor: muiTheme.palette.background.paper,
|
||||||
|
}}>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell key={column.accessor} onClick={() => handleSort(column.accessor)} sx={{ cursor: "pointer" }}>
|
||||||
|
<Box display="flex">
|
||||||
|
{orderBy === column.accessor ?
|
||||||
|
(order === "asc" ? (<IconArrowDown />) : (<IconArrowUp />)) : (
|
||||||
|
<IconArrowDown sx={{ color: "transparent" }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Typography>{column.label}</Typography>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody sx={{
|
||||||
|
overflowY: "scroll"
|
||||||
|
}}>
|
||||||
|
{sortedData?.map((row: T, rowIndex: number) => (
|
||||||
|
<>
|
||||||
|
<TableRow key={rowIndex}
|
||||||
|
onClick={() => tableRowSingleClicked(row)}
|
||||||
|
onDoubleClick={() => tableRowDoubleClicked(row)}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
tableRowContextMenu(e, row);
|
||||||
|
tableRowSingleClicked(row);
|
||||||
|
}}
|
||||||
|
style={{ cursor: "pointer", backgroundColor: selectedRow === row ? muiTheme.palette.action.selected : '' }}
|
||||||
|
>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell key={column.accessor}>
|
||||||
|
{customizer && customizer(column.accessor, row) !== null
|
||||||
|
? customizer(column.accessor, row)
|
||||||
|
: <Typography variant='body1'>{(row as any)[column.accessor]}</Typography>}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
{(selectedRow == row) ?
|
||||||
|
(<TableRow key={rowIndex + "_1"}>
|
||||||
|
<TableCell colSpan={columns.length}>
|
||||||
|
{
|
||||||
|
expandableRender(row)?.expandElement
|
||||||
|
}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>): null
|
||||||
|
}
|
||||||
|
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
126
apps/ui/web/src/app/features/table/sortableTable.tsx
Normal file
126
apps/ui/web/src/app/features/table/sortableTable.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../store";
|
||||||
|
import IconArrowUp from '@mui/icons-material/ArrowUpward';
|
||||||
|
import IconArrowDown from '@mui/icons-material/ArrowDownward';
|
||||||
|
import { Table, TableHead, TableRow, TableCell, TableBody, Typography, Box, useTheme, TableContainer } from "@mui/material";
|
||||||
|
import { TablePropetyConfig, TableCellCustomizer, TableRowActionEvents } from "./table";
|
||||||
|
|
||||||
|
|
||||||
|
export default function SimpleTable<T>({ items, columns, customizer, onRowClickedEvent }: { items: Array<T>, columns: Array<TablePropetyConfig>, customizer?: TableCellCustomizer<T>, onRowClickedEvent?: TableRowActionEvents<T> }) {
|
||||||
|
const muiTheme = useTheme();
|
||||||
|
|
||||||
|
const [order, setOrder] = useState<'asc' | 'desc'>('asc');
|
||||||
|
const [orderBy, setOrderBy] = useState<string>('');
|
||||||
|
const [selectedRow, setSelectedRow] = useState<T | null>(null);
|
||||||
|
|
||||||
|
const tableRowSingleClicked = (row: T | null) => {
|
||||||
|
setSelectedRow(row);
|
||||||
|
if (row && onRowClickedEvent) {
|
||||||
|
onRowClickedEvent.click(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tableRowDoubleClicked = (row: T | null) => {
|
||||||
|
setSelectedRow(row);
|
||||||
|
if (row && onRowClickedEvent) {
|
||||||
|
onRowClickedEvent.doubleClick(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableRowContextMenu = (e: React.MouseEvent<HTMLTableRowElement, MouseEvent> , row: T | null) => {
|
||||||
|
if (row && onRowClickedEvent && onRowClickedEvent.contextMenu) {
|
||||||
|
e.preventDefault()
|
||||||
|
onRowClickedEvent.contextMenu(row, e.pageX, e.pageY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSort = (property: string) => {
|
||||||
|
const isAsc = orderBy === property && order === 'asc';
|
||||||
|
setOrder(isAsc ? 'desc' : 'asc');
|
||||||
|
setOrderBy(property);
|
||||||
|
};
|
||||||
|
|
||||||
|
const compareValues = (a: any, b: any, orderBy: string) => {
|
||||||
|
if (typeof a[orderBy] === 'string') {
|
||||||
|
return a[orderBy].localeCompare(b[orderBy]);
|
||||||
|
} else if (typeof a[orderBy] === 'number') {
|
||||||
|
return a[orderBy] - b[orderBy];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedData = items.slice().sort((a, b) => {
|
||||||
|
if (order === 'asc') {
|
||||||
|
return compareValues(a, b, orderBy);
|
||||||
|
} else {
|
||||||
|
return compareValues(b, a, orderBy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleSort(columns[0].accessor)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column", // Bruk column-fleksretning
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}>
|
||||||
|
<TableContainer sx={{
|
||||||
|
flex: 1,
|
||||||
|
overflowY: "auto",
|
||||||
|
position: "relative", // Legg til denne linjen for å justere layout
|
||||||
|
maxHeight: "100%" // Legg til denne linjen for å begrense høyden
|
||||||
|
}}>
|
||||||
|
<Table>
|
||||||
|
<TableHead sx={{
|
||||||
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
backgroundColor: muiTheme.palette.background.paper,
|
||||||
|
}}>
|
||||||
|
<TableRow>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell key={column.accessor} onClick={() => handleSort(column.accessor)} sx={{ cursor: "pointer" }}>
|
||||||
|
<Box display="flex">
|
||||||
|
{orderBy === column.accessor ?
|
||||||
|
(order === "asc" ? (<IconArrowDown />) : (<IconArrowUp />)) : (
|
||||||
|
<IconArrowDown sx={{ color: "transparent" }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Typography>{column.label}</Typography>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody sx={{
|
||||||
|
overflowY: "scroll"
|
||||||
|
}}>
|
||||||
|
{sortedData?.map((row: T, rowIndex: number) => (
|
||||||
|
<TableRow key={rowIndex}
|
||||||
|
onClick={() => tableRowSingleClicked(row)}
|
||||||
|
onDoubleClick={() => tableRowDoubleClicked(row)}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
tableRowContextMenu(e, row);
|
||||||
|
tableRowSingleClicked(row);
|
||||||
|
}}
|
||||||
|
style={{ cursor: "pointer", backgroundColor: selectedRow === row ? muiTheme.palette.action.selected : '' }}
|
||||||
|
>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<TableCell key={column.accessor}>
|
||||||
|
{customizer && customizer(column.accessor, row) !== null
|
||||||
|
? customizer(column.accessor, row)
|
||||||
|
: <Typography variant='body1'>{(row as any)[column.accessor]}</Typography>}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
17
apps/ui/web/src/app/features/table/table.ts
Normal file
17
apps/ui/web/src/app/features/table/table.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export interface TablePropetyConfig {
|
||||||
|
label: string
|
||||||
|
accessor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableCellCustomizer<T> {
|
||||||
|
(accessor: string, data: T): JSX.Element | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullableTableRowActionEvents<T> = TableRowActionEvents<T> | null;
|
||||||
|
export interface TableRowActionEvents<T> {
|
||||||
|
click: (row: T) => void;
|
||||||
|
doubleClick: (row: T) => void;
|
||||||
|
contextMenu?: (row: T, x: number, y: number) => void;
|
||||||
|
}
|
||||||
40
apps/ui/web/src/app/store/chained-events-slice.ts
Normal file
40
apps/ui/web/src/app/store/chained-events-slice.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
|
export interface EventGroup {
|
||||||
|
referenceId: string,
|
||||||
|
created: number,
|
||||||
|
fileName: string|null,
|
||||||
|
events: EventChain[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventChain {
|
||||||
|
eventId: string,
|
||||||
|
eventName: string,
|
||||||
|
created: number,
|
||||||
|
success: boolean,
|
||||||
|
skipped: boolean,
|
||||||
|
failure: boolean,
|
||||||
|
events: Array<EventChain>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventGroups {
|
||||||
|
groups: Array<EventGroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: EventGroups = {
|
||||||
|
groups: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const chainedEventsSlice = createSlice({
|
||||||
|
name: "ChainedEvents",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
set: (state, action: PayloadAction<Array<EventGroup>>) => {
|
||||||
|
state.groups = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { set } = chainedEventsSlice.actions;
|
||||||
|
export default chainedEventsSlice.reducer;
|
||||||
Loading…
Reference in New Issue
Block a user