当我编辑它时,手风琴已经接近了

问题描述 投票:0回答:1

当我尝试编辑播放列表名称时,手风琴正在关闭,当我的鼠标在播放列表名称输入字段的输入字段上处于活动状态时,如果我按键盘上的空格键,则它是顶部播放列表往下走,最后一个就上来了。

这是为什么?

堆栈: NextJS + ShadCN UI + TailwindCSS


import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { PlusCircle, Trash2, Upload } from 'lucide-react';
import { getS3ObjectUrl, listS3Objects } from '@/lib/routes';
import { useEffect, useRef, useState } from 'react';

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import Image from 'next/image';
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Modal from "@/components/Modal";
import { useAuth } from "@/hooks/useAuth";
import { useRouter } from 'next/navigation';
import { v4 as uuidv4 } from 'uuid';

interface Playlist {
    end_time: string;
    images: { duration: number; url: string }[];
    start_time: string;
    videos: { duration: number; url: string }[];
    weekdays: string;
}

interface ScheduledPlaylist {
    [playlistName: string]: Playlist;
}

interface PlaylistData {
    id: number;
    data: ScheduledPlaylist;
}

interface PlaylistFormProps {
    onSave: (schedules: PlaylistData) => void;
    initialSchedules: PlaylistData;
    isEditing?: boolean;
    devices?: number;
}

interface MediaItem {
    name: string;
    url: string;
    presignedUrl: string;
    size: number;
}

interface S3Object {
    Key: string;
    Size: number;
}

