Refactor launcher and studio modules
This commit is contained in:
parent
a20d298be2
commit
ec3e0f5138
34 changed files with 10300 additions and 8600 deletions
641
src/launcher/useLauncherRequestBoard.ts
Normal file
641
src/launcher/useLauncherRequestBoard.ts
Normal file
|
|
@ -0,0 +1,641 @@
|
|||
// @ts-nocheck
|
||||
import { useEffect, useState } from "react";
|
||||
import type {
|
||||
AdminDetailTab,
|
||||
BoardTab,
|
||||
LauncherRequest,
|
||||
LauncherRequestAnalysisItem,
|
||||
} from "./types";
|
||||
import {
|
||||
createLauncherRequest,
|
||||
deleteLauncherRequest,
|
||||
loadLauncherRecentSaveEvents,
|
||||
loadLauncherRequestMeta,
|
||||
loadLauncherRequests,
|
||||
promoteLauncherAdminRequest,
|
||||
requeueLauncherAdminRequest,
|
||||
saveLauncherAdminRequest,
|
||||
triggerLauncherPendingQueue,
|
||||
verifyLauncherAdminPassword,
|
||||
} from "./requestApi";
|
||||
import {
|
||||
appendUniqueString,
|
||||
cloneLauncherRequest,
|
||||
hydrateLauncherRequestForUi,
|
||||
isAdminAccessError,
|
||||
isNeedsReviewRequest,
|
||||
isQueuedPendingRequest,
|
||||
normalizeStringList,
|
||||
openAdminPanelWindow,
|
||||
removeStringValue,
|
||||
requestMatchesFilters,
|
||||
} from "./utils";
|
||||
|
||||
export function useLauncherRequestBoard({ adminWindowMode }) {
|
||||
const adminPanelOpen = adminWindowMode;
|
||||
const [activeBoardTab, setActiveBoardTab] = useState<BoardTab>(adminWindowMode ? "requests" : "news");
|
||||
const [requests, setRequests] = useState<LauncherRequest[]>([]);
|
||||
const [requestsLoading, setRequestsLoading] = useState(true);
|
||||
const [requestsError, setRequestsError] = useState("");
|
||||
const [requestDraftOpen, setRequestDraftOpen] = useState(false);
|
||||
const [requestDraft, setRequestDraft] = useState("");
|
||||
const [requestSubmitting, setRequestSubmitting] = useState(false);
|
||||
const [requestMutatingId, setRequestMutatingId] = useState("");
|
||||
const [requestSearchText, setRequestSearchText] = useState("");
|
||||
const [requestFilterMenuOpen, setRequestFilterMenuOpen] = useState(false);
|
||||
const [requestStatusFilters, setRequestStatusFilters] = useState<string[]>([]);
|
||||
const [requestTagFilters, setRequestTagFilters] = useState<string[]>([]);
|
||||
const [allowedRequestTags, setAllowedRequestTags] = useState<string[]>([]);
|
||||
const [expandedRequestIds, setExpandedRequestIds] = useState<string[]>([]);
|
||||
const [adminAccessGranted, setAdminAccessGranted] = useState(false);
|
||||
const [adminPassword, setAdminPassword] = useState("");
|
||||
const [adminPasswordDraft, setAdminPasswordDraft] = useState("");
|
||||
const [adminAuthSubmitting, setAdminAuthSubmitting] = useState(false);
|
||||
const [adminPasswordError, setAdminPasswordError] = useState("");
|
||||
const [selectedAdminRequestId, setSelectedAdminRequestId] = useState("");
|
||||
const [selectedAdminAnalysisIndex, setSelectedAdminAnalysisIndex] = useState(0);
|
||||
const [adminSearchText, setAdminSearchText] = useState("");
|
||||
const [adminFilterMenuOpen, setAdminFilterMenuOpen] = useState(false);
|
||||
const [adminStatusFilters, setAdminStatusFilters] = useState<string[]>([]);
|
||||
const [adminTagFilters, setAdminTagFilters] = useState<string[]>([]);
|
||||
const [adminEditorDraft, setAdminEditorDraft] = useState<LauncherRequest | null>(null);
|
||||
const [adminDetailTab, setAdminDetailTab] = useState<AdminDetailTab>("routing");
|
||||
const [adminSaving, setAdminSaving] = useState(false);
|
||||
const [recentSaveEvents, setRecentSaveEvents] = useState([]);
|
||||
const [logsLoading, setLogsLoading] = useState(false);
|
||||
const [logsModalOpen, setLogsModalOpen] = useState(false);
|
||||
const [logsError, setLogsError] = useState("");
|
||||
const [queueTriggering, setQueueTriggering] = useState(false);
|
||||
const [requeueingMode, setRequeueingMode] = useState<"" | "saved" | "draft">("");
|
||||
const [adminNotice, setAdminNotice] = useState("");
|
||||
|
||||
async function loadRequests(options?: { silent?: boolean }): Promise<void> {
|
||||
const silent = options?.silent === true;
|
||||
if (!silent) {
|
||||
setRequestsLoading(true);
|
||||
}
|
||||
try {
|
||||
const payload = await loadLauncherRequests();
|
||||
setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []);
|
||||
setRequestsError("");
|
||||
} catch (nextError: unknown) {
|
||||
setRequestsError(String(nextError || "Failed to load requests."));
|
||||
} finally {
|
||||
if (!silent) {
|
||||
setRequestsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRequestMeta(): Promise<void> {
|
||||
try {
|
||||
const payload = await loadLauncherRequestMeta();
|
||||
setAllowedRequestTags(Array.isArray(payload.allowedTags) ? payload.allowedTags : []);
|
||||
} catch {
|
||||
setAllowedRequestTags([]);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentSaveEvents(): Promise<void> {
|
||||
setLogsLoading(true);
|
||||
try {
|
||||
const payload = await loadLauncherRecentSaveEvents(adminPassword);
|
||||
setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []);
|
||||
setLogsError("");
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
}
|
||||
setLogsError(String(nextError || "Failed to load admin logs."));
|
||||
} finally {
|
||||
setLogsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyAdminPassword(password: string): Promise<void> {
|
||||
await verifyLauncherAdminPassword(password);
|
||||
}
|
||||
|
||||
async function refreshAdminData(options?: { includeLogs?: boolean; silentRequests?: boolean }): Promise<void> {
|
||||
await loadRequests({ silent: options?.silentRequests === true });
|
||||
if (options?.includeLogs && adminAccessGranted && adminPassword) {
|
||||
await loadRecentSaveEvents();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void loadRequests();
|
||||
void loadRequestMeta();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!adminPanelOpen || !adminAccessGranted || !adminPassword) {
|
||||
return;
|
||||
}
|
||||
void loadRecentSaveEvents();
|
||||
}, [adminPanelOpen, adminAccessGranted, adminPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeBoardTab !== "requests") {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
const refreshBoard = async (): Promise<void> => {
|
||||
if (!adminPanelOpen) {
|
||||
try {
|
||||
const payload = await loadLauncherRequests();
|
||||
if (!cancelled) {
|
||||
setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []);
|
||||
}
|
||||
} catch {
|
||||
// Keep the current list visible during background refresh failures.
|
||||
}
|
||||
}
|
||||
if (!adminPanelOpen || !adminAccessGranted || !adminPassword) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = await loadLauncherRecentSaveEvents(adminPassword);
|
||||
if (!cancelled) {
|
||||
setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []);
|
||||
}
|
||||
} catch {
|
||||
// Avoid surfacing noisy polling failures in the admin panel.
|
||||
}
|
||||
};
|
||||
const intervalId = window.setInterval(() => {
|
||||
void refreshBoard();
|
||||
}, 15000);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearInterval(intervalId);
|
||||
};
|
||||
}, [activeBoardTab, adminPanelOpen, adminAccessGranted, adminPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!adminPanelOpen || !adminAccessGranted) {
|
||||
return;
|
||||
}
|
||||
if (requests.length === 0) {
|
||||
setSelectedAdminRequestId("");
|
||||
setAdminEditorDraft(null);
|
||||
return;
|
||||
}
|
||||
const selectedRequest = requests.find((entry) => entry.id === selectedAdminRequestId);
|
||||
if (selectedRequest) {
|
||||
if (!adminEditorDraft || adminEditorDraft.id !== selectedRequest.id) {
|
||||
setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(selectedRequest)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
setSelectedAdminRequestId(requests[0].id);
|
||||
setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(requests[0])));
|
||||
}, [adminPanelOpen, adminAccessGranted, requests, selectedAdminRequestId, adminEditorDraft]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedAdminAnalysisIndex(0);
|
||||
}, [selectedAdminRequestId]);
|
||||
|
||||
useEffect(() => {
|
||||
setAdminDetailTab("routing");
|
||||
}, [selectedAdminRequestId]);
|
||||
|
||||
async function handleAddRequest(): Promise<void> {
|
||||
const text = requestDraft.trim();
|
||||
if (!text) {
|
||||
setRequestsError("Write a request before saving it.");
|
||||
return;
|
||||
}
|
||||
setRequestSubmitting(true);
|
||||
try {
|
||||
const payload = await createLauncherRequest(text);
|
||||
setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []);
|
||||
setRequestDraft("");
|
||||
setRequestDraftOpen(false);
|
||||
setRequestsError("");
|
||||
setAdminNotice("Request saved. The VPS queue worker will pick it up if analysis autorun is enabled.");
|
||||
if (adminPanelOpen && adminAccessGranted) {
|
||||
void loadRecentSaveEvents();
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
void refreshAdminData({ includeLogs: adminPanelOpen && adminAccessGranted, silentRequests: true });
|
||||
}, 3500);
|
||||
} catch (nextError: unknown) {
|
||||
setRequestsError(String(nextError || "Failed to save request."));
|
||||
} finally {
|
||||
setRequestSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdminPanelToggle(): Promise<void> {
|
||||
setRequestDraftOpen(false);
|
||||
setRequestsError("");
|
||||
setLogsError("");
|
||||
if (adminWindowMode) {
|
||||
return;
|
||||
}
|
||||
if (!openAdminPanelWindow()) {
|
||||
setAdminNotice("Allow popups to open the admin review window.");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdminUnlock(): Promise<void> {
|
||||
const submittedPassword = adminPasswordDraft.trim();
|
||||
if (!submittedPassword) {
|
||||
setAdminPasswordError("Enter the admin password to continue.");
|
||||
return;
|
||||
}
|
||||
setAdminAuthSubmitting(true);
|
||||
setAdminPasswordError("");
|
||||
try {
|
||||
await verifyAdminPassword(submittedPassword);
|
||||
setAdminPassword(submittedPassword);
|
||||
setAdminAccessGranted(true);
|
||||
setAdminNotice("Admin access granted.");
|
||||
await refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
} catch (nextError: unknown) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError(String(nextError || "Failed to unlock the admin panel."));
|
||||
} finally {
|
||||
setAdminAuthSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectAdminRequest(requestId: string): void {
|
||||
const nextRequest = requests.find((entry) => entry.id === requestId);
|
||||
setSelectedAdminRequestId(requestId);
|
||||
setSelectedAdminAnalysisIndex(0);
|
||||
setAdminEditorDraft(nextRequest ? cloneLauncherRequest(hydrateLauncherRequestForUi(nextRequest)) : null);
|
||||
setAdminNotice("");
|
||||
setAdminPasswordError("");
|
||||
}
|
||||
|
||||
function updateAdminDraft(updater: (current: LauncherRequest) => LauncherRequest): void {
|
||||
setAdminEditorDraft((current) => (current ? updater(current) : current));
|
||||
}
|
||||
|
||||
function updateAdminDraftItem(
|
||||
itemIndex: number,
|
||||
updater: (item: LauncherRequestAnalysisItem) => LauncherRequestAnalysisItem,
|
||||
): void {
|
||||
updateAdminDraft((current) => {
|
||||
const next = cloneLauncherRequest(current);
|
||||
if (!next.analysis) {
|
||||
next.analysis = {
|
||||
state: "needs_review",
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
const items = Array.isArray(next.analysis.items) ? [...next.analysis.items] : [];
|
||||
const existingItem = items[itemIndex] || {};
|
||||
items[itemIndex] = updater({
|
||||
...existingItem,
|
||||
tags: Array.isArray(existingItem.tags) ? [...existingItem.tags] : [],
|
||||
affectedSystems: Array.isArray(existingItem.affectedSystems) ? [...existingItem.affectedSystems] : [],
|
||||
affectedFiles: Array.isArray(existingItem.affectedFiles) ? [...existingItem.affectedFiles] : [],
|
||||
reviewOptions: Array.isArray(existingItem.reviewOptions) ? [...existingItem.reviewOptions] : [],
|
||||
});
|
||||
next.analysis.items = items;
|
||||
next.analysis.itemCount = items.length;
|
||||
next.analysis.updatedAt = new Date().toISOString();
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSaveAdminRequest(): Promise<void> {
|
||||
if (!adminEditorDraft) {
|
||||
return;
|
||||
}
|
||||
setAdminSaving(true);
|
||||
try {
|
||||
const payload = await saveLauncherAdminRequest(adminPassword, adminEditorDraft);
|
||||
const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests;
|
||||
setRequests(nextRequests);
|
||||
const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft);
|
||||
setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed)));
|
||||
setAdminNotice(`Saved admin changes for "${adminEditorDraft.title}".`);
|
||||
if (adminPanelOpen) {
|
||||
void loadRecentSaveEvents();
|
||||
}
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError("Admin access expired. Enter the password again.");
|
||||
}
|
||||
setLogsError(String(nextError || "Failed to save admin changes."));
|
||||
} finally {
|
||||
setAdminSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleApproveAdminRequest(): Promise<void> {
|
||||
if (!adminEditorDraft) {
|
||||
return;
|
||||
}
|
||||
const nextDraft = cloneLauncherRequest(adminEditorDraft);
|
||||
if (!nextDraft.analysis) {
|
||||
setLogsError("This request has no analysis to approve yet.");
|
||||
return;
|
||||
}
|
||||
const items = Array.isArray(nextDraft.analysis.items) ? nextDraft.analysis.items : [];
|
||||
if (items.length === 0) {
|
||||
setLogsError("This request does not have a structured analysis item to approve yet.");
|
||||
return;
|
||||
}
|
||||
nextDraft.analysis.items = items.map((item) => ({
|
||||
...item,
|
||||
statusRecommendation: "active",
|
||||
}));
|
||||
nextDraft.analysis.state = "processed";
|
||||
nextDraft.analysis.updatedAt = new Date().toISOString();
|
||||
setAdminEditorDraft(nextDraft);
|
||||
setAdminSaving(true);
|
||||
try {
|
||||
await saveLauncherAdminRequest(adminPassword, nextDraft);
|
||||
const promotePayload = await promoteLauncherAdminRequest(adminPassword, nextDraft);
|
||||
const nextRequests = Array.isArray(promotePayload.requests) ? promotePayload.requests.map(hydrateLauncherRequestForUi) : [];
|
||||
setRequests(nextRequests);
|
||||
const fallbackSelection = nextRequests[0] || null;
|
||||
setSelectedAdminRequestId(fallbackSelection?.id || "");
|
||||
setAdminEditorDraft(fallbackSelection ? cloneLauncherRequest(hydrateLauncherRequestForUi(fallbackSelection)) : null);
|
||||
setAdminNotice(`Approved "${nextDraft.title}" and promoted its active request item${(nextDraft.analysis.items?.length || 0) === 1 ? "" : "s"}.`);
|
||||
if (adminPanelOpen) {
|
||||
void loadRecentSaveEvents();
|
||||
}
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError("Admin access expired. Enter the password again.");
|
||||
}
|
||||
setLogsError(String(nextError || "Failed to approve this request."));
|
||||
} finally {
|
||||
setAdminSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRequeueAnalysis(mode: "saved" | "draft"): Promise<void> {
|
||||
if (!adminEditorDraft) {
|
||||
return;
|
||||
}
|
||||
setRequeueingMode(mode);
|
||||
setLogsError("");
|
||||
try {
|
||||
const payload = await requeueLauncherAdminRequest(adminPassword, adminEditorDraft, mode);
|
||||
const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests;
|
||||
setRequests(nextRequests);
|
||||
const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft);
|
||||
setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed)));
|
||||
if (payload.launched) {
|
||||
setAdminNotice(mode === "draft"
|
||||
? "Edited draft resubmitted to the analyzer."
|
||||
: "Saved request resubmitted to the analyzer.");
|
||||
} else {
|
||||
const reason = String(payload.reason || "no-op");
|
||||
if (reason === "request-analysis-already-running") {
|
||||
setAdminNotice("The queue worker is already running. This request will be picked up on the next pass.");
|
||||
} else if (reason === "request-not-queued") {
|
||||
setAdminNotice("That request is not currently eligible for review reruns.");
|
||||
} else {
|
||||
setAdminNotice(`Review rerun returned: ${reason}.`);
|
||||
}
|
||||
}
|
||||
await refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
window.setTimeout(() => {
|
||||
void refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
}, 4200);
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError("Admin access expired. Enter the password again.");
|
||||
}
|
||||
setLogsError(String(nextError || "Failed to requeue this request for review."));
|
||||
} finally {
|
||||
setRequeueingMode("");
|
||||
}
|
||||
}
|
||||
|
||||
function handleToggleExpandedRequest(requestId: string): void {
|
||||
setExpandedRequestIds((current) => (
|
||||
current.includes(requestId)
|
||||
? current.filter((entry) => entry !== requestId)
|
||||
: [...current, requestId]
|
||||
));
|
||||
}
|
||||
|
||||
async function handleDeleteRequest(requestEntry: LauncherRequest): Promise<void> {
|
||||
const confirmed = window.confirm(`Delete this request?\n\n${requestEntry.title}`);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
setRequestMutatingId(requestEntry.id);
|
||||
try {
|
||||
const payload = await deleteLauncherRequest(adminPassword, requestEntry.id);
|
||||
setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []);
|
||||
setRequestsError("");
|
||||
setExpandedRequestIds((current) => current.filter((entry) => entry !== requestEntry.id));
|
||||
setAdminNotice(`Deleted request "${requestEntry.title}".`);
|
||||
if (adminPanelOpen) {
|
||||
void loadRecentSaveEvents();
|
||||
}
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError("Admin access expired. Enter the password again.");
|
||||
}
|
||||
setRequestsError(String(nextError || "Failed to delete request."));
|
||||
} finally {
|
||||
setRequestMutatingId("");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleProcessPendingQueue(): Promise<void> {
|
||||
setQueueTriggering(true);
|
||||
try {
|
||||
const payload = await triggerLauncherPendingQueue(adminPassword);
|
||||
if (payload.launched) {
|
||||
setAdminNotice(`Queue worker launched for ${payload.queuedPendingCount ?? 0} pending request${payload.queuedPendingCount === 1 ? "" : "s"}.`);
|
||||
} else {
|
||||
const reason = String(payload.reason || "no-op");
|
||||
if (reason === "no-pending-requests") {
|
||||
setAdminNotice("No unprocessed pending requests are waiting in the queue.");
|
||||
} else if (reason === "request-analysis-already-running") {
|
||||
setAdminNotice("The request analysis worker is already running on the VPS.");
|
||||
} else if (reason === "request-analysis-not-configured") {
|
||||
setAdminNotice("Request analysis is not configured on the server.");
|
||||
} else {
|
||||
setAdminNotice(`Queue trigger returned: ${reason}.`);
|
||||
}
|
||||
}
|
||||
await refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
if (payload.launched) {
|
||||
window.setTimeout(() => {
|
||||
void refreshAdminData({ includeLogs: true, silentRequests: true });
|
||||
}, 4200);
|
||||
}
|
||||
} catch (nextError: unknown) {
|
||||
if (isAdminAccessError(nextError)) {
|
||||
setAdminAccessGranted(false);
|
||||
setAdminPassword("");
|
||||
setAdminPasswordError("Admin access expired. Enter the password again.");
|
||||
}
|
||||
setLogsError(String(nextError || "Failed to trigger the queue worker."));
|
||||
} finally {
|
||||
setQueueTriggering(false);
|
||||
}
|
||||
}
|
||||
|
||||
const requestCount = requests.length;
|
||||
const pendingRequestCount = requests.filter((entry) => entry.status === "pending").length;
|
||||
const activeRequestCount = requests.filter((entry) => entry.status === "active").length;
|
||||
const implementedRequestCount = requests.filter((entry) => entry.status === "implemented").length;
|
||||
const queuedPendingRequestCount = requests.filter(isQueuedPendingRequest).length;
|
||||
const needsReviewRequestCount = requests.filter(isNeedsReviewRequest).length;
|
||||
const requestTags = (allowedRequestTags.length > 0
|
||||
? allowedRequestTags
|
||||
: Array.from(new Set(
|
||||
requests
|
||||
.flatMap((entry) => Array.isArray(entry.tags) ? entry.tags : [])
|
||||
.map((entry) => String(entry || "").trim())
|
||||
.filter(Boolean),
|
||||
))).sort((a, b) => a.localeCompare(b));
|
||||
const requestTagFilterOptions = requestTags
|
||||
.map((tag) => ({
|
||||
tag,
|
||||
count: requests.filter((entry) => entry.tags.includes(tag)).length,
|
||||
}))
|
||||
.filter((entry) => entry.count > 0);
|
||||
const requestStatusFilterOptions = [
|
||||
{ id: "pending", label: "Pending", count: pendingRequestCount },
|
||||
{ id: "queued", label: "Queued", count: queuedPendingRequestCount },
|
||||
{ id: "review", label: "Needs Review", count: needsReviewRequestCount },
|
||||
{ id: "active", label: "Active", count: activeRequestCount },
|
||||
{ id: "implemented", label: "Implemented", count: implementedRequestCount },
|
||||
].filter((entry) => entry.count > 0);
|
||||
const filteredRequests = requests.filter((entry) => requestMatchesFilters(
|
||||
entry,
|
||||
requestSearchText,
|
||||
requestStatusFilters,
|
||||
requestTagFilters,
|
||||
));
|
||||
const adminFilteredRequests = requests.filter((entry) => requestMatchesFilters(
|
||||
entry,
|
||||
adminSearchText,
|
||||
adminStatusFilters,
|
||||
adminTagFilters,
|
||||
));
|
||||
const selectedAnalysisItem = adminEditorDraft?.analysis?.items?.[selectedAdminAnalysisIndex] || null;
|
||||
const standardizedTagOptions = normalizeStringList([
|
||||
...allowedRequestTags,
|
||||
...requestTags,
|
||||
...requests.flatMap((entry) => [
|
||||
...entry.tags,
|
||||
...(Array.isArray(entry.analysis?.routing?.suggestedTags) ? entry.analysis.routing.suggestedTags : []),
|
||||
...(Array.isArray(entry.analysis?.items) ? entry.analysis.items.flatMap((item) => Array.isArray(item.tags) ? item.tags : []) : []),
|
||||
]),
|
||||
...(adminEditorDraft?.tags || []),
|
||||
...(Array.isArray(adminEditorDraft?.analysis?.routing?.suggestedTags) ? adminEditorDraft.analysis.routing.suggestedTags : []),
|
||||
...(Array.isArray(selectedAnalysisItem?.tags) ? selectedAnalysisItem.tags : []),
|
||||
]);
|
||||
const categoryOptions = normalizeStringList([
|
||||
...standardizedTagOptions,
|
||||
...requests.map((entry) => entry.category),
|
||||
...requests.flatMap((entry) => Array.isArray(entry.analysis?.items) ? entry.analysis.items.map((item) => String(item.primaryCategory || "")) : []),
|
||||
String(adminEditorDraft?.category || ""),
|
||||
String(selectedAnalysisItem?.primaryCategory || ""),
|
||||
]);
|
||||
const boardTitle = adminWindowMode ? "Worldshaper Admin" : "Worldshaper Board";
|
||||
const boardHint = adminWindowMode
|
||||
? `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented`
|
||||
: (activeBoardTab === "news"
|
||||
? "Latest announcements"
|
||||
: `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented`);
|
||||
|
||||
return {
|
||||
adminPanelOpen,
|
||||
activeBoardTab,
|
||||
setActiveBoardTab,
|
||||
requests,
|
||||
requestsLoading,
|
||||
requestsError,
|
||||
requestDraftOpen,
|
||||
setRequestDraftOpen,
|
||||
requestDraft,
|
||||
setRequestDraft,
|
||||
requestSubmitting,
|
||||
requestMutatingId,
|
||||
requestSearchText,
|
||||
setRequestSearchText,
|
||||
requestFilterMenuOpen,
|
||||
setRequestFilterMenuOpen,
|
||||
requestStatusFilters,
|
||||
setRequestStatusFilters,
|
||||
requestTagFilters,
|
||||
setRequestTagFilters,
|
||||
allowedRequestTags,
|
||||
expandedRequestIds,
|
||||
adminAccessGranted,
|
||||
adminPassword,
|
||||
adminPasswordDraft,
|
||||
setAdminPasswordDraft,
|
||||
adminAuthSubmitting,
|
||||
adminPasswordError,
|
||||
selectedAdminRequestId,
|
||||
selectedAdminAnalysisIndex,
|
||||
setSelectedAdminAnalysisIndex,
|
||||
adminSearchText,
|
||||
setAdminSearchText,
|
||||
adminFilterMenuOpen,
|
||||
setAdminFilterMenuOpen,
|
||||
adminStatusFilters,
|
||||
setAdminStatusFilters,
|
||||
adminTagFilters,
|
||||
setAdminTagFilters,
|
||||
adminEditorDraft,
|
||||
adminDetailTab,
|
||||
setAdminDetailTab,
|
||||
adminSaving,
|
||||
recentSaveEvents,
|
||||
logsLoading,
|
||||
logsModalOpen,
|
||||
setLogsModalOpen,
|
||||
logsError,
|
||||
queueTriggering,
|
||||
requeueingMode,
|
||||
adminNotice,
|
||||
refreshAdminData,
|
||||
loadRecentSaveEvents,
|
||||
handleAddRequest,
|
||||
handleAdminPanelToggle,
|
||||
handleAdminUnlock,
|
||||
handleSelectAdminRequest,
|
||||
updateAdminDraft,
|
||||
updateAdminDraftItem,
|
||||
handleSaveAdminRequest,
|
||||
handleApproveAdminRequest,
|
||||
handleRequeueAnalysis,
|
||||
handleToggleExpandedRequest,
|
||||
handleDeleteRequest,
|
||||
handleProcessPendingQueue,
|
||||
requestCount,
|
||||
pendingRequestCount,
|
||||
activeRequestCount,
|
||||
implementedRequestCount,
|
||||
queuedPendingRequestCount,
|
||||
needsReviewRequestCount,
|
||||
requestTags,
|
||||
requestTagFilterOptions,
|
||||
requestStatusFilterOptions,
|
||||
filteredRequests,
|
||||
adminFilteredRequests,
|
||||
selectedAnalysisItem,
|
||||
standardizedTagOptions,
|
||||
categoryOptions,
|
||||
boardTitle,
|
||||
boardHint,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue