More work on the massic refactor to functional react

This commit is contained in:
Colin Dawson 2026-01-30 09:44:21 +00:00
parent 471e239591
commit 8308515c9b
34 changed files with 2239 additions and 1681 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"chat.tools.terminal.autoApprove": {
"npx tsc": true
}
}

View File

@ -4,27 +4,50 @@
"AuditLogs": "Audit Logs",
"BlockedIPAddresses": "Blocked IP addresses",
"BlockedIPs": "Blocked IPs",
"Changes": "Changes",
"ClientDomainManager": "Client Domain Manager",
"ClientDomains": "Client Domains",
"Comment": "Comment",
"CustomFieldManager": "Custom Field Manager",
"CustomFields": "Custom Fields",
"DisplayName": "Display Name",
"EntityDisplayName": "Entity Display Name",
"e-print": "e-print",
"e-suite": "e-suite",
"ErrorLogs": "Error Logs",
"ExceptionJson": "Exception JSON",
"ExceptionLogs": "Exception Logs",
"Forms": "Forms",
"FormTemplateManager": "Form Template Manager",
"Glossaries": "Glossaries",
"GlossaryManager": "Glossary Manager",
"Home": "Home",
"Id": "Id",
"Application": "Application",
"Message": "Message",
"ShowJSON": "Show JSON",
"ShowStackTrace": "Show stack trace",
"OccuredAt": "Occured At",
"IPAddress": "IP Address",
"IPAddressUnblocked": "IP Address '{{ip}}' unblocked.",
"Loading": "Loading",
"Name": "Name",
"NumberOfAttempts": "Number Of Attempts",
"NewValue": "New Value",
"OldValue": "Old Value",
"PressAgainToUnblock": "Press again to unblock",
"Sequence": "Sequence",
"SequenceManager": "Sequence Manager",
"SiteManager": "Site Manager",
"SpecificationManager": "Specification Manager",
"StackTrace": "Stack Trace",
"SsoManager": "Sso Manager",
"Support": "Support",
"SupportingData": "Supporting Data",
"Timing": "Timing",
"Type": "Type",
"UnblockedInMinutes": "Unblocked In (Minutes)",
"UserManager": "User Manager",
"Users": "Users",
"Name": "Name",
"Loading": "Loading"
"UserName": "User Name",
"Users": "Users"
}

View File

@ -1,30 +0,0 @@
{
"Admin": "Admin (In french)",
"AuditLog": "Audit Logs (In french)",
"AuditLogs": "Audit Logs (In french)",
"BlockedIPAddresses": "Blocked IP addresses (In french)",
"BlockedIPs": "Blocked IPs (In french)",
"ClientDomainManager": "Client Domain Manager (In french)",
"ClientDomains": "Client Domains (In french)",
"CustomFieldManager": "Custom Field Manager (In french)",
"CustomFields": "Custom Fields (In french)",
"e-print": "e-print (In french)",
"e-suite": "e-suite (In french)",
"ErrorLogs": "Error Logs (In french)",
"ExceptionLogs": "Exception Logs (In french)",
"Forms": "Forms (In french)",
"FormTemplateManager": "Form Template Manager (In french)",
"Glossaries": "Glossaries (In french)",
"GlossaryManager": "Glossary Manager (In french)",
"Home": "Home (In french)",
"Sequence": "Sequence (In french)",
"SequenceManager": "Sequence Manager (In french)",
"SiteManager": "Site Manager (In french)",
"SpecificationManager": "Specification Manager (In french)",
"SsoManager": "Sso Manager (In french)",
"Support": "Support (In french)",
"UserManager": "User Manager (In french)",
"Users": "Users (In french)",
"Name": "Name (In french)",
"Loading": "Loading (In french)"
}

26
src/Sass/_errorLogs.scss Normal file
View File

@ -0,0 +1,26 @@
.errorlogs-pre {
white-space: pre;
overflow-x: auto;
font-family: Menlo, Monaco, "Courier New", monospace;
background: transparent; /* removed fixed white background to respect app theme */
border: 1px solid transparent; /* keep layout without forcing a light border color */
padding: 0.5rem;
border-radius: 0.25rem;
margin: 0;
font-size: 0.9rem;
}
.errorlogs-ExceptionJson {
max-width: 800px;
max-height: 600px;
}
.errorlogs-StackTraceJson {
max-width: 500px;
max-height: 600px;
}
.errorlogs-SupportingData {
max-width: 500px;
max-height: 600px;
}

View File

