import { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import api from "../api/client"; import { useAuth } from "../auth/AuthContext"; import SearchBar from "../components/SearchBar"; import ConfirmDialog from "../components/ConfirmDialog"; import { getLocalizedValue, getLanguageName, normalizeColor, formatDuration, } from "./melodyUtils"; const MELODY_TYPES = ["", "orthodox", "catholic", "all"]; const MELODY_TONES = ["", "normal", "festive", "cheerful", "lamentation"]; // All available columns with their defaults const ALL_COLUMNS = [ { key: "status", label: "Status", defaultOn: true }, { key: "color", label: "Color", defaultOn: true }, { key: "name", label: "Name", defaultOn: true, alwaysOn: true }, { key: "description", label: "Description", defaultOn: false }, { key: "type", label: "Type", defaultOn: true }, { key: "tone", label: "Tone", defaultOn: true }, { key: "totalNotes", label: "Total Notes", defaultOn: true }, { key: "minSpeed", label: "Min Speed", defaultOn: false }, { key: "maxSpeed", label: "Max Speed", defaultOn: false }, { key: "tags", label: "Tags", defaultOn: false }, { key: "speed", label: "Speed", defaultOn: false }, { key: "duration", label: "Duration", defaultOn: false }, { key: "totalRunDuration", label: "Total Run", defaultOn: false }, { key: "pauseDuration", label: "Pause", defaultOn: false }, { key: "infiniteLoop", label: "Infinite", defaultOn: false }, { key: "noteAssignments", label: "Note Assignments", defaultOn: false }, { key: "isTrueRing", label: "True Ring", defaultOn: true }, { key: "docId", label: "Document ID", defaultOn: false }, { key: "pid", label: "PID", defaultOn: false }, ]; function getDefaultVisibleColumns() { const saved = localStorage.getItem("melodyListColumns"); if (saved) { try { return JSON.parse(saved); } catch { // ignore } } return ALL_COLUMNS.filter((c) => c.defaultOn).map((c) => c.key); } export default function MelodyList() { const [melodies, setMelodies] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [search, setSearch] = useState(""); const [typeFilter, setTypeFilter] = useState(""); const [toneFilter, setToneFilter] = useState(""); const [statusFilter, setStatusFilter] = useState(""); const [displayLang, setDisplayLang] = useState("en"); const [melodySettings, setMelodySettings] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [unpublishTarget, setUnpublishTarget] = useState(null); const [actionLoading, setActionLoading] = useState(null); const [visibleColumns, setVisibleColumns] = useState(getDefaultVisibleColumns); const [showColumnPicker, setShowColumnPicker] = useState(false); const columnPickerRef = useRef(null); const navigate = useNavigate(); const { hasPermission } = useAuth(); const canEdit = hasPermission("melodies", "edit"); useEffect(() => { api.get("/settings/melody").then((ms) => { setMelodySettings(ms); setDisplayLang(ms.primary_language || "en"); }); }, []); // Close column picker on outside click useEffect(() => { const handleClick = (e) => { if (columnPickerRef.current && !columnPickerRef.current.contains(e.target)) { setShowColumnPicker(false); } }; if (showColumnPicker) { document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); } }, [showColumnPicker]); const fetchMelodies = async () => { setLoading(true); setError(""); try { const params = new URLSearchParams(); if (search) params.set("search", search); if (typeFilter) params.set("type", typeFilter); if (toneFilter) params.set("tone", toneFilter); if (statusFilter) params.set("status", statusFilter); const qs = params.toString(); const data = await api.get(`/melodies${qs ? `?${qs}` : ""}`); setMelodies(data.melodies); setTotal(data.total); } catch (err) { setError(err.message); } finally { setLoading(false); } }; useEffect(() => { fetchMelodies(); }, [search, typeFilter, toneFilter, statusFilter]); const handleDelete = async () => { if (!deleteTarget) return; try { await api.delete(`/melodies/${deleteTarget.id}`); setDeleteTarget(null); fetchMelodies(); } catch (err) { setError(err.message); setDeleteTarget(null); } }; const handlePublish = async (row) => { setActionLoading(row.id); try { await api.post(`/melodies/${row.id}/publish`); fetchMelodies(); } catch (err) { setError(err.message); } finally { setActionLoading(null); } }; const handleUnpublish = async () => { if (!unpublishTarget) return; setActionLoading(unpublishTarget.id); try { await api.post(`/melodies/${unpublishTarget.id}/unpublish`); setUnpublishTarget(null); fetchMelodies(); } catch (err) { setError(err.message); setUnpublishTarget(null); } finally { setActionLoading(null); } }; const toggleColumn = (key) => { const col = ALL_COLUMNS.find((c) => c.key === key); if (col?.alwaysOn) return; setVisibleColumns((prev) => { const next = prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]; localStorage.setItem("melodyListColumns", JSON.stringify(next)); return next; }); }; const isVisible = (key) => visibleColumns.includes(key); const getDisplayName = (nameVal) => getLocalizedValue(nameVal, displayLang, "Untitled"); const renderCellValue = (key, row) => { const info = row.information || {}; const ds = row.default_settings || {}; switch (key) { case "status": return ( {row.status === "published" ? "Live" : "Draft"} ); case "color": return info.color ? ( ) : ( ); case "name": return (
{getLocalizedValue(info.description, displayLang) || "-"}
)}| {col.key === "color" ? "" : col.label} | ))} {canEdit && ()} |
|---|---|
| {renderCellValue(col.key, row)} | ))} {canEdit && (
e.stopPropagation()}
>
{row.status === "draft" ? (
) : (
)}
|
)}