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",
"Cancel": "Cancel",
"Changes": "Changes",
"Close": "Close",
"Confirm": "Confirm",
"ConfirmEmailResent": "Confirm e-mail resent",
"ClientDomainManager": "Client Domain Manager",
@ -68,6 +69,7 @@
"PasswordIsRequired": "Password is required",
"PasswordMinLength": "Password must be at least {{minPasswordLength}} characters",
"PasswordsMustMatch": "You need to confirm by typing exactly the same as the new password",
"PressAgainToDelete": "Press again to delete",
"PressAgainToUnblock": "Press again to unblock",
"ResetPassword": "Reset Password",
"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";
interface AutocompleteProps {
@ -8,110 +8,99 @@ interface AutocompleteProps {
onSelect: (item: Option) => void;
}
interface AutocompleteState {
filteredOptions: Option[];
}
const Autocomplete: React.FC<AutocompleteProps> = ({
options,
selectedOptions,
placeholder,
onSelect,
}) => {
const [filteredOptions, setFilteredOptions] = useState<Option[]>([]);
const inputRef = useRef<HTMLDivElement>(null);
export default class Autocomplete extends React.PureComponent<
AutocompleteProps,
AutocompleteState
> {
private inputRef;
constructor(props: AutocompleteProps) {
super(props);
this.state = { filteredOptions: [] };
this.inputRef = React.createRef<HTMLDivElement>();
}
private filterOptions(filterTerm: string) {
const filterOptions = useCallback(
(filterTerm: string) => {
if (filterTerm !== "") {
let filtered =
this.props.options?.filter((x) =>
options?.filter((x) =>
x.name.toLowerCase().includes(filterTerm.toLowerCase()),
) ?? [];
filtered = filtered.filter(
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id),
(x) => !selectedOptions?.some((y) => x._id === y._id),
);
this.setState({
filteredOptions: filtered,
});
setFilteredOptions(filtered);
} else {
this.setState({
filteredOptions: [],
});
setFilteredOptions([]);
}
}
private showOptions() {
let filtered = this.props.options?.filter((x) => x.name) ?? [];
filtered = filtered.filter(
(x) => !this.props.selectedOptions?.some((y) => x._id === y._id),
},
[options, selectedOptions],
);
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;
setFilteredOptions([]);
}, []);
this.setState({
filteredOptions: [],
});
};
private handleBlur = (event: React.FocusEvent) => {
const handleBlur = useCallback((event: React.FocusEvent) => {
setTimeout(() => {
if (
this.inputRef.current &&
this.inputRef.current.contains(document.activeElement)
inputRef.current &&
inputRef.current.contains(document.activeElement)
) {
return;
}
this.setState({ filteredOptions: [] });
setFilteredOptions([]);
}, 0);
}, []);
useEffect(() => {
document.addEventListener("click", hideAutocomplete);
return () => {
document.removeEventListener("click", hideAutocomplete);
};
}, [hideAutocomplete]);
componentDidMount() {
document.addEventListener("click", this.hideAutocomplete);
}
componentWillUnmount() {
document.removeEventListener("click", this.hideAutocomplete);
}
render() {
const { placeholder, onSelect } = this.props;
const { filteredOptions } = this.state;
const handleSelect = useCallback(
(item: Option) => {
onSelect(item);
setFilteredOptions([]);
},
[onSelect],
);
return (
<div
className="autocomplete"
ref={this.inputRef}
onBlur={this.handleBlur}
ref={inputRef}
onBlur={handleBlur}
tabIndex={-1}
>
<input
className="autocomplete-text-input"
type="text"
onChange={(e) => {
this.filterOptions(e.target.value);
filterOptions(e.target.value);
}}
onFocus={(e) => {
this.showOptions();
onFocus={() => {
showOptions();
}}
placeholder={placeholder}
/>
{filteredOptions.length > 0 && (
<ul className="autocomplete-options">
{filteredOptions.map((x, i) => (
{filteredOptions.map((x) => (
<li key={x._id} value={x._id} tabIndex={0}>
<button
className="autocomplete-option text-left"
onClick={(e) => {
onSelect(x);
this.setState({ filteredOptions: [] });
onClick={() => {
handleSelect(x);
}}
>
{x.name}
@ -122,5 +111,6 @@ export default class Autocomplete extends React.PureComponent<
)}
</div>
);
}
}
};
export default Autocomplete;

View File

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

View File

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

View File

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

View File

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

View File

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