Skip to content

Commit 6fa1981

Browse files
authored
JSON editor note fix (#3260)
1 parent 6072e72 commit 6fa1981

File tree

1 file changed

+126
-106
lines changed
  • frontend/src/app/json-editor/_components

1 file changed

+126
-106
lines changed
Lines changed: 126 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Button } from "@/components/ui/button";
22
import { Input } from "@/components/ui/input";
33
import {
4-
Select,
5-
SelectContent,
6-
SelectItem,
7-
SelectTrigger,
8-
SelectValue,
4+
Select,
5+
SelectContent,
6+
SelectItem,
7+
SelectTrigger,
8+
SelectValue,
99
} from "@/components/ui/select";
1010
import { AlertColors } from "@/config/siteConfig";
1111
import { cn } from "@/lib/utils";
@@ -15,116 +15,136 @@ import { ScriptSchema, type Script } from "../_schemas/schemas";
1515
import { memo, useCallback, useRef } from "react";
1616

1717
type NoteProps = {
18-
script: Script;
19-
setScript: (script: Script) => void;
20-
setIsValid: (isValid: boolean) => void;
21-
setZodErrors: (zodErrors: z.ZodError | null) => void;
18+
script: Script;
19+
setScript: (script: Script) => void;
20+
setIsValid: (isValid: boolean) => void;
21+
setZodErrors: (zodErrors: z.ZodError | null) => void;
2222
};
2323

