feat: (web) add quest table component for admin quests page

- Add getAllQuests() method to quest.service.ts
- Add GET /api/quests endpoint to server.ts
- Create QuestTable component with data display, formatting, and states
- Update AdminQuests.tsx to fetch and display quests above the form
- Add onSuccess callback to QuestForm for refresh handling
This commit is contained in:
syntaxbullet
2026-01-16 15:12:41 +01:00
parent d243a11bd3
commit 3ef9773990
5 changed files with 377 additions and 2 deletions

View File

@@ -1,8 +1,70 @@
import React from "react";
import { QuestForm } from "../components/quest-form";
import { QuestTable } from "../components/quest-table";
import { SectionHeader } from "../components/section-header";
import { toast } from "sonner";
interface QuestListItem {
id: number;
name: string;
description: string | null;
triggerEvent: string;
requirements: { target?: number };
rewards: { xp?: number; balance?: number };
}
export function AdminQuests() {
const [quests, setQuests] = React.useState<QuestListItem[]>([]);
const [isLoading, setIsLoading] = React.useState(true);
const [refreshKey, setRefreshKey] = React.useState(0);
const [lastCreatedQuestId, setLastCreatedQuestId] = React.useState<number | null>(null);
const fetchQuests = React.useCallback(async () => {
setIsLoading(true);
try {
const response = await fetch("/api/quests");
if (!response.ok) {
throw new Error("Failed to fetch quests");
}
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
setQuests(data.data);
}
} catch (error) {
console.error("Error fetching quests:", error);
toast.error("Failed to load quests", {
description: error instanceof Error ? error.message : "Unknown error",
});
} finally {
setIsLoading(false);
}
}, []);
React.useEffect(() => {
fetchQuests();
}, [fetchQuests]);
React.useEffect(() => {
if (lastCreatedQuestId !== null) {
const element = document.getElementById(`quest-row-${lastCreatedQuestId}`);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "center" });
element.classList.add("bg-primary/10");
setTimeout(() => {
element.classList.remove("bg-primary/10");
}, 2000);
}
setLastCreatedQuestId(null);
}
}, [lastCreatedQuestId, quests]);
const handleQuestCreated = () => {
fetchQuests();
toast.success("Quest list updated", {
description: "The quest inventory has been refreshed.",
});
};
return (
<main className="pt-8 px-8 pb-12 max-w-7xl mx-auto space-y-12">
<SectionHeader
@@ -11,8 +73,16 @@ export function AdminQuests() {
description="Create and manage quests for the Aurora RPG students."
/>
<div className="animate-in fade-in slide-in-from-bottom-4 duration-700">
<QuestTable
quests={quests}
isLoading={isLoading}
onRefresh={fetchQuests}
/>
</div>
<div className="animate-in fade-in slide-up duration-700">
<QuestForm />
<QuestForm onSuccess={handleQuestCreated} />
</div>
</main>
);