updated forms along with addg tabs
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"react-modal": "^3.16.3",
|
||||
"react-router": "^7.8.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"react-tabs": "^6.1.0",
|
||||
"tailwindcss": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,14 +3,18 @@ import NavigationArrow from "../UI/NavigationArrow";
|
||||
|
||||
type SnapshotContainerProps = {
|
||||
side: string;
|
||||
settingsPage?: boolean;
|
||||
};
|
||||
|
||||
export const SnapshotContainer = ({ side }: SnapshotContainerProps) => {
|
||||
export const SnapshotContainer = ({
|
||||
side,
|
||||
settingsPage,
|
||||
}: SnapshotContainerProps) => {
|
||||
const { canvasRef } = useGetOverviewSnapshot(side);
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-video">
|
||||
<NavigationArrow side={side} />
|
||||
<NavigationArrow side={side} settingsPage={settingsPage} />
|
||||
<canvas ref={canvasRef} className="w-full h-full object-contain block " />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,21 +5,20 @@ import type {
|
||||
} from "../../types/types";
|
||||
|
||||
const CameraSettingFields = () => {
|
||||
const initialValues = {
|
||||
const initialValues: CameraSettingValues = {
|
||||
friendlyName: "",
|
||||
cameraAddress: "",
|
||||
userName: "",
|
||||
password: "",
|
||||
setupCamera: 1,
|
||||
};
|
||||
|
||||
const validateValues = (values: CameraSettingValues) => {
|
||||
const errors: CameraSettingErrorValues = {};
|
||||
|
||||
if (!values.friendlyName) errors.friendlyName = "Required";
|
||||
if (!values.cameraAddress) errors.cameraAddress = "Required";
|
||||
if (!values.userName) errors.userName = "Required";
|
||||
if (!values.password) errors.password = "Required";
|
||||
console.log(errors);
|
||||
return errors;
|
||||
};
|
||||
|
||||
@@ -35,100 +34,101 @@ const CameraSettingFields = () => {
|
||||
validate={validateValues}
|
||||
validateOnChange={false}
|
||||
>
|
||||
{({ errors }) => {
|
||||
return (
|
||||
{({ errors, touched, setFieldValue }) => (
|
||||
<Form className="flex flex-col space-y-4 p-2">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<label htmlFor="friendlyName" className="relative">
|
||||
Friendly Name
|
||||
</label>
|
||||
{errors.friendlyName && (
|
||||
<small className="absolute right-0 text-red-500">
|
||||
{errors?.friendlyName}
|
||||
<div className="flex flex-col space-y-2 relative">
|
||||
<label htmlFor="friendlyName">Friendly Name</label>
|
||||
{touched.friendlyName && errors.friendlyName && (
|
||||
<small className="absolute right-0 top-0 text-red-500">
|
||||
{errors.friendlyName}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
name={"friendlyName"}
|
||||
id="friendlyName"
|
||||
name="friendlyName"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg"
|
||||
placeholder="Enter camera name"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="setupCamera" className="relative">
|
||||
Setup Camera
|
||||
</label>
|
||||
|
||||
<div className="flex flex-col space-y-2 relative">
|
||||
<label htmlFor="setupCamera">Setup Camera</label>
|
||||
<Field
|
||||
as="select"
|
||||
id="setupCamera"
|
||||
name="setupCamera"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setFieldValue("setupCamera", parseInt(e.target.value, 10))
|
||||
}
|
||||
>
|
||||
<option value={1} className="bg-[#253445]">
|
||||
1
|
||||
</option>
|
||||
<option value={2} className="bg-[#253445]">
|
||||
2
|
||||
</option>
|
||||
<option value={3} className="bg-[#253445]">
|
||||
3
|
||||
</option>
|
||||
<option value={4} className="bg-[#253445]">
|
||||
4
|
||||
</option>
|
||||
<option value={1}>1</option>
|
||||
<option value={2}>2</option>
|
||||
<option value={3}>3</option>
|
||||
<option value={4}>4</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
|
||||
<div className="flex flex-col space-y-2 relative">
|
||||
<label htmlFor="cameraAddress">Camera Address</label>
|
||||
{errors.cameraAddress && (
|
||||
<small className="absolute right-0 text-red-500">
|
||||
{errors?.cameraAddress}
|
||||
{touched.cameraAddress && errors.cameraAddress && (
|
||||
<small className="absolute right-0 top-0 text-red-500">
|
||||
{errors.cameraAddress}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
name={"cameraAddress"}
|
||||
id="cameraAddress"
|
||||
name="cameraAddress"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg"
|
||||
placeholder="123, London Road..."
|
||||
autoComplete="street-address"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
|
||||
<div className="flex flex-col space-y-2 relative">
|
||||
<label htmlFor="userName">User Name</label>
|
||||
{errors.userName && (
|
||||
<small className="absolute right-0 text-red-500">
|
||||
{errors?.userName}
|
||||
{touched.userName && errors.userName && (
|
||||
<small className="absolute right-0 top-0 text-red-500">
|
||||
{errors.userName}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
name={"userName"}
|
||||
id="userName"
|
||||
name="userName"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg"
|
||||
placeholder="Enter user name"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
|
||||
<div className="flex flex-col space-y-2 relative">
|
||||
<label htmlFor="password">Password</label>
|
||||
{errors.password && (
|
||||
<small className="absolute right-0 text-red-500">
|
||||
{errors?.password}
|
||||
{touched.password && errors.password && (
|
||||
<small className="absolute right-0 top-0 text-red-500">
|
||||
{errors.password}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
name={"password"}
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
className="p-2 border border-gray-400 rounded-lg"
|
||||
placeholder="Enter password"
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-800 rounded-lg p-2 mx-auto"
|
||||
className="bg-blue-800 text-white rounded-lg p-2 mx-auto"
|
||||
>
|
||||
Save settings
|
||||
</button>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ const FrontCameraOverviewCard = () => {
|
||||
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] h-auto")}>
|
||||
<div className="flex flex-col space-y-3 h-full" {...handlers}>
|
||||
<CardHeader title="Front Overiew" icon={faCamera} />
|
||||
<SnapshotContainer side="CameraFront" />
|
||||
<SnapshotContainer side="TargetDetectionFront" />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -7,15 +7,17 @@ import CardHeader from "../UI/CardHeader";
|
||||
const OverviewVideoContainer = ({
|
||||
title,
|
||||
side,
|
||||
settingsPage,
|
||||
}: {
|
||||
title: string;
|
||||
side: string;
|
||||
settingsPage?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Card className={clsx("min-h-[40vh] md:min-h-[60vh] h-auto")}>
|
||||
<div className="relative flex flex-col space-y-3 h-full">
|
||||
<CardHeader title={title} icon={faCamera} />
|
||||
<SnapshotContainer side={side} />
|
||||
<SnapshotContainer side={side} settingsPage={settingsPage} />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Formik, Form, Field } from "formik";
|
||||
|
||||
const OutputForm = () => {
|
||||
const initialValues = {
|
||||
includeVRM: false,
|
||||
includeMotion: false,
|
||||
includeTimestamp: false,
|
||||
timeStampFormat: "utc",
|
||||
};
|
||||
return <div>OutputForm</div>;
|
||||
};
|
||||
|
||||
export default OutputForm;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { GB } from "country-flag-icons/react/3x2";
|
||||
import { formatNumberPlate } from "../../utils/utils";
|
||||
import type { Sighting } from "../../types/types";
|
||||
import type { SightingType } from "../../types/types";
|
||||
|
||||
type NumberPlateProps = {
|
||||
sighting: Sighting;
|
||||
sighting: SightingType;
|
||||
};
|
||||
|
||||
const NumberPlate = ({ sighting }: NumberPlateProps) => {
|
||||
|
||||
14
src/components/SettingForms/BearerType/BearerTypeCard.tsx
Normal file
14
src/components/SettingForms/BearerType/BearerTypeCard.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Card from "../../UI/Card";
|
||||
import CardHeader from "../../UI/CardHeader";
|
||||
import BearerTypeFields from "./BearerTypeFields";
|
||||
|
||||
const BearerTypeCard = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title="Bearer Type" />
|
||||
<BearerTypeFields />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default BearerTypeCard;
|
||||
35
src/components/SettingForms/BearerType/BearerTypeFields.tsx
Normal file
35
src/components/SettingForms/BearerType/BearerTypeFields.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Field, useFormikContext } from "formik";
|
||||
|
||||
import FormToggle from "../components/FormToggle";
|
||||
|
||||
export const ValuesComponent = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const BearerTypeFields = () => {
|
||||
const { values } = useFormikContext();
|
||||
console.log(values);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<label htmlFor="format">Format</label>
|
||||
<Field
|
||||
as="select"
|
||||
name="format"
|
||||
id="format"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="JSON">JSON</option>
|
||||
<option value="BOF2">BOF2</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<FormToggle name="enabled" label="Enabled" />
|
||||
<FormToggle name="verbose" label="Verbose" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BearerTypeFields;
|
||||
14
src/components/SettingForms/Channel1-JSON/ChannelCard.tsx
Normal file
14
src/components/SettingForms/Channel1-JSON/ChannelCard.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Card from "../../UI/Card";
|
||||
import CardHeader from "../../UI/CardHeader";
|
||||
import ChannelFields from "./ChannelFields";
|
||||
|
||||
const ChannelCard = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title="Channel 1 (JSON)" />
|
||||
<ChannelFields />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelCard;
|
||||
64
src/components/SettingForms/Channel1-JSON/ChannelFields.tsx
Normal file
64
src/components/SettingForms/Channel1-JSON/ChannelFields.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import FormGroup from "../components/FormGroup";
|
||||
|
||||
const ChannelFields = () => {
|
||||
useFormikContext();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<FormGroup>
|
||||
<label htmlFor="backoffice" className="m-0">
|
||||
Back Office URL
|
||||
</label>
|
||||
<Field
|
||||
name={"backOfficeURL"}
|
||||
type="text"
|
||||
id="backoffice"
|
||||
placeholder="https://www.backoffice.com"
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="username">Username</label>
|
||||
<Field
|
||||
name={"username"}
|
||||
type="text"
|
||||
id="username"
|
||||
placeholder="Back office username"
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="password">Password</label>
|
||||
<Field
|
||||
name={"password"}
|
||||
type="password"
|
||||
id="password"
|
||||
placeholder="Back office password"
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="connectTimeoutSeconds">Connect Timeout Seconds</label>
|
||||
<Field
|
||||
name={"connectTimeoutSeconds"}
|
||||
type="number"
|
||||
id="connectTimeoutSeconds"
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="readTimeoutSeconds">Read Timeout Seconds</label>
|
||||
<Field
|
||||
name={"readTimeoutSeconds"}
|
||||
type="number"
|
||||
id="readTimeoutSeconds"
|
||||
placeholder="https://example.com"
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelFields;
|
||||
12
src/components/SettingForms/NPED/NPEDCard.tsx
Normal file
12
src/components/SettingForms/NPED/NPEDCard.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import Card from "../../UI/Card";
|
||||
import CardHeader from "../../UI/CardHeader";
|
||||
|
||||
const NPEDCard = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title={"NPED Config and Hotlist"} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default NPEDCard;
|
||||
0
src/components/SettingForms/NPED/NPEDFields.tsx
Normal file
0
src/components/SettingForms/NPED/NPEDFields.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Card from "../../UI/Card";
|
||||
import CardHeader from "../../UI/CardHeader";
|
||||
import OverviewTextFields from "./OverviewTextFields";
|
||||
|
||||
const OverviewTextCard = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title={"Overview Text"} />
|
||||
<OverviewTextFields />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewTextCard;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import FormGroup from "../components/FormGroup";
|
||||
import FormToggle from "../components/FormToggle";
|
||||
|
||||
const OverviewTextFields = () => {
|
||||
useFormikContext();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<FormGroup>
|
||||
<label htmlFor="overviewQuality">Include VRM</label>
|
||||
<FormToggle name="includeVRM" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="includeMotion">Include Motion</label>
|
||||
<FormToggle name="includeMotion" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label htmlFor="includeCameraName">Include Camera Name</label>
|
||||
<FormToggle name="includeCameraName" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="includeTimestamp">Include Timestamp</label>
|
||||
<FormToggle name="includeTimestamp" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="">Timestamp Format</label>
|
||||
<Field
|
||||
as="select"
|
||||
name="timestampFormat"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value={"UTC"}>UTC</option>
|
||||
<option value={"LOCAL"}>Local</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="overlayPosition">Overlay Position</label>
|
||||
<Field
|
||||
as="select"
|
||||
name="overlayPosition"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value={"Top"}>Top</option>
|
||||
<option value={"Bottom"}>Bottom</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewTextFields;
|
||||
84
src/components/SettingForms/SettingForms/SettingForms.tsx
Normal file
84
src/components/SettingForms/SettingForms/SettingForms.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Formik, Form } from "formik";
|
||||
import BearerTypeCard from "../BearerType/BearerTypeCard";
|
||||
import ChannelCard from "../Channel1-JSON/ChannelCard";
|
||||
import type { InitialValuesForm } from "../../../types/types";
|
||||
import { useState } from "react";
|
||||
import AdvancedToggle from "../../UI/AdvancedToggle";
|
||||
import OverviewTextCard from "../OverviewText/OverviewTextCard";
|
||||
import SightingDataCard from "../SightingData/SightingDataCard";
|
||||
|
||||
const SettingForms = () => {
|
||||
const [advancedToggle, setAdvancedToggle] = useState(false);
|
||||
|
||||
const initialValues = {
|
||||
format: "JSON",
|
||||
enabled: false,
|
||||
verbose: false,
|
||||
backOfficeURL: "",
|
||||
username: "",
|
||||
password: "",
|
||||
connectTimeoutSeconds: 0,
|
||||
readTimeoutSeconds: 0,
|
||||
overviewQuality: "high",
|
||||
overviewImageScaleFactor: "full",
|
||||
overviewType: "Plate Overview",
|
||||
invertMotion: false,
|
||||
maxPlateValueLength: 0,
|
||||
vrmToTransit: "plain VRM ASCII (default)",
|
||||
staticReadAction: "Use Lane Direction",
|
||||
noRegionAction: "send",
|
||||
countryCodeType: "IBAN 2 Character code (default)",
|
||||
filterMinConfidence: 0,
|
||||
filterMaxConfidence: 100,
|
||||
overviewQualityOverride: 0,
|
||||
sightingDataEnabled: false,
|
||||
sighthingDataVerbose: false,
|
||||
includeVRM: false,
|
||||
includeMotion: false,
|
||||
includeTimestamp: false,
|
||||
timestampFormat: "UTC",
|
||||
includeCameraName: false,
|
||||
customFieldA: "",
|
||||
customFieldB: "",
|
||||
customFieldC: "",
|
||||
customFieldD: "",
|
||||
overlayPosition: "Top",
|
||||
};
|
||||
|
||||
const handleSubmit = (values: InitialValuesForm) => {
|
||||
alert(JSON.stringify(values));
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
||||
<Form className="flex flex-col space-y-3">
|
||||
<div className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-2 px-2 sm:px-4 lg:px-0 w-full">
|
||||
<BearerTypeCard />
|
||||
<ChannelCard />
|
||||
</div>
|
||||
<AdvancedToggle
|
||||
advancedToggle={advancedToggle}
|
||||
onAdvancedChange={setAdvancedToggle}
|
||||
/>
|
||||
{advancedToggle && (
|
||||
<>
|
||||
<div className="md:col-span-2">
|
||||
<SightingDataCard />
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<OverviewTextCard />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-1/4 text-white bg-blue-700 hover:bg-blue-800 font-small rounded-lg text-sm px-2 py-2.5"
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingForms;
|
||||
@@ -0,0 +1,14 @@
|
||||
import Card from "../../UI/Card";
|
||||
import CardHeader from "../../UI/CardHeader";
|
||||
import SightingDataFields from "./SightingDataFields";
|
||||
|
||||
const SightingDataCard = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title={"Sighting Data"} />
|
||||
<SightingDataFields />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SightingDataCard;
|
||||
123
src/components/SettingForms/SightingData/SightingDataFields.tsx
Normal file
123
src/components/SettingForms/SightingData/SightingDataFields.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Field, useFormikContext } from "formik";
|
||||
import FormGroup from "../components/FormGroup";
|
||||
import FormToggle from "../components/FormToggle";
|
||||
|
||||
const SightingDataFields = () => {
|
||||
useFormikContext();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<FormGroup>
|
||||
<label htmlFor="overviewQuality">Overview Quality</label>
|
||||
<Field
|
||||
name="overviewQuality"
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="HIGH">High</option>
|
||||
<option value="MEDIUM">Medium</option>
|
||||
<option value="LOW">Low</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="overviewImageScaleFactor">
|
||||
Overview Image Scale Factor
|
||||
</label>
|
||||
<Field
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="HIGH">Full</option>
|
||||
<option value="MEDIUM">3/4</option>
|
||||
<option value="LOW">2/4</option>
|
||||
<option value="LOW">1/4</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="overviewType">Overview Type</label>
|
||||
<Field
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="PlainOverview">Plain Overview</option>
|
||||
<option value="IncludePlatePatches">Include Plate Patches</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="invertMotion">Invert Motion</label>
|
||||
<FormToggle name="invertMotion" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="maxPlateValueLength">Max Plate Value Length</label>
|
||||
<Field
|
||||
name="maxPlateValueLength"
|
||||
type={"number"}
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="vrmToTransit">VRM To Transit</label>
|
||||
<Field
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="PlainOverview">plain VRM ASCII (default)</option>
|
||||
<option value="IncludePlatePatches">plain VRM ASCII (default)</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="staticReadAction">Static Read Action</label>
|
||||
<Field
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="UseLaneDirection">Use Lane Direction</option>
|
||||
<option value="IncludePlatePatches">plain VRM ASCII (default)</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="noRegionAction">No Region Action</label>
|
||||
<Field
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="UseLaneDirection">Send</option>
|
||||
<option value="IncludePlatePatches">plain VRM ASCII (default)</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="countryCodeType">Country Code Type</label>
|
||||
<Field
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]"
|
||||
>
|
||||
<option value="IBAN 2 Character code (default)">
|
||||
IBAN 2 Character code (default)
|
||||
</option>
|
||||
<option value="IncludePlatePatches">plain VRM ASCII (default)</option>
|
||||
</Field>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label htmlFor="overviewQualityOverride">
|
||||
Overview Quality Override
|
||||
</label>
|
||||
<Field
|
||||
name="maxPlateValueLength"
|
||||
type={"number"}
|
||||
className="p-1.5 border border-gray-400 rounded-lg"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="sightingDataEnabled">Sighting Data Enabled</label>
|
||||
<FormToggle name="sightingDataEnabled" />
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="sighthingDataVerbose">Sighting Data Verbose</label>
|
||||
<FormToggle name="sighthingDataVerbose" />
|
||||
</FormGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SightingDataFields;
|
||||
15
src/components/SettingForms/components/FormGroup.tsx
Normal file
15
src/components/SettingForms/components/FormGroup.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
type FormGroupProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const FormGroup = ({ children }: FormGroupProps) => {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row items-center justify-between">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormGroup;
|
||||
17
src/components/SettingForms/components/FormToggle.tsx
Normal file
17
src/components/SettingForms/components/FormToggle.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Field } from "formik";
|
||||
|
||||
const FormToggle = ({ name, label }: { name: string; label?: string }) => {
|
||||
return (
|
||||
<label className="flex items-center gap-3 cursor-pointer select-none w-50">
|
||||
<span className="text-sm">{label}</span>
|
||||
<Field id={name} type="checkbox" name={name} className="sr-only peer" />
|
||||
<div
|
||||
className="relative w-10 h-5 rounded-full bg-gray-300 transition peer-checked:bg-blue-500 after:content-['']
|
||||
after:absolute after:top-0.5 after:left-0.5 after:w-4 after:h-4 after:rounded-full after:bg-white after:shadow after:transition
|
||||
after:duration-300 peer-checked:after:translate-x-5"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormToggle;
|
||||
32
src/components/UI/AdvancedToggle.tsx
Normal file
32
src/components/UI/AdvancedToggle.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
type AdvancedToggleProps = {
|
||||
advancedToggle: boolean;
|
||||
onAdvancedChange: (advancedToggle: boolean) => void;
|
||||
};
|
||||
|
||||
const AdvancedToggle = ({
|
||||
advancedToggle,
|
||||
onAdvancedChange,
|
||||
}: AdvancedToggleProps) => {
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<label className="flex flex-row space-x-2">
|
||||
<span>Advanced Settings</span>
|
||||
<input
|
||||
name="advancedSettings"
|
||||
type="checkbox"
|
||||
id="advancedSettings"
|
||||
className="sr-only peer"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="relative w-10 h-5 rounded-full bg-gray-300 transition peer-checked:bg-blue-500 after:content-['']
|
||||
after:absolute after:top-0.5 after:left-0.5 after:w-4 after:h-4 after:rounded-full after:bg-white after:shadow after:transition
|
||||
after:duration-300 peer-checked:after:translate-x-5"
|
||||
onClick={() => onAdvancedChange(!advancedToggle)}
|
||||
></div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedToggle;
|
||||
@@ -5,7 +5,7 @@ import clsx from "clsx";
|
||||
|
||||
type CameraOverviewHeaderProps = {
|
||||
title: string;
|
||||
icon: IconProp;
|
||||
icon?: IconProp;
|
||||
};
|
||||
|
||||
const CardHeader = ({ title, icon }: CameraOverviewHeaderProps) => {
|
||||
@@ -15,7 +15,7 @@ const CardHeader = ({ title, icon }: CameraOverviewHeaderProps) => {
|
||||
"w-full border-b border-gray-600 flex flex-row items-center space-x-2 md:mb-6"
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} className="size-4" />
|
||||
{icon && <FontAwesomeIcon icon={icon} className="size-4" />}
|
||||
<h2 className="text-xl">{title}</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,25 +4,48 @@ import { useNavigate } from "react-router";
|
||||
|
||||
type NavigationArrowProps = {
|
||||
side: string;
|
||||
settingsPage?: boolean;
|
||||
};
|
||||
|
||||
const NavigationArrow = ({ side }: NavigationArrowProps) => {
|
||||
const NavigationArrow = ({ side, settingsPage }: NavigationArrowProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigationDest = (side: string) => {
|
||||
if (side === "CameraFront") {
|
||||
navigate("/front-camera-settings");
|
||||
} else if (side === "Rear") {
|
||||
if (settingsPage) {
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (side === "TargetDetectionFront") {
|
||||
navigate("/front-camera-settings");
|
||||
} else if (side === "TargetDetectionRear") {
|
||||
navigate("/Rear-Camera-settings");
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
};
|
||||
|
||||
if (settingsPage) {
|
||||
return (
|
||||
<>
|
||||
{side === "CameraFront" || side === "Rear" ? (
|
||||
{side === "TargetDetectionFront" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowLeft}
|
||||
className="absolute top-[50%] left-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{side === "TargetDetectionFront" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowLeft}
|
||||
className="absolute top-[50%] left-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
|
||||
@@ -5,8 +5,8 @@ const apiUrl = import.meta.env.VITE_BASEURL;
|
||||
|
||||
async function fetchSnapshot(cameraSide: string) {
|
||||
const response = await fetch(
|
||||
`http://100.116.253.81/Colour-preview`
|
||||
//`${apiUrl}/${cameraSide}-preview`
|
||||
// `http://100.116.253.81/Colour-preview`
|
||||
`${apiUrl}/${cameraSide}-preview`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Cannot reach endpoint");
|
||||
|
||||
@@ -15,7 +15,11 @@ const FrontCamera = () => {
|
||||
className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-4 px-2 sm:px-4 lg:px-0 w-full"
|
||||
{...handlers}
|
||||
>
|
||||
<OverviewVideoContainer title={"Front Camera"} side="Front" />
|
||||
<OverviewVideoContainer
|
||||
title={"Front Camera"}
|
||||
side="TargetDetectionFront"
|
||||
settingsPage={true}
|
||||
/>
|
||||
<CameraSettings title="Front Camera Settings" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,11 @@ const RearCamera = () => {
|
||||
{...handlers}
|
||||
>
|
||||
<CameraSettings title="Rear Camera Settings" />
|
||||
<OverviewVideoContainer title={"Rear Camera"} side={"Rear"} />
|
||||
<OverviewVideoContainer
|
||||
title={"Rear Camera"}
|
||||
side={"TargetDetectionRear"}
|
||||
settingsPage={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import Output from "../components/Output/Output";
|
||||
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
||||
import "react-tabs/style/react-tabs.css";
|
||||
import NPEDCard from "../components/SettingForms/NPED/NPEDCard";
|
||||
import SettingForms from "../components/SettingForms/SettingForms/SettingForms";
|
||||
|
||||
const SystemSettings = () => {
|
||||
return (
|
||||
<div className="m-4">
|
||||
<Tabs selectedTabClassName="bg-gray-300 text-gray-900 font-semibold border-none">
|
||||
<TabList>
|
||||
<Tab>Output</Tab>
|
||||
<Tab>Integrations</Tab>
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<SettingForms />
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<div className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-2 px-2 sm:px-4 lg:px-0 w-full">
|
||||
<Output />
|
||||
<Output />
|
||||
<NPEDCard />
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,3 +40,20 @@ export type CameraSettingValues = {
|
||||
export type CameraSettingErrorValues = Partial<
|
||||
Record<keyof CameraSettingValues, string>
|
||||
>;
|
||||
|
||||
export type BearerTypeFieldType = {
|
||||
format: string;
|
||||
enabled: boolean;
|
||||
verbose: boolean;
|
||||
};
|
||||
|
||||
export type InitialValuesForm = {
|
||||
format: string;
|
||||
enabled: boolean;
|
||||
verbose: boolean;
|
||||
backOfficeURL: string;
|
||||
username: string;
|
||||
password: string;
|
||||
connectTimeoutSeconds: number;
|
||||
readTimeoutSeconds: number;
|
||||
};
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -1029,7 +1029,7 @@ chownr@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4"
|
||||
integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
|
||||
|
||||
clsx@^2.1.1:
|
||||
clsx@^2.0.0, clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
@@ -1761,7 +1761,7 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.5.0, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@@ -1830,6 +1830,14 @@ react-swipeable@^7.0.2:
|
||||
resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.2.tgz#ef8858096a47144ba7060675af1cd672e00ecc12"
|
||||
integrity sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w==
|
||||
|
||||
react-tabs@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-6.1.0.tgz#a1fc9d9b8db4c6e7bb327a1b6783bc51a1c457a1"
|
||||
integrity sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==
|
||||
dependencies:
|
||||
clsx "^2.0.0"
|
||||
prop-types "^15.5.0"
|
||||
|
||||
react@^19.1.1:
|
||||
version "19.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af"
|
||||
|
||||
Reference in New Issue
Block a user