diff --git a/src/components/SettingForms/BearerType/BearerTypeFields.tsx b/src/components/SettingForms/BearerType/BearerTypeFields.tsx index d2207c6..c20fa0a 100644 --- a/src/components/SettingForms/BearerType/BearerTypeFields.tsx +++ b/src/components/SettingForms/BearerType/BearerTypeFields.tsx @@ -1,69 +1,35 @@ -import { Field, Form, Formik } from "formik"; +import { Field, useFormikContext } from "formik"; import FormToggle from "../components/FormToggle"; -import { useCameraOutput } from "../../../hooks/useCameraOutput"; -import { cleanArray } from "../../../utils/utils"; import FormGroup from "../components/FormGroup"; -import type { BearerTypeFieldType } from "../../../types/types"; - -export const ValuesComponent = () => { - return null; -}; +import type { BearerTypeFieldType, InitialValuesForm } from "../../../types/types"; const BearerTypeFields = () => { - const { dispatcherQuery, dispatcherMutation } = useCameraOutput(); - - const format = dispatcherQuery?.data?.propFormat?.value; - const rawOptions = dispatcherQuery?.data?.propFormat?.accepted; - const enabled = dispatcherQuery?.data?.propEnabled?.value; - const verbose = dispatcherQuery?.data?.propVerbose?.value; - const options = cleanArray(rawOptions); - - const initialValues: BearerTypeFieldType = { - format: format ?? "JSON", - enabled: enabled === "true", - verbose: verbose === "true", - }; - - const handleSubmit = async (values: BearerTypeFieldType) => { - await dispatcherMutation.mutateAsync(values); - }; + useFormikContext(); return ( - - {({ isSubmitting }) => ( -
-
- - - - {options?.map((option: string) => ( - - ))} - - - -
- - -
-
- -
-
- )} -
+
+ + + + + + + + +
+ +
+
+
); }; diff --git a/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx b/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx index a75cb6e..3311e09 100644 --- a/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx +++ b/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx @@ -1,12 +1,47 @@ +import { useFormikContext, type FormikTouched } from "formik"; import Card from "../../UI/Card"; import CardHeader from "../../UI/CardHeader"; import ChannelFields from "./ChannelFields"; +import type { BearerTypeFieldType, InitialValuesForm } from "../../../types/types"; +import { useCameraBackOfficeOutput } from "../../../hooks/useBackOfficeConfig"; +import { useEffect, useMemo } from "react"; + +type ChannelCardProps = { + touched: FormikTouched; + isSubmitting: boolean; +}; + +const ChannelCard = ({ touched, isSubmitting }: ChannelCardProps) => { + const { values, setFieldValue } = useFormikContext(); + const { backOfficeQuery } = useCameraBackOfficeOutput(values?.format); + + const mapped = useMemo(() => { + const d = backOfficeQuery.data; + return { + backOfficeURL: d?.propBackofficeURL?.value ?? "", + username: d?.propUsername?.value ?? "", + password: d?.propPassword?.value ?? "", + connectTimeoutSeconds: Number(d?.propConnectTimeoutSeconds?.value), + readTimeoutSeconds: Number(d?.propReadTimeoutSeconds?.value), + }; + }, [backOfficeQuery.data]); + + useEffect(() => { + if (!backOfficeQuery.isSuccess) return; + for (const [key, value] of Object.entries(mapped)) { + setFieldValue(key, value); + } + }, [backOfficeQuery.isSuccess, mapped, setFieldValue]); -const ChannelCard = () => { return ( - - + + ); }; diff --git a/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx b/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx index ef554b4..1c8dbaa 100644 --- a/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx +++ b/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx @@ -1,79 +1,52 @@ -import { Field, Form, Formik, useFormikContext } from "formik"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Field, useFormikContext, type FormikTouched } from "formik"; import FormGroup from "../components/FormGroup"; import { useEffect, useState } from "react"; import { faEyeSlash, faEye } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useCameraOutput } from "../../../hooks/useCameraOutput"; -import type { InitialValuesForm, InitialValuesFormErrors } from "../../../types/types"; +import type { BearerTypeFieldType, InitialValuesForm } from "../../../types/types"; import { toast } from "sonner"; +import type { UseQueryResult } from "@tanstack/react-query"; -const ChannelFields = () => { +type ChannelFieldsProps = { + touched: FormikTouched; + isSubmitting: boolean; + + backOfficeData: UseQueryResult; + format?: string; +}; + +const ChannelFields = ({ touched, isSubmitting, format }: ChannelFieldsProps) => { const [showPwd, setShowPwd] = useState(false); - const { backOfficeQuery, backOfficeMutation } = useCameraOutput(); - - const backOfficeURL = backOfficeQuery?.data?.propBackofficeURL?.value; - const username = backOfficeQuery?.data?.propUsername?.value; - const password = backOfficeQuery?.data?.propPassword?.value; - const connectTimeoutSeconds = backOfficeQuery?.data?.propConnectTimeoutSeconds?.value; - const readTimeoutSeconds = backOfficeQuery?.data?.propReadTimeoutSeconds?.value; - - const initialValues: InitialValuesForm = { - backOfficeURL: backOfficeURL ?? "", - username: username ?? "", - password: password ?? "", - connectTimeoutSeconds: Number(connectTimeoutSeconds), - readTimeoutSeconds: Number(readTimeoutSeconds), - }; - - const handleSubmit = async (values: InitialValuesForm) => { - await backOfficeMutation.mutateAsync(values); - }; + const { submitCount, isValid, values, errors } = useFormikContext(); const ValidationToastOnce = () => { - const { submitCount, isValid } = useFormikContext(); useEffect(() => { if (submitCount > 0 && !isValid) { toast.error("Check fields are filled in"); } - }, [submitCount, isValid]); + }, []); return null; }; - const validateValues = (values: InitialValuesForm): InitialValuesFormErrors => { - const errors: InitialValuesFormErrors = {}; - - const url = values.backOfficeURL?.trim(); - const username = values.username?.trim(); - const password = values.password?.trim(); - - if (!url) { - errors.backOfficeURL = "Required"; - } - - if (!username) errors.username = "Required"; - if (!password) errors.password = "Required"; - - const read = Number(values.readTimeoutSeconds); - if (!Number.isFinite(read)) { - errors.readTimeoutSeconds = "Must be a number"; - } else if (read < 0) { - errors.readTimeoutSeconds = "Must be ≥ 0"; - } - - const connect = Number(values.connectTimeoutSeconds); - if (!Number.isFinite(connect)) { - errors.connectTimeoutSeconds = "Must be a number"; - } else if (connect < 0) { - errors.connectTimeoutSeconds = "Must be ≥ 0"; - } - - return errors; - }; - return ( - - {({ errors, touched, isSubmitting }) => ( -
+ <> + {format?.toLowerCase() !== "bof2" && format?.toLowerCase() !== "json" ? ( + <> +
+
+ Format coming soon +
+ +

+ Output configuration currently supports JSON or{" "} + BOF2.
More formats will be added in future + updates. +

+
+ + ) : ( + <>
+ + {format?.toLowerCase() === "bof2" && ( + <> +
+

{values.format} Constants

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + )}
+ - + )} -
+ ); }; diff --git a/src/components/SettingForms/SettingForms/SettingForms.tsx b/src/components/SettingForms/SettingForms/SettingForms.tsx index 632d076..f89b538 100644 --- a/src/components/SettingForms/SettingForms/SettingForms.tsx +++ b/src/components/SettingForms/SettingForms/SettingForms.tsx @@ -1,12 +1,120 @@ +import { Form, Formik } from "formik"; import BearerTypeCard from "../BearerType/BearerTypeCard"; import ChannelCard from "../Channel1-JSON/ChannelCard"; +import { useCameraOutput, useGetDispatcherConfig } from "../../../hooks/useCameraOutput"; +import type { + BearerTypeFieldType, + InitialValuesForm, + InitialValuesFormErrors, + OptionalBOF2Constants, +} from "../../../types/types"; +import { useQueryClient } from "@tanstack/react-query"; +import { useUpdateBackOfficeConfig } from "../../../hooks/useBackOfficeConfig"; +import { useFormVaidate } from "../../../hooks/useFormValidate"; const SettingForms = () => { + const qc = useQueryClient(); + const { dispatcherQuery, dispatcherMutation, backOfficeDispatcherMutation } = useCameraOutput(); + const { backOfficeMutation } = useUpdateBackOfficeConfig(); + const { bof2ConstantsQuery } = useGetDispatcherConfig(); + const { validateMutation } = useFormVaidate(); + + const format = dispatcherQuery?.data?.propFormat?.value; + const enabled = dispatcherQuery?.data?.propEnabled?.value; + + const FFID = bof2ConstantsQuery?.data?.propFeedIdentifier?.value; + const SCID = bof2ConstantsQuery?.data?.propSourceIdentifier?.value; + const GPSFormat = bof2ConstantsQuery?.data?.propGpsFormat?.value; + const timestampSource = bof2ConstantsQuery?.data?.propTimeZoneType?.value; + + const initialValues: BearerTypeFieldType & InitialValuesForm & OptionalBOF2Constants = { + format: format ?? "JSON", + enabled: enabled === "true", + backOfficeURL: "", + username: "", + password: "", + connectTimeoutSeconds: Number(5), + readTimeoutSeconds: Number(15), + + // Bof2 - optional constants + FFID: FFID ?? "", + SCID: SCID ?? "", + timestampSource: timestampSource ?? "", + GPSFormat: GPSFormat ?? "", + }; + + const validateValues = (values: InitialValuesForm): InitialValuesFormErrors => { + const errors: InitialValuesFormErrors = {}; + + const url = values.backOfficeURL?.trim(); + const username = values.username?.trim(); + const password = values.password?.trim(); + if (!url) { + errors.backOfficeURL = "Required"; + } + + if (!username) errors.username = "Required"; + if (!password) errors.password = "Required"; + + const read = Number(values.readTimeoutSeconds); + if (!Number.isFinite(read)) { + errors.readTimeoutSeconds = "Must be a number"; + } else if (read < 0) { + errors.readTimeoutSeconds = "Must be ≥ 0"; + } + + const connect = Number(values.connectTimeoutSeconds); + if (!Number.isFinite(connect)) { + errors.connectTimeoutSeconds = "Must be a number"; + } else if (connect < 0) { + errors.connectTimeoutSeconds = "Must be ≥ 0"; + } + return errors; + }; + + const handleSubmit = async (values: BearerTypeFieldType & InitialValuesForm & OptionalBOF2Constants) => { + // if (formErrors && Object.entries(formErrors).length > 0) { + // return; + // } + const dispatcherData = { + format: values.format, + enabled: values.enabled, + }; + const result = await dispatcherMutation.mutateAsync(dispatcherData); + + if (result?.id) { + qc.invalidateQueries({ queryKey: ["dispatcher"] }); + qc.invalidateQueries({ queryKey: ["backoffice", values.format] }); + const validResponse = await validateMutation.mutateAsync(values); + if (validResponse?.reason === "OK") { + await backOfficeMutation.mutateAsync(values); + } else { + return; + } + } + + if (values.format.toLowerCase() === "bof2") { + const bof2ConstantsData: OptionalBOF2Constants = { + FFID: values.FFID, + SCID: values.SCID, + timestampSource: values.timestampSource, + GPSFormat: values.GPSFormat, + }; + await backOfficeDispatcherMutation.mutateAsync(bof2ConstantsData); + } + }; + return ( -
- - -
+ + {({ isSubmitting, touched }) => ( +
+
+ + +
+
+ )} +
); }; diff --git a/src/components/SettingForms/Sound/SoundSettingsFields.tsx b/src/components/SettingForms/Sound/SoundSettingsFields.tsx index 41d039e..c2f1fb1 100644 --- a/src/components/SettingForms/Sound/SoundSettingsFields.tsx +++ b/src/components/SettingForms/Sound/SoundSettingsFields.tsx @@ -32,7 +32,6 @@ const SoundSettingsFields = () => { hotlistSoundVolume: state.hotlistSoundVolume, soundOptions: [...(state.soundOptions ?? [])], }; - dispatch({ type: "UPDATE", payload: updatedValues }); const result = await mutation.mutateAsync({ diff --git a/src/components/SettingForms/Sound/SoundUpload.tsx b/src/components/SettingForms/Sound/SoundUpload.tsx index c30d389..30a03e6 100644 --- a/src/components/SettingForms/Sound/SoundUpload.tsx +++ b/src/components/SettingForms/Sound/SoundUpload.tsx @@ -4,16 +4,21 @@ import type { SoundUploadValue } from "../../../types/types"; import { useSoundContext } from "../../../context/SoundContext"; import { toast } from "sonner"; import { useCameraBlackboard } from "../../../hooks/useCameraBlackboard"; +import { useFileUpload } from "../../../hooks/useFileUpload"; const SoundUpload = () => { const { state, dispatch } = useSoundContext(); const { mutation } = useCameraBlackboard(); + const { mutation: fileMutation } = useFileUpload({ + queryKey: state.sightingSound ? [state.sightingSound] : undefined, + }); const initialValues: SoundUploadValue = { name: "", soundFile: null, soundFileName: "", soundUrl: "", + uploadedAt: Date.now(), }; const handleSubmit = async (values: SoundUploadValue) => { @@ -37,10 +42,9 @@ const SoundUpload = () => { path: "soundSettings", value: updatedValues, }); + await fileMutation.mutateAsync(values.soundFile); if (result.reason !== "OK") { toast.error("Cannot update sound settings"); - } else { - toast.success(`${values.name} file added`); } dispatch({ type: "ADD", payload: values }); @@ -48,7 +52,7 @@ const SoundUpload = () => { return ( - {({ setFieldValue, errors, setFieldError, values }) => ( + {({ setFieldValue, errors, setFieldError }) => (