refactor selects
This commit is contained in:
1 parent
20d1d1407b
commit
7cab326ad3
4 files changed
+180
-255
No files matched your search
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
Controller,
|
||||
useFormContext,
|
||||
type FieldValues,
|
||||
type FieldPath,
|
||||
} from "react-hook-form";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { Select } from "@/Components/inputs";
|
||||
|
||||
export interface FormSelectOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface FormSelectProps<T extends FieldValues> extends Omit<
|
||||
React.ComponentProps<typeof Select>,
|
||||
"name" | "error" | "value" | "onChange"
|
||||
> {
|
||||
name: FieldPath<T>;
|
||||
options: FormSelectOption[];
|
||||
fallbackValue?: string | number; // display value while the field is undefined
|
||||
onValueChange?: (value: string | number) => void; // side effects, see Notes
|
||||
}
|
||||
|
||||
export const FormSelectField = <T extends FieldValues>({
|
||||
name,
|
||||
options,
|
||||
fallbackValue,
|
||||
onValueChange,
|
||||
...rest
|
||||
}: FormSelectProps<T>) => {
|
||||
const { control } = useFormContext<T>();
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? fallbackValue ?? ""}
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
onValueChange?.(e.target.value as string | number);
|
||||
}}
|
||||
error={!!fieldState.error}
|
||||
{...rest}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,10 @@
|
||||
import { useMemo } from "react";
|
||||
import { monitorSchema, type MonitorFormData } from "@/Validation/monitor";
|
||||
import { DefaultHttpMethod, DefaultPageSpeedStrategy } from "@/Types/Monitor";
|
||||
import {
|
||||
DefaultHttpMethod,
|
||||
DefaultMonitorMatchMethod,
|
||||
DefaultPageSpeedStrategy,
|
||||
} from "@/Types/Monitor";
|
||||
import type { Monitor, MonitorType } from "@/Types/Monitor";
|
||||
|
||||
interface UseMonitorFormOptions {
|
||||
@@ -43,7 +47,7 @@ export const getMonitorDefaults = (
|
||||
method: data?.method ?? DefaultHttpMethod,
|
||||
ignoreTlsErrors: data?.ignoreTlsErrors || false,
|
||||
useAdvancedMatching: data?.useAdvancedMatching || false,
|
||||
matchMethod: data?.matchMethod || "",
|
||||
matchMethod: data?.matchMethod || DefaultMonitorMatchMethod,
|
||||
expectedValue: data?.expectedValue || "",
|
||||
jsonPath: data?.jsonPath || "",
|
||||
customUpCodes: data?.customUpCodes || [],
|
||||
@@ -138,7 +142,7 @@ export const getMonitorDefaults = (
|
||||
method: DefaultHttpMethod,
|
||||
ignoreTlsErrors: false,
|
||||
useAdvancedMatching: false,
|
||||
matchMethod: "",
|
||||
matchMethod: DefaultMonitorMatchMethod,
|
||||
expectedValue: "",
|
||||
jsonPath: "",
|
||||
customUpCodes: [],
|
||||
|
||||
@@ -8,7 +8,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTheme } from "@mui/material";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Link from "@mui/material/Link";
|
||||
import { HeaderDeleteControls } from "@/Components/monitors";
|
||||
@@ -23,7 +22,6 @@ import {
|
||||
} from "@/Components/design-elements";
|
||||
import {
|
||||
Button,
|
||||
Select,
|
||||
SwitchComponent as Switch,
|
||||
SliderWithLabel,
|
||||
Dialog,
|
||||
@@ -42,6 +40,12 @@ import {
|
||||
HttpMethods,
|
||||
SelectableMonitorTypes,
|
||||
monitorTypeLabelKey,
|
||||
PageSpeedStrategies,
|
||||
DnsRecordTypes,
|
||||
MonitorIntervalOptions,
|
||||
DefaultMonitorMatchMethod,
|
||||
MonitorMatchMethods,
|
||||
GeoCheckIntervalOptions,
|
||||
} from "@/Types/Monitor";
|
||||
import type { Notification } from "@/Types/Notification";
|
||||
import type { Tag } from "@/Types/Tag";
|
||||
@@ -54,6 +58,7 @@ import { FormNumberField } from "@/Components/inputs/forms/FormNumberField";
|
||||
import { FormTextField } from "@/Components/inputs/forms/FormTextField";
|
||||
import { FormRadioGroup } from "@/Components/inputs/forms/FormRadioGroupField";
|
||||
import { FormMultiSelectField } from "@/Components/inputs/forms/FormMultiSelectField";
|
||||
import { FormSelectField } from "@/Components/inputs/forms/FormSelectField";
|
||||
|
||||
interface GeneralSettingsConfig {
|
||||
urlLabel: string;
|
||||
@@ -444,26 +449,18 @@ const CreateMonitorPage = () => {
|
||||
|
||||
{/* Strategy field - only for pagespeed type */}
|
||||
{generalSettingsConfig.showStrategy && (
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="strategy"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? DefaultPageSpeedStrategy}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.strategy.label"
|
||||
)}
|
||||
error={!!fieldState.error}
|
||||
>
|
||||
<MenuItem value="desktop">
|
||||
{t("pages.createMonitor.form.general.option.strategy.desktop")}
|
||||
</MenuItem>
|
||||
<MenuItem value="mobile">
|
||||
{t("pages.createMonitor.form.general.option.strategy.mobile")}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.strategy.label"
|
||||
)}
|
||||
options={PageSpeedStrategies.map((strategy) => ({
|
||||
value: strategy,
|
||||
label: t(
|
||||
`pages.createMonitor.form.general.option.strategy.${strategy}`
|
||||
),
|
||||
}))}
|
||||
fallbackValue={DefaultPageSpeedStrategy}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -480,34 +477,21 @@ const CreateMonitorPage = () => {
|
||||
|
||||
{/* Game select - only for game type */}
|
||||
{generalSettingsConfig.showGameSelect && (
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="gameId"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.game.label"
|
||||
)}
|
||||
error={!!fieldState.error}
|
||||
>
|
||||
<MenuItem value="">
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.game.placeholder"
|
||||
)}{" "}
|
||||
</MenuItem>
|
||||
{games &&
|
||||
Object.entries(games).map(([key, game]) => (
|
||||
<MenuItem
|
||||
key={key}
|
||||
value={key}
|
||||
>
|
||||
{game.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
fieldLabel={t("pages.createMonitor.form.general.option.game.label")}
|
||||
options={[
|
||||
{
|
||||
value: "",
|
||||
label: t(
|
||||
"pages.createMonitor.form.general.option.game.placeholder"
|
||||
),
|
||||
},
|
||||
...Object.entries(games ?? {}).map(([key, game]) => ({
|
||||
value: key,
|
||||
label: game.name,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -546,51 +530,17 @@ const CreateMonitorPage = () => {
|
||||
/>
|
||||
)}
|
||||
{generalSettingsConfig.showDnsRecordType && (
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="dnsRecordType"
|
||||
control={control}
|
||||
defaultValue="A"
|
||||
render={({ field, fieldState }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? "A"}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.label"
|
||||
)}
|
||||
error={!!fieldState.error}
|
||||
>
|
||||
<MenuItem value={"A"}>
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.value.A"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={"AAAA"}>
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.value.AAAA"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={"CNAME"}>
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.value.CNAME"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={"MX"}>
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.value.MX"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={"NS"}>
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.value.NS"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={"TXT"}>
|
||||
{t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.value.TXT"
|
||||
)}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.general.option.dnsRecordType.label"
|
||||
)}
|
||||
options={DnsRecordTypes.map((recordType) => ({
|
||||
value: recordType,
|
||||
label: t(
|
||||
`pages.createMonitor.form.general.option.dnsRecordType.value.${recordType}`
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
<FormTextField
|
||||
@@ -608,70 +558,17 @@ const CreateMonitorPage = () => {
|
||||
title={t("pages.createMonitor.form.frequency.title")}
|
||||
subtitle={t("pages.createMonitor.form.frequency.description")}
|
||||
rightContent={
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="interval"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? 60000}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.label"
|
||||
)}
|
||||
error={!!fieldState.error}
|
||||
>
|
||||
<MenuItem value={15000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fifteenSeconds"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={30000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.thirtySeconds"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={60000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.oneMinute"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={120000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.twoMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={180000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.threeMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={240000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fourMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={300000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fiveMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={600000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.tenMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={900000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.fifteenMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={1800000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.value.thirtyMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.frequency.option.frequency.label"
|
||||
)}
|
||||
options={MonitorIntervalOptions.map((option) => ({
|
||||
value: option.value,
|
||||
label: t(
|
||||
`pages.createMonitor.form.frequency.option.frequency.value.${option.labelKey}`
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -881,50 +778,33 @@ const CreateMonitorPage = () => {
|
||||
subtitle={t("pages.createMonitor.form.advanced.description")}
|
||||
rightContent={
|
||||
<Stack spacing={theme.spacing(LAYOUT.MD)}>
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Stack spacing={theme.spacing(LAYOUT.MD)}>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? DefaultHttpMethod}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value as HttpMethod;
|
||||
field.onChange(value);
|
||||
// HEAD has no response body, reset advanced matching fields if selected
|
||||
if (value === "HEAD") {
|
||||
setValue("useAdvancedMatching", false);
|
||||
setValue("matchMethod", "");
|
||||
setValue("expectedValue", "");
|
||||
setValue("jsonPath", "");
|
||||
}
|
||||
}}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.advanced.option.method.label"
|
||||
)}
|
||||
>
|
||||
{HttpMethods.map((method) => (
|
||||
<MenuItem
|
||||
key={method}
|
||||
value={method}
|
||||
>
|
||||
{method}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.text.secondary}
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{t(
|
||||
`pages.createMonitor.form.advanced.option.method.description.${field.value ?? DefaultHttpMethod}`
|
||||
)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
fieldLabel={t("pages.createMonitor.form.advanced.option.method.label")}
|
||||
fallbackValue={DefaultHttpMethod}
|
||||
onValueChange={(value) => {
|
||||
// HEAD has no response body, reset advanced matching fields if selected
|
||||
if (value === "HEAD") {
|
||||
setValue("useAdvancedMatching", false);
|
||||
setValue("matchMethod", DefaultMonitorMatchMethod);
|
||||
setValue("expectedValue", "");
|
||||
setValue("jsonPath", "");
|
||||
}
|
||||
}}
|
||||
options={HttpMethods.map((method) => ({
|
||||
value: method,
|
||||
label: method,
|
||||
}))}
|
||||
/>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.text.secondary}
|
||||
sx={{ opacity: 0.8 }}
|
||||
>
|
||||
{t(
|
||||
`pages.createMonitor.form.advanced.option.method.description.${watchedMethod}`
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<FormMultiSelectField
|
||||
name="customUpCodes"
|
||||
@@ -962,34 +842,18 @@ const CreateMonitorPage = () => {
|
||||
)}
|
||||
{watchedUseAdvancedMatching && watchedMethod !== "HEAD" && (
|
||||
<Stack spacing={theme.spacing(LAYOUT.MD)}>
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="matchMethod"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? "equal"}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.label"
|
||||
)}
|
||||
>
|
||||
<MenuItem value="equal">
|
||||
{t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.equal"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="include">
|
||||
{t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.include"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value="regex">
|
||||
{t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.regex"
|
||||
)}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.advanced.option.matchMethod.label"
|
||||
)}
|
||||
fallbackValue={DefaultMonitorMatchMethod}
|
||||
options={MonitorMatchMethods.map((method) => ({
|
||||
value: method,
|
||||
label: t(
|
||||
`pages.createMonitor.form.advanced.option.matchMethod.${method}`
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
|
||||
<FormTextField
|
||||
@@ -1065,39 +929,17 @@ const CreateMonitorPage = () => {
|
||||
"pages.createMonitor.form.geoChecks.option.locations.label"
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
<FormSelectField
|
||||
name="geoCheckInterval"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value ?? 300000}
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.geoChecks.option.interval.label"
|
||||
)}
|
||||
>
|
||||
<MenuItem value={300000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.geoChecks.option.interval.value.fiveMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={600000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.geoChecks.option.interval.value.tenMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={900000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.geoChecks.option.interval.value.fifteenMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
<MenuItem value={1800000}>
|
||||
{t(
|
||||
"pages.createMonitor.form.geoChecks.option.interval.value.thirtyMinutes"
|
||||
)}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
fieldLabel={t(
|
||||
"pages.createMonitor.form.geoChecks.option.interval.label"
|
||||
)}
|
||||
options={GeoCheckIntervalOptions.map((option) => ({
|
||||
value: option.value,
|
||||
label: t(
|
||||
`pages.createMonitor.form.geoChecks.option.interval.value.${option.labelKey}`
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -48,6 +48,23 @@ export const monitorTypeLabelKey: Record<SelectableMonitorType, string> = {
|
||||
dns: "optionDns",
|
||||
};
|
||||
|
||||
export const MonitorIntervalOptions = [
|
||||
{ value: 15000, labelKey: "fifteenSeconds" },
|
||||
{ value: 30000, labelKey: "thirtySeconds" },
|
||||
{ value: 60000, labelKey: "oneMinute" },
|
||||
{ value: 120000, labelKey: "twoMinutes" },
|
||||
{ value: 180000, labelKey: "threeMinutes" },
|
||||
{ value: 240000, labelKey: "fourMinutes" },
|
||||
{ value: 300000, labelKey: "fiveMinutes" },
|
||||
{ value: 600000, labelKey: "tenMinutes" },
|
||||
{ value: 900000, labelKey: "fifteenMinutes" },
|
||||
{ value: 1800000, labelKey: "thirtyMinutes" },
|
||||
] as const;
|
||||
|
||||
export const GeoCheckIntervalOptions = MonitorIntervalOptions.filter(
|
||||
({ value }) => value >= 300000
|
||||
);
|
||||
|
||||
export const DnsRecordTypes = ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] as const;
|
||||
export type DnsRecordType = (typeof DnsRecordTypes)[number];
|
||||
|
||||
@@ -70,7 +87,9 @@ export const MonitorStatuses = [
|
||||
] as const;
|
||||
export type MonitorStatus = (typeof MonitorStatuses)[number];
|
||||
|
||||
export type MonitorMatchMethod = "equal" | "include" | "regex" | "";
|
||||
export const MonitorMatchMethods = ["equal", "include", "regex"] as const;
|
||||
export type MonitorMatchMethod = (typeof MonitorMatchMethods)[number] | "";
|
||||
export const DefaultMonitorMatchMethod: MonitorMatchMethod = "equal";
|
||||
|
||||
export const PageSpeedStrategies = ["desktop", "mobile"] as const;
|
||||
export type PageSpeedStrategy = (typeof PageSpeedStrategies)[number];
|
||||
|
||||
Reference in new issue
Block a user