2424
function Note({
25-
script,
26-
setScript,
27-
setIsValid,
28-
setZodErrors,
25+
script,
26+
setScript,
27+
setIsValid,
28+
setZodErrors,
2929
}: NoteProps) {
30-
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
30+
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
3131

32-
const addNote = useCallback(() => {
33-
setScript({
34-
...script,
35-
notes: [...script.notes, { text: "", type: "" }],
36-
});
37-
}, [script, setScript]);
32+
const addNote = useCallback(() => {
33+
setScript({
34+
...script,
35+
notes: [...script.notes, { text: "", type: "" }],
36+
});
37+
}, [script, setScript]);
3838

39-
const updateNote = useCallback((
40-
index: number,
41-
key: keyof Script["notes"][number],
42-
value: string,
43-
) => {
44-
const updated: Script = {
45-
...script,
46-
notes: script.notes.map((note, i) =>
47-
i === index ? { ...note, [key]: value } : note,
48-
),
49-
};
50-
const result = ScriptSchema.safeParse(updated);
51-
setIsValid(result.success);
52-
setZodErrors(result.success ? null : result.error);
53-
setScript(updated);
54-
// Restore focus after state update
55-
if (key === "text") {
56-
setTimeout(() => {
57-
inputRefs.current[index]?.focus();
58-
}, 0);
59-
}
60-
}, [script, setScript, setIsValid, setZodErrors]);
39+
const updateNote = useCallback((
40+
index: number,
41+
key: keyof Script["notes"][number],
42+
value: string,
43+
) => {
44+
const updated: Script = {
45+
...script,
46+
notes: script.notes.map((note, i) =>
47+
i === index ? { ...note, [key]: value } : note,
48+
),
49+
};
50+
const result = ScriptSchema.safeParse(updated);
51+
setIsValid(result.success);
52+
setZodErrors(result.success ? null : result.error);
53+
setScript(updated);
54+
// Restore focus after state update
55+
if (key === "text") {
56+
setTimeout(() => {
57+
inputRefs.current[index]?.focus();
58+
}, 0);
59+
}
60+
}, [script, setScript, setIsValid, setZodErrors]);
6161

62-
const removeNote = useCallback((index: number) => {
63-
setScript({
64-
...script,
65-
notes: script.notes.filter((_, i) => i !== index),
66-
});
67-
}, [script, setScript]);
62+
const removeNote = useCallback((index: number) => {
63+
setScript({
64+
...script,
65+
notes: script.notes.filter((_, i) => i !== index),
66+
});
67+
}, [script, setScript]);
6868

69-
const NoteItem = memo(
70-
({ note, index }: { note: Script["notes"][number]; index: number }) => (
71-
<div className="space-y-2 border p-4 rounded">
72-
<Input
73-
placeholder="Note Text"
74-
value={note.text}
75-
onChange={(e) => updateNote(index, "text", e.target.value)}
76-
ref={(el) => {
77-
inputRefs.current[index] = el;
78-
}}
79-
/>
80-
<Select
81-
value={note.type}
82-
onValueChange={(value) => updateNote(index, "type", value)}
83-
>
84-
<SelectTrigger className="flex-1">
85-
<SelectValue placeholder="Type" />
86-
</SelectTrigger>
87-
<SelectContent>
88-
{Object.keys(AlertColors).map((type) => (
89-
<SelectItem key={type} value={type}>
90-
<span className="flex items-center gap-2">
91-
{type.charAt(0).toUpperCase() + type.slice(1)}{" "}
92-
<div
93-
className={cn(
94-
"size-4 rounded-full border",
95-
AlertColors[type as keyof typeof AlertColors],
96-
)}
97-
/>
98-
</span>
99-
</SelectItem>
69+
return (
70+
<>
71+
<h3 className="text-xl font-semibold">Notes</h3>
72+
{script.notes.map((note, index) => (
73+
<NoteItem key={index} note={note} index={index} updateNote={updateNote} removeNote={removeNote} />
10074
))}
101-
</SelectContent>
102-
</Select>
103-
<Button
104-
size="sm"
105-
variant="destructive"
106-
type="button"
107-
onClick={() => removeNote(index)}
108-
>
109-
<Trash2 className="mr-2 h-4 w-4" /> Remove Note
110-
</Button>
111-
</div>
112-
),
113-
);
75+
<Button type="button" size="sm" onClick={addNote}>
76+
<PlusCircle className="mr-2 h-4 w-4" /> Add Note
77+
</Button>
78+
</>
79+
);
80+
}
11481

115-
NoteItem.displayName = 'NoteItem';
82+
const NoteItem = memo(
83+
({
84+
note,
85+
index,
86+
updateNote,
87+
removeNote,
88+
}: {
89+
note: Script["notes"][number];
90+
index: number;
91+
updateNote: (index: number, key: keyof Script["notes"][number], value: string) => void;
92+
removeNote: (index: number) => void;
93+
}) => {
94+
const inputRef = useRef<HTMLInputElement | null>(null);
95+
96+
const handleTextChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
97+
updateNote(index, "text", e.target.value);
98+
setTimeout(() => {
99+
inputRef.current?.focus();
100+
}, 0);
101+
}, [index, updateNote]);
102+
103+
return (
104+
<div className="space-y-2 border p-4 rounded">
105+
<Input
106+
placeholder="Note Text"
107+
value={note.text}
108+
onChange={handleTextChange}
109+
ref={inputRef}
110+
/>
111+
<Select
112+
value={note.type}
113+
onValueChange={(value) => updateNote(index, "type", value)}
114+
>
115+
<SelectTrigger className="flex-1">
116+
<SelectValue placeholder="Type" />
117+
</SelectTrigger>
118+
<SelectContent>
119+
{Object.keys(AlertColors).map((type) => (
120+
<SelectItem key={type} value={type}>
121+
<span className="flex items-center gap-2">
122+
{type.charAt(0).toUpperCase() + type.slice(1)}{" "}
123+
<div
124+
className={cn(
125+
"size-4 rounded-full border",
126+
AlertColors[type as keyof typeof AlertColors],
127+
)}
128+
/>
129+
</span>
130+
</SelectItem>
131+
))}
132+
</SelectContent>
133+
</Select>
134+
<Button
135+
size="sm"
136+
variant="destructive"
137+
type="button"
138+
onClick={() => removeNote(index)}
139+
>
140+
<Trash2 className="mr-2 h-4 w-4" /> Remove Note
141+
</Button>
142+
</div>
143+
);
144+
}
145+
);
146+
147+
NoteItem.displayName = 'NoteItem';
116148

117-
return (
118-
<>
119-
<h3 className="text-xl font-semibold">Notes</h3>
120-
{script.notes.map((note, index) => (
121-
<NoteItem key={index} note={note} index={index} />
122-
))}
123-
<Button type="button" size="sm" onClick={addNote}>
124-
<PlusCircle className="mr-2 h-4 w-4" /> Add Note
125-
</Button>
126-
</>
127-
);
128-
}
129149

130-
export default memo(Note);
150+
export default memo(Note);

0 commit comments

Comments
 (0)