@ -0,0 +1,63 @@
.expandable-cell {
display: block; // stack: button on top, content below
.expandable-cell__button {
border: none;
background: transparent;
padding: 0;
cursor: pointer;
color: inherit;
font-size: 1rem;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.expandable-cell__title {
margin-right: 0.25rem;
font-size: 0.95rem;
line-height: 1;
}
.expandable-cell__content_defaults {
max-width: 600px;
max-height: 600px;
}
.expandable-cell__content {
margin-top: 0.5rem;
width: 100%;
overflow-x: auto; /* horizontal scrollbar always visible if needed */
overflow-y: auto; /* vertical scrollbar always visible if needed */
transition: height 240ms ease;
}
.expandable-cell__content-inner {
transition: opacity 180ms ease;
opacity: 1;
min-width: max-content; /* allow long lines to trigger horizontal scroll */
width: fit-content;
/* Remove overflow properties from here */
height: 100%;
}
.expandable-cell__icon {
display: inline-block;
transform-origin: center;
transition: transform 180ms ease;
transform: rotate(0deg);
}
&.is-open {
.expandable-cell__icon {
transform: rotate(90deg);
}
}
&:not(.is-open) {
.expandable-cell__content-inner {
opacity: 0;
}
}
}

View File

@ -1,7 +1,7 @@
@import "../../node_modules/bootstrap/scss/functions";
//default variable overrides
@import './_esuiteVariables.scss';
@import "./_esuiteVariables.scss";
@import "../../node_modules/bootstrap/scss/variables";
@import "../../node_modules/bootstrap/scss/variables-dark";
@ -26,6 +26,8 @@
@import "./pill.scss";
@import "./multiSelect.scss";
@import "./horizionalTabs";
@import "./_expandableCell.scss";
@import "./_errorLogs.scss";
//Changes needed to make MS Edge behave the same as other browsers
input::-ms-reveal {

View File

@ -0,0 +1,69 @@
import React, { useState, useRef } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
interface Props {
children: React.ReactNode;
defaultCollapsed?: boolean;
className?: string;
contentClassName?: string;
title?: React.ReactNode;
}
export default function ExpandableCell({
children,
defaultCollapsed = true,
className,
contentClassName = "expandable-cell__content_defaults",
title,
}: Props) {
const [open, setOpen] = useState(!defaultCollapsed);
const contentRef = useRef<HTMLDivElement | null>(null);
const contentHeight = contentRef.current
? contentRef.current.scrollHeight
: 0;
const calcMaxHeight = open ? `${contentHeight}px` : "0px";
const classes = ["expandable-cell", className, open ? "is-open" : ""]
.filter(Boolean)
.join(" ");
return (
<div className={classes}>
<button
type="button"
aria-expanded={open}
onClick={() => setOpen((s) => !s)}
className="expandable-cell__button"
aria-label={
typeof title === "string"
? `${open ? "Hide" : "Show"} ${title}`
: undefined
}
>
{title ? <span className="expandable-cell__title">{title}</span> : null}
<FontAwesomeIcon
className="expandable-cell__icon"
icon={faChevronRight}
/>
</button>
<div
className={["expandable-cell__content", contentClassName]
.filter(Boolean)
.join(" ")}
style={{ height: calcMaxHeight }}
aria-hidden={!open}
>
<div
ref={contentRef}
className="expandable-cell__content-inner"
style={{ height: "100%" }}
>
{children}
</div>
</div>
</div>
);
}

View File

@ -1,109 +1,119 @@
import React from "react";
import { faAngleDoubleLeft, faAngleLeft, faAngleRight, faAngleDoubleRight } from "@fortawesome/free-solid-svg-icons";
import React, { useCallback } from "react";
import {
faAngleDoubleLeft,
faAngleLeft,
faAngleRight,
faAngleDoubleRight,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Paginated } from "../../services/Paginated";
import Button, { ButtonType } from "./Button";
import { InputType } from "./Input";
interface PaginationProps<T> {
data : Paginated<T>
onChangePage : (page: number, pageSize: number) => void;
data: Paginated<T> | null;
onChangePage: (page: number, pageSize: number) => void;
onUnselect?: () => void;
}
interface PaginationState {
}
class Pagination<T> extends React.Component<PaginationProps<T>, PaginationState> {
state = { }
changePage( page : number, pageSize : number ){
const {onChangePage, onUnselect} = this.props;
function Pagination<T>({
data,
onChangePage,
onUnselect,
}: PaginationProps<T>): JSX.Element {
const changePage = useCallback(
(page: number, pageSize: number) => {
onChangePage(page, pageSize);
if (onUnselect) {
onUnselect();
}
}
if (onUnselect) onUnselect();
},
[onChangePage, onUnselect],
);
clickFirst = () => {
const { pageSize } = this.props.data;
if (data === null) return <></>;
this.changePage(1, pageSize );
}
const { page, pageSize, totalPages, count } = data;
clickPrevious = () => {
const { page, pageSize } = this.props.data;
const clickFirst = () => changePage(1, pageSize);
const clickPrevious = () => changePage(page - 1, pageSize);
const clickNext = () => changePage(page + 1, pageSize);
const clickLast = () => changePage(totalPages, pageSize);
this.changePage(page - 1, pageSize);
}
clickNext = () => {
const { page, pageSize } = this.props.data;
this.changePage(page + 1, pageSize);
}
clickLast = () => {
const { totalPages, pageSize } = this.props.data;
this.changePage(totalPages, pageSize);
}
PageSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const { page } = this.props.data;
let newPageSize : number = +input.value;
this.changePage(page, newPageSize);
}
handlePageSelect = ( e : React.ChangeEvent<HTMLInputElement>) =>
{
const { pageSize, totalPages } = this.props.data;
const input = e.currentTarget;
const newPage = Number(input.value);
const PageSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newPageSize = Number(e.currentTarget.value);
changePage(page, newPageSize);
};
const handlePageSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const newPage = Number(e.currentTarget.value);
if (1 <= newPage && newPage <= totalPages) {
this.changePage(newPage, pageSize);
}
changePage(newPage, pageSize);
}
};
render() {
const pageSizeOptions = [10, 25, 50, 100];
const { data } = this.props;
if (data === null)
return <></>
const pageSizeOptions = [{ _id: "10", name: "10" },
{ _id: "25", name: "25" },
{ _id: "50", name: "50" },
{ _id: "100", name: "100" }];
return ( <div className="d-flex py-2 bg-body-tertiary pagination">
return (
<div className="d-flex py-2 bg-body-tertiary pagination">
<span className="px-2">
<select value={data.pageSize} className="form-select" onChange={this.PageSizeChange}>
{pageSizeOptions.map(({ _id, name }) => (
<option key={_id} value={_id}>
{name}
<select
value={pageSize}
className="form-select"
onChange={PageSizeChange}
>
{pageSizeOptions.map((n) => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
</span>
<span>
<Button className="me-1" buttonType={ButtonType.primary} onClick={this.clickFirst} disabled={data.page < 2}><FontAwesomeIcon icon={faAngleDoubleLeft}/></Button>
<Button className="me-1"buttonType={ButtonType.primary} onClick={this.clickPrevious} disabled={data.page < 2}><FontAwesomeIcon icon={faAngleLeft}/></Button>
<span className="me-1"><input type={InputType.number} value={data.page} min={1} max={data.totalPages} onChange={this.handlePageSelect}/> of {data.totalPages}</span>
<Button className="me-1" buttonType={ButtonType.primary} onClick={this.clickNext} disabled={data.page >= data.totalPages}><FontAwesomeIcon icon={faAngleRight}/></Button>
<Button className="me-1" buttonType={ButtonType.primary} onClick={this.clickLast} disabled={data.page >= data.totalPages}><FontAwesomeIcon icon={faAngleDoubleRight}/></Button>
<span className="me-1">{data.count} Items</span>
<Button
className="me-1"
buttonType={ButtonType.primary}
onClick={clickFirst}
disabled={page < 2}
>
<FontAwesomeIcon icon={faAngleDoubleLeft} />
</Button>
<Button
className="me-1"
buttonType={ButtonType.primary}
onClick={clickPrevious}
disabled={page < 2}
>
<FontAwesomeIcon icon={faAngleLeft} />
</Button>
<span className="me-1">
<input
type={InputType.number}
value={page}
min={1}
max={totalPages}
onChange={handlePageSelect}
/>{" "}
of {totalPages}
</span>
</div>);
}
<Button
className="me-1"
buttonType={ButtonType.primary}
onClick={clickNext}
disabled={page >= totalPages}
>
<FontAwesomeIcon icon={faAngleRight} />
</Button>
<Button
className="me-1"
buttonType={ButtonType.primary}
onClick={clickLast}
disabled={page >= totalPages}
>
<FontAwesomeIcon icon={faAngleDoubleRight} />
</Button>
<span className="me-1">{count} Items</span>
</span>
</div>
);
}
export default Pagination;

View File

@ -1,22 +1,16 @@
import React from "react";
import authenticationService from "../../modules/frame/services/authenticationService";
interface PermissionProps{
privilegeKey : string;
interface PermissionProps {
privilegeKey: string;
children: React.ReactNode;
}
class Permission extends React.Component<PermissionProps> {
render() {
const { privilegeKey, children } = this.props;
const hasAccess = authenticationService.hasAccess( privilegeKey );
const Permission: React.FC<PermissionProps> = ({ privilegeKey, children }) => {
const hasAccess = authenticationService.hasAccess(privilegeKey);
if (hasAccess === false)
return ( <></> );
else
return ( <>{children}</> );
}
}
if (!hasAccess) return null;
return <>{children}</>;
};
export default Permission;

View File

@ -1,21 +1,13 @@
import React from "react";
import { useEffect } from "react";
interface RedirectProps {
to : string
to: string;
}
interface RedirectState {
}
class Redirect extends React.Component<RedirectProps, RedirectState> {
render() {
const {to} = this.props;
export default function Redirect({ to }: RedirectProps) {
useEffect(() => {
window.location.replace(to);
}, [to]);
return null;
}
}
export default Redirect;

View File

@ -1,55 +1,75 @@
import React from "react";
import Option from "./option";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
export interface SelectProps {
includeLabel? : boolean,
name : string,
label : string,
error? : string,
value : unknown
options? : Option[],
includeBlankFirstEntry? : boolean,
includeLabel?: boolean;
name: string;
label: string;
error?: string;
value: unknown;
options?: Option[];
includeBlankFirstEntry?: boolean;
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
}
function GenerateValue( value : unknown )
{
let actualValue = value;
if (value === true)
return "true";
if (value === false)
return "false";
return actualValue as string | number | readonly string[] | undefined;
function GenerateValue(value: unknown) {
if (value === true) return "true";
if (value === false) return "false";
return value as string | number | readonly string[] | undefined;
}
class Select extends React.Component<SelectProps> {
render() {
const { includeLabel, name, label, error, value, options, includeBlankFirstEntry, onChange, ...rest } = this.props;
const actualValue = GenerateValue( value);
export default function Select({
includeLabel,
name,
label,
error,
value,
options,
includeBlankFirstEntry,
onChange,
...rest
}: SelectProps) {
const { t } = useTranslation<typeof Namespaces.Common>();
const actualValue = GenerateValue(value);
return (
<div className="form-group">
{(includeLabel===undefined || includeLabel===true) && <label htmlFor={name}>{label}</label>}
{!options && <select multiple {...rest} id={name} value={actualValue} className="form-control loading" name={name} onChange={onChange}>
<option value="loading..." />
</select>}
{options &&
<select {...rest} id={name} value={actualValue} className="form-control" name={name} onChange={onChange}>
{includeBlankFirstEntry && <option value=""/>}
{(includeLabel === undefined || includeLabel === true) && (
<label htmlFor={name}>{label}</label>
)}
{!options && (
<select
multiple
{...rest}
id={name}
value={actualValue}
className="form-control loading"
name={name}
onChange={onChange}
>
<option value="loading...">{t("Loading")}...</option>
</select>
)}
{options && (
<select
{...rest}
id={name}
value={actualValue}
className="form-control"
name={name}
onChange={onChange}
>
{includeBlankFirstEntry && <option value="" />}
{options?.map(({ _id, name }) => (
<option key={_id} value={_id}>
{name}
</option>
))}
</select>
}
)}
{error && <div className="alert alert-danger">{error}</div>}
</div>
);
}
};
export default Select;
}

View File

@ -1,18 +1,10 @@
import React from 'react';
import React from "react";
interface TabProps{
interface TabProps {
label: string;
children : JSX.Element | JSX.Element[];
children: React.ReactNode;
}
class Tab extends React.Component<TabProps> {
render() {
return (
<div>
</div>
);
}
export default function Tab({ label, children }: TabProps): JSX.Element {
return <div data-tab-label={label}>{children}</div>;
}
export default Tab;

View File

@ -1,36 +1,25 @@
import React from 'react';
import React, { useCallback } from "react";
interface TabHeaderProps{
isActive : boolean,
label : string
onClick : any;
interface TabHeaderProps {
isActive: boolean;
label: string;
onClick: (label: string) => void;
}
class TabHeader extends React.Component<TabHeaderProps> {
onClick = () => {
const { label, onClick } = this.props;
onClick(label);
};
render() {
const {
export default function TabHeader({
isActive,
label,
onClick,
props: { isActive, label },
} = this;
}: TabHeaderProps) {
const handleClick = useCallback(() => onClick(label), [onClick, label]);
let className = "tab-list-item";
if (isActive === true) {
className += " tab-list-active";
}
const className = isActive
? "tab-list-item tab-list-active"
: "tab-list-item";
return (
<li className={className} onClick={onClick}>
<li className={className} onClick={handleClick}>
{label}
</li>
);
}
}
export default TabHeader;

View File

@ -1,70 +1,112 @@
import { Component } from 'react';
import { Paginated } from '../../services/Paginated';
import Column from './columns';
import TableBody, { AuditParams } from './TableBody';
import TableFooter from './TableFooter';
import TableHeader from './TableHeader';
import debounce from 'lodash.debounce';
import React, { useEffect, useState } from "react";
import { Paginated } from "../../services/Paginated";
import Column from "./columns";
import TableBody, { AuditParams } from "./TableBody";
import TableFooter from "./TableFooter";
import TableHeader from "./TableHeader";
import debounce from "lodash.debounce";
export interface PublishedTableProps<T> {
data: Paginated<T>,
sortColumn? : Column<T>,
selectedRow? : T;
onChangePage? : (page: number, pageSize : number) => {};
onSort? : (sortColumn : Column<T>) => void;
onSearch?: ( name: string, value: string) => void;
canEdit? : ( item : T ) => boolean;
canDelete?: ( item : T ) => boolean;
onDelete?: ( item? : T ) => void;
data: Paginated<T>;
sortColumn?: Column<T>;
selectedRow?: T;
onChangePage?: (page: number, pageSize: number) => {};
onSort?: (sortColumn: Column<T>) => void;
onSearch?: (name: string, value: string) => void;
canEdit?: (item: T) => boolean;
canDelete?: (item: T) => boolean;
onDelete?: (item?: T) => void;
onSelectRow?: (item: T) => void;
onUnselectRow?: () => void;
}
export interface TableProps<T> extends PublishedTableProps<T> {
keyName : string;
columns : Column<T>[];
editPath? : string;
onAuditParams?: ( item : T ) => AuditParams;
secondaryAudit? : boolean;
keyName: string;
columns: Column<T>[];
editPath?: string;
onAuditParams?: (item: T) => AuditParams;
secondaryAudit?: boolean;
}
interface TableState{
debouncedOnSearch?: any
}
export default function Table<T>(props: TableProps<T>): JSX.Element {
const {
data,
keyName,
selectedRow,
columns,
sortColumn,
editPath,
canEdit,
canDelete,
onSort,
onChangePage,
onDelete,
onAuditParams,
onSelectRow,
secondaryAudit,
onUnselectRow,
onSearch,
} = props;
class Table<T> extends Component<TableProps<T>, TableState> {
state : TableState = {
}
const [debouncedOnSearch, setDebouncedOnSearch] = useState<any>(undefined);
componentDidMount(): void {
const {onSearch } = this.props;
useEffect(() => {
const debounceDelay = 200;
const debouncedOnSearch = onSearch === undefined ? undefined : debounce(onSearch, debounceDelay);
this.setState( {
debouncedOnSearch,
})
if (!onSearch) {
setDebouncedOnSearch(undefined);
return;
}
render() {
const { data, keyName, selectedRow, columns, sortColumn, editPath, canEdit, canDelete, onSort, onChangePage, onDelete, onAuditParams, onSelectRow, secondaryAudit, onUnselectRow } = this.props;
const { debouncedOnSearch } = this.state;
const d = debounce(onSearch, debounceDelay);
setDebouncedOnSearch(() => d);
const showEdit = (editPath != null) && (editPath !== "");
return () => {
if ((d as any).cancel) (d as any).cancel();
};
}, [onSearch]);
const showEdit = editPath != null && editPath !== "";
const showDelete = onDelete != null;
const showAudit = onAuditParams != null;
const showSecondaryAudit = showAudit && secondaryAudit === true;
return (
<>
<table className='table table-sm table-striped table-bordered'>
<TableHeader columns={columns} sortColumn={sortColumn} showDelete={showDelete} showEdit={showEdit} showAudit={showAudit} showSecondaryAudit={showSecondaryAudit} onSort={onSort} onSearch={debouncedOnSearch} />
<TableBody data={data.data} keyName={keyName} columns={columns} selectedRow={selectedRow} canEdit={canEdit} canDelete={canDelete} onDelete={onDelete} editPath={editPath} onAuditParams={onAuditParams} onSelectRow={onSelectRow} showSecondaryAudit={showSecondaryAudit}/>
<TableFooter data={data} columns={columns} showDelete={showDelete} showEdit={showEdit} showAudit={showAudit} showSecondaryAudit={showSecondaryAudit} onChangePage={onChangePage} onUnselectRow={onUnselectRow} />
<table className="table table-sm table-striped table-bordered">
<TableHeader
columns={columns}
sortColumn={sortColumn}
showDelete={showDelete}
showEdit={showEdit}
showAudit={showAudit}
showSecondaryAudit={showSecondaryAudit}
onSort={onSort}
onSearch={debouncedOnSearch}
/>
<TableBody
data={data.data}
keyName={keyName}
columns={columns}
selectedRow={selectedRow}
canEdit={canEdit}
canDelete={canDelete}
onDelete={onDelete}
editPath={editPath}
onAuditParams={onAuditParams}
onSelectRow={onSelectRow}
showSecondaryAudit={showSecondaryAudit}
/>
<TableFooter
data={data}
columns={columns}
showDelete={showDelete}
showEdit={showEdit}
showAudit={showAudit}
showSecondaryAudit={showSecondaryAudit}
onChangePage={onChangePage}
onUnselectRow={onUnselectRow}
/>
</table>
</>
);
}
}
export default Table;

View File

@ -1,138 +1,170 @@
import React, { Component } from "react";
import React from "react";
import deepFind from "../../utils/deepfind";
import Column from "./columns";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBook, faBookJournalWhills, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faBook,
faBookJournalWhills,
faEdit,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { Link } from "react-router-dom";
import ConfirmButton from "./ConfirmButton";
import { Buffer } from 'buffer';
import { Buffer } from "buffer";
import Button, { ButtonType } from "./Button";
import { DateView } from "./DateView";
export interface AuditParams{
entityName : string,
primaryKey : string
export interface AuditParams {
entityName: string;
primaryKey: string;
}
export interface TableBodyProps<T>{
data : T[] | undefined;
keyName : string;
columns : Column<T>[];
editPath? : string;
selectedRow? : T;
canEdit? : ( item : T ) => boolean;
canDelete? : ( item : T ) => boolean;
onDelete?: ( item? : T ) => void;
onAuditParams?: ( item : T ) => AuditParams;
onSelectRow? : ( item : T ) => void;
showSecondaryAudit : boolean;
export interface TableBodyProps<T> {
data: T[] | undefined;
keyName: string;
columns: Column<T>[];
editPath?: string;
selectedRow?: T;
canEdit?: (item: T) => boolean;
canDelete?: (item: T) => boolean;
onDelete?: (item?: T) => void;
onAuditParams?: (item: T) => AuditParams;
onSelectRow?: (item: T) => void;
showSecondaryAudit: boolean;
}
class TableBody<T> extends Component<TableBodyProps<T>> {
resolvePath = ( path : string, args : string[]) => {
export default function TableBody<T>({
data,
keyName,
selectedRow,
columns,
editPath,
canEdit,
canDelete,
onDelete,
onAuditParams,
onSelectRow,
showSecondaryAudit,
}: TableBodyProps<T>): JSX.Element {
const resolvePath = (path: string, args: string[]) => {
let modifiedPath = path;
let index : number = 0;
while ( index < args.length )
{
modifiedPath = modifiedPath.replace("{"+index+"}", args[index])
let index = 0;
while (index < args.length) {
modifiedPath = modifiedPath.replace("{" + index + "}", args[index]);
index++;
}
return modifiedPath;
}
};
renderCell = (item : T, column : Column<T>) => {
const {keyName} = this.props;
const renderCell = (item: T, column: Column<T>) => {
if (column.content) return column.content(item);
const foundItem = deepFind(item, column.path || column.key)
let columnContent : JSX.Element;
const foundItem = deepFind(item, column.path || column.key);
let columnContent: JSX.Element;
if (foundItem instanceof Date) {
columnContent = <DateView value={foundItem}/>
}
else if (typeof foundItem === "object"){
columnContent = <DateView value={foundItem} />;
} else if (typeof foundItem === "object") {
columnContent = <></>;
}
else {
columnContent = <>
{foundItem}
</>;
} else {
columnContent = <>{foundItem}</>;
}
const linkPath = column.link;
if (linkPath !== undefined){
const resolvedlinkPath = this.resolvePath( linkPath, [ (item as any)[keyName] ] );
if (linkPath !== undefined) {
const resolvedlinkPath = resolvePath(linkPath, [(item as any)[keyName]]);
columnContent = <Link to={resolvedlinkPath}>{columnContent}</Link>;
}
return <>
{columnContent}
</>;
return <>{columnContent}</>;
};
clickRow = ( value : T ) =>
{
const { onSelectRow } = this.props;
if (onSelectRow !== undefined)
onSelectRow( value );
}
createKey = (item : T, column : Column<T>) => {
const { keyName } = this.props;
return (item as any)[keyName] + '_' + (column.path || column.key);
const clickRow = (value: T) => {
if (onSelectRow !== undefined) onSelectRow(value);
};
handleAuditParams = ( item : T, primaryOnly : boolean ) => {
const { onAuditParams } = this.props;
const createKey = (item: T, column: Column<T>) => {
return (item as any)[keyName] + "_" + (column.path || column.key);
};
const handleAuditParams = (item: T, primaryOnly: boolean) => {
if (onAuditParams !== undefined) {
var auditParams = onAuditParams(item);
let json = JSON.stringify(auditParams);
var params = Buffer.from(json).toString('base64') ;
const auditParams = onAuditParams(item);
const json = JSON.stringify(auditParams);
const params = Buffer.from(json).toString("base64");
var queryString = "";
if (primaryOnly===false)
queryString += "?primaryOnly=" + primaryOnly;
let queryString = "";
if (primaryOnly === false) queryString += "?primaryOnly=" + primaryOnly;
return "/audit/" + params + queryString;
}
return "";
}
};
render() {
const { data, keyName, selectedRow, columns, editPath, canEdit, canDelete, onDelete, onAuditParams, showSecondaryAudit } = this.props;
const showDelete:boolean = onDelete != null;
const showEdit:boolean = (editPath != null) && (editPath !== "");
const showAudit:boolean = onAuditParams !== undefined;
const showDelete: boolean = onDelete != null;
const showEdit: boolean = editPath != null && editPath !== "";
const showAudit: boolean = onAuditParams !== undefined;
return (
<tbody>
{data?.map((item) => {
let classNames = "";
if (selectedRow === item)
{
classNames+="table-primary";
}
const classNames = selectedRow === item ? "table-primary" : "";
return (<tr className={classNames} key={(item as any)[keyName]}>
return (
<tr className={classNames} key={(item as any)[keyName]}>
{columns.map((column) => (
<td key={this.createKey(item, column)} onClick={ () => this.clickRow(item)}>{this.renderCell(item, column)}</td>
<td key={createKey(item, column)} onClick={() => clickRow(item)}>
{renderCell(item, column)}
</td>
))}
{showEdit && <td className="align-middle">{(canEdit === undefined || canEdit(item)) && <Button buttonType={ButtonType.primary} to={this.resolvePath( editPath!, [ (item as any)[keyName] ] )}><FontAwesomeIcon icon={faEdit}/></Button>}</td>}
{showDelete && <td className="align-middle">{(canDelete === undefined || canDelete(item)) && <ConfirmButton buttonType={ButtonType.primary} keyValue={item} onClick={onDelete} confirmMessage={"Press again to delete"} ><FontAwesomeIcon icon={faTrash}/></ConfirmButton>}</td>}
{showAudit && <td className="align-middle"><Link to={this.handleAuditParams(item, true)}><Button buttonType={ButtonType.primary}><FontAwesomeIcon icon={faBook}/></Button></Link></td>}
{showAudit && showSecondaryAudit && <td className="align-middle"><Link to={this.handleAuditParams(item, false)}><Button buttonType={ButtonType.secondary}><FontAwesomeIcon icon={faBookJournalWhills}/></Button></Link></td>}
</tr>)
{showEdit && (
<td className="align-middle">
{(canEdit === undefined || canEdit(item)) && (
<Button
buttonType={ButtonType.primary}
to={resolvePath(editPath!, [(item as any)[keyName]])}
>
<FontAwesomeIcon icon={faEdit} />
</Button>
)}
</td>
)}
{showDelete && (
<td className="align-middle">
{(canDelete === undefined || canDelete(item)) && (
<ConfirmButton
buttonType={ButtonType.primary}
keyValue={item}
onClick={onDelete}
confirmMessage={"Press again to delete"}
>
<FontAwesomeIcon icon={faTrash} />
</ConfirmButton>
)}
</td>
)}
{showAudit && (
<td className="align-middle">
<Link to={handleAuditParams(item, true)}>
<Button buttonType={ButtonType.primary}>
<FontAwesomeIcon icon={faBook} />
</Button>
</Link>
</td>
)}
{showAudit && showSecondaryAudit && (
<td className="align-middle">
<Link to={handleAuditParams(item, false)}>
<Button buttonType={ButtonType.secondary}>
<FontAwesomeIcon icon={faBookJournalWhills} />
</Button>
</Link>
</td>
)}
</tr>
);
})}
</tbody>
);
}
}
export default TableBody;

View File

@ -1,40 +1,51 @@
import React, { Component } from "react";
import React from "react";
import { Paginated } from "../../services/Paginated";
import Column from "./columns";
import Pagination from "./Pagination";
export interface TableFooterProps<T>{
data : Paginated<T>;
columns : Column<T>[];
showEdit : boolean;
showDelete : boolean;
showAudit : boolean;
showSecondaryAudit : boolean;
export interface TableFooterProps<T> {
data: Paginated<T>;
columns: Column<T>[];
showEdit: boolean;
showDelete: boolean;
showAudit: boolean;
showSecondaryAudit: boolean;
onChangePage?: (page: number, pageSize: number) => void;
onUnselectRow?: () => void;
}
class TableFooter<T> extends Component<TableFooterProps<T>> {
render() {
const { data, columns, showEdit, showDelete, showAudit, showSecondaryAudit, onChangePage, onUnselectRow} = this.props;
export default function TableFooter<T>({
data,
columns,
showEdit,
showDelete,
showAudit,
showSecondaryAudit,
onChangePage,
onUnselectRow,
}: TableFooterProps<T>): JSX.Element | null {
let staticColumnCount = 0;
if (showEdit) staticColumnCount++;
if (showDelete) staticColumnCount++;
if (showAudit) staticColumnCount++;
if (showAudit && showSecondaryAudit) staticColumnCount++;
let pagination = onChangePage === undefined ? undefined : <Pagination data={data} onChangePage={onChangePage} onUnselect={onUnselectRow} />;
const pagination =
onChangePage === undefined ? undefined : (
<Pagination
data={data}
onChangePage={onChangePage}
onUnselect={onUnselectRow}
/>
);
if (pagination)
return <tfoot>
if (!pagination) return null;
return (
<tfoot>
<tr>
<td colSpan={columns.length+staticColumnCount}>{pagination}</td>
<td colSpan={columns.length + staticColumnCount}>{pagination}</td>
</tr>
</tfoot>
return <></>
}
);
}
export default TableFooter;

View File

@ -1,94 +1,109 @@
import React, { ChangeEvent, useCallback } from "react";
import { faSortAsc, faSortDesc } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ChangeEvent, Component } from "react";
import Column from "./columns";
import Input, { InputType } from "./Input";
export interface TableHeaderProps<T>{
sortColumn? : Column<T>;
columns : Column<T>[];
showDelete? : boolean;
showEdit? : boolean;
showAudit? : boolean;
showSecondaryAudit? : boolean;
onSort? : (sortColumn : Column<T>) => void;
onSearch?: ( name: string, value: string) => void;
export interface TableHeaderProps<T> {
sortColumn?: Column<T>;
columns: Column<T>[];
showDelete?: boolean;
showEdit?: boolean;
showAudit?: boolean;
showSecondaryAudit?: boolean;
onSort?: (sortColumn: Column<T>) => void;
onSearch?: (name: string, value: string) => void;
}
class TableHeader<T> extends Component<TableHeaderProps<T>> {
columnsMatch = ( left? : Column<T>, right? : Column<T>) =>
{
if (left?.key !== right?.key) return false;
return true;
export default function TableHeader<T>({
sortColumn,
columns,
showEdit,
showDelete,
showAudit,
showSecondaryAudit,
onSort,
onSearch,
}: TableHeaderProps<T>): JSX.Element {
const columnsMatch = useCallback((left?: Column<T>, right?: Column<T>) => {
return left?.key === right?.key;
}, []);
const raiseSort = useCallback(
(column: Column<T>) => {
let sc = sortColumn;
if (sc) {
if (columnsMatch(column, sc)) {
sc.order = sc.order === "asc" ? "desc" : "asc";
} else {
sc = column;
sc.order = "asc";
}
raiseSort = (column : Column<T>) => {
let sortColumn = this.props.sortColumn;
if (sortColumn) {
if (this.columnsMatch(column, sortColumn)){
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
}
else {
sortColumn = column;
sortColumn.order = "asc";
if (onSort) onSort(sc);
}
},
[sortColumn, onSort, columnsMatch],
);
const { onSort } = this.props;
if (onSort)
onSort(sortColumn);
}
};
renderSortIcon = (column : Column<T>) => {
const { sortColumn } = this.props;
const renderSortIcon = useCallback(
(column: Column<T>) => {
if (!sortColumn) return null;
if (!columnsMatch(column, sortColumn)) return null;
return sortColumn.order === "asc" ? (
<FontAwesomeIcon icon={faSortAsc} />
) : (
<FontAwesomeIcon icon={faSortDesc} />
);
},
[sortColumn, columnsMatch],
);
if (!this.columnsMatch(column, sortColumn)) return null;
const changeSearch = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
if (onSearch) onSearch(e.target.name, e.target.value);
},
[onSearch],
);
if (sortColumn?.order === "asc") return <FontAwesomeIcon icon={faSortAsc}/>
return <FontAwesomeIcon icon={faSortDesc}/>
};
changeSearch = (e: ChangeEvent<HTMLInputElement>) =>
{
const {onSearch} = this.props;
if (onSearch)
onSearch(e?.target?.name, e?.target?.value);
};
render() {
const { columns, showEdit, showDelete, showAudit, showSecondaryAudit, onSearch } = this.props;
let searchRow = <></>;
if (onSearch)
searchRow = <tr>
{columns.map((column) =>
const searchRow = onSearch ? (
<tr>
{columns.map((column) => (
<th key={column.path || column.key}>
{
(column.searchable === undefined || column.searchable === true ) &&
<Input name={column.path || column.key} label={""} error={""} type={InputType.text} onChange={this.changeSearch} />
}
</th>
{(column.searchable === undefined || column.searchable === true) && (
<Input
name={column.path || column.key}
label={""}
error={""}
type={InputType.text}
onChange={changeSearch}
/>
)}
</th>
))}
{showEdit && <th></th>}
{showDelete && <th></th>}
{showAudit && <th></th>}
{showAudit && showSecondaryAudit && <th></th>}
</tr>;
</tr>
) : (
<></>
);
return (
<thead>
<tr>
{columns.map((column) =>
<th className="text-nowrap" key={column.path || column.key} scope="col" onClick={() => this.raiseSort(column)}>
{column.label} {this.renderSortIcon(column)}
{columns.map((column) => (
<th
className="text-nowrap"
key={column.path || column.key}
scope="col"
onClick={() => raiseSort(column)}
>
{column.label} {renderSortIcon(column)}
</th>
)}
))}
{showEdit && <th scope="col"></th>}
{showDelete && <th scope="col"></th>}
{showAudit && <th scope="col"></th>}
@ -97,7 +112,4 @@ class TableHeader<T> extends Component<TableHeaderProps<T>> {
{searchRow}
</thead>
);
}
}
export default TableHeader;

View File

@ -1,8 +1,8 @@
import React from "react";
import React, { useEffect, useState } from "react";
import TextEditor from "./ckeditor/TextEditor";
import customFieldsService from '../../modules/manager/customfields/services/customFieldsService';
import customFieldsService from "../../modules/manager/customfields/services/customFieldsService";
interface customfieldType {
interface CustomFieldType {
type: string;
name: string;
guid: string | undefined;
@ -10,57 +10,58 @@ interface customfieldType {
}
interface TemplateEditorProps {
className : string
name : string;
data : string;
showFields : boolean;
onChange : ( name : string, value : string ) => void;
className: string;
name: string;
data: string;
showFields: boolean;
onChange: (name: string, value: string) => void;
}
interface TemplateEditorState {
customfields: Array<customfieldType>;
ready : boolean;
}
export default function TemplateEditor({
className,
name,
data,
showFields,
onChange,
}: TemplateEditorProps) {
const [customfields, setCustomfields] = useState<CustomFieldType[]>([]);
const [ready, setReady] = useState(false);
class TemplateEditor extends React.Component<TemplateEditorProps, TemplateEditorState> {
state = {
customfields : [],
ready: false
}
useEffect(() => {
let mounted = true;
async componentWillMount() {
async function load() {
const pageData = await customFieldsService.getFields(0, 10, "name", true);
const customfields = pageData.data.map(
(x) => {
return {
const fields: CustomFieldType[] = pageData.data.map((x: any) => ({
type: "CustomField",
name: x.name,
guid: x.guid,
id: x.id
id: x.id,
}));
if (mounted) {
setCustomfields(fields);
setReady(true);
}
}
load();
return () => {
mounted = false;
};
}, []);
if (!ready) return <></>;
return (
<TextEditor
className={className}
name={name}
data={data}
onChange={onChange}
customFields={customfields}
/>
);
const { className, name, data, onChange } = this.props;
this.setState( {
ready : true,
customfields,
});
}
render() {
const { className, name, data, onChange } = this.props;
const { ready, customfields } = this.state;
if (!ready)
return <></>
return <TextEditor className={className} name={name} data={data} onChange={onChange} customFields={customfields} />;
}
}
export default TemplateEditor;

View File

@ -1,91 +1,105 @@
import React from "react";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
import formsService, { CreateFormInstance, EditFormInstance } from "../../modules/manager/forms/services/formsService";
import parse, { HTMLReactParserOptions, domToReact } from 'html-react-parser';
import formsService, {
CreateFormInstance,
EditFormInstance,
} from "../../modules/manager/forms/services/formsService";
import parse, { HTMLReactParserOptions, domToReact } from "html-react-parser";
import { CustomField } from "../../modules/manager/customfields/services/customFieldsService";
import Form, { FormState } from "./Form";
import { toast } from "react-toastify";
import Loading from "./Loading";
interface TemplateFillerProps {
templateId? : GeneralIdRef;
templateId?: GeneralIdRef;
formInstanceId?: GeneralIdRef;
onValidationChanged? : () => {};
onValidationChanged?: () => {};
}
interface TemplateFillerState extends FormState {
customFields? : CustomField[],
template : {
customFields?: CustomField[];
template: {
name?: string;
templateId? : GeneralIdRef,
version? : bigint,
templateId?: GeneralIdRef;
version?: bigint;
definition?: string;
}
};
}
class TemplateFiller extends Form<TemplateFillerProps, any, TemplateFillerState> {
state : TemplateFillerState = {
class TemplateFiller extends Form<
TemplateFillerProps,
any,
TemplateFillerState
> {
state: TemplateFillerState = {
loaded: false,
customFields : undefined,
template : {
customFields: undefined,
template: {
name: undefined,
templateId: undefined,
version: undefined,
definition: undefined,
},
data : {},
errors: {}
}
schema = {
data: {},
errors: {},
};
schema = {};
componentDidMount(): void {
this.loadTemplate();
}
componentDidUpdate(prevProps: Readonly<TemplateFillerProps>, prevState: Readonly<TemplateFillerState>, snapshot?: any): void {
if ((prevProps.formInstanceId !== this.props.formInstanceId) || (prevProps.templateId !== this.props.templateId))
componentDidUpdate(
prevProps: Readonly<TemplateFillerProps>,
prevState: Readonly<TemplateFillerState>,
snapshot?: any,
): void {
if (
prevProps.formInstanceId !== this.props.formInstanceId ||
prevProps.templateId !== this.props.templateId
)
this.loadTemplate();
if (this.props.onValidationChanged)
{
const prevErrorCount = Object.keys(prevState.errors).length > 0
const errorCount = Object.keys(this.state.errors).length > 0
if (this.props.onValidationChanged) {
const prevErrorCount = Object.keys(prevState.errors).length > 0;
const errorCount = Object.keys(this.state.errors).length > 0;
if (prevErrorCount !== errorCount) {
this.props.onValidationChanged();
}
}
}
loadTemplate = async () => {
const { templateId, formInstanceId } = this.props;
let loadedData : any;
let loadedData: any;
if (templateId !== undefined){
if (templateId !== undefined) {
loadedData = await formsService.getForm(templateId?.id, templateId?.guid);
//Get the form definiton for the template provided by templateId and load.
loadedData.templateId = undefined;
loadedData.customFieldValues = undefined;
loadedData.updatedVersion = undefined;
} else if (formInstanceId !== undefined){
loadedData = await formsService.getFormInstance(formInstanceId?.id, formInstanceId?.guid);
} else if (formInstanceId !== undefined) {
loadedData = await formsService.getFormInstance(
formInstanceId?.id,
formInstanceId?.guid,
);
console.log("formInstanceId", loadedData);
} else {
loadedData = {
name: undefined,
id : undefined,
id: undefined,
guid: undefined,
version: undefined,
definition: undefined,
customFieldDefinitions: undefined,
templateId : undefined,
customFieldValues : undefined,
updatedVersion : undefined
}
templateId: undefined,
customFieldValues: undefined,
updatedVersion: undefined,
};
}
const { template, data } = this.state;
@ -96,86 +110,95 @@ class TemplateFiller extends Form<TemplateFillerProps, any, TemplateFillerState>
template.definition = loadedData.definition;
const customFields = loadedData.customFieldDefinitions;
this.setCustomFieldValues(data, loadedData.customFieldValues, customFields );
this.setCustomFieldValues(data, loadedData.customFieldValues, customFields);
this.setState({ loaded: true, template, customFields, data });
}
};
parseDefinition = ( definition: string, customFieldDefinitions: CustomField[]) => {
parseDefinition = (
definition: string,
customFieldDefinitions: CustomField[],
) => {
const options: HTMLReactParserOptions = {
replace: (domNode) => {
const domNodeAsAny : any = domNode;
if (domNodeAsAny.name === "span"){
if (domNodeAsAny.attribs.fieldtype === "CustomField")
{
const customField = customFieldDefinitions.filter( x => x.guid === domNodeAsAny.attribs.guid)[0];
const domNodeAsAny: any = domNode;
if (domNodeAsAny.name === "span") {
if (domNodeAsAny.attribs.fieldtype === "CustomField") {
const customField = customFieldDefinitions.filter(
(x) => x.guid === domNodeAsAny.attribs.guid,
)[0];
return this.renderCustomField(customField, false);
}
} else if (domNodeAsAny.name === "p") {
return (
<div className="p">
{domToReact(domNodeAsAny.children, options)}
</div>
);
}
else if (domNodeAsAny.name === "p"){
return <div className="p">{domToReact(domNodeAsAny.children, options)}</div>
}
}
}
},
};
return parse(definition, options);
}
};
hasValidationErrors = () : boolean => {
hasValidationErrors = (): boolean => {
const { errors } = this.state;
const result = Object.keys(errors).length > 0;
return result;
}
};
async Save() {
const { errors } = this.state;
const { templateId, version } = this.state.template;
const { formInstanceId } = this.props;
if ( Object.keys(errors).length > 0 )
{
if (Object.keys(errors).length > 0) {
toast.error("There are errors on the form");
throw new Error( "There are errors on the form");
throw new Error("There are errors on the form");
}
const customFieldValues = this.CustomFieldValues();
if (formInstanceId !== undefined){
if (templateId === undefined)
throw Error("TemplateId cannot be null");
if (formInstanceId !== undefined) {
if (templateId === undefined) throw Error("TemplateId cannot be null");
if (version === undefined)
throw Error("Version cannot be null");
if (version === undefined) throw Error("Version cannot be null");
const editFormInstance : EditFormInstance = { formInstanceId, templateId, version, customFieldValues };
const editFormInstance: EditFormInstance = {
formInstanceId,
templateId,
version,
customFieldValues,
};
await formsService.putFormInstance(editFormInstance);
}
else {
if (templateId !== undefined && version !== undefined)
{
} else {
if (templateId !== undefined && version !== undefined) {
//const customFieldValues = this.CustomFieldValues();
const formInstance : CreateFormInstance = { templateId, version, customFieldValues };
const formInstance: CreateFormInstance = {
templateId,
version,
customFieldValues,
};
return await formsService.postFormInstance(formInstance);
}
else
throw new Error("template unknown");
} else throw new Error("template unknown");
}
}
render() {
const { loaded, template, customFields } = this.state;
let parsedDefinition : any;
let parsedDefinition: any;
if (template.definition)
parsedDefinition = this.parseDefinition(template.definition, customFields!);
else
parsedDefinition = <></>;
parsedDefinition = this.parseDefinition(
template.definition,
customFields!,
);
else parsedDefinition = <></>;
return (
<Loading loaded={loaded}>
<div className="ck-content form-editor">
{parsedDefinition}
</div>
<div className="ck-content form-editor">{parsedDefinition}</div>
</Loading>
);
}

View File

@ -9,10 +9,14 @@ export interface ToggleSliderProps extends ToggleProps {
defaultChecked: boolean;
}
class ToggleSlider extends React.Component<ToggleSliderProps> {
render() {
const { name, label, error, readOnly, defaultChecked, ...rest } = this.props;
export default function ToggleSlider({
name,
label,
error,
readOnly,
defaultChecked,
...rest
}: ToggleSliderProps) {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
@ -20,7 +24,4 @@ class ToggleSlider extends React.Component<ToggleSliderProps> {
{error && <div className="alert alert-danger">{error}</div>}
</div>
);
}
}
export default ToggleSlider;

View File

@ -1,94 +1,97 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import Select from "../common/Select";
import Option from "../common/option";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
import customFieldsService, { CustomField } from "../../modules/manager/customfields/services/customFieldsService";
import customFieldsService, {
CustomField,
} from "../../modules/manager/customfields/services/customFieldsService";
interface CustomFieldPickerProps {
name: string;
label: string;
error?: string;
value: any;
exclude? : CustomField[];
onChange?: (name: string, id: GeneralIdRef, displayValue : string) => void;
exclude?: CustomField[];
onChange?: (name: string, id: GeneralIdRef, displayValue: string) => void;
}
interface CustomFieldPickerState {
options?: Option[];
}
export default function CustomFieldPicker({
name,
label,
error,
value,
exclude,
onChange,
}: CustomFieldPickerProps) {
const [options, setOptions] = useState<Option[]>([]);
class CustomFieldPicker extends React.Component<CustomFieldPickerProps, CustomFieldPickerState> {
state = { options: [] as Option[] };
async componentDidMount() {
const pagedData = await customFieldsService.getFields(0, 10, "name", true);
useEffect(() => {
async function load() {
const pagedData = await customFieldsService.getFields(
0,
10,
"name",
true,
);
if (pagedData) {
const options: Option[] = (pagedData.data as any[]).map((x: { id: any; name: any }) => {
return {
const opts: Option[] = (pagedData.data as any[]).map(
(x: { id: any; name: any }) => ({
_id: x.id,
name: x.name,
};
});
this.setState({ options });
}),
);
setOptions(opts);
}
}
GetOptionById = (value: string ) => {
const { options } = this.state;
load();
}, []);
for( var option of options)
{
if (String(option._id) === value)
return option.name;
const getOptionById = useCallback(
(val: string) => {
for (const option of options) {
if (String(option._id) === val) return option.name;
}
return "";
}
},
[options],
);
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { onChange } = this.props;
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const generalIdRef = MakeGeneralIdRef(BigInt(input.value));
const displayValue = this.GetOptionById(input.value);
const displayValue = getOptionById(input.value);
if (onChange) onChange(input.name, generalIdRef, displayValue);
};
getFilteredOptions = () => {
const { exclude } = this.props;
const { options } = this.state;
let filteredOptions = options.filter( (o) => {
if (exclude)
{
for( var exlcudedItem of exclude)
{
const idAsString : string = String(exlcudedItem.id);
const oid : string = String(o._id);
if (oid === idAsString) {
return false;
}
}
}
return true;
}
},
[onChange, getOptionById],
);
return filteredOptions;
const getFilteredOptions = useCallback(() => {
return options.filter((o) => {
if (exclude) {
for (const excludedItem of exclude) {
const idAsString: string = String(excludedItem.id);
const oid: string = String(o._id);
if (oid === idAsString) return false;
}
}
return true;
});
}, [options, exclude]);
render() {
const { name, label, error, value } = this.props;
const filteredOptions = this.getFilteredOptions();
const filteredOptions = getFilteredOptions();
return (
<Select name={name} label={label} error={error} value={value?.id} options={filteredOptions} includeBlankFirstEntry={true} onChange={this.handleChange} />
<Select
name={name}
label={label}
error={error}
value={value?.id}
options={filteredOptions}
includeBlankFirstEntry={true}
onChange={handleChange}
/>
);
}
}
export default CustomFieldPicker;

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import Select from "../common/Select";
import Option from "../common/option";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
@ -17,101 +17,95 @@ interface DomainPickerProps {
onChange?: (name: string, value: CustomFieldValue[]) => void;
}
interface DomainPickerState {
options?: Option[];
selectedOptions: Option[];
}
export default function DomainPicker({
includeLabel,
name,
label,
error,
values,
minEntries,
maxEntries,
onChange,
}: DomainPickerProps) {
const [options, setOptions] = useState<Option[]>([]);
const [selectedOptions, setSelectedOptions] = useState<Option[]>([]);
class DomainPicker extends React.Component<DomainPickerProps, DomainPickerState> {
state = { options: [] as Option[], selectedOptions: [] as Option[] };
async componentDidMount() {
const { values } = this.props;
useEffect(() => {
async function load() {
const pagedData = await domainsService.getDomains(0, 10, "name", true);
if (pagedData) {
const options: Option[] | undefined = pagedData.data.map((x: { id: any; name: any }) => {
return {
const opts: Option[] = pagedData.data.map(
(x: { id: any; name: any }) => ({
_id: x.id,
name: x.name,
};
});
}),
);
const selectedOptions: Option[] = [];
const selected: Option[] = [];
if (values) {
for (const option of values) {
const foundOption = options.filter((x) => Number(x._id) === Number((option.value as GeneralIdRef).id))[0];
selectedOptions.push(foundOption);
const foundOption = opts.filter(
(x) =>
Number(x._id) === Number((option.value as GeneralIdRef).id),
)[0];
if (foundOption) selected.push(foundOption);
}
}
this.setState({ options, selectedOptions });
setOptions(opts);
setSelectedOptions(selected);
}
}
doOnChange = (newSelectedOptions: Option[]) => {
const { onChange } = this.props;
load();
}, [values]);
const { name } = this.props;
var values: CustomFieldValue[] = newSelectedOptions.map((x) => {
return {
const doOnChange = useCallback(
(newSelectedOptions: Option[]) => {
const vals: CustomFieldValue[] = newSelectedOptions.map((x) => ({
value: MakeGeneralIdRef(x._id as unknown as bigint),
displayValue: x.name,
};
});
}));
if (onChange) onChange(name, values);
};
if (onChange) onChange(name, vals);
},
[onChange, name],
);
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
let { options, selectedOptions } = this.state;
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const id: number = Number(input.value);
selectedOptions = options.filter((x) => x._id === id);
const newSelected = options.filter((x) => x._id === id);
setSelectedOptions(newSelected);
doOnChange(newSelected);
},
[options, doOnChange],
);
this.setState({ selectedOptions });
this.doOnChange(selectedOptions);
};
const handleAdd = useCallback(
(item: Option) => {
const newSelected = [...selectedOptions, item];
setSelectedOptions(newSelected);
doOnChange(newSelected);
},
[selectedOptions, doOnChange],
);
handleAdd = (item: Option) => {
const { selectedOptions } = this.state;
const handleDelete = useCallback(
(item: Option) => {
const newSelected = selectedOptions.filter((x) => x !== item);
setSelectedOptions(newSelected);
doOnChange(newSelected);
},
[selectedOptions, doOnChange],
);
selectedOptions.push(item);
this.setState({ selectedOptions });
this.doOnChange(selectedOptions);
};
handleDelete = (item: Option) => {
let { selectedOptions } = this.state;
selectedOptions = selectedOptions.filter((x) => x !== item);
this.setState({ selectedOptions });
this.doOnChange(selectedOptions);
};
render() {
const { includeLabel, name, label, error, values, maxEntries } = this.props;
const { options, selectedOptions } = this.state;
// return (
// <Select
// name={name}
// label={label}
// error={error}
// value={String(value?.id)}
// options={options}
// includeBlankFirstEntry={false}
// onChange={this.handleChange}
// />
// );
if (maxEntries == 1) {
let value = selectedOptions[0]?._id;
if (maxEntries === 1) {
const value = selectedOptions[0]?._id;
return (
<Select
includeLabel={includeLabel}
@ -121,7 +115,7 @@ class DomainPicker extends React.Component<DomainPickerProps, DomainPickerState>
value={value}
options={options}
includeBlankFirstEntry={true}
onChange={this.handleChange}
onChange={handleChange}
/>
);
} else {
@ -133,12 +127,9 @@ class DomainPicker extends React.Component<DomainPickerProps, DomainPickerState>
error={error}
options={options}
selectedOptions={selectedOptions}
onAdd={this.handleAdd}
onDelete={this.handleDelete}
></MultiSelect>
onAdd={handleAdd}
onDelete={handleDelete}
/>
);
}
}
}
export default DomainPicker;

View File

@ -1,11 +1,11 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import Select from "../common/Select";
import Option from "../common/option";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
import formsService from "../../modules/manager/forms/services/formsService";
interface FormTemplatePickerProps {
includeLabel? : boolean;
includeLabel?: boolean;
name: string;
label: string;
error?: string;
@ -13,52 +13,62 @@ interface FormTemplatePickerProps {
onChange?: (name: string, value: GeneralIdRef) => void;
}
interface FormTemplatePickerState {
options?: Option[];
}
export default function FormTemplatePicker({
includeLabel,
name,
label,
error,
value,
onChange,
}: FormTemplatePickerProps) {
const [options, setOptions] = useState<Option[]>([]);
class FormTemplatePicker extends React.Component<FormTemplatePickerProps, FormTemplatePickerState> {
state = { options: [] as Option[] };
async componentDidMount() {
const formTemplates = await formsService.getForms(0,10,"name",true);
useEffect(() => {
async function load() {
const formTemplates = await formsService.getForms(0, 10, "name", true);
if (formTemplates) {
const options: Option[] | undefined = (formTemplates.data as any[]).map((x) => {
return {
const opts: Option[] = (formTemplates.data as any[]).map((x) => ({
_id: x.id,
name: x.name,
};
});
this.setState({ options });
}));
setOptions(opts);
}
}
doOnChange = (name: string, value: bigint) => {
const { onChange } = this.props;
load();
}, []);
const generalIdRef = MakeGeneralIdRef(value);
if (onChange) onChange(name, generalIdRef);
};
const doOnChange = useCallback(
(n: string, v: bigint) => {
const generalIdRef = MakeGeneralIdRef(v);
if (onChange) onChange(n, generalIdRef);
},
[onChange],
);
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
this.doOnChange(input.name, BigInt(input.value));
};
render() {
const { includeLabel, name, label, error, value } = this.props;
const { options } = this.state;
doOnChange(input.name, BigInt(input.value));
},
[doOnChange],
);
let id = "";
if (!((value === undefined || Number.isNaN(value.id) ))) {
if (value !== undefined && !Number.isNaN(value.id)) {
id = String(value.id);
}
return (
<Select includeLabel={includeLabel} name={name} label={label} error={error} value={id} options={options} includeBlankFirstEntry={true} onChange={this.handleChange} />
<Select
includeLabel={includeLabel}
name={name}
label={label}
error={error}
value={id}
options={options}
includeBlankFirstEntry={true}
onChange={handleChange}
/>
);
}
}
export default FormTemplatePicker;

View File

@ -1,8 +1,11 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import Select from "../common/Select";
import Option from "../common/option";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
import glossariesService, { CustomFieldValue, SystemGlossaries } from "../../modules/manager/glossary/services/glossaryService";
import glossariesService, {
CustomFieldValue,
SystemGlossaries,
} from "../../modules/manager/glossary/services/glossaryService";
import MultiSelect from "../common/MultiSelect";
interface GlossaryPickerProps {
@ -16,94 +19,96 @@ interface GlossaryPickerProps {
onChange?: (name: string, values: CustomFieldValue[]) => void;
}
interface GlossaryPickerState {
options?: Option[];
selectedOptions: Option[];
}
export default function GlossaryPicker({
includeLabel,
name,
label,
rootItem,
error,
values,
maxEntries,
onChange,
}: GlossaryPickerProps) {
const [options, setOptions] = useState<Option[]>([]);
const [selectedOptions, setSelectedOptions] = useState<Option[]>([]);
class GlossaryPicker extends React.Component<GlossaryPickerProps, GlossaryPickerState> {
state = {
options: [] as Option[],
selectedOptions: [] as Option[],
};
async componentDidMount() {
const { rootItem, values } = this.props;
useEffect(() => {
async function load() {
const actualRootItem = rootItem ?? SystemGlossaries;
const glossary = await glossariesService.getGlossaryItem(actualRootItem);
if (glossary) {
const options: Option[] | undefined = glossary.children.map((x: { id: any; name: any }) => {
return {
const opts: Option[] = glossary.children.map(
(x: { id: any; name: any }) => ({
_id: x.id,
name: x.name,
};
});
}),
);
const selectedOptions: Option[] = [];
const selected: Option[] = [];
if (values) {
for (const option of values) {
const foundOption = options.filter((x) => Number(x._id) === Number((option.value as GeneralIdRef).id))[0];
selectedOptions.push(foundOption);
const foundOption = opts.filter(
(x) =>
Number(x._id) === Number((option.value as GeneralIdRef).id),
)[0];
if (foundOption) selected.push(foundOption);
}
}
this.setState({ options, selectedOptions });
setOptions(opts);
setSelectedOptions(selected);
}
}
doOnChange = (newSelectedOptions: Option[]) => {
const { onChange } = this.props;
load();
}, [rootItem, values]);
const { name } = this.props;
var values: CustomFieldValue[] = newSelectedOptions.map((x) => {
return {
const doOnChange = useCallback(
(newSelectedOptions: Option[]) => {
const vals: CustomFieldValue[] = newSelectedOptions.map((x) => ({
value: MakeGeneralIdRef(x._id as unknown as bigint),
displayValue: x.name,
};
});
}));
if (onChange) onChange(name, values);
};
if (onChange) onChange(name, vals);
},
[onChange, name],
);
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
let { options, selectedOptions } = this.state;
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const id: number = Number(input.value);
selectedOptions = options.filter((x) => x._id === id);
const newSelected = options.filter((x) => x._id === id);
setSelectedOptions(newSelected);
doOnChange(newSelected);
},
[options, doOnChange],
);
this.setState({ selectedOptions });
this.doOnChange(selectedOptions);
};
const handleAdd = useCallback(
(item: Option) => {
const newSelected = [...selectedOptions, item];
setSelectedOptions(newSelected);
doOnChange(newSelected);
},
[selectedOptions, doOnChange],
);
handleAdd = (item: Option) => {
const { selectedOptions } = this.state;
const handleDelete = useCallback(
(item: Option) => {
const newSelected = selectedOptions.filter((x) => x !== item);
setSelectedOptions(newSelected);
doOnChange(newSelected);
},
[selectedOptions, doOnChange],
);
selectedOptions.push(item);
this.setState({ selectedOptions });
this.doOnChange(selectedOptions);
};
handleDelete = (item: Option) => {
let { selectedOptions } = this.state;
selectedOptions = selectedOptions.filter((x) => x !== item);
this.setState({ selectedOptions });
this.doOnChange(selectedOptions);
};
render() {
const { includeLabel, name, label, error, maxEntries } = this.props;
const { options, selectedOptions } = this.state;
if (maxEntries == 1) {
let value = selectedOptions[0]?._id;
if (maxEntries === 1) {
const value = selectedOptions[0]?._id;
return (
<Select
includeLabel={includeLabel}
@ -113,7 +118,7 @@ class GlossaryPicker extends React.Component<GlossaryPickerProps, GlossaryPicker
value={value}
options={options}
includeBlankFirstEntry={true}
onChange={this.handleChange}
onChange={handleChange}
/>
);
} else {
@ -125,12 +130,9 @@ class GlossaryPicker extends React.Component<GlossaryPickerProps, GlossaryPicker
error={error}
options={options}
selectedOptions={selectedOptions}
onAdd={this.handleAdd}
onDelete={this.handleDelete}
></MultiSelect>
onAdd={handleAdd}
onDelete={handleDelete}
/>
);
}
}
}
export default GlossaryPicker;

View File

@ -1,11 +1,11 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import sequenceService from "../../modules/manager/sequence/services/sequenceService";
import Select from "./../common/Select";
import Option from "../common/option";
import { GeneralIdRef } from "./../../utils/GeneralIdRef";
interface SequencePickerProps {
includeLabel? : boolean;
includeLabel?: boolean;
name: string;
label: string;
error?: string;
@ -13,46 +13,55 @@ interface SequencePickerProps {
onChange?: (name: string, value: GeneralIdRef) => void;
}
interface SequencePickerState {
options?: Option[];
}
export default function SequencePicker({
includeLabel,
name,
label,
error,
value,
onChange,
}: SequencePickerProps) {
const [options, setOptions] = useState<Option[] | undefined>(undefined);
class SequencePicker extends React.Component<SequencePickerProps, SequencePickerState> {
state = { options: undefined };
async componentDidMount() {
useEffect(() => {
async function load() {
const pagedData = await sequenceService.getSequences(0, 10, "name", true);
if (pagedData) {
const options: Option[] = (pagedData.data as any[]).map((x: { id: any; name: any }) => {
return {
const opts: Option[] = (pagedData.data as any[]).map(
(x: { id: any; name: any }) => ({
_id: x.id,
name: x.name,
};
});
this.setState({ options });
}),
);
setOptions(opts);
}
}
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { onChange } = this.props;
load();
}, []);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const generalIdRef: GeneralIdRef = {
id: BigInt(input.value),
};
if (onChange) onChange(input.name, generalIdRef);
};
render() {
const { includeLabel, name, label, error, value } = this.props;
const { options } = this.state;
},
[onChange],
);
return (
<Select includeLabel={includeLabel} name={name} label={label} error={error} value={String(value?.id)} options={options} includeBlankFirstEntry={true} onChange={this.handleChange} />
<Select
includeLabel={includeLabel}
name={name}
label={label}
error={error}
value={String(value?.id)}
options={options}
includeBlankFirstEntry={true}
onChange={handleChange}
/>
);
}
}
export default SequencePicker;

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import Select from "../common/Select";
import Option from "../common/option";
import { GeneralIdRef } from "../../utils/GeneralIdRef";
@ -9,50 +9,61 @@ interface SsoProviderPickerProps {
label: string;
error?: string;
value: any;
domain? : GeneralIdRef;
domain?: GeneralIdRef;
onChange?: (name: string, value: GeneralIdRef) => void;
}
interface SsoProviderPickerState {
options?: Option[];
}
export default function SsoProviderPicker({
name,
label,
error,
value,
domain,
onChange,
}: SsoProviderPickerProps) {
const [options, setOptions] = useState<Option[] | undefined>(undefined);
class SsoProviderPicker extends React.Component<SsoProviderPickerProps, SsoProviderPickerState> {
state = { options: undefined };
async componentDidMount() {
const pagedData = await ssoManagerService.getSsoProviders(0, 10, "name", true);
useEffect(() => {
async function load() {
const pagedData = await ssoManagerService.getSsoProviders(
0,
10,
"name",
true,
);
if (pagedData) {
const options: Option[] = (pagedData.data as any[]).map(x => {
return {
const opts: Option[] = (pagedData.data as any[]).map((x) => ({
_id: x.id,
name: x.name,
};
});
this.setState({ options });
}));
setOptions(opts);
}
}
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { onChange } = this.props;
load();
}, []);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const generalIdRef: GeneralIdRef = {
id: BigInt(input.value),
};
if (onChange) onChange(input.name, generalIdRef);
};
render() {
const { name, label, error, value } = this.props;
const { options } = this.state;
},
[onChange],
);
return (
<Select name={name} label={label} error={error} value={String(value?.id)} options={options} includeBlankFirstEntry={true} onChange={this.handleChange} />
<Select
name={name}
label={label}
error={error}
value={String(value?.id)}
options={options}
includeBlankFirstEntry={true}
onChange={handleChange}
/>
);
}
}
export default SsoProviderPicker;

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState, useCallback } from "react";
import Select from "./../common/Select";
import Option from "../common/option";
import { GeneralIdRef } from "./../../utils/GeneralIdRef";
@ -9,50 +9,56 @@ interface UserPickerProps {
label: string;
error?: string;
value: any;
domain? : GeneralIdRef;
domain?: GeneralIdRef;
onChange?: (name: string, value: GeneralIdRef) => void;
}
interface UserPickerState {
options?: Option[];
}
export default function UserPicker({
name,
label,
error,
value,
domain,
onChange,
}: UserPickerProps) {
const [options, setOptions] = useState<Option[] | undefined>(undefined);
class UserPicker extends React.Component<UserPickerProps, UserPickerState> {
state = { options: undefined };
async componentDidMount() {
useEffect(() => {
async function load() {
const pagedData = await userService.getUsers(0, 10, "name", true);
if (pagedData) {
const options: Option[] = (pagedData.data as any[]).map(x => {
return {
const opts: Option[] = (pagedData.data as any[]).map((x) => ({
_id: x.id,
name: x.displayName,
};
});
this.setState({ options });
}));
setOptions(opts);
}
}
handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const { onChange } = this.props;
load();
}, []);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const generalIdRef: GeneralIdRef = {
id: BigInt(input.value),
};
if (onChange) onChange(input.name, generalIdRef);
};
render() {
const { name, label, error, value } = this.props;
const { options } = this.state;
},
[onChange],
);
return (
<Select name={name} label={label} error={error} value={String(value?.id)} options={options} includeBlankFirstEntry={true} onChange={this.handleChange} />
<Select
name={name}
label={label}
error={error}
value={String(value?.id)}
options={options}
includeBlankFirstEntry={true}
onChange={handleChange}
/>
);
}
}
export default UserPicker;

View File

@ -1,114 +1,118 @@
import React, { Component } from 'react';
import Column from '../../components/common/columns';
import { Paginated } from '../../services/Paginated';
import withRouter from '../../utils/withRouter';
import AuditTable from './components/auditTable';
import auditService, { AuditLogEntry } from './services/auditService';
import equal from "fast-deep-equal";
import Loading from '../../components/common/Loading';
import React, { useEffect, useState, useCallback } from "react";
import Column from "../../components/common/columns";
import { Paginated } from "../../services/Paginated";
import AuditTable from "./components/auditTable";
import auditService, { AuditLogEntry } from "./services/auditService";
import Loading from "../../components/common/Loading";
import { useLocation, useParams } from "react-router-dom";
interface AuditState{
loaded: boolean;
pagedData : Paginated<AuditLogEntry>,
sortColumn : Column<AuditLogEntry>,
filters: Map<string, string>;
}
const useQuery = () => new URLSearchParams(useLocation().search);
class Audit extends Component< any, any, AuditState> {
state = {
loaded: false,
pagedData : { page: 1,
pageSize : 10,
export default function Audit() {
const location = useLocation();
const params = useParams();
const query = useQuery();
const [loaded, setLoaded] = useState(false);
const [pagedData, setPagedData] = useState<Paginated<AuditLogEntry>>({
page: 1,
pageSize: 10,
count: 0,
totalPages: 1,
data: [] },
sortColumn: { key: "dateTime", label: "Timing", order: "desc" },
filters: new Map<string, string>()
}
data: [],
});
componentDidMount = () => {
this.loadData();
const [sortColumn, setSortColumn] = useState<Column<AuditLogEntry>>({
key: "dateTime",
label: "Timing",
order: "desc",
});
const [filters, setFilters] = useState<Map<string, string>>(new Map());
const doSearch = useCallback(async () => {
const { page, pageSize } = pagedData;
const auditId = params.auditId ?? "";
const primaryOnly = query.get("primaryOnly");
const isPrimaryOnly =
primaryOnly === null || primaryOnly.toLowerCase() === "true";
const result = await auditService.getLog(
auditId,
isPrimaryOnly,
page,
pageSize,
sortColumn.key,
sortColumn.order === "asc",
filters,
);
if (result) {
setPagedData(result);
setLoaded(true);
} else {
setLoaded(false);
}
}, [pagedData, sortColumn, filters, params.auditId, query]);
const changePage = async (page: number, pageSize: number) => {
setPagedData((prev) => ({
...prev,
page,
pageSize,
}));
setLoaded(false);
};
componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<any>, snapshot?: AuditState | undefined): void {
if (!(equal(this.props?.router.location.search, prevProps?.router.location.search) && equal(this.props?.router.params.auditId, prevProps?.router.params.auditId)))
{
let {pagedData, sortColumn} = this.state;
const onSort = async (column: Column<AuditLogEntry>) => {
setSortColumn(column);
setLoaded(false);
};
pagedData = { page: 1,
pageSize : 10,
const onSearch = async (name: string, value: string) => {
setFilters((prev) => {
const updated = new Map(prev);
updated.set(name, value);
return updated;
});
setLoaded(false);
};
// Load data on mount and whenever page/sort/filter changes
useEffect(() => {
doSearch();
}, [doSearch]);
// Reset pagination + sort when URL search or auditId changes
useEffect(() => {
setPagedData({
page: 1,
pageSize: 10,
count: 0,
totalPages: 1,
data: [] };
sortColumn = { key: "dateTime", label: "Timing", order: "desc" };
data: [],
});
this.setState({pagedData, sortColumn});
setSortColumn({
key: "dateTime",
label: "Timing",
order: "desc",
});
this.changePage(pagedData.page, pagedData.pageSize);
}
}
loadData = async () => {
const { page, pageSize } = this.state.pagedData;
await this.changePage(page, pageSize);
}
changePage = async(page: number, pageSize : number) =>{
const { pagedData } = this.state;
pagedData.page = page;
pagedData.pageSize = pageSize;
this.setState( {loaded: false, pagedData } );
await this.doSearch();
}
onSort = async(sortColumn : Column<AuditLogEntry>) => {
this.setState({loaded: false, sortColumn});
await this.doSearch();
}
onSearch = async ( name: string, value: string) => {
const { filters } = this.state;
filters.set(name, value);
this.setState( { filters });
await this.doSearch();
};
doSearch = async () => {
const {page, pageSize } = this.state.pagedData;
const {sortColumn, filters } = this.state;
const auditId = this.props?.router?.params?.auditId ?? "";
var primaryOnly = this.props.router.query.get("primaryOnly");
const isPrimaryOnly : boolean = (primaryOnly === null) || (primaryOnly.toLowerCase() === "true");
const pagedData = await auditService.getLog(auditId, isPrimaryOnly, page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ loaded: true, pagedData });
}
else {
this.setState({ loaded: false });
}
}
render(): JSX.Element {
const {loaded, pagedData, sortColumn } = this.state;
setLoaded(false);
}, [location.search, params.auditId]);
return (
<Loading loaded={loaded}>
<div>
<AuditTable data={pagedData} sortColumn={sortColumn} onChangePage={this.changePage} onSort={this.onSort} onSearch={this.onSearch}/>
<AuditTable
data={pagedData}
sortColumn={sortColumn}
onChangePage={changePage}
onSort={onSort}
onSearch={onSearch}
/>
</div>
</Loading>
);
}
};
const HOCAudit = withRouter(Audit);
export default HOCAudit;
}

View File

@ -1,119 +1,161 @@
import React from "react";
import React, { useCallback, useMemo } from "react";
import Column from "../../../components/common/columns";
import Table, { PublishedTableProps } from "../../../components/common/Table";
import { Paginated } from "../../../services/Paginated";
import { AuditLogEntry } from "../services/auditService";
import { Namespaces } from "../../../i18n/i18n";
import { useTranslation } from "react-i18next";
interface AuditFieldValues{
OldDisplayName? : string,
OldValue? : string,
NewDisplayName? : string
NewValue? : string
interface AuditFieldValues {
OldDisplayName?: string;
OldValue?: string;
NewDisplayName?: string;
NewValue?: string;
}
interface AuditFieldChanges{
interface AuditFieldChanges {
[fieldname: string]: AuditFieldValues;
}
interface AuditFieldChangeValues{
fieldName : string;
oldDisplayName? : string,
oldValue? : string,
newDisplayName? : string
newValue? : string
interface AuditFieldChangeValues {
fieldName: string;
oldDisplayName?: string;
oldValue?: string;
newDisplayName?: string;
newValue?: string;
}
class AuditTable extends React.Component<PublishedTableProps<AuditLogEntry>> {
fieldColumns : Column<AuditFieldChangeValues>[] = [
{ key: "fieldName", label: "Field" },
{ key: "oldDisplayName", label: "Old Value", content: (item) => {
export default function AuditTable(props: PublishedTableProps<AuditLogEntry>) {
const { data, sortColumn, onChangePage, onSearch, onSort } = props;
const { t } = useTranslation<typeof Namespaces.Common>();
const fieldColumns: Column<AuditFieldChangeValues>[] = useMemo(
() => [
{ key: "fieldName", label: t("Field") },
{
key: "oldDisplayName",
label: t("OldValue"),
content: (item) => {
if (item.oldDisplayName !== undefined)
return <>{item.oldDisplayName}</>
return <>{item.oldValue !== undefined ? String(item.oldValue) : ""}</>
} },
{ key: "newDisplayName", label: "New Value", content: (item) => {
return <>{item.oldDisplayName}</>;
return (
<>{item.oldValue !== undefined ? String(item.oldValue) : ""}</>
);
},
},
{
key: "newDisplayName",
label: t("NewValue"),
content: (item) => {
if (item.newDisplayName !== undefined)
return <>{item.newDisplayName}</>
return <>{item.newValue !== undefined ? String(item.newValue) : ""}</>
} },
]
return <>{item.newDisplayName}</>;
return (
<>{item.newValue !== undefined ? String(item.newValue) : ""}</>
);
},
},
],
[t],
);
columns : Column<AuditLogEntry>[] = [
{ key: "dateTime", label: "Timing", order: "asc", searchable: false },
{ key: "userDisplayName", label: "User Name", order: "asc" },
{ key: "comment", label: "Comment", order: "asc" },
{ key: "entityDisplayName", label: "Entity Display Name", order: "asc", searchable: false },
{ key: "type", label: "Type", order: "asc" },
{ key: "displayName", label: "DisplayName", order: "asc" },
{ key: "fields", label: "Changes", order: "asc", searchable: false, content: (item)=>{
if (item.type === "Delete" || item.type === "Purge")
const columns: Column<AuditLogEntry>[] = useMemo(
() => [
{ key: "dateTime", label: t("Timing"), order: "asc", searchable: false },
{ key: "userDisplayName", label: t("User Name"), order: "asc" },
{ key: "comment", label: t("Comment"), order: "asc" },
{
if (item.displayName !== "")
return <></>
key: "entityDisplayName",
label: t("EntityDisplayName"),
order: "asc",
searchable: false,
},
{ key: "type", label: t("Type"), order: "asc" },
{ key: "displayName", label: t("DisplayName"), order: "asc" },
{
key: "fields",
label: t("Changes"),
order: "asc",
searchable: false,
content: (item) => {
if (
(item.type === "Delete" || item.type === "Purge") &&
item.displayName !== ""
) {
return <></>;
}
const fieldsObject : AuditFieldChanges = JSON.parse(item!.fields);
const fieldsObject: AuditFieldChanges = JSON.parse(item!.fields);
const data: AuditFieldChangeValues[] = [];
let data : AuditFieldChangeValues[] = [];
Object.keys(fieldsObject).forEach(
(key, index) => {
let dataItem : AuditFieldChangeValues =
{
Object.keys(fieldsObject).forEach((key) => {
const dataItem: AuditFieldChangeValues = {
fieldName: key,
oldValue : fieldsObject[key].OldValue,
oldDisplayName : fieldsObject[key].OldDisplayName,
newValue : fieldsObject[key].NewValue,
newDisplayName : fieldsObject[key].NewDisplayName
}
data.push( dataItem );
oldValue: fieldsObject[key].OldValue,
oldDisplayName: fieldsObject[key].OldDisplayName,
newValue: fieldsObject[key].NewValue,
newDisplayName: fieldsObject[key].NewDisplayName,
};
data.push(dataItem);
});
let paginated : Paginated<AuditFieldChangeValues> = {
const paginated: Paginated<AuditFieldChangeValues> = {
count: 0,
page: 1,
pageSize: 10,
totalPages: 0,
data: data
}
data,
};
let fieldColumns : Column<AuditFieldChangeValues>[];
fieldColumns = this.fieldColumns;
if (item.type === "Create" || item.type === "Restore")
{
fieldColumns = this.fieldColumns.filter( x => x.key !== "oldDisplayName");
}
if (item.type === "Delete" || item.type === "Purge")
{
fieldColumns = this.fieldColumns.filter( x => x.key !== "newDisplayName");
}
return <Table data={ paginated } keyName="id" columns={fieldColumns} />;
} },
let displayColumns: Column<AuditFieldChangeValues>[] = [
...fieldColumns,
];
raiseSort = (sortColumn : Column<AuditLogEntry>) => {
this.setState({sortColumn});
if (this.props.onSort !== undefined)
this.props.onSort(sortColumn);
if (item.type === "Create" || item.type === "Restore") {
displayColumns = displayColumns.filter(
(x) => x.key !== "oldDisplayName",
);
}
handleAuditParams = (item: any) => {
if (item.type === "Delete" || item.type === "Purge") {
displayColumns = displayColumns.filter(
(x) => x.key !== "newDisplayName",
);
}
return (
<Table data={paginated} keyName="id" columns={displayColumns} />
);
},
},
],
[fieldColumns, t],
);
const raiseSort = useCallback(
(sortCol: Column<AuditLogEntry>) => {
if (onSort !== undefined) onSort(sortCol);
},
[onSort],
);
const handleAuditParams = useCallback((item: any) => {
return {
entityName : item.entityName,
primaryKey : item.primaryKey
}
}
entityName: item.entityName,
primaryKey: item.primaryKey,
};
}, []);
render() {
const { data, sortColumn, onChangePage, onSearch } = this.props;
return <Table data={ data } keyName="id" columns={this.columns} sortColumn={sortColumn} onSort={this.raiseSort} onChangePage={onChangePage} onSearch={onSearch} onAuditParams={this.handleAuditParams} secondaryAudit={true} />;
}
return (
<Table
data={data}
keyName="id"
columns={columns}
sortColumn={sortColumn}
onSort={raiseSort}
onChangePage={onChangePage}
onSearch={onSearch}
onAuditParams={handleAuditParams}
secondaryAudit={true}
/>
);
}
export default AuditTable;

View File

@ -1,81 +1,110 @@
import React, { Component } from 'react';
import { toast } from 'react-toastify';
import Column from '../../components/common/columns';
import { Paginated } from '../../services/Paginated';
import BlockedIPsTable from './components/blockedIPsTable';
import blockedIPsService, { BlockedIPEntry } from './services/blockedIPsService';
import React, { useCallback, useEffect, useState } from "react";
import { toast } from "react-toastify";
import Column from "../../components/common/columns";
import { Paginated } from "../../services/Paginated";
import BlockedIPsTable from "./components/blockedIPsTable";
import blockedIPsService, {
BlockedIPEntry,
} from "./services/blockedIPsService";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
interface BlockedIPsState {
pagedData: Paginated<BlockedIPEntry>,
sortColumn: Column<BlockedIPEntry>,
filters: Map<string, string>;
}
class BlockedIPs extends Component<any, any, BlockedIPsState> {
state = {
pagedData: {
export default function BlockedIPs() {
const { t } = useTranslation<typeof Namespaces.Common>();
const [pagedData, setPagedData] = useState<Paginated<BlockedIPEntry>>({
page: 1,
pageSize: 10,
count: 0,
totalPages: 1,
data: []
data: [],
});
const [sortColumn, setSortColumn] = useState<Column<BlockedIPEntry>>({
key: "ipAddress",
label: t("IPAddress"),
order: "asc",
});
const [filters, setFilters] = useState<Map<string, string>>(
() => new Map<string, string>(),
);
const loadPage = useCallback(
async (page: number, pageSize: number) => {
const paged = await blockedIPsService.getBlockedIps(
page,
pageSize,
sortColumn.key,
sortColumn.order === "asc",
filters,
);
if (paged) setPagedData(paged);
},
sortColumn: { key: "ipAddress", label: "IP Address", order: "asc" },
filters: new Map<string, string>()
}
[sortColumn, filters],
);
componentDidMount = async () => {
const { page, pageSize } = this.state.pagedData;
useEffect(() => {
loadPage(pagedData.page, pagedData.pageSize);
}, []); // initial load only
await this.changePage(page, pageSize);
}
const handleSort = useCallback(
async (col: Column<BlockedIPEntry>) => {
setSortColumn(col);
changePage = async (page: number, pageSize: number) => {
const { sortColumn, filters } = this.state;
const paged = await blockedIPsService.getBlockedIps(
pagedData.page,
pagedData.pageSize,
col.key,
col.order === "asc",
filters,
);
const pagedData = await blockedIPsService.getBlockedIps(page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ pagedData });
}
}
if (paged) setPagedData(paged);
},
[pagedData.page, pagedData.pageSize, filters],
);
onSort = async (sortColumn: Column<BlockedIPEntry>) => {
const { page, pageSize } = this.state.pagedData;
const { filters } = this.state;
const pagedData = await blockedIPsService.getBlockedIps(page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ pagedData, sortColumn });
}
}
const handleSearch = useCallback(
async (name: string, value: string) => {
const newFilters = new Map(filters);
newFilters.set(name, value);
setFilters(newFilters);
onSearch = async (name: string, value: string) => {
const { page, pageSize } = this.state.pagedData;
const { sortColumn, filters } = this.state;
filters.set(name, value);
const paged = await blockedIPsService.getBlockedIps(
pagedData.page,
pagedData.pageSize,
sortColumn.key,
sortColumn.order === "asc",
newFilters,
);
const pagedData = await blockedIPsService.getBlockedIps(page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ filters, pagedData });
}
};
if (paged) setPagedData(paged);
},
[filters, pagedData.page, pagedData.pageSize, sortColumn],
);
onUnblock = async (item?: BlockedIPEntry) => {
const handleUnblock = useCallback(
async (item?: BlockedIPEntry) => {
const response = await blockedIPsService.UnBlockIp(item?.ipAddress);
if (response) {
this.componentDidMount();
toast.info(`IP Address '${item?.ipAddress}' Unblocked.`);
await loadPage(pagedData.page, pagedData.pageSize);
toast.info(t("IPAddressUnblocked", { ip: item?.ipAddress }));
}
}
render(): JSX.Element {
const { pagedData, sortColumn } = this.state;
},
[pagedData.page, pagedData.pageSize, loadPage, t],
);
return (
<div>
<BlockedIPsTable data={pagedData} sortColumn={sortColumn} onChangePage={this.changePage} onSort={this.onSort} onSearch={this.onSearch} onDelete={this.onUnblock} />
<BlockedIPsTable
data={pagedData}
sortColumn={sortColumn}
onChangePage={loadPage}
onSort={handleSort}
onSearch={handleSearch}
onDelete={handleUnblock}
/>
</div>
);
}
};
export default BlockedIPs;
}

View File

@ -1,39 +1,78 @@
import { faUnlock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
import React, { useCallback, useMemo } from "react";
import { ButtonType } from "../../../components/common/Button";
import Column from "../../../components/common/columns";
import ConfirmButton from "../../../components/common/ConfirmButton";
import Table, { PublishedTableProps } from "../../../components/common/Table";
import authentication from "../../frame/services/authenticationService";
import { BlockedIPEntry } from "../services/blockedIPsService";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../i18n/i18n";
class BlockedIPsTable extends React.Component<PublishedTableProps<BlockedIPEntry>> {
canUnblockBlockedIPAddress = authentication.hasAccess("UnlockIPAddress");
export default function BlockedIPsTable(
props: PublishedTableProps<BlockedIPEntry>,
) {
const { t } = useTranslation<typeof Namespaces.Common>();
const { data, sortColumn, onChangePage, onSearch, onSort, onDelete } = props;
columns : Column<BlockedIPEntry>[] = [
{ key: "ipAddress", label: "IP Address", order: "asc" },
{ key: "numberOfAttempts", label: "Number of Attempts", order: "asc", searchable: false },
{ key: "blockedAt", label: "Date", order: "asc", searchable: false },
{ key: "unblockedIn", label: "Unblocked In (Minutes)", order: "asc", searchable: false },
const canUnblockBlockedIPAddress =
authentication.hasAccess("UnlockIPAddress");
const columns: Column<BlockedIPEntry>[] = useMemo(
() => [
{ key: "ipAddress", label: t("IPAddress"), order: "asc" },
{
key: "action", label: "", searchable: false, content: (item) => {
if (this.canUnblockBlockedIPAddress) return <><ConfirmButton buttonType={ButtonType.primary} keyValue={item} onClick={this.props.onDelete} confirmMessage={"Press again to unblock"} ><FontAwesomeIcon icon={faUnlock} /></ConfirmButton></>; return (<></>)
}
}
];
key: "numberOfAttempts",
label: t("NumberOfAttempts"),
order: "asc",
searchable: false,
},
{ key: "blockedAt", label: t("Date"), order: "asc", searchable: false },
{
key: "unblockedIn",
label: t("UnblockedInMinutes"),
order: "asc",
searchable: false,
},
{
key: "action",
label: "",
searchable: false,
content: (item) =>
canUnblockBlockedIPAddress ? (
<ConfirmButton
buttonType={ButtonType.primary}
keyValue={item}
onClick={onDelete}
confirmMessage={t("PressAgainToUnblock")}
>
<FontAwesomeIcon icon={faUnlock} />
</ConfirmButton>
) : (
<></>
),
},
],
[canUnblockBlockedIPAddress, onDelete, t],
);
raiseSort = (sortColumn : Column<BlockedIPEntry>) => {
this.setState({sortColumn});
if (this.props.onSort !== undefined)
this.props.onSort(sortColumn);
}
const raiseSort = useCallback(
(sortCol: Column<BlockedIPEntry>) => {
if (onSort !== undefined) onSort(sortCol);
},
[onSort],
);
render() {
const { data, sortColumn, onChangePage, onSearch } = this.props;
return <Table data={data} keyName="ipAddress" columns={this.columns} sortColumn={sortColumn} onSort={this.raiseSort} onChangePage={onChangePage} onSearch={onSearch}/>;
}
return (
<Table
data={data}
keyName="ipAddress"
columns={columns}
sortColumn={sortColumn}
onSort={raiseSort}
onChangePage={onChangePage}
onSearch={onSearch}
/>
);
}
export default BlockedIPsTable;

View File

@ -1,27 +1,122 @@
import React from "react";
import React, { useMemo } from "react";
import Column from "../../../components/common/columns";
import Table, { PublishedTableProps } from "../../../components/common/Table";
import { ErrorLog } from "../services/errorLogsService";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../i18n/i18n";
class ErrorLogsTable extends React.Component<PublishedTableProps<ErrorLog>> {
columns: Column<ErrorLog>[] = [
{ key: "id", label: "Id", order: "asc" },
{ key: "application", label: "Application", order: "asc" },
{ key: "message", label: "Message", order: "asc" },
{ key: "occuredAt", label: "Occured At", order: "asc", searchable: false }
];
import ExpandableCell from "../../../components/common/ExpandableCell";
import { max } from "date-fns";
raiseSort = (sortColumn: Column<ErrorLog>) => {
this.setState({ sortColumn });
if (this.props.onSort !== undefined)
this.props.onSort(sortColumn);
export default function ErrorLogsTable(
props: PublishedTableProps<ErrorLog>,
): JSX.Element {
const { t } = useTranslation<typeof Namespaces.Common>();
const { data, sortColumn, onChangePage, onSearch, onSort } = props;
const columns: Column<ErrorLog>[] = useMemo(
() => [
{ key: "id", label: t("Id"), order: "asc" },
{ key: "application", label: t("Application"), order: "asc" },
{ key: "message", label: t("Message"), order: "asc" },
{
key: "occuredAt",
label: t("OccuredAt"),
order: "asc",
searchable: false,
},
{
key: "exceptionJson",
label: t("ExceptionJson"),
order: "asc",
searchable: false,
content: (item: ErrorLog) => {
const raw = item.exceptionJson ?? "";
try {
const parsed = JSON.parse(raw);
const pretty = JSON.stringify(parsed, null, 2)
.replace(/\\r\\n/g, "\n")
.replace(/\\n/g, "\n");
return (
<ExpandableCell
title={t("ShowJSON")}
contentClassName="errorlogs-ExceptionJson"
>
<pre className="errorlogs-pre">{pretty}</pre>
</ExpandableCell>
);
} catch {
const safe = raw.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n");
return (
<ExpandableCell title={t("ShowJSON")}>
<pre className="errorlogs-pre">{safe}</pre>
</ExpandableCell>
);
}
render() {
const { data, sortColumn, onChangePage, onSearch } = this.props;
return <Table data={data} keyName="id" columns={this.columns} sortColumn={sortColumn} onSort={this.raiseSort} onChangePage={onChangePage} onSearch={onSearch} />;
},
},
//cjd- hiding the stack trace as this information is already availalbe in the exceptionJson field
// {
// key: "stackTrace",
// label: t("StackTrace"),
// order: "asc",
// searchable: false,
// content: (item: ErrorLog) => (
// <ExpandableCell
// title={t("ShowStackTrace")}
// contentClassName="errorlogs-StackTraceJson"
// >
// <pre className="errorlogs-pre">{item.stackTrace ?? ""}</pre>
// </ExpandableCell>
// ),
// },
{
key: "supportingData",
label: t("SupportingData"),
order: "asc",
searchable: false,
content: (item: ErrorLog) => {
const raw = item.supportingData ?? "";
try {
const parsed = JSON.parse(raw);
const pretty = JSON.stringify(parsed, null, 2)
.replace(/\\r\\n/g, "\n")
.replace(/\\n/g, "\n");
return (
<ExpandableCell
title={t("ShowJSON")}
contentClassName="errorlogs-SupportingData"
>
<pre className="errorlogs-pre">{pretty}</pre>
</ExpandableCell>
);
} catch {
const safe = raw.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n");
return (
<ExpandableCell title={t("ShowJSON")}>
<pre className="errorlogs-pre">{safe}</pre>
</ExpandableCell>
);
}
},
},
],
[t],
);
const raiseSort = (sortColumnParam: Column<ErrorLog>) => {
if (onSort) onSort(sortColumnParam);
};
return (
<Table
data={data}
keyName="id"
columns={columns}
sortColumn={sortColumn}
onSort={raiseSort}
onChangePage={onChangePage}
onSearch={onSearch}
/>
);
}
export default ErrorLogsTable;

View File

@ -1,72 +1,101 @@
import { Component } from 'react';
import Column from '../../components/common/columns';
import { Paginated } from '../../services/Paginated';
import ErrorLogsTable from './components/errorLogsTable';
import errorLogsService, { ErrorLog } from './services/errorLogsService';
import { useCallback, useEffect, useState } from "react";
import Column from "../../components/common/columns";
import { Paginated } from "../../services/Paginated";
import ErrorLogsTable from "./components/errorLogsTable";
import errorLogsService, { ErrorLog } from "./services/errorLogsService";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
interface ErrorLogState {
pagedData: Paginated<ErrorLog>,
sortColumn: Column<ErrorLog>,
filters: Map<string, string>;
}
class ErrorLogs extends Component<any, any, ErrorLogState> {
state = {
pagedData: {
const ErrorLogs = (): JSX.Element => {
const { t } = useTranslation<typeof Namespaces.Common>();
const [pagedData, setPagedData] = useState<Paginated<ErrorLog>>({
page: 1,
pageSize: 10,
count: 0,
totalPages: 1,
data: []
data: [],
});
const [sortColumn, setSortColumn] = useState<Column<ErrorLog>>({
key: "Id",
label: t("Id"),
order: "desc",
});
const [filters, setFilters] = useState<Map<string, string>>(
new Map<string, string>(),
);
const loadErrorLogs = useCallback(
async (
page: number,
pageSize: number,
column: Column<ErrorLog>,
filterMap: Map<string, string>,
) => {
const data = await errorLogsService.getErrorLogs(
page,
pageSize,
column.key,
column.order === "asc",
filterMap,
);
if (data) {
setPagedData(data);
}
},
sortColumn: { key: "Id", label: "Id", order: "desc" },
filters: new Map<string, string>()
}
[],
);
componentDidMount = async () => {
const { page, pageSize } = this.state.pagedData;
useEffect(() => {
loadErrorLogs(pagedData.page, pagedData.pageSize, sortColumn, filters);
}, [loadErrorLogs]);
await this.changePage(page, pageSize);
}
const changePage = useCallback(
async (page: number, pageSize: number) => {
await loadErrorLogs(page, pageSize, sortColumn, filters);
},
[loadErrorLogs, sortColumn, filters],
);
changePage = async (page: number, pageSize: number) => {
const { sortColumn, filters } = this.state;
const onSort = useCallback(
async (nextSortColumn: Column<ErrorLog>) => {
setSortColumn(nextSortColumn);
await loadErrorLogs(
pagedData.page,
pagedData.pageSize,
nextSortColumn,
filters,
);
},
[loadErrorLogs, pagedData.page, pagedData.pageSize, filters],
);
const pagedData = await errorLogsService.getErrorLogs(page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ pagedData });
}
}
const onSearch = useCallback(
async (name: string, value: string) => {
const nextFilters = new Map(filters);
nextFilters.set(name, value);
setFilters(nextFilters);
onSort = async (sortColumn: Column<ErrorLog>) => {
const { page, pageSize } = this.state.pagedData;
const { filters } = this.state;
const pagedData = await errorLogsService.getErrorLogs(page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ pagedData, sortColumn });
}
}
onSearch = async (name: string, value: string) => {
const { page, pageSize } = this.state.pagedData;
const { sortColumn, filters } = this.state;
filters.set(name, value);
const pagedData = await errorLogsService.getErrorLogs(page, pageSize, sortColumn.key, sortColumn.order === "asc", filters);
if (pagedData) {
this.setState({ filters, pagedData });
}
};
render(): JSX.Element {
const { pagedData, sortColumn } = this.state;
await loadErrorLogs(
pagedData.page,
pagedData.pageSize,
sortColumn,
nextFilters,
);
},
[loadErrorLogs, pagedData.page, pagedData.pageSize, sortColumn, filters],
);
return (
<div>
<ErrorLogsTable data={pagedData} sortColumn={sortColumn} onChangePage={this.changePage} onSort={this.onSort} onSearch={this.onSearch} />
<ErrorLogsTable
data={pagedData}
sortColumn={sortColumn}
onChangePage={changePage}
onSort={onSort}
onSearch={onSearch}
/>
</div>
);
}
};
export default ErrorLogs;

View File

@ -4,31 +4,42 @@ import MapToJson from "../../../utils/MapToJson";
const apiEndpoint = "/exceptionlogs";
export type ErrorLog =
{
export type ErrorLog = {
id: bigint;
application: string;
aessage: string;
occuredAt: Date;
}
stackTrace: string;
supportingData: string;
exceptionJson: string;
};
export async function getErrorLogs(page: number, pageSize: number, sortKey: string, sortAscending: boolean, filters?: Map<string, string>): Promise<Paginated<ErrorLog>> {
export async function getErrorLogs(
page: number,
pageSize: number,
sortKey: string,
sortAscending: boolean,
filters?: Map<string, string>,
): Promise<Paginated<ErrorLog>> {
const filterString = MapToJson(filters);
const response = await httpService.get<Paginated<ErrorLog>>(apiEndpoint + "/exceptionlogs",
{ params: {
const response = await httpService.get<Paginated<ErrorLog>>(
apiEndpoint + "/exceptionlogs",
{
params: {
page: page,
pageSize: pageSize,
sortKey : sortKey,
sortKey: sortKey,
sortAscending: sortAscending,
filters : filterString
} } );
filters: filterString,
},
},
);
return response?.data;
}
const errorLogsService = {
getErrorLogs
getErrorLogs,
};
export default errorLogsService;