https://dev.kronoz.win/r/marzneshin.git
1import {
2 Button,
3 FormControl,
4 FormField,
5 FormItem,
6 FormLabel,
7 Input,
8 Popover,
9 PopoverContent,
10 PopoverTrigger,
11 Select,
12 SelectContent,
13 SelectItem,
14 SelectTrigger,
15 SelectValue,
16} from "@marzneshin/common/components";
17import type { FieldError, FieldErrors } from "react-hook-form";
18import { useFormContext } from "react-hook-form";
19import { useTranslation } from "react-i18next";
20import { ListPlus, MailWarning, TrashIcon } from "lucide-react";
21import type { FC } from "react";
22import { useState } from "react";
23import { noiseTypes } from "../profiles";
24
25interface NoiseForm {
26 noise: Array<{
27 type: string;
28 packet: string;
29 delay: string;
30 } | null>;
31}
32
33const NoiseErrorPopover: FC<{
34 errors?: { packet?: FieldError; type?: FieldError; delay?: FieldError };
35}> = ({ errors }) => {
36 const { t } = useTranslation();
37
38 if (!errors || (!errors.packet && !errors.type && !errors.delay))
39 return null;
40
41 return (
42 <Popover>
43 <PopoverTrigger>
44 <MailWarning className="p-0 size-5 text-destructive bg-primary-background" />
45 </PopoverTrigger>
46 <PopoverContent className="bg-destructive-accent text-sm">
47 <ul className="my-6 ml-3 list-disc [&>li]:mt-1 mt-0">
48 {errors.type?.message && (
49 <li>
50 <b>Type:</b> {t(errors.type.message as string)}
51 </li>
52 )}
53 {errors.packet?.message && (
54 <li>
55 <b>Packet:</b> {t(errors.packet.message as string)}
56 </li>
57 )}
58 {errors.delay?.message && (
59 <li>
60 <b>Interval:</b> {t(errors.delay.message as string)}
61 </li>
62 )}
63 </ul>
64 </PopoverContent>
65 </Popover>
66 );
67};
68
69export const NoiseField = () => {
70 const { t } = useTranslation();
71 const form = useFormContext();
72 const [forms, setForms] = useState<NoiseForm["noise"][number][]>(
73 form.getValues("noise") || [],
74 );
75
76 const errors = form.formState.errors as FieldErrors<NoiseForm>;
77
78 const addForm = () => {
79 if (forms.length < 5) {
80 const newForms = [
81 ...forms,
82 { type: "rand", packet: "", delay: "" },
83 ];
84 setForms(newForms);
85 form.setValue("noise", newForms, { shouldValidate: true });
86 }
87 };
88
89 const removeForm = (index: number) => {
90 const updatedForms = forms.filter((_, i) => i !== index);
91 setForms(updatedForms);
92 form.setValue("noise", updatedForms, { shouldValidate: true });
93 };
94
95 const enableForm = (index: number) => {
96 const updatedForms = [...forms];
97 updatedForms[index] = { type: "", packet: "", delay: "" };
98 setForms(updatedForms);
99 form.setValue("noise", updatedForms, { shouldValidate: true });
100 };
101
102 return (
103 <div>
104 {forms.length > 0 ? (
105 forms.map((formData, index) => (
106 <FormItem key={index} className="my-2 w-full">
107 {index === 0 && (
108 <FormLabel className="flex flex-row justify-between items-center">
109 {t("Noise")}
110 <div className="flex flex-row items-center gap-2">
111 {errors.noise?.[index] && (
112 <NoiseErrorPopover
113 // errors={errors.noise[index]}
114 />
115 )}
116 {forms.length < 5 && (
117 <Button
118 className="p-0 size-5 bg-primary-background"
119 variant="secondary"
120 onClick={(e) => {
121 e.preventDefault();
122 addForm();
123 }}
124 >
125 <ListPlus />
126 </Button>
127 )}
128 </div>
129 </FormLabel>
130 )}
131 <FormControl>
132 <div className="flex flex-row w-full items-center gap-2">
133 {formData ? (
134 <>
135 <FormField
136 name={`noise.${index}.type`}
137 render={({ field }) => (
138 <Select
139 onValueChange={
140 field.onChange
141 }
142 defaultValue={field.value}
143 >
144 <FormControl>
145 <SelectTrigger className="w-2/5">
146 <SelectValue placeholder="Noise Type" />
147 </SelectTrigger>
148 </FormControl>
149 <SelectContent>
150 {noiseTypes.map(
151 (option) => (
152 <SelectItem
153 value={
154 option
155 }
156 >
157 {option}
158 </SelectItem>
159 ),
160 )}
161 </SelectContent>
162 </Select>
163 )}
164 />
165 <div className="w-full hstack">
166 <FormField
167 name={`noise.${index}.packet`}
168 render={({ field }) => (
169 <Input
170 className="border rounded-none rounded-s-lg p-2 w-full"
171 placeholder="Packet"
172 {...field}
173 />
174 )}
175 />
176 <FormField
177 name={`noise.${index}.delay`}
178 render={({ field }) => (
179 <Input
180 placeholder="Delay"
181 className="border w-full rounded-none rounded-e-lg p-2"
182 {...field}
183 />
184 )}
185 />
186 </div>
187 <Button
188 variant="destructive"
189 className="p-0 size-5 bg-primary-background"
190 onClick={(e) => {
191 e.preventDefault();
192 removeForm(index);
193 }}
194 >
195 <TrashIcon className="text-red-400" />
196 </Button>
197 </>
198 ) : (
199 <Button
200 className="border w-full"
201 variant="ghost"
202 onClick={(e) => {
203 e.preventDefault();
204 enableForm(index);
205 }}
206 >
207 {t("Enable Form")}
208 </Button>
209 )}
210 </div>
211 </FormControl>
212 </FormItem>
213 ))
214 ) : (
215 <FormItem className="my-2 w-full">
216 <FormLabel className="flex flex-row justify-between items-center">
217 {t("Noise")}
218 </FormLabel>
219
220 <Button
221 className="border w-full"
222 variant="ghost"
223 onClick={(e) => {
224 e.preventDefault();
225 addForm();
226 }}
227 >
228 {t("disabled")}
229 </Button>
230 </FormItem>
231 )}
232 </div>
233 );
234};