mirror of
				https://github.com/community-scripts/ProxmoxVE.git
				synced 2025-11-04 02:12:49 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			150 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { Button } from "@/components/ui/button";
 | 
						|
import { Input } from "@/components/ui/input";
 | 
						|
import {
 | 
						|
    Select,
 | 
						|
    SelectContent,
 | 
						|
    SelectItem,
 | 
						|
    SelectTrigger,
 | 
						|
    SelectValue,
 | 
						|
} from "@/components/ui/select";
 | 
						|
import { AlertColors } from "@/config/siteConfig";
 | 
						|
import { cn } from "@/lib/utils";
 | 
						|
import { PlusCircle, Trash2 } from "lucide-react";
 | 
						|
import { z } from "zod";
 | 
						|
import { ScriptSchema, type Script } from "../_schemas/schemas";
 | 
						|
import { memo, useCallback, useRef } from "react";
 | 
						|
 | 
						|
type NoteProps = {
 | 
						|
    script: Script;
 | 
						|
    setScript: (script: Script) => void;
 | 
						|
    setIsValid: (isValid: boolean) => void;
 | 
						|
    setZodErrors: (zodErrors: z.ZodError | null) => void;
 | 
						|
};
 | 
						|
 | 
						|
function Note({
 | 
						|
    script,
 | 
						|
    setScript,
 | 
						|
    setIsValid,
 | 
						|
    setZodErrors,
 | 
						|
}: NoteProps) {
 | 
						|
    const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
 | 
						|
 | 
						|
    const addNote = useCallback(() => {
 | 
						|
        setScript({
 | 
						|
            ...script,
 | 
						|
            notes: [...script.notes, { text: "", type: "" }],
 | 
						|
        });
 | 
						|
    }, [script, setScript]);
 | 
						|
 | 
						|
    const updateNote = useCallback((
 | 
						|
        index: number,
 | 
						|
        key: keyof Script["notes"][number],
 | 
						|
        value: string,
 | 
						|
    ) => {
 | 
						|
        const updated: Script = {
 | 
						|
            ...script,
 | 
						|
            notes: script.notes.map((note, i) =>
 | 
						|
                i === index ? { ...note, [key]: value } : note,
 | 
						|
            ),
 | 
						|
        };
 | 
						|
        const result = ScriptSchema.safeParse(updated);
 | 
						|
        setIsValid(result.success);
 | 
						|
        setZodErrors(result.success ? null : result.error);
 | 
						|
        setScript(updated);
 | 
						|
        // Restore focus after state update
 | 
						|
        if (key === "text") {
 | 
						|
            setTimeout(() => {
 | 
						|
                inputRefs.current[index]?.focus();
 | 
						|
            }, 0);
 | 
						|
        }
 | 
						|
    }, [script, setScript, setIsValid, setZodErrors]);
 | 
						|
 | 
						|
    const removeNote = useCallback((index: number) => {
 | 
						|
        setScript({
 | 
						|
            ...script,
 | 
						|
            notes: script.notes.filter((_, i) => i !== index),
 | 
						|
        });
 | 
						|
    }, [script, setScript]);
 | 
						|
 | 
						|
    return (
 | 
						|
        <>
 | 
						|
            <h3 className="text-xl font-semibold">Notes</h3>
 | 
						|
            {script.notes.map((note, index) => (
 | 
						|
                <NoteItem key={index} note={note} index={index} updateNote={updateNote} removeNote={removeNote} />
 | 
						|
            ))}
 | 
						|
            <Button type="button" size="sm" onClick={addNote}>
 | 
						|
                <PlusCircle className="mr-2 h-4 w-4" /> Add Note
 | 
						|
            </Button>
 | 
						|
        </>
 | 
						|
    );
 | 
						|
}
 | 
						|
 | 
						|
const NoteItem = memo(
 | 
						|
    ({
 | 
						|
        note,
 | 
						|
        index,
 | 
						|
        updateNote,
 | 
						|
        removeNote,
 | 
						|
    }: {
 | 
						|
        note: Script["notes"][number];
 | 
						|
        index: number;
 | 
						|
        updateNote: (index: number, key: keyof Script["notes"][number], value: string) => void;
 | 
						|
        removeNote: (index: number) => void;
 | 
						|
    }) => {
 | 
						|
        const inputRef = useRef<HTMLInputElement | null>(null);
 | 
						|
 | 
						|
        const handleTextChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
 | 
						|
            updateNote(index, "text", e.target.value);
 | 
						|
            setTimeout(() => {
 | 
						|
                inputRef.current?.focus();
 | 
						|
            }, 0);
 | 
						|
        }, [index, updateNote]);
 | 
						|
 | 
						|
        return (
 | 
						|
            <div className="space-y-2 border p-4 rounded">
 | 
						|
                <Input
 | 
						|
                    placeholder="Note Text"
 | 
						|
                    value={note.text}
 | 
						|
                    onChange={handleTextChange}
 | 
						|
                    ref={inputRef}
 | 
						|
                />
 | 
						|
                <Select
 | 
						|
                    value={note.type}
 | 
						|
                    onValueChange={(value) => updateNote(index, "type", value)}
 | 
						|
                >
 | 
						|
                    <SelectTrigger className="flex-1">
 | 
						|
                        <SelectValue placeholder="Type" />
 | 
						|
                    </SelectTrigger>
 | 
						|
                    <SelectContent>
 | 
						|
                        {Object.keys(AlertColors).map((type) => (
 | 
						|
                            <SelectItem key={type} value={type}>
 | 
						|
                                <span className="flex items-center gap-2">
 | 
						|
                                    {type.charAt(0).toUpperCase() + type.slice(1)}{" "}
 | 
						|
                                    <div
 | 
						|
                                        className={cn(
 | 
						|
                                            "size-4 rounded-full border",
 | 
						|
                                            AlertColors[type as keyof typeof AlertColors],
 | 
						|
                                        )}
 | 
						|
                                    />
 | 
						|
                                </span>
 | 
						|
                            </SelectItem>
 | 
						|
                        ))}
 | 
						|
                    </SelectContent>
 | 
						|
                </Select>
 | 
						|
                <Button
 | 
						|
                    size="sm"
 | 
						|
                    variant="destructive"
 | 
						|
                    type="button"
 | 
						|
                    onClick={() => removeNote(index)}
 | 
						|
                >
 | 
						|
                    <Trash2 className="mr-2 h-4 w-4" /> Remove Note
 | 
						|
                </Button>
 | 
						|
            </div>
 | 
						|
        );
 | 
						|
    }
 | 
						|
);
 | 
						|
 | 
						|
NoteItem.displayName = 'NoteItem';
 | 
						|
 | 
						|
 | 
						|
export default memo(Note); |