const PlaylistForm: React.FC<PlaylistFormProps> = ({ onSave, initialSchedules, isEditing = false, devices = 0 }) => {
    const router = useRouter();
    const [schedules, setSchedules] = useState<PlaylistData>(initialSchedules);
    const [error, setError] = useState<string | null>(null);
    const [openItems, setOpenItems] = useState<string[]>([]);
    const [isMediaModalOpen, setIsMediaModalOpen] = useState(false);
    const [mediaList, setMediaList] = useState<MediaItem[]>([]);
    const [searchTerm, setSearchTerm] = useState('');
    const [currentPlaylist, setCurrentPlaylist] = useState('');
    const [currentMediaType, setCurrentMediaType] = useState<'images' | 'videos'>('images');
    const { authToken } = useAuth();
    const fileInputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        if (isEditing && initialSchedules.id) {
            fetchScheduledPlaylist(initialSchedules.id);
        } else {
            setSchedules(initialSchedules);
            setOpenItems(Object.keys(initialSchedules.data));
        }
    }, [initialSchedules, isEditing]);

    const fetchScheduledPlaylist = async (id: number) => {
       //  fetchScheduledPlaylist code here
    };

    const addSchedule = () => {
         // addSchedule code here
    };

    const updatePlaylistName = (oldName: string, newName: string) => {
        if (oldName === newName) return;
        setSchedules(prev => {
            const newData = { ...prev.data };
            const playlistData = newData[oldName];
            delete newData[oldName];
            newData[newName] = playlistData;
            return { ...prev, data: newData };
        });
        setOpenItems(prev => prev.map(item => item === oldName ? newName : item));
    };

    const updateSchedule = (playlistName: string, field: keyof Playlist, value: unknown) => {
         // update schedule code here
    };

    const updateWeekday = (playlistName: string, dayIndex: number) => {
       // updateWeekday code here
    };

    const addMedia = (playlistName: string, type: 'images' | 'videos', url: string, duration: number = 20) => {
         // addMedia code here
    };

    const updateMedia = (playlistName: string, type: 'images' | 'videos', index: number, field: 'url' | 'duration', value: string | number) => {
        const updatedMedia = schedules.data[playlistName][type].map((item, i) =>
            i === index ? { ...item, [field]: value } : item
        );
        updateSchedule(playlistName, type, updatedMedia);
    };

    const removeMedia = (playlistName: string, type: 'images' | 'videos', index: number) => {
        const updatedMedia = schedules.data[playlistName][type].filter((_, i) => i !== index);
        updateSchedule(playlistName, type, updatedMedia);
    };

    const deletePlaylist = (playlistName: string) => {
        if (!confirm(`Are you sure you want to delete the playlist "${playlistName}"?`)) return;
        const newSchedules = { ...schedules };
        delete newSchedules.data[playlistName];
        setSchedules(newSchedules);
    };

    const deleteSetup = async () => {
        if (!confirm("Are you sure you want to delete this entire setup?")) return;
        try {
            const response = await fetch(`/api/proxy/scheduled_playlists/${initialSchedules.id}`, {
                method: 'DELETE',
                headers: {
                    'Authorization': `Bearer ${authToken}`,
                },
            });
            if (response.ok) {
                router.push('/dashboard/setups');
            } else {
                setError("Failed to delete setup");
            }
        } catch (error) {
            console.error("Error deleting setup:", error);
            setError("Error deleting setup");
        }
    };

    const savePlaylist = async () => {
         // savePlaylist code here
    };

    const fetchMediaList = async () => {
       // fetchMediaList code here
    };

    const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
         // handleUpload code here
    };

    const renderMediaSection = (playlistName: string, type: 'images' | 'videos') => {
        return (
            // code here
    };

    return (
        <div className="mt-8 space-y-6">
            <h2 className="text-2xl font-bold">Playlists</h2>

            {error && (
                <Alert variant="destructive">
                    <AlertTitle>Error</AlertTitle>
                    <AlertDescription>{error}</AlertDescription>
                </Alert>
            )}

            <Accordion
                type="multiple"
                value={openItems}
                onValueChange={setOpenItems}
                className="w-full"
            >
                {Object.entries(schedules.data).map(([playlistName, playlist]) => (
                    <AccordionItem value={playlistName} key={uuidv4()}>
                        <AccordionTrigger>
                            {playlistName}
                        </AccordionTrigger>


                        <AccordionContent>
                            <div className="space-y-4">
                                <div>

                                    <Label>Playlist Name</Label>
                                    <Input
                                        value={playlistName}
                                        onChange={(e) => updatePlaylistName(playlistName, e.target.value)}

                                        className="mt-1 bg-white text-black"
                                    />
                                </div>

                                <Button
                                    variant="ghost"
                                    size="sm"
                                    onClick={(e) => {
                                        e.stopPropagation();
                                        deletePlaylist(playlistName);
                                    }}
                                    className="hover:bg-red-100 p-2 mr-2"
                                >
                                    <Trash2 size={16} className="text-red-500" />
                                </Button>
                            </div>
                            <div className="space-y-4">
                                <div className="flex space-x-4">
                                    <div className="w-1/2">
                                        <Label>Start Time</Label>
                                        <Input
                                            type="time"
                                            value={playlist.start_time}
                                            onChange={(e) => updateSchedule(playlistName, 'start_time', e.target.value)}
                                            className="mt-1 bg-white text-black"
                                        />
                                    </div>
                                    <div className="w-1/2">
                                        <Label>End Time</Label>
                                        <Input
                                            type="time"
                                            value={playlist.end_time}
                                            onChange={(e) => updateSchedule(playlistName, 'end_time', e.target.value)}
                                            className="mt-1 bg-white text-black"
                                        />
                                    </div>
                                </div>
                                <div>
                                    <Label className="mb-2 block">Weekdays</Label>
                                    <div className="flex space-x-4 mt-2">
                                        {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((day, index) => (
                                            <div key={day} className="flex items-center">
                                                <Checkbox
                                                    id={`${day}-${playlistName}`}
                                                    checked={playlist.weekdays[index] === '1'}
                                                    onCheckedChange={() => updateWeekday(playlistName, index)}
                                                />
                                                <Label htmlFor={`${day}-${playlistName}`} className="ml-1">{day}</Label>
                                            </div>
                                        ))}
                                    </div>
                                </div>
                                {renderMediaSection(playlistName, 'images')}
                                {renderMediaSection(playlistName, 'videos')}
                            </div>
                        </AccordionContent>
                    </AccordionItem>
                ))}
            </Accordion>

            <Button onClick={addSchedule} variant="outline" className="mt-4 dark:border-white border-black">
                <PlusCircle size={20} className="mr-2" /> Add Another Playlist
            </Button>

            <div className="flex space-x-12 items-center">
                <Button onClick={savePlaylist} className="min-w-fit py-4 text-lg mt-8 bg-green-600 hover:bg-green-800 text-white">
                    {isEditing ? "Update Setup" : "Save Setup"}
                </Button>
                {isEditing && (
                    <Button onClick={deleteSetup} className="min-w-fit py-4 text-lg mt-8 bg-red-600 hover:bg-red-800 text-white">
                        Delete Setup
                    </Button>
                )}
            </div>

            <Modal isOpen={isMediaModalOpen} onClose={() => setIsMediaModalOpen(false)}>
                <div className="p-4">
                    <h2 className="text-xl font-bold mb-4">Select Media</h2>
                    <div className="flex items-center mb-4">
                        <Input
                            type="text"
                            placeholder="Search media..."
                            value={searchTerm}
                            onChange={(e) => setSearchTerm(e.target.value)}
                            className="flex-grow mr-2"
                        />
                        <input
                            type="file"
                            ref={fileInputRef}
                            style={{ display: 'none' }}
                            onChange={handleUpload}
                            accept="image/*,video/*"
                        />
                        <Button
                            variant="outline"
                            size="icon"
                            title="Upload media"
                            onClick={() => fileInputRef.current?.click()}
                        >
                            <Upload size={20} />
                        </Button>
                    </div>
                    <div className="space-y-4">
                        <div>
                            <h3 className="text-lg font-medium mb-2">Images</h3>
                            <div className="grid grid-cols-3 gap-4 max-h-[30vh] overflow-y-auto">
                                {mediaList
                                    .filter(media =>
                                        media.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
                                        media.name.match(/\.(jpeg|jpg|gif|png)$/i)
                                    )
                                    .map((media) => (
                                        <button
                                            type="button"
                                            key={media.name}
                                            className="cursor-pointer text-left"
                                            onClick={() => handleMediaSelection(media)}
                                        >
                                            <Image
                                                src={media.presignedUrl}
                                                alt={media.name}
                                                width={100}
                                                height={100}
                                                className="w-full h-24 object-cover"
                                            />
                                            <p className="mt-2 text-sm truncate">{media.name}</p>
                                            <p className="text-xs text-gray-500">{(media.size / 1024 / 1024).toFixed(2)} MB</p>
                                        </button>
                                    ))}
                            </div>
                        </div>
                        <div>
                            <h3 className="text-lg font-medium mb-2">Videos</h3>
                            <div className="grid grid-cols-3 gap-4 max-h-[30vh] overflow-y-auto">
                                {mediaList
                                    .filter(media =>
                                        media.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
                                        media.name.match(/\.(mp4|webm|ogg)$/i)
                                    )
                                    .map((media) => (
                                        <button
                                            type="button"
                                            key={media.name}
                                            className="cursor-pointer text-left"
                                            onClick={() => handleMediaSelection(media)}
                                        >
                                            <video src={media.presignedUrl} className="w-full h-24 object-cover">
                                                <track kind="captions" />
                                                Your browser does not support the video tag.
                                            </video>
                                            <p className="mt-2 text-sm truncate">{media.name}</p>
                                            <p className="text-xs text-gray-500">{(media.size / 1024 / 1024).toFixed(2)} MB</p>
                                        </button>
                                    ))}
                            </div>
                        </div>
                    </div>
                </div>
            </Modal>
        </div>
    );
};

export default PlaylistForm;

我尝试将输入字段移到手风琴内容之外,但它不起作用,请帮助我找到该错误。

提前致谢,感谢帮助

reactjs next.js frontend accordion shadcnui
1个回答
0
投票

这似乎是一个传播错误。试试这个:

<AccordionContent>
<div className="space-y-4">
    <div>
        <Label>Playlist Name</Label>
        <Input
            value={playlistName}
            onChange={(e) => updatePlaylistName(playlistName, e.target.value)}
            onKeyDown={(e) => {
                if (e.key === ' ') {
                    e.stopPropagation();
                }
            }}
            className="mt-1 bg-white text-black"
        />
    </div>

    <Button
        variant="ghost"
        size="sm"
        onClick={(e) => {
            e.stopPropagation();
            deletePlaylist(playlistName);
        }}
        className="hover:bg-red-100 p-2 mr-2"
    >
        <Trash2 size={16} className="text-red-500" />
    </Button>
</div>
<div className="space-y-4">
    <div className="flex space-x-4">
        <div className="w-1/2">
            <Label>Start Time</Label>
            <Input
                type="time"
                value={playlist.start_time}
                onChange={(e) => updateSchedule(playlistName, 'start_time', e.target.value)}
                className="mt-1 bg-white text-black"
            />
        </div>
        <div className="w-1/2">
            <Label>End Time</Label>
            <Input
                type="time"
                value={playlist.end_time}
                onChange={(e) => updateSchedule(playlistName, 'end_time', e.target.value)}
                className="mt-1 bg-white text-black"
            />
        </div>
    </div>
    <div>
        <Label className="mb-2 block">Weekdays</Label>
        <div className="flex space-x-4 mt-2">
            {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((day, index) => (
                <div key={day} className="flex items-center">
                    <Checkbox
                        id={`${day}-${playlistName}`}
                        checked={playlist.weekdays[index] === '1'}
                        onCheckedChange={() => updateWeekday(playlistName, index)}
                    />
                    <Label htmlFor={`${day}-${playlistName}`} className="ml-1">{day}</Label>
                </div>
            ))}
        </div>
    </div>
    {renderMediaSection(playlistName, 'images')}
    {renderMediaSection(playlistName, 'videos')}
</div>

问题是,当您单击输入时,您也会触发手风琴,因此您需要在单击输入时停止事件传播。

© www.soinside.com 2019 - 2024. All rights reserved.