From 933c101cbc8b015647bff8e8dcacd019d28d5dc5 Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Mon, 3 Nov 2025 15:01:13 +0000 Subject: [PATCH] - refactored to fetch confis based on formats - send configs based on formats --- .../BearerType/BearerTypeCard.tsx | 8 +- .../BearerType/BearerTypeFields.tsx | 95 +++---- .../Channel1-JSON/ChannelCard.tsx | 56 +++- .../Channel1-JSON/ChannelFields.tsx | 241 +++++++++--------- .../SettingForms/SettingForms.tsx | 62 ++++- src/hooks/useCameraBackOfficeOutput.ts | 74 ++++++ src/hooks/useCameraOutput.ts | 65 +---- src/types/types.ts | 3 +- 8 files changed, 353 insertions(+), 251 deletions(-) create mode 100644 src/hooks/useCameraBackOfficeOutput.ts diff --git a/src/components/SettingForms/BearerType/BearerTypeCard.tsx b/src/components/SettingForms/BearerType/BearerTypeCard.tsx index 2e42a60..0435c54 100644 --- a/src/components/SettingForms/BearerType/BearerTypeCard.tsx +++ b/src/components/SettingForms/BearerType/BearerTypeCard.tsx @@ -2,11 +2,15 @@ import Card from "../../UI/Card"; import CardHeader from "../../UI/CardHeader"; import BearerTypeFields from "./BearerTypeFields"; -const BearerTypeCard = () => { +type BearerTypeCardProps = { + options: string[]; +}; + +const BearerTypeCard = ({ options }: BearerTypeCardProps) => { return ( - + ); }; diff --git a/src/components/SettingForms/BearerType/BearerTypeFields.tsx b/src/components/SettingForms/BearerType/BearerTypeFields.tsx index d2207c6..720e2e8 100644 --- a/src/components/SettingForms/BearerType/BearerTypeFields.tsx +++ b/src/components/SettingForms/BearerType/BearerTypeFields.tsx @@ -1,69 +1,46 @@ -import { Field, Form, Formik } from "formik"; +import { Field, Form, 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"; +import type { BearerTypeFieldType, InitialValuesForm } from "../../../types/types"; -export const ValuesComponent = () => { - return null; +type BearerTypeFieldsProps = { + options: string[]; }; -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); - }; - +const BearerTypeFields = ({ options }: BearerTypeFieldsProps) => { + useFormikContext(); return ( - - {({ isSubmitting }) => ( -
-
- - - - {options?.map((option: string) => ( - - ))} - - - -
- - -
-
- + +
+ + + + {options?.map((option: string) => ( + + ))} + + + +
+ +
- - )} - +
+ {/* */} +
+ ); }; diff --git a/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx b/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx index a75cb6e..f983616 100644 --- a/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx +++ b/src/components/SettingForms/Channel1-JSON/ChannelCard.tsx @@ -1,12 +1,62 @@ +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/useCameraBackOfficeOutput"; +import { useEffect, useMemo } from "react"; + +type ChannelCardProps = { + touched: FormikTouched; + isSubmitting: boolean; + handleFormSubmit: (values: BearerTypeFieldType & InitialValuesForm) => Promise; +}; + +const ChannelCard = ({ touched, isSubmitting, handleFormSubmit }: ChannelCardProps) => { + const { values, resetForm } = useFormikContext(); + const { backOfficeQuery, backOfficeMutation } = 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; + resetForm({ values: { ...values, ...mapped } }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [backOfficeQuery.isSuccess, mapped]); + + console.log(backOfficeQuery.isFetching); + const handleSubmit = async () => { + const backOfficeData = { + format: values.format, + backOfficeURL: values.backOfficeURL, + connectTimeoutSeconds: values.connectTimeoutSeconds, + password: values.password, + readTimeoutSeconds: values.readTimeoutSeconds, + username: values.username, + }; + await backOfficeMutation.mutateAsync(backOfficeData); + }; -const ChannelCard = () => { return ( - - + + ); }; diff --git a/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx b/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx index ef554b4..a2000b0 100644 --- a/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx +++ b/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx @@ -1,43 +1,33 @@ -import { Field, Form, Formik, useFormikContext } from "formik"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Field, Form, 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, InitialValuesFormErrors } from "../../../types/types"; import { toast } from "sonner"; +import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query"; -const ChannelFields = () => { +type ChannelFieldsProps = { + touched: FormikTouched; + isSubmitting: boolean; + backOfficeMutation: UseMutationResult; + backOfficeData: UseQueryResult; + handleFormSubmit: (values: BearerTypeFieldType & InitialValuesForm) => Promise; + handleSubmit: () => void; +}; + +const ChannelFields = ({ + touched, + isSubmitting, + backOfficeMutation, + handleFormSubmit, + handleSubmit, +}: 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 ValidationToastOnce = () => { - const { submitCount, isValid } = useFormikContext(); - useEffect(() => { - if (submitCount > 0 && !isValid) { - toast.error("Check fields are filled in"); - } - }, [submitCount, isValid]); - return null; - }; + const { submitCount, isValid, values, setErrors, errors } = useFormikContext< + BearerTypeFieldType & InitialValuesForm + >(); const validateValues = (values: InitialValuesForm): InitialValuesFormErrors => { const errors: InitialValuesFormErrors = {}; @@ -66,97 +56,112 @@ const ChannelFields = () => { } else if (connect < 0) { errors.connectTimeoutSeconds = "Must be ≥ 0"; } - return errors; }; + const onSubmit = (values: BearerTypeFieldType & InitialValuesForm) => { + const validationErrors = validateValues(values); + if (Object.values(validationErrors).length > 0) { + setErrors(validationErrors); + return; + } + handleFormSubmit(values); + handleSubmit(); + }; + + const ValidationToastOnce = () => { + useEffect(() => { + if (submitCount > 0 && !isValid) { + toast.error("Check fields are filled in"); + } + }, []); + return null; + }; + return ( - - {({ errors, touched, isSubmitting }) => ( -
-
- - + +
+ + - - - - - - - - -
- - setShowPwd((s) => !s)} - icon={showPwd ? faEyeSlash : faEye} - /> -
-
- - - - - - - - - + + + + + + + + +
+ + setShowPwd((s) => !s)} + icon={showPwd ? faEyeSlash : faEye} + />
- - - - )} - +
+ + + + + + + + + +
+ + + ); }; diff --git a/src/components/SettingForms/SettingForms/SettingForms.tsx b/src/components/SettingForms/SettingForms/SettingForms.tsx index 632d076..f0369e5 100644 --- a/src/components/SettingForms/SettingForms/SettingForms.tsx +++ b/src/components/SettingForms/SettingForms/SettingForms.tsx @@ -1,12 +1,66 @@ +import { Formik } from "formik"; import BearerTypeCard from "../BearerType/BearerTypeCard"; import ChannelCard from "../Channel1-JSON/ChannelCard"; +import { useCameraOutput } from "../../../hooks/useCameraOutput"; +import type { BearerTypeFieldType, InitialValuesForm } from "../../../types/types"; +import { cleanArray } from "../../../utils/utils"; +import { useQueryClient } from "@tanstack/react-query"; const SettingForms = () => { + const qc = useQueryClient(); + 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 handleFormSubmit = async (values: BearerTypeFieldType & InitialValuesForm) => { + console.log(values); + 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 initialValues: BearerTypeFieldType & InitialValuesForm = { + format: format ?? "JSON", + enabled: enabled === "true", + verbose: verbose === "true", + backOfficeURL: "", + username: "", + password: "", + connectTimeoutSeconds: Number(5), + readTimeoutSeconds: Number(15), + }; + + const handleSubmit = async (values: BearerTypeFieldType) => { + console.log(values); + // await dispatcherMutation.mutateAsync(values); + }; + return ( -
- - -
+ + {({ isSubmitting, touched }) => ( +
+ + +
+ )} +
); }; diff --git a/src/hooks/useCameraBackOfficeOutput.ts b/src/hooks/useCameraBackOfficeOutput.ts new file mode 100644 index 0000000..75f5205 --- /dev/null +++ b/src/hooks/useCameraBackOfficeOutput.ts @@ -0,0 +1,74 @@ +import { useMutation, useQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; +import { toast } from "sonner"; +import type { InitialValuesForm } from "../types/types"; +import { CAM_BASE } from "../utils/config"; + +const getBackOfficeConfig = async (format: string) => { + const response = await fetch(`${CAM_BASE}/api/fetch-config?id=Dispatcher-${format?.toLowerCase()}`); + if (!response.ok) throw new Error("Cannot get Back Office configuration"); + return response.json(); +}; + +const updateBackOfficeConfig = async (data: InitialValuesForm) => { + const updateConfigPayload = { + id: `Dispatcher-${data.format.toLowerCase()}`, + fields: [ + { + property: "propBackofficeURL", + value: data.backOfficeURL, + }, + { + property: "propConnectTimeoutSeconds", + value: data.connectTimeoutSeconds, + }, + { + property: "propPassword", + value: data.password, + }, + { + property: "propReadTimeoutSeconds", + value: data.readTimeoutSeconds, + }, + { + property: "propUsername", + value: data.username, + }, + ], + }; + console.log(updateConfigPayload); + const response = await fetch(`${CAM_BASE}/api/update-config?id=Dispatcher-${data.format.toLowerCase()}`, { + method: "POST", + body: JSON.stringify(updateConfigPayload), + }); + if (!response.ok) throw new Error("Cannot update Back Office configuration"); + return response.json(); +}; + +export const useCameraBackOfficeOutput = (format: string) => { + const backOfficeQuery = useQuery({ + queryKey: ["backoffice", format], + queryFn: () => getBackOfficeConfig(format), + enabled: !!format, + }); + + const backOfficeMutation = useMutation({ + mutationKey: ["backOfficeUpdate"], + mutationFn: updateBackOfficeConfig, + onError: (error) => toast.error(error.message), + onSuccess: (data) => { + if (data) { + toast.success("Settings successfully updated"); + } + }, + }); + + useEffect(() => { + if (backOfficeQuery.isError) toast.error(backOfficeQuery.error.message); + }, [backOfficeQuery?.error?.message, backOfficeQuery.isError]); + + return { + backOfficeQuery, + backOfficeMutation, + }; +}; diff --git a/src/hooks/useCameraOutput.ts b/src/hooks/useCameraOutput.ts index 9c79c33..4a13500 100644 --- a/src/hooks/useCameraOutput.ts +++ b/src/hooks/useCameraOutput.ts @@ -2,7 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { CAM_BASE } from "../utils/config"; import { useEffect } from "react"; import { toast } from "sonner"; -import type { BearerTypeFieldType, InitialValuesForm } from "../types/types"; +import type { BearerTypeFieldType } from "../types/types"; const getDispatcherConfig = async () => { const response = await fetch(`${CAM_BASE}/api/fetch-config?id=Dispatcher`); @@ -18,7 +18,6 @@ const updateDispatcherConfig = async (data: BearerTypeFieldType) => { property: "propEnabled", value: data.enabled, }, - // Todo: figure out how to add verbose conditionally { property: "propFormat", value: data.format, @@ -33,57 +32,12 @@ const updateDispatcherConfig = async (data: BearerTypeFieldType) => { return response.json(); }; -const getBackOfficeConfig = async () => { - const response = await fetch(`${CAM_BASE}/api/fetch-config?id=Dispatcher-json`); - if (!response.ok) throw new Error("Cannot get Back Office configuration"); - return response.json(); -}; - -const updateBackOfficeConfig = async (data: InitialValuesForm) => { - const updateConfigPayload = { - id: "Dispatcher-json", - fields: [ - { - property: "propBackofficeURL", - value: data.backOfficeURL, - }, - { - property: "propConnectTimeoutSeconds", - value: data.connectTimeoutSeconds, - }, - { - property: "propPassword", - value: data.password, - }, - { - property: "propReadTimeoutSeconds", - value: data.readTimeoutSeconds, - }, - { - property: "propUsername", - value: data.username, - }, - ], - }; - const response = await fetch(`${CAM_BASE}/api/update-config?id=Dispatcher-json`, { - method: "POST", - body: JSON.stringify(updateConfigPayload), - }); - if (!response.ok) throw new Error("Cannot update Back Office configuration"); - return response.json(); -}; - export const useCameraOutput = () => { const dispatcherQuery = useQuery({ queryKey: ["dispatcher"], queryFn: getDispatcherConfig, }); - const backOfficeQuery = useQuery({ - queryKey: ["backoffice"], - queryFn: getBackOfficeConfig, - }); - const dispatcherMutation = useMutation({ mutationFn: updateDispatcherConfig, mutationKey: ["dispatcherUpdate"], @@ -95,29 +49,12 @@ export const useCameraOutput = () => { }, }); - const backOfficeMutation = useMutation({ - mutationKey: ["backOfficeUpdate"], - mutationFn: updateBackOfficeConfig, - onError: (error) => toast.error(error.message), - onSuccess: (data) => { - if (data) { - toast.success("Settings successfully updated"); - } - }, - }); - useEffect(() => { if (dispatcherQuery.isError) toast.error(dispatcherQuery.error.message); }, [dispatcherQuery?.error?.message, dispatcherQuery.isError]); - useEffect(() => { - if (backOfficeQuery.isError) toast.error(backOfficeQuery.error.message); - }, [backOfficeQuery?.error?.message, backOfficeQuery.isError]); - return { dispatcherQuery, dispatcherMutation, - backOfficeQuery, - backOfficeMutation, }; }; diff --git a/src/types/types.ts b/src/types/types.ts index 45d8c79..ca22586 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -48,10 +48,11 @@ export type CameraSettingErrorValues = Partial