CODEX - Moar Changes

This commit is contained in:
2026-02-22 21:20:16 +02:00
parent dd25f66c16
commit 5a0faad429

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useMemo } from "react"; import { useState, useEffect, useRef, useMemo } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import api from "../api/client"; import api from "../api/client";
import { useAuth } from "../auth/AuthContext"; import { useAuth } from "../auth/AuthContext";
@@ -14,6 +14,12 @@ import {
const MELODY_TYPES = ["", "orthodox", "catholic", "all"]; const MELODY_TYPES = ["", "orthodox", "catholic", "all"];
const MELODY_TONES = ["", "normal", "festive", "cheerful", "lamentation"]; const MELODY_TONES = ["", "normal", "festive", "cheerful", "lamentation"];
const NOTE_LABELS = "ABCDEFGHIJKLMNOP"; const NOTE_LABELS = "ABCDEFGHIJKLMNOP";
const SORTABLE_COLUMN_TO_KEY = {
name: "name",
totalActiveBells: "totalActiveBells",
dateCreated: "dateCreated",
dateEdited: "dateEdited",
};
// All available columns with their defaults // All available columns with their defaults
const ALL_COLUMNS = [ const ALL_COLUMNS = [
@@ -271,7 +277,7 @@ export default function MelodyList() {
switch (key) { switch (key) {
case "name": case "name":
return getDisplayName(info.name).toLowerCase(); return getDisplayName(info.name).toLowerCase();
case "totalBells": case "totalActiveBells":
return Number(info.totalActiveBells || 0); return Number(info.totalActiveBells || 0);
case "dateEdited": case "dateEdited":
return parseDateValue(metadata.dateEdited); return parseDateValue(metadata.dateEdited);
@@ -300,6 +306,24 @@ export default function MelodyList() {
}); });
}, [melodies, createdByFilter, sortBy, sortDir]); // eslint-disable-line react-hooks/exhaustive-deps }, [melodies, createdByFilter, sortBy, sortDir]); // eslint-disable-line react-hooks/exhaustive-deps
const handleSortClick = (columnKey) => {
const nextSortKey = SORTABLE_COLUMN_TO_KEY[columnKey];
if (!nextSortKey) return;
if (sortBy === nextSortKey) {
setSortDir((prev) => (prev === "asc" ? "desc" : "asc"));
return;
}
setSortBy(nextSortKey);
setSortDir(nextSortKey === "dateCreated" || nextSortKey === "dateEdited" ? "desc" : "asc");
};
const getSortIndicator = (columnKey) => {
const sortKey = SORTABLE_COLUMN_TO_KEY[columnKey];
if (!sortKey) return null;
if (sortBy !== sortKey) return "↕";
return sortDir === "asc" ? "▲" : "▼";
};
const renderCellValue = (key, row) => { const renderCellValue = (key, row) => {
const info = row.information || {}; const info = row.information || {};
const ds = row.default_settings || {}; const ds = row.default_settings || {};
@@ -398,7 +422,44 @@ export default function MelodyList() {
</div> </div>
); );
case "duration": case "duration":
return ds.duration != null ? formatDuration(ds.duration) : "-"; if (ds.duration == null) return "-";
if (Number(ds.duration) === 0) {
return (
<span
className="px-2 py-0.5 text-xs rounded-full"
style={{ backgroundColor: "rgba(88,156,250,0.18)", color: "var(--text-link)" }}
>
Single Run
</span>
);
}
{
const allDurationValues = melodySettings?.duration_values || [];
const nonZeroDurations = allDurationValues.filter((v) => Number(v) > 0);
const idx = nonZeroDurations.indexOf(Number(ds.duration));
const percent = idx >= 0 && nonZeroDurations.length > 0
? Math.round(((idx + 1) / nonZeroDurations.length) * 100)
: 0;
return (
<div className="min-w-28">
<div className="text-xs mb-1" style={{ color: "var(--text-secondary)" }}>
{formatDuration(ds.duration)}
</div>
<div
className="w-full h-2 rounded-full"
style={{ backgroundColor: "var(--bg-primary)", border: "1px solid var(--border-primary)" }}
>
<div
className="h-full rounded-full transition-all"
style={{
width: `${Math.max(0, Math.min(100, percent))}%`,
backgroundColor: speedBarColor(percent),
}}
/>
</div>
</div>
);
}
case "totalRunDuration": case "totalRunDuration":
return ds.totalRunDuration ?? "-"; return ds.totalRunDuration ?? "-";
case "pauseDuration": case "pauseDuration":
@@ -417,14 +478,14 @@ export default function MelodyList() {
); );
case "noteAssignments": case "noteAssignments":
return ds.noteAssignments?.length > 0 ? ( return ds.noteAssignments?.length > 0 ? (
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-nowrap gap-1 whitespace-nowrap">
{ds.noteAssignments.map((assignedBell, noteIdx) => ( {ds.noteAssignments.map((assignedBell, noteIdx) => (
<div <div
key={noteIdx} key={noteIdx}
className="flex flex-col items-center rounded-md border" className="flex flex-col items-center rounded-md border"
style={{ style={{
minWidth: "30px", minWidth: "26px",
padding: "3px 5px", padding: "3px 3px",
backgroundColor: "var(--bg-card-hover)", backgroundColor: "var(--bg-card-hover)",
borderColor: "var(--border-primary)", borderColor: "var(--border-primary)",
}} }}
@@ -434,7 +495,7 @@ export default function MelodyList() {
</span> </span>
<div className="w-full my-0.5" style={{ height: "1px", backgroundColor: "var(--border-primary)" }} /> <div className="w-full my-0.5" style={{ height: "1px", backgroundColor: "var(--border-primary)" }} />
<span className="text-xs leading-tight" style={{ color: "var(--text-muted)" }}> <span className="text-xs leading-tight" style={{ color: "var(--text-muted)" }}>
{assignedBell > 0 ? assignedBell : "—"} {assignedBell > 0 ? assignedBell : "—"}
</span> </span>
</div> </div>
))} ))}
@@ -551,26 +612,6 @@ export default function MelodyList() {
<option value="draft">Drafts</option> <option value="draft">Drafts</option>
</select> </select>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className={selectClass}
>
<option value="dateCreated">Sort: Date Created</option>
<option value="dateEdited">Sort: Date Edited</option>
<option value="name">Sort: Name</option>
<option value="totalBells">Sort: Total Bells</option>
</select>
<select
value={sortDir}
onChange={(e) => setSortDir(e.target.value)}
className={selectClass}
>
<option value="desc">Desc</option>
<option value="asc">Asc</option>
</select>
<div className="relative" ref={creatorPickerRef}> <div className="relative" ref={creatorPickerRef}>
<button <button
type="button" type="button"
@@ -724,10 +765,33 @@ export default function MelodyList() {
{activeColumns.map((col) => ( {activeColumns.map((col) => (
<th <th
key={col.key} key={col.key}
className={`px-4 py-3 text-left font-medium ${col.key === "color" ? "w-8 px-2" : ""}`} className={`px-4 py-3 text-left font-medium ${
col.key === "color" ? "w-8 px-2" : col.key === "noteAssignments" ? "min-w-[420px]" : ""
}`}
style={{ color: "var(--text-muted)" }} style={{ color: "var(--text-muted)" }}
> >
{col.key === "color" ? "" : col.label} {col.key === "color" ? "" : (
<div className="inline-flex items-center gap-1">
<span>{col.label}</span>
{SORTABLE_COLUMN_TO_KEY[col.key] ? (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleSortClick(col.key);
}}
className="text-xs leading-none px-1 rounded cursor-pointer"
style={{
color: sortBy === SORTABLE_COLUMN_TO_KEY[col.key] ? "var(--accent)" : "var(--text-muted)",
backgroundColor: sortBy === SORTABLE_COLUMN_TO_KEY[col.key] ? "rgba(116,184,22,0.14)" : "transparent",
}}
title={`Sort by ${col.label}`}
>
{getSortIndicator(col.key)}
</button>
) : null}
</div>
)}
</th> </th>
))} ))}
{canEdit && ( {canEdit && (
@@ -748,7 +812,9 @@ export default function MelodyList() {
{activeColumns.map((col) => ( {activeColumns.map((col) => (
<td <td
key={col.key} key={col.key}
className={`px-4 py-3 ${col.key === "color" ? "w-8 px-2" : ""}`} className={`px-4 py-3 ${
col.key === "color" ? "w-8 px-2" : col.key === "noteAssignments" ? "min-w-[420px]" : ""
}`}
style={{ color: "var(--text-primary)" }} style={{ color: "var(--text-primary)" }}
> >
{renderCellValue(col.key, row)} {renderCellValue(col.key, row)}
@@ -819,3 +885,4 @@ export default function MelodyList() {
</div> </div>
); );
} }