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,110 +8,99 @@ 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
> {
private inputRef;
constructor(props: AutocompleteProps) {
super(props);
this.state = { filteredOptions: [] };
this.inputRef = React.createRef<HTMLDivElement>();
}
private filterOptions(filterTerm: string) {
if (filterTerm !== "") { if (filterTerm !== "") {
let filtered = let filtered =
this.props.options?.filter((x) => options?.filter((x) =>
x.name.toLowerCase().includes(filterTerm.toLowerCase()), x.name.toLowerCase().includes(filterTerm.toLowerCase()),
) ?? []; ) ?? [];
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,
});
} else { } else {
this.setState({ setFilteredOptions([]);
filteredOptions: [],
});
} }
} },
[options, selectedOptions],
private showOptions() {
let filtered = this.props.options?.filter((x) => x.name) ?? [];
filtered = filtered.filter(
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id),
); );
this.setState({
filteredOptions: filtered,
});
}
private hideAutocomplete = (event: any) => { const showOptions = useCallback(() => {
let filtered = options?.filter((x) => x.name) ?? [];
filtered = filtered.filter(
(x) => !selectedOptions?.some((y) => x._id === y._id),
);
setFilteredOptions(filtered);
}, [options, selectedOptions]);
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);
}, []);
useEffect(() => {
document.addEventListener("click", hideAutocomplete);
return () => {
document.removeEventListener("click", hideAutocomplete);
}; };
}, [hideAutocomplete]);
componentDidMount() { const handleSelect = useCallback(
document.addEventListener("click", this.hideAutocomplete); (item: Option) => {
} onSelect(item);
setFilteredOptions([]);
componentWillUnmount() { },
document.removeEventListener("click", this.hideAutocomplete); [onSelect],
} );
render() {
const { placeholder, onSelect } = this.props;
const { filteredOptions } = this.state;
return ( return (
<div <div
className="autocomplete" className="autocomplete"
ref={this.inputRef} ref={inputRef}
onBlur={this.handleBlur} onBlur={handleBlur}
tabIndex={-1} tabIndex={-1}
> >
<input <input
className="autocomplete-text-input" className="autocomplete-text-input"
type="text" type="text"
onChange={(e) => { onChange={(e) => {
this.filterOptions(e.target.value); filterOptions(e.target.value);
}} }}
onFocus={(e) => { onFocus={() => {
this.showOptions(); showOptions();
}} }}
placeholder={placeholder} placeholder={placeholder}
/> />
{filteredOptions.length > 0 && ( {filteredOptions.length > 0 && (
<ul className="autocomplete-options"> <ul className="autocomplete-options">
{filteredOptions.map((x, i) => ( {filteredOptions.map((x) => (
<li key={x._id} value={x._id} tabIndex={0}> <li key={x._id} value={x._id} tabIndex={0}>
<button <button
className="autocomplete-option text-left" className="autocomplete-option text-left"
onClick={(e) => { onClick={() => {
onSelect(x); handleSelect(x);
this.setState({ filteredOptions: [] });
}} }}
> >
{x.name} {x.name}
@ -122,5 +111,6 @@ export default class Autocomplete extends React.PureComponent<
)} )}
</div> </div>
); );
} };
}
export default Autocomplete;

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,6 +1,8 @@
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;
@ -19,19 +21,19 @@ export default function Pill({
className, className,
onClick, onClick,
readOnly, readOnly,
flash = false flash = false,
}: PillProps) { }: PillProps) {
const { t } = useTranslation(Namespaces.Common);
const handleOnClick = () => { const handleOnClick = () => {
onClick(pillKey); onClick(pillKey);
}; };
let classNames = "pill"; let classNames = "pill";
if (className) if (className) classNames += " " + className;
classNames += " " + className;
if (flash) if (flash) classNames += " flash";
classNames += " flash";
return ( return (
<div className={classNames}> <div className={classNames}>
@ -39,9 +41,9 @@ export default function Pill({
{!readOnly && ( {!readOnly && (
<button <button
type="button" type="button"
className={`close ${!enabled ? 'd-none': ''}`} className={`close ${!enabled ? "d-none" : ""}`}
data-dismiss="alert" data-dismiss="alert"
aria-label="Close" aria-label={t("Close") as string}
onClick={handleOnClick} onClick={handleOnClick}
> >
<FontAwesomeIcon icon={faXmark} /> <FontAwesomeIcon icon={faXmark} />

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,5 +1,3 @@
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];