CODEX - Moar Changes
This commit is contained in:
@@ -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 api from "../api/client";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
@@ -14,6 +14,12 @@ import {
|
||||
const MELODY_TYPES = ["", "orthodox", "catholic", "all"];
|
||||
const MELODY_TONES = ["", "normal", "festive", "cheerful", "lamentation"];
|
||||
const NOTE_LABELS = "ABCDEFGHIJKLMNOP";
|
||||
const SORTABLE_COLUMN_TO_KEY = {
|
||||
name: "name",
|
||||
totalActiveBells: "totalActiveBells",
|
||||
dateCreated: "dateCreated",
|
||||
dateEdited: "dateEdited",
|
||||
};
|
||||
|
||||
// All available columns with their defaults
|
||||
const ALL_COLUMNS = [
|
||||
@@ -271,7 +277,7 @@ export default function MelodyList() {
|
||||
switch (key) {
|
||||
case "name":
|
||||
return getDisplayName(info.name).toLowerCase();
|
||||
case "totalBells":
|
||||
case "totalActiveBells":
|
||||
return Number(info.totalActiveBells || 0);
|
||||
case "dateEdited":
|
||||
return parseDateValue(metadata.dateEdited);
|
||||
@@ -300,6 +306,24 @@ export default function MelodyList() {
|
||||
});
|
||||
}, [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 info = row.information || {};
|
||||
const ds = row.default_settings || {};
|
||||
@@ -398,7 +422,44 @@ export default function MelodyList() {
|
||||
</div>
|
||||
);
|
||||
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":
|
||||
return ds.totalRunDuration ?? "-";
|
||||
case "pauseDuration":
|
||||
@@ -417,14 +478,14 @@ export default function MelodyList() {
|
||||
);
|
||||
case "noteAssignments":
|
||||
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) => (
|
||||
<div
|
||||
key={noteIdx}
|
||||
className="flex flex-col items-center rounded-md border"
|
||||
style={{
|
||||
minWidth: "30px",
|
||||
padding: "3px 5px",
|
||||
minWidth: "26px",
|
||||
padding: "3px 3px",
|
||||
backgroundColor: "var(--bg-card-hover)",
|
||||
borderColor: "var(--border-primary)",
|
||||
}}
|
||||
@@ -434,7 +495,7 @@ export default function MelodyList() {
|
||||
</span>
|
||||
<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)" }}>
|
||||
{assignedBell > 0 ? assignedBell : "—"}
|
||||
{assignedBell > 0 ? assignedBell : "—"}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -551,26 +612,6 @@ export default function MelodyList() {
|
||||
<option value="draft">Drafts</option>
|
||||
</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}>
|
||||
<button
|
||||
type="button"
|
||||
@@ -724,10 +765,33 @@ export default function MelodyList() {
|
||||
{activeColumns.map((col) => (
|
||||
<th
|
||||
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)" }}
|
||||
>
|
||||
{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>
|
||||
))}
|
||||
{canEdit && (
|
||||
@@ -748,7 +812,9 @@ export default function MelodyList() {
|
||||
{activeColumns.map((col) => (
|
||||
<td
|
||||
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)" }}
|
||||
>
|
||||
{renderCellValue(col.key, row)}
|
||||
@@ -819,3 +885,4 @@ export default function MelodyList() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user