feat: Implement an admin quest management table, enhance toast notifications with descriptions, and add new agent documentation.
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
|
||||
import { cn } from "../lib/utils";
|
||||
import { FileText, RefreshCw } from "lucide-react";
|
||||
import { FileText, RefreshCw, Trash2, Pencil, Star, Coins } from "lucide-react";
|
||||
|
||||
interface QuestListItem {
|
||||
id: number;
|
||||
@@ -20,6 +21,8 @@ interface QuestTableProps {
|
||||
isInitialLoading: boolean;
|
||||
isRefreshing: boolean;
|
||||
onRefresh?: () => void;
|
||||
onDelete?: (id: number) => void;
|
||||
onEdit?: (id: number) => void;
|
||||
}
|
||||
|
||||
const TRIGGER_EVENT_LABELS: Record<string, string> = {
|
||||
@@ -67,7 +70,7 @@ function TruncatedText({ text, maxLength = 100 }: { text: string; maxLength?: nu
|
||||
function QuestTableSkeleton() {
|
||||
return (
|
||||
<div className="space-y-3 animate-pulse">
|
||||
<div className="grid grid-cols-7 gap-4 px-4 py-2 text-sm font-medium text-muted-foreground">
|
||||
<div className="grid grid-cols-8 gap-4 px-4 py-2 text-sm font-medium text-muted-foreground">
|
||||
<Skeleton className="h-4 w-8" />
|
||||
<Skeleton className="h-4 w-32" />
|
||||
<Skeleton className="h-4 w-48" />
|
||||
@@ -77,7 +80,7 @@ function QuestTableSkeleton() {
|
||||
<Skeleton className="h-4 w-24" />
|
||||
</div>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div key={i} className="grid grid-cols-7 gap-4 px-4 py-3 border-t border-border/50">
|
||||
<div key={i} className="grid grid-cols-8 gap-4 px-4 py-3 border-t border-border/50">
|
||||
<Skeleton className="h-5 w-8" />
|
||||
<Skeleton className="h-5 w-32" />
|
||||
<Skeleton className="h-5 w-48" />
|
||||
@@ -85,6 +88,7 @@ function QuestTableSkeleton() {
|
||||
<Skeleton className="h-5 w-16" />
|
||||
<Skeleton className="h-5 w-24" />
|
||||
<Skeleton className="h-5 w-24" />
|
||||
<Skeleton className="h-5 w-16" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -105,7 +109,7 @@ function EmptyQuestState() {
|
||||
);
|
||||
}
|
||||
|
||||
function QuestTableContent({ quests }: { quests: QuestListItem[] }) {
|
||||
function QuestTableContent({ quests, onDelete, onEdit }: { quests: QuestListItem[]; onDelete?: (id: number) => void; onEdit?: (id: number) => void }) {
|
||||
if (quests.length === 0) {
|
||||
return <EmptyQuestState />;
|
||||
}
|
||||
@@ -136,6 +140,9 @@ function QuestTableContent({ quests }: { quests: QuestListItem[] }) {
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground w-32">
|
||||
AU Reward
|
||||
</th>
|
||||
<th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground w-24">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -170,7 +177,7 @@ function QuestTableContent({ quests }: { quests: QuestListItem[] }) {
|
||||
<td className="py-3 px-4 text-sm text-foreground">
|
||||
{rewards?.xp ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span>⭐</span>
|
||||
<Star className="w-4 h-4 text-amber-400" />
|
||||
<span className="font-mono">{rewards.xp}</span>
|
||||
</span>
|
||||
) : (
|
||||
@@ -180,13 +187,51 @@ function QuestTableContent({ quests }: { quests: QuestListItem[] }) {
|
||||
<td className="py-3 px-4 text-sm text-foreground">
|
||||
{rewards?.balance ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span>🪙</span>
|
||||
<Coins className="w-4 h-4 text-amber-500" />
|
||||
<span className="font-mono">{rewards.balance}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => onEdit?.(quest.id)}
|
||||
className="p-1.5 rounded-md hover:bg-muted/50 transition-colors text-muted-foreground hover:text-foreground"
|
||||
title="Edit quest"
|
||||
>
|
||||
<Pencil className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
toast("Delete this quest?", {
|
||||
description: "This action cannot be undone.",
|
||||
action: {
|
||||
label: "Delete",
|
||||
onClick: () => onDelete?.(quest.id)
|
||||
},
|
||||
cancel: {
|
||||
label: "Cancel",
|
||||
onClick: () => {}
|
||||
},
|
||||
style: {
|
||||
background: "var(--destructive)",
|
||||
color: "var(--destructive-foreground)"
|
||||
},
|
||||
actionButtonStyle: {
|
||||
background: "var(--destructive)",
|
||||
color: "var(--destructive-foreground)"
|
||||
}
|
||||
});
|
||||
}}
|
||||
className="p-1.5 rounded-md hover:bg-muted/50 transition-colors text-muted-foreground hover:text-destructive"
|
||||
title="Delete quest"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
@@ -196,7 +241,7 @@ function QuestTableContent({ quests }: { quests: QuestListItem[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function QuestTable({ quests, isInitialLoading, isRefreshing, onRefresh }: QuestTableProps) {
|
||||
export function QuestTable({ quests, isInitialLoading, isRefreshing, onRefresh, onDelete, onEdit }: QuestTableProps) {
|
||||
const showSkeleton = isInitialLoading && quests.length === 0;
|
||||
|
||||
return (
|
||||
@@ -235,7 +280,7 @@ export function QuestTable({ quests, isInitialLoading, isRefreshing, onRefresh }
|
||||
{showSkeleton ? (
|
||||
<QuestTableSkeleton />
|
||||
) : (
|
||||
<QuestTableContent quests={quests} />
|
||||
<QuestTableContent quests={quests} onDelete={onDelete} onEdit={onEdit} />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user