From 2765ebfc3bdcd2c1d1bb9602208303dca1fb959d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 7 Aug 2026 11:50:47 -0700 Subject: [PATCH] bug fixes --- client/src/Components/inputs/AutoComplete.tsx | 4 +- .../inputs/forms/FormColorField.tsx | 4 +- .../inputs/forms/FormDatePickerField.tsx | 43 ---------- .../inputs/forms/FormDateTimePickerField.tsx | 81 +++++++++++++++++++ .../inputs/forms/FormMultiSelectField.tsx | 24 ++++-- .../Components/inputs/forms/FormTextField.tsx | 5 +- .../inputs/forms/FormTimePickerField.tsx | 43 ---------- client/src/Pages/Auth/Login/index.tsx | 1 - client/src/Pages/Auth/Recovery/index.tsx | 1 - client/src/Pages/Auth/Register/index.tsx | 1 - client/src/Pages/CreateMonitor/index.tsx | 2 +- client/src/Pages/Maintenance/create/index.tsx | 6 +- client/src/Pages/Settings/index.tsx | 3 +- client/src/Pages/StatusPage/Create/index.tsx | 3 + client/src/locales/en.json | 5 +- 15 files changed, 122 insertions(+), 104 deletions(-) delete mode 100644 client/src/Components/inputs/forms/FormDatePickerField.tsx create mode 100644 client/src/Components/inputs/forms/FormDateTimePickerField.tsx delete mode 100644 client/src/Components/inputs/forms/FormTimePickerField.tsx diff --git a/client/src/Components/inputs/AutoComplete.tsx b/client/src/Components/inputs/AutoComplete.tsx index 9d3a8c7ef..75e8daf47 100644 --- a/client/src/Components/inputs/AutoComplete.tsx +++ b/client/src/Components/inputs/AutoComplete.tsx @@ -6,6 +6,7 @@ import Stack from "@mui/material/Stack"; import { useTheme } from "@mui/material/styles"; import { FieldLabel } from "./FieldLabel"; import { ChevronDown } from "lucide-react"; +import { useTranslation } from "react-i18next"; type AutoCompleteInputProps = Omit< AutocompleteProps, @@ -31,12 +32,13 @@ export const AutoCompleteInput = ({ ...props }: AutoCompleteInputProps) => { const theme = useTheme(); + const { t } = useTranslation(); const multiple = props.multiple; const { renderOption: customRenderOption, ...autocompleteProps } = props; const defaultRenderInput = (params: any) => ( diff --git a/client/src/Components/inputs/forms/FormColorField.tsx b/client/src/Components/inputs/forms/FormColorField.tsx index a806ace51..4d8578318 100644 --- a/client/src/Components/inputs/forms/FormColorField.tsx +++ b/client/src/Components/inputs/forms/FormColorField.tsx @@ -20,12 +20,14 @@ export const FormColorField = ({ ( + render={({ field, fieldState }) => ( )} /> diff --git a/client/src/Components/inputs/forms/FormDatePickerField.tsx b/client/src/Components/inputs/forms/FormDatePickerField.tsx deleted file mode 100644 index c9b012c14..000000000 --- a/client/src/Components/inputs/forms/FormDatePickerField.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - Controller, - useFormContext, - type FieldPath, - type FieldValues, -} from "react-hook-form"; -import dayjs from "dayjs"; -import { DatePicker } from "@/Components/inputs"; - -interface FormDatePickerFieldProps { - name: FieldPath; - fieldLabel?: string; - format?: string; - onValueChange?: (value: string) => void; // e.g. () => trigger("startDate") -} - -export const FormDatePickerField = ({ - name, - fieldLabel, - format = "YYYY-MM-DD", - onValueChange, -}: FormDatePickerFieldProps) => { - const { control } = useFormContext(); - return ( - ( - { - const next = date ? date.format(format) : ""; - field.onChange(next); - onValueChange?.(next); - }} - error={!!fieldState.error} - helperText={fieldState.error?.message} - /> - )} - /> - ); -}; diff --git a/client/src/Components/inputs/forms/FormDateTimePickerField.tsx b/client/src/Components/inputs/forms/FormDateTimePickerField.tsx new file mode 100644 index 000000000..71a17afd0 --- /dev/null +++ b/client/src/Components/inputs/forms/FormDateTimePickerField.tsx @@ -0,0 +1,81 @@ +import { + Controller, + useFormContext, + type FieldPath, + type FieldValues, +} from "react-hook-form"; +import dayjs, { type Dayjs } from "dayjs"; +import { DatePicker, TimePicker } from "@/Components/inputs"; + +export interface FormPickerFieldProps { + name: FieldPath; + fieldLabel?: string; + format?: string; + onValueChange?: (value: string) => void; // e.g. () => trigger("startDate") +} + +interface DayjsPickerProps { + fieldLabel?: string; + value: Dayjs | null; + onChange: (value: Dayjs | null) => void; + error?: boolean; + helperText?: string; +} + +interface FormDayjsPickerFieldProps< + T extends FieldValues, +> extends FormPickerFieldProps { + picker: React.ComponentType; + format: string; +} + +const FormDayjsPickerField = ({ + name, + fieldLabel, + format, + onValueChange, + picker: Picker, +}: FormDayjsPickerFieldProps) => { + const { control } = useFormContext(); + return ( + ( + { + const next = value ? value.format(format) : ""; + field.onChange(next); + onValueChange?.(next); + }} + error={!!fieldState.error} + helperText={fieldState.error?.message} + /> + )} + /> + ); +}; + +export const FormDatePickerField = ({ + format = "YYYY-MM-DD", + ...rest +}: FormPickerFieldProps) => ( + +); + +export const FormTimePickerField = ({ + format = "HH:mm", + ...rest +}: FormPickerFieldProps) => ( + +); diff --git a/client/src/Components/inputs/forms/FormMultiSelectField.tsx b/client/src/Components/inputs/forms/FormMultiSelectField.tsx index 8a44a0121..d38f75239 100644 --- a/client/src/Components/inputs/forms/FormMultiSelectField.tsx +++ b/client/src/Components/inputs/forms/FormMultiSelectField.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { Controller, useFormContext, @@ -25,6 +26,7 @@ interface FormMultiSelectFieldProps; options: O[]; fieldLabel?: string; + placeholder?: string; description?: string; renderRow?: (option: O) => React.ReactNode; renderOptionContent?: (option: O) => React.ReactNode; @@ -35,6 +37,7 @@ export const FormMultiSelectField = (); const theme = useTheme(); const { t } = useTranslation(); + const optionsById = useMemo( + () => new Map(options.map((o) => [o.id, o])), + [options] + ); return ( { const selectedIds: O["id"][] = field.value ?? []; const selected = selectedIds - .map((id) => options.find((o) => o.id === id)) + .map((id) => optionsById.get(id)) .filter((o): o is O => o !== undefined); + // Drag indexes refer to the rendered (resolved) list, not the raw id + // array — ids missing from options are filtered out above, so reorder + // the rendered ids and re-append unresolved ids at the end. const handleDragEnd = (result: DropResult) => { if (!result.destination) return; - const reordered = Array.from(selectedIds); - const [removed] = reordered.splice(result.source.index, 1); - reordered.splice(result.destination.index, 0, removed); - field.onChange(reordered); + const reordered = selected.map((o) => o.id); + const [moved] = reordered.splice(result.source.index, 1); + reordered.splice(result.destination.index, 0, moved); + const unresolved = selectedIds.filter((id) => !optionsById.has(id)); + field.onChange([...reordered, ...unresolved]); }; const rowContent = (option: O) => @@ -74,7 +85,7 @@ export const FormMultiSelectField = { field.onChange(selectedIds.filter((id) => id !== option.id)); }} - aria-label={t("common.buttons.remove")} + aria-label={t("common.buttons.removeItem", { item: option.name })} > @@ -93,6 +104,7 @@ export const FormMultiSelectField = o.id)); }} fieldLabel={fieldLabel} + placeholder={placeholder} renderOptionContent={renderOptionContent} error={!!fieldState.error} helperText={fieldState.error?.message ?? ""} diff --git a/client/src/Components/inputs/forms/FormTextField.tsx b/client/src/Components/inputs/forms/FormTextField.tsx index 984821ed7..3cda2a978 100644 --- a/client/src/Components/inputs/forms/FormTextField.tsx +++ b/client/src/Components/inputs/forms/FormTextField.tsx @@ -9,9 +9,10 @@ import { TextField } from "@/Components/inputs"; interface FormTextFieldProps extends Omit< React.ComponentProps, - "name" | "error" + "name" | "error" | "helperText" > { name: FieldPath; + helperText?: string; } export const FormTextField = ({ @@ -27,11 +28,11 @@ export const FormTextField = ({ )} /> diff --git a/client/src/Components/inputs/forms/FormTimePickerField.tsx b/client/src/Components/inputs/forms/FormTimePickerField.tsx deleted file mode 100644 index 2db830a50..000000000 --- a/client/src/Components/inputs/forms/FormTimePickerField.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - Controller, - useFormContext, - type FieldPath, - type FieldValues, -} from "react-hook-form"; -import dayjs from "dayjs"; -import { TimePicker } from "@/Components/inputs"; - -interface FormTimePickerFieldProps { - name: FieldPath; - fieldLabel?: string; - format?: string; - onValueChange?: (value: string) => void; // e.g. () => trigger("startDate") -} - -export const FormTimePickerField = ({ - name, - fieldLabel, - format = "HH:mm", - onValueChange, -}: FormTimePickerFieldProps) => { - const { control } = useFormContext(); - return ( - ( - { - const next = time ? time.format(format) : ""; - field.onChange(next); - onValueChange?.(next); - }} - error={!!fieldState.error} - helperText={fieldState.error?.message} - /> - )} - /> - ); -}; diff --git a/client/src/Pages/Auth/Login/index.tsx b/client/src/Pages/Auth/Login/index.tsx index f380b5b90..0aeb17dd8 100644 --- a/client/src/Pages/Auth/Login/index.tsx +++ b/client/src/Pages/Auth/Login/index.tsx @@ -62,7 +62,6 @@ const LoginPage = () => { > diff --git a/client/src/Pages/Auth/Recovery/index.tsx b/client/src/Pages/Auth/Recovery/index.tsx index 05ada5821..8680115af 100644 --- a/client/src/Pages/Auth/Recovery/index.tsx +++ b/client/src/Pages/Auth/Recovery/index.tsx @@ -41,7 +41,6 @@ const ForgotPasswordPage = () => { > diff --git a/client/src/Pages/Auth/Register/index.tsx b/client/src/Pages/Auth/Register/index.tsx index 387d091f3..af396e6c7 100644 --- a/client/src/Pages/Auth/Register/index.tsx +++ b/client/src/Pages/Auth/Register/index.tsx @@ -127,7 +127,6 @@ const RegisterPage = () => { /> { )} {/* Alert Thresholds - only for hardware type */} - {generalSettingsConfig.showSecret && showStep(1) && ( + {watchedType === "hardware" && showStep(1) && ( { /> {/* Email Port */} - { sortable name="monitors" fieldLabel={t("pages.statusPages.form.monitors.option.monitors.label")} + placeholder={t( + "pages.statusPages.form.monitors.option.monitors.placeholder" + )} options={monitors} renderRow={(monitor) => ( diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 6a96090e4..104a27b8f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -61,7 +61,7 @@ "importFromJSON": "Import from JSON", "clearFilters": "Clear filters", "removeUser": "Remove user", - "remove": "Remove" + "removeItem": "Remove {{item}}" }, "charts": { "labels": { @@ -105,6 +105,9 @@ "responseTime": "Response time" }, "form": { + "autocomplete": { + "placeholder": "Type to search" + }, "role": { "option": { "role": {