bug fixes
This commit is contained in:
1 parent
d0c8da1653
commit
2765ebfc3b
15 files changed
+122
-104
No files matched your search
@@ -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<any, boolean, boolean, boolean>,
|
||||
@@ -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) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={placeholder ?? "Type to search"}
|
||||
placeholder={placeholder ?? t("common.form.autocomplete.placeholder")}
|
||||
error={error}
|
||||
helperText={helperText}
|
||||
/>
|
||||
|
||||
@@ -20,12 +20,14 @@ export const FormColorField = <T extends FieldValues>({
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
render={({ field, fieldState }) => (
|
||||
<ColorInput
|
||||
format="hex"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
fieldLabel={fieldLabel}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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<T extends FieldValues> {
|
||||
name: FieldPath<T>;
|
||||
fieldLabel?: string;
|
||||
format?: string;
|
||||
onValueChange?: (value: string) => void; // e.g. () => trigger("startDate")
|
||||
}
|
||||
|
||||
export const FormDatePickerField = <T extends FieldValues>({
|
||||
name,
|
||||
fieldLabel,
|
||||
format = "YYYY-MM-DD",
|
||||
onValueChange,
|
||||
}: FormDatePickerFieldProps<T>) => {
|
||||
const { control } = useFormContext<T>();
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<DatePicker
|
||||
fieldLabel={fieldLabel}
|
||||
value={field.value ? dayjs(field.value, format) : null}
|
||||
onChange={(date) => {
|
||||
const next = date ? date.format(format) : "";
|
||||
field.onChange(next);
|
||||
onValueChange?.(next);
|
||||
}}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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<T extends FieldValues> {
|
||||
name: FieldPath<T>;
|
||||
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<T> {
|
||||
picker: React.ComponentType<DayjsPickerProps>;
|
||||
format: string;
|
||||
}
|
||||
|
||||
const FormDayjsPickerField = <T extends FieldValues>({
|
||||
name,
|
||||
fieldLabel,
|
||||
format,
|
||||
onValueChange,
|
||||
picker: Picker,
|
||||
}: FormDayjsPickerFieldProps<T>) => {
|
||||
const { control } = useFormContext<T>();
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Picker
|
||||
fieldLabel={fieldLabel}
|
||||
value={field.value ? dayjs(field.value, format) : null}
|
||||
onChange={(value) => {
|
||||
const next = value ? value.format(format) : "";
|
||||
field.onChange(next);
|
||||
onValueChange?.(next);
|
||||
}}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const FormDatePickerField = <T extends FieldValues>({
|
||||
format = "YYYY-MM-DD",
|
||||
...rest
|
||||
}: FormPickerFieldProps<T>) => (
|
||||
<FormDayjsPickerField
|
||||
{...rest}
|
||||
format={format}
|
||||
picker={DatePicker}
|
||||
/>
|
||||
);
|
||||
|
||||
export const FormTimePickerField = <T extends FieldValues>({
|
||||
format = "HH:mm",
|
||||
...rest
|
||||
}: FormPickerFieldProps<T>) => (
|
||||
<FormDayjsPickerField
|
||||
{...rest}
|
||||
format={format}
|
||||
picker={TimePicker}
|
||||
/>
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Controller,
|
||||
useFormContext,
|
||||
@@ -25,6 +26,7 @@ interface FormMultiSelectFieldProps<T extends FieldValues, O extends MultiSelect
|
||||
name: FieldPath<T>;
|
||||
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 = <T extends FieldValues, O extends MultiSelec
|
||||
name,
|
||||
options,
|
||||
fieldLabel,
|
||||
placeholder,
|
||||
description,
|
||||
renderRow,
|
||||
renderOptionContent,
|
||||
@@ -43,6 +46,10 @@ export const FormMultiSelectField = <T extends FieldValues, O extends MultiSelec
|
||||
const { control } = useFormContext<T>();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const optionsById = useMemo(
|
||||
() => new Map<O["id"], O>(options.map((o) => [o.id, o])),
|
||||
[options]
|
||||
);
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
@@ -50,15 +57,19 @@ export const FormMultiSelectField = <T extends FieldValues, O extends MultiSelec
|
||||
render={({ field, fieldState }) => {
|
||||
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 = <T extends FieldValues, O extends MultiSelec
|
||||
onClick={() => {
|
||||
field.onChange(selectedIds.filter((id) => id !== option.id));
|
||||
}}
|
||||
aria-label={t("common.buttons.remove")}
|
||||
aria-label={t("common.buttons.removeItem", { item: option.name })}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</IconButton>
|
||||
@@ -93,6 +104,7 @@ export const FormMultiSelectField = <T extends FieldValues, O extends MultiSelec
|
||||
field.onChange(newValue.map((o) => o.id));
|
||||
}}
|
||||
fieldLabel={fieldLabel}
|
||||
placeholder={placeholder}
|
||||
renderOptionContent={renderOptionContent}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
|
||||
@@ -9,9 +9,10 @@ import { TextField } from "@/Components/inputs";
|
||||
|
||||
interface FormTextFieldProps<T extends FieldValues> extends Omit<
|
||||
React.ComponentProps<typeof TextField>,
|
||||
"name" | "error"
|
||||
"name" | "error" | "helperText"
|
||||
> {
|
||||
name: FieldPath<T>;
|
||||
helperText?: string;
|
||||
}
|
||||
|
||||
export const FormTextField = <T extends FieldValues>({
|
||||
@@ -27,11 +28,11 @@ export const FormTextField = <T extends FieldValues>({
|
||||
<TextField
|
||||
{...field}
|
||||
type="text"
|
||||
{...rest}
|
||||
value={field.value ?? ""}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? rest.helperText ?? ""}
|
||||
fullWidth
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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<T extends FieldValues> {
|
||||
name: FieldPath<T>;
|
||||
fieldLabel?: string;
|
||||
format?: string;
|
||||
onValueChange?: (value: string) => void; // e.g. () => trigger("startDate")
|
||||
}
|
||||
|
||||
export const FormTimePickerField = <T extends FieldValues>({
|
||||
name,
|
||||
fieldLabel,
|
||||
format = "HH:mm",
|
||||
onValueChange,
|
||||
}: FormTimePickerFieldProps<T>) => {
|
||||
const { control } = useFormContext<T>();
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TimePicker
|
||||
fieldLabel={fieldLabel}
|
||||
value={field.value ? dayjs(field.value, format) : null}
|
||||
onChange={(time) => {
|
||||
const next = time ? time.format(format) : "";
|
||||
field.onChange(next);
|
||||
onValueChange?.(next);
|
||||
}}
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -62,7 +62,6 @@ const LoginPage = () => {
|
||||
>
|
||||
<FormTextField
|
||||
name="email"
|
||||
type="email"
|
||||
fieldLabel={t("pages.auth.common.form.option.email.label")}
|
||||
placeholder={t("pages.auth.common.form.option.email.placeholder")}
|
||||
/>
|
||||
|
||||
@@ -41,7 +41,6 @@ const ForgotPasswordPage = () => {
|
||||
>
|
||||
<FormTextField
|
||||
name="email"
|
||||
type="email"
|
||||
fieldLabel={t("pages.auth.common.form.option.email.label")}
|
||||
placeholder={t("pages.auth.common.form.option.email.placeholder")}
|
||||
/>
|
||||
|
||||
@@ -127,7 +127,6 @@ const RegisterPage = () => {
|
||||
/>
|
||||
<FormTextField
|
||||
name="email"
|
||||
type="email"
|
||||
disabled={!!token}
|
||||
fieldLabel={t("common.form.email.option.email.label")}
|
||||
placeholder={t("common.form.email.option.email.placeholder")}
|
||||
|
||||
@@ -572,7 +572,7 @@ const CreateMonitorPage = () => {
|
||||
)}
|
||||
|
||||
{/* Alert Thresholds - only for hardware type */}
|
||||
{generalSettingsConfig.showSecret && showStep(1) && (
|
||||
{watchedType === "hardware" && showStep(1) && (
|
||||
<ConfigBox
|
||||
title={t("pages.createMonitor.form.thresholds.title")}
|
||||
subtitle={t("pages.createMonitor.form.thresholds.description")}
|
||||
|
||||
@@ -20,8 +20,10 @@ import { useEffect } from "react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js";
|
||||
import { FormTextField } from "@/Components/inputs/forms/FormTextField";
|
||||
import { FormSelectField } from "@/Components/inputs/forms/FormSelectField";
|
||||
import { FormDatePickerField } from "@/Components/inputs/forms/FormDatePickerField";
|
||||
import { FormTimePickerField } from "@/Components/inputs/forms/FormTimePickerField";
|
||||
import {
|
||||
FormDatePickerField,
|
||||
FormTimePickerField,
|
||||
} from "@/Components/inputs/forms/FormDateTimePickerField";
|
||||
import { FormMultiSelectField } from "@/Components/inputs/forms/FormMultiSelectField";
|
||||
import { FormNumberField } from "@/Components/inputs/forms/FormNumberField";
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import { CHECK_TTL_SENTINEL } from "@/Types/Check";
|
||||
import { FormTextField } from "@/Components/inputs/forms/FormTextField";
|
||||
import { FormSliderField } from "@/Components/inputs/forms/FormSliderField";
|
||||
import { FormSwitchField } from "@/Components/inputs/forms/FormSwitchField";
|
||||
import { FormNumberField } from "@/Components/inputs/forms/FormNumberField";
|
||||
|
||||
interface Timezone {
|
||||
id: string;
|
||||
@@ -596,7 +597,7 @@ export const SettingsPage = () => {
|
||||
/>
|
||||
|
||||
{/* Email Port */}
|
||||
<FormTextField
|
||||
<FormNumberField
|
||||
name="systemEmailPort"
|
||||
fieldLabel={t("pages.settings.form.email.option.port.label")}
|
||||
placeholder={t("pages.settings.form.email.option.port.placeholder")}
|
||||
|
||||
@@ -311,6 +311,9 @@ const CreateStatusPage = () => {
|
||||
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) => (
|
||||
<Typography flexGrow={1}>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in new issue
Block a user