"use client" import { useEffect, useState, useCallback } from "react" import { useAuth } from "@/contexts/auth-context" import { useRouter } from "next/navigation" import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Badge } from "@/components/ui/badge" import { Search, Edit, Save, X, Layers, Plus, Trash2, GripVertical } from "lucide-react" import { getApiUrl } from "@/lib/api-config" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" interface SequenceSong { position: number song_id: number song_title: string } interface Sequence { id: number name: string slug: string description: string | null notes: string | null songs: SequenceSong[] } interface Song { id: number title: string slug: string } export default function AdminSequencesPage() { const { user, token, loading: authLoading } = useAuth() const router = useRouter() const [sequences, setSequences] = useState([]) const [allSongs, setAllSongs] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") const [editingSequence, setEditingSequence] = useState(null) const [isCreating, setIsCreating] = useState(false) const [newSequence, setNewSequence] = useState({ name: "", description: "", notes: "", song_ids: [] as number[] }) const [saving, setSaving] = useState(false) const [songSearch, setSongSearch] = useState("") const fetchSequences = useCallback(async () => { if (!token) return try { const res = await fetch(`${getApiUrl()}/sequences?limit=1000`, { headers: { Authorization: `Bearer ${token}` } }) if (res.ok) setSequences(await res.json()) } catch (e) { console.error("Failed to fetch sequences", e) } finally { setLoading(false) } }, [token]) const fetchSongs = useCallback(async () => { if (!token) return try { const res = await fetch(`${getApiUrl()}/songs?limit=1000`, { headers: { Authorization: `Bearer ${token}` } }) if (res.ok) { const data = await res.json() setAllSongs(data.songs || data) } } catch (e) { console.error("Failed to fetch songs", e) } }, [token]) useEffect(() => { if (authLoading) return if (!user) { router.push("/login") return } if (user.role !== "admin") { router.push("/") return } fetchSequences() fetchSongs() }, [user, router, authLoading, fetchSequences, fetchSongs]) const createSequence = async () => { if (!token || !newSequence.name) return setSaving(true) try { const res = await fetch(`${getApiUrl()}/sequences`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify(newSequence) }) if (res.ok) { fetchSequences() setIsCreating(false) setNewSequence({ name: "", description: "", notes: "", song_ids: [] }) } } catch (e) { console.error("Failed to create sequence", e) } finally { setSaving(false) } } const deleteSequence = async (id: number) => { if (!token) return if (!confirm("Delete this sequence?")) return try { await fetch(`${getApiUrl()}/sequences/${id}`, { method: "DELETE", headers: { Authorization: `Bearer ${token}` } }) fetchSequences() } catch (e) { console.error("Failed to delete sequence", e) } } const addSongToNew = (songId: number) => { if (!newSequence.song_ids.includes(songId)) { setNewSequence({ ...newSequence, song_ids: [...newSequence.song_ids, songId] }) } } const removeSongFromNew = (songId: number) => { setNewSequence({ ...newSequence, song_ids: newSequence.song_ids.filter(id => id !== songId) }) } const filteredSequences = sequences.filter(s => s.name.toLowerCase().includes(search.toLowerCase()) ) const filteredSongs = allSongs.filter(s => s.title.toLowerCase().includes(songSearch.toLowerCase()) ).slice(0, 20) if (loading) { return (
{[1, 2, 3].map(i =>
)}
) } return (

Sequence Management

Sequences are named groupings of consecutive songs, like "Autumn Crossing" (Travelers > Elmeg the Wise).

setSearch(e.target.value)} className="pl-9" />
{filteredSequences.map(seq => ( ))}
Sequence Name Songs Actions

{seq.name}

{seq.description && (

{seq.description}

)}
{seq.songs.map((s, i) => ( {s.song_title} {i < seq.songs.length - 1 && >} ))} {seq.songs.length === 0 && ( No songs )}
{filteredSequences.length === 0 && (
No sequences found. Create one to get started!
)}
{/* Create Dialog */} Create New Sequence
setNewSequence({ ...newSequence, name: e.target.value })} />