641 lines
24 KiB
TypeScript
641 lines
24 KiB
TypeScript
// @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,
|
|
};
|
|
}
|