Next round of refactoring done

This commit is contained in:
Colin Dawson 2026-01-31 00:18:01 +00:00
parent 81a01fe9f3
commit 4a2d65f360
7 changed files with 137 additions and 150 deletions

View File

@ -16,6 +16,7 @@
"BlockedIPs": "Blocked IPs", "BlockedIPs": "Blocked IPs",
"Cancel": "Cancel", "Cancel": "Cancel",
"Changes": "Changes", "Changes": "Changes",
"Close": "Close",
"Confirm": "Confirm", "Confirm": "Confirm",
"ConfirmEmailResent": "Confirm e-mail resent", "ConfirmEmailResent": "Confirm e-mail resent",
"ClientDomainManager": "Client Domain Manager", "ClientDomainManager": "Client Domain Manager",
@ -68,6 +69,7 @@
"PasswordIsRequired": "Password is required", "PasswordIsRequired": "Password is required",
"PasswordMinLength": "Password must be at least {{minPasswordLength}} characters", "PasswordMinLength": "Password must be at least {{minPasswordLength}} characters",
"PasswordsMustMatch": "You need to confirm by typing exactly the same as the new password", "PasswordsMustMatch": "You need to confirm by typing exactly the same as the new password",
"PressAgainToDelete": "Press again to delete",
"PressAgainToUnblock": "Press again to unblock", "PressAgainToUnblock": "Press again to unblock",
"ResetPassword": "Reset Password", "ResetPassword": "Reset Password",
"ResendConfirm": "Resend Confirm", "ResendConfirm": "Resend Confirm",

View File

@ -1,4 +1,4 @@
import * as React from "react"; import React, { useState, useCallback, useEffect, useRef } from "react";
import Option from "./option"; import Option from "./option";
interface AutocompleteProps { interface AutocompleteProps {
@ -8,119 +8,109 @@ interface AutocompleteProps {
onSelect: (item: Option) => void; onSelect: (item: Option) => void;
} }
interface AutocompleteState { const Autocomplete: React.FC<AutocompleteProps> = ({
filteredOptions: Option[]; options,
} selectedOptions,
placeholder,
onSelect,
}) => {
const [filteredOptions, setFilteredOptions] = useState<Option[]>([]);
const inputRef = useRef<HTMLDivElement>(null);
export default class Autocomplete extends React.PureComponent< const filterOptions = useCallback(
AutocompleteProps, (filterTerm: string) => {
AutocompleteState if (filterTerm !== "") {
> { let filtered =
private inputRef; options?.filter((x) =>
constructor(props: AutocompleteProps) { x.name.toLowerCase().includes(filterTerm.toLowerCase()),
super(props); ) ?? [];
this.state = { filteredOptions: [] }; filtered = filtered.filter(
this.inputRef = React.createRef<HTMLDivElement>(); (x) => !selectedOptions?.some((y) => x._id === y._id),
} );
setFilteredOptions(filtered);
} else {
setFilteredOptions([]);
}
},
[options, selectedOptions],
);
private filterOptions(filterTerm: string) { const showOptions = useCallback(() => {
if (filterTerm !== "") { let filtered = options?.filter((x) => x.name) ?? [];
let filtered =
this.props.options?.filter((x) =>
x.name.toLowerCase().includes(filterTerm.toLowerCase()),
) ?? [];
filtered = filtered.filter(
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id),
);
this.setState({
filteredOptions: filtered,
});
} else {
this.setState({
filteredOptions: [],
});
}
}
private showOptions() {
let filtered = this.props.options?.filter((x) => x.name) ?? [];
filtered = filtered.filter( filtered = filtered.filter(
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id), (x) => !selectedOptions?.some((y) => x._id === y._id),
); );
this.setState({ setFilteredOptions(filtered);
filteredOptions: filtered, }, [options, selectedOptions]);
});
}
private hideAutocomplete = (event: any) => { const hideAutocomplete = useCallback((event: any) => {
if (event.target.classList.contains("autocomplete-text-input")) return; if (event.target.classList.contains("autocomplete-text-input")) return;
setFilteredOptions([]);
}, []);
this.setState({ const handleBlur = useCallback((event: React.FocusEvent) => {
filteredOptions: [],
});
};
private handleBlur = (event: React.FocusEvent) => {
setTimeout(() => { setTimeout(() => {
if ( if (
this.inputRef.current && inputRef.current &&
this.inputRef.current.contains(document.activeElement) inputRef.current.contains(document.activeElement)
) { ) {
return; return;
} }
setFilteredOptions([]);
this.setState({ filteredOptions: [] });
}, 0); }, 0);
}; }, []);
componentDidMount() { useEffect(() => {
document.addEventListener("click", this.hideAutocomplete); document.addEventListener("click", hideAutocomplete);
} return () => {
document.removeEventListener("click", hideAutocomplete);
};
}, [hideAutocomplete]);
componentWillUnmount() { const handleSelect = useCallback(
document.removeEventListener("click", this.hideAutocomplete); (item: Option) => {
} onSelect(item);
setFilteredOptions([]);
},
[onSelect],
);
render() { return (
const { placeholder, onSelect } = this.props; <div
const { filteredOptions } = this.state; className="autocomplete"
ref={inputRef}
onBlur={handleBlur}
tabIndex={-1}
>
<input
className="autocomplete-text-input"
type="text"
onChange={(e) => {
filterOptions(e.target.value);
}}
onFocus={() => {
showOptions();
}}
placeholder={placeholder}
/>
{filteredOptions.length > 0 && (
<ul className="autocomplete-options">
{filteredOptions.map((x) => (
<li key={x._id} value={x._id} tabIndex={0}>
<button
className="autocomplete-option text-left"
onClick={() => {
handleSelect(x);
}}
>
{x.name}
</button>
</li>
))}
</ul>
)}
</div>
);
};
return ( export default Autocomplete;
<div
className="autocomplete"
ref={this.inputRef}
onBlur={this.handleBlur}
tabIndex={-1}
>
<input
className="autocomplete-text-input"
type="text"
onChange={(e) => {
this.filterOptions(e.target.value);
}}
onFocus={(e) => {
this.showOptions();
}}
placeholder={placeholder}
/>
{filteredOptions.length > 0 && (
<ul className="autocomplete-options">
{filteredOptions.map((x, i) => (
<li key={x._id} value={x._id} tabIndex={0}>
<button
className="autocomplete-option text-left"
onClick={(e) => {
onSelect(x);
this.setState({ filteredOptions: [] });
}}
>
{x.name}
</button>
</li>
))}
</ul>
)}
</div>
);
}
}

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState, useCallback, useMemo } from "react"; import React, { useEffect, useState, useCallback, useMemo } from "react";
import { Navigate } from "react-router-dom";
import TabHeader from "./TabHeader"; import TabHeader from "./TabHeader";
interface HorizontalTabsProps { interface HorizontalTabsProps {
@ -8,7 +7,6 @@ interface HorizontalTabsProps {
const HorizontalTabs: React.FC<HorizontalTabsProps> = ({ children }) => { const HorizontalTabs: React.FC<HorizontalTabsProps> = ({ children }) => {
const [activeTab, setActiveTab] = useState<string>(""); const [activeTab, setActiveTab] = useState<string>("");
const [redirect, setRedirect] = useState<string>("");
// Set initial tab on mount // Set initial tab on mount
useEffect(() => { useEffect(() => {
@ -26,10 +24,6 @@ const HorizontalTabs: React.FC<HorizontalTabsProps> = ({ children }) => {
return match ? match.props.children : <></>; return match ? match.props.children : <></>;
}, [children, activeTab]); }, [children, activeTab]);
if (redirect !== "") {
return <Navigate to={redirect} />;
}
// If only one tab, just render its content // If only one tab, just render its content
if (children.length === 1) { if (children.length === 1) {
return <>{activeTabChildren}</>; return <>{activeTabChildren}</>;

View File

@ -1,52 +1,54 @@
import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as React from "react"; import * as React from "react";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
interface PillProps { interface PillProps {
pillKey: any; pillKey: any;
displayText: string; displayText: string;
enabled?: boolean; enabled?: boolean;
onClick: (item: any) => void; onClick: (item: any) => void;
className?: string; className?: string;
readOnly?: boolean; readOnly?: boolean;
flash?: boolean; flash?: boolean;
} }
export default function Pill({ export default function Pill({
pillKey, pillKey,
displayText, displayText,
enabled = true, enabled = true,
className, className,
onClick, onClick,
readOnly, readOnly,
flash = false flash = false,
}: PillProps) { }: PillProps) {
const handleOnClick = () => { const { t } = useTranslation(Namespaces.Common);
onClick(pillKey);
};
let classNames = "pill"; const handleOnClick = () => {
onClick(pillKey);
};
if (className) let classNames = "pill";
classNames += " " + className;
if (flash) if (className) classNames += " " + className;
classNames += " flash";
return ( if (flash) classNames += " flash";
<div className={classNames}>
{displayText} return (
{!readOnly && ( <div className={classNames}>
<button {displayText}
type="button" {!readOnly && (
className={`close ${!enabled ? 'd-none': ''}`} <button
data-dismiss="alert" type="button"
aria-label="Close" className={`close ${!enabled ? "d-none" : ""}`}
onClick={handleOnClick} data-dismiss="alert"
> aria-label={t("Close") as string}
<FontAwesomeIcon icon={faXmark} /> onClick={handleOnClick}
</button> >
)} <FontAwesomeIcon icon={faXmark} />
</div> </button>
); )}
</div>
);
} }

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next";
import deepFind from "../../utils/deepfind"; import deepFind from "../../utils/deepfind";
import Column from "./columns"; import Column from "./columns";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -46,6 +47,7 @@ export default function TableBody<T>({
onSelectRow, onSelectRow,
showSecondaryAudit, showSecondaryAudit,
}: TableBodyProps<T>): JSX.Element { }: TableBodyProps<T>): JSX.Element {
const { t } = useTranslation();
const resolvePath = (path: string, args: string[]) => { const resolvePath = (path: string, args: string[]) => {
let modifiedPath = path; let modifiedPath = path;
let index = 0; let index = 0;
@ -137,7 +139,7 @@ export default function TableBody<T>({
buttonType={ButtonType.primary} buttonType={ButtonType.primary}
keyValue={item} keyValue={item}
onClick={onDelete} onClick={onDelete}
confirmMessage={"Press again to delete"} confirmMessage={t("PressAgainToDelete")}
> >
<FontAwesomeIcon icon={faTrash} /> <FontAwesomeIcon icon={faTrash} />
</ConfirmButton> </ConfirmButton>

View File

@ -1,6 +1,4 @@
import { GeneralIdRef } from "../../utils/GeneralIdRef";
export default interface Option { export default interface Option {
_id: string | number; _id: string | number;
name: string; name: string;
} }

View File

@ -6,7 +6,6 @@ import { determineInitialLocale } from "../modules/frame/services/lanugageServic
export const Namespaces = { export const Namespaces = {
Common: "common", Common: "common",
Frame: "frame",
} as const; } as const;
export type Namespace = (typeof Namespaces)[keyof typeof Namespaces]; export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];