Prettier format pending files
This commit is contained in:
parent
3371f2a53b
commit
170b5747bd
16 changed files with 130 additions and 123 deletions
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -18,11 +18,9 @@ export default async function handler(req: any, res: any) {
|
||||||
|
|
||||||
res.status(200).json(achievements || []);
|
res.status(200).json(achievements || []);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
res
|
res.status(500).json({
|
||||||
.status(500)
|
error: error.message || "Failed to fetch achievements",
|
||||||
.json({
|
});
|
||||||
error: error.message || "Failed to fetch achievements",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(405).json({ error: "Method not allowed" });
|
res.status(405).json({ error: "Method not allowed" });
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -26,7 +26,7 @@ export default async function handler(req: any, res: any) {
|
||||||
id,
|
id,
|
||||||
full_name
|
full_name
|
||||||
)
|
)
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.order("created_at", { ascending: false });
|
.order("created_at", { ascending: false });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -25,7 +25,7 @@ export default async function handler(req: any, res: any) {
|
||||||
full_name,
|
full_name,
|
||||||
email
|
email
|
||||||
)
|
)
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.order("created_at", { ascending: false });
|
.order("created_at", { ascending: false });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -21,7 +21,8 @@ export default async function handler(req: any, res: any) {
|
||||||
.update({
|
.update({
|
||||||
approval_status,
|
approval_status,
|
||||||
approved_by: req.user?.id, // Assumes middleware sets req.user
|
approved_by: req.user?.id, // Assumes middleware sets req.user
|
||||||
approved_at: approval_status === "approved" ? new Date().toISOString() : null,
|
approved_at:
|
||||||
|
approval_status === "approved" ? new Date().toISOString() : null,
|
||||||
})
|
})
|
||||||
.eq("user_id", id)
|
.eq("user_id", id)
|
||||||
.select();
|
.select();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -22,7 +22,7 @@ export default async function handler(req: any, res: any) {
|
||||||
id,
|
id,
|
||||||
email
|
email
|
||||||
)
|
)
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.order("created_at", { ascending: false });
|
.order("created_at", { ascending: false });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -25,7 +25,7 @@ export default async function handler(req: any, res: any) {
|
||||||
id,
|
id,
|
||||||
email
|
email
|
||||||
)
|
)
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.order("created_at", { ascending: false });
|
.order("created_at", { ascending: false });
|
||||||
|
|
||||||
|
|
@ -46,11 +46,9 @@ export default async function handler(req: any, res: any) {
|
||||||
|
|
||||||
res.status(200).json(formattedOpp);
|
res.status(200).json(formattedOpp);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
res
|
res.status(500).json({
|
||||||
.status(500)
|
error: error.message || "Failed to fetch opportunities",
|
||||||
.json({
|
});
|
||||||
error: error.message || "Failed to fetch opportunities",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(405).json({ error: "Method not allowed" });
|
res.status(405).json({ error: "Method not allowed" });
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
process.env.VITE_SUPABASE_URL!,
|
process.env.VITE_SUPABASE_URL!,
|
||||||
process.env.SUPABASE_SERVICE_ROLE!
|
process.env.SUPABASE_SERVICE_ROLE!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function handler(req: any, res: any) {
|
export default async function handler(req: any, res: any) {
|
||||||
|
|
@ -14,7 +14,11 @@ export default async function handler(req: any, res: any) {
|
||||||
const updateData: any = {};
|
const updateData: any = {};
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
if (!["open", "in_progress", "filled", "closed", "cancelled"].includes(status)) {
|
if (
|
||||||
|
!["open", "in_progress", "filled", "closed", "cancelled"].includes(
|
||||||
|
status,
|
||||||
|
)
|
||||||
|
) {
|
||||||
return res.status(400).json({ error: "Invalid status" });
|
return res.status(400).json({ error: "Invalid status" });
|
||||||
}
|
}
|
||||||
updateData.status = status;
|
updateData.status = status;
|
||||||
|
|
@ -37,11 +41,9 @@ export default async function handler(req: any, res: any) {
|
||||||
|
|
||||||
res.status(200).json(data);
|
res.status(200).json(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
res
|
res.status(500).json({
|
||||||
.status(500)
|
error: error.message || "Failed to update opportunity",
|
||||||
.json({
|
});
|
||||||
error: error.message || "Failed to update opportunity",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(405).json({ error: "Method not allowed" });
|
res.status(405).json({ error: "Method not allowed" });
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,7 @@ export default async function handler(req: any, res: any) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
const {
|
const { id, project_id, build_type, limit = 50, offset = 0 } = query;
|
||||||
id,
|
|
||||||
project_id,
|
|
||||||
build_type,
|
|
||||||
limit = 50,
|
|
||||||
offset = 0,
|
|
||||||
} = query;
|
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
// Get single build
|
// Get single build
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,8 @@ export default async function handler(req: any, res: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all projects with filters
|
// List all projects with filters
|
||||||
let dbQuery = supabase
|
let dbQuery = supabase.from("gameforge_projects").select(
|
||||||
.from("gameforge_projects")
|
`
|
||||||
.select(
|
|
||||||
`
|
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
|
@ -52,8 +50,8 @@ export default async function handler(req: any, res: any) {
|
||||||
created_at,
|
created_at,
|
||||||
user_profiles!lead_id(id, full_name, avatar_url)
|
user_profiles!lead_id(id, full_name, avatar_url)
|
||||||
`,
|
`,
|
||||||
{ count: "exact" },
|
{ count: "exact" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status) dbQuery = dbQuery.eq("status", status);
|
if (status) dbQuery = dbQuery.eq("status", status);
|
||||||
if (platform) dbQuery = dbQuery.eq("platform", platform);
|
if (platform) dbQuery = dbQuery.eq("platform", platform);
|
||||||
|
|
@ -140,9 +138,7 @@ export default async function handler(req: any, res: any) {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (project?.lead_id !== userId) {
|
if (project?.lead_id !== userId) {
|
||||||
return res
|
return res.status(403).json({ error: "Only project lead can update" });
|
||||||
.status(403)
|
|
||||||
.json({ error: "Only project lead can update" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateData: any = {};
|
const updateData: any = {};
|
||||||
|
|
@ -157,7 +153,8 @@ export default async function handler(req: any, res: any) {
|
||||||
updateData.actual_release_date = actual_release_date;
|
updateData.actual_release_date = actual_release_date;
|
||||||
if (budget !== undefined) updateData.budget = budget;
|
if (budget !== undefined) updateData.budget = budget;
|
||||||
if (current_spend !== undefined) updateData.current_spend = current_spend;
|
if (current_spend !== undefined) updateData.current_spend = current_spend;
|
||||||
if (repository_url !== undefined) updateData.repository_url = repository_url;
|
if (repository_url !== undefined)
|
||||||
|
updateData.repository_url = repository_url;
|
||||||
if (documentation_url !== undefined)
|
if (documentation_url !== undefined)
|
||||||
updateData.documentation_url = documentation_url;
|
updateData.documentation_url = documentation_url;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,13 @@ export default async function handler(req: any, res: any) {
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
const { user_id, project_id, role, limit = 50, offset = 0 } = query;
|
const { user_id, project_id, role, limit = 50, offset = 0 } = query;
|
||||||
|
|
||||||
let dbQuery = supabase
|
let dbQuery = supabase.from("gameforge_team_members").select(
|
||||||
.from("gameforge_team_members")
|
`
|
||||||
.select(
|
|
||||||
`
|
|
||||||
*,
|
*,
|
||||||
user_profiles(id, full_name, avatar_url, email)
|
user_profiles(id, full_name, avatar_url, email)
|
||||||
`,
|
`,
|
||||||
{ count: "exact" },
|
{ count: "exact" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (user_id) dbQuery = dbQuery.eq("user_id", user_id).single();
|
if (user_id) dbQuery = dbQuery.eq("user_id", user_id).single();
|
||||||
if (project_id) dbQuery = dbQuery.contains("project_ids", [project_id]);
|
if (project_id) dbQuery = dbQuery.contains("project_ids", [project_id]);
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export default function AdminFoundationManager() {
|
||||||
const [selectedMentor, setSelectedMentor] = useState<Mentor | null>(null);
|
const [selectedMentor, setSelectedMentor] = useState<Mentor | null>(null);
|
||||||
const [approvalDialogOpen, setApprovalDialogOpen] = useState(false);
|
const [approvalDialogOpen, setApprovalDialogOpen] = useState(false);
|
||||||
const [approvalAction, setApprovalAction] = useState<"approve" | "reject">(
|
const [approvalAction, setApprovalAction] = useState<"approve" | "reject">(
|
||||||
"approve"
|
"approve",
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -146,12 +146,12 @@ export default function AdminFoundationManager() {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ approval_status: approvalAction }),
|
body: JSON.stringify({ approval_status: approvalAction }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to update mentor");
|
if (!response.ok) throw new Error("Failed to update mentor");
|
||||||
aethexToast.success(
|
aethexToast.success(
|
||||||
`Mentor ${approvalAction === "approve" ? "approved" : "rejected"}`
|
`Mentor ${approvalAction === "approve" ? "approved" : "rejected"}`,
|
||||||
);
|
);
|
||||||
setApprovalDialogOpen(false);
|
setApprovalDialogOpen(false);
|
||||||
setSelectedMentor(null);
|
setSelectedMentor(null);
|
||||||
|
|
@ -164,16 +164,17 @@ export default function AdminFoundationManager() {
|
||||||
|
|
||||||
const handlePublishCourse = async (courseId: string, publish: boolean) => {
|
const handlePublishCourse = async (courseId: string, publish: boolean) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/admin/foundation/courses/${courseId}`, {
|
const response = await fetch(
|
||||||
method: "PUT",
|
`/api/admin/foundation/courses/${courseId}`,
|
||||||
headers: { "Content-Type": "application/json" },
|
{
|
||||||
body: JSON.stringify({ is_published: publish }),
|
method: "PUT",
|
||||||
});
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ is_published: publish }),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to update course");
|
if (!response.ok) throw new Error("Failed to update course");
|
||||||
aethexToast.success(
|
aethexToast.success(`Course ${publish ? "published" : "unpublished"}`);
|
||||||
`Course ${publish ? "published" : "unpublished"}`
|
|
||||||
);
|
|
||||||
fetchCourses();
|
fetchCourses();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
aethexToast.error("Failed to update course");
|
aethexToast.error("Failed to update course");
|
||||||
|
|
@ -183,9 +184,12 @@ export default function AdminFoundationManager() {
|
||||||
|
|
||||||
const handleDeleteCourse = async (courseId: string) => {
|
const handleDeleteCourse = async (courseId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/admin/foundation/courses/${courseId}`, {
|
const response = await fetch(
|
||||||
method: "DELETE",
|
`/api/admin/foundation/courses/${courseId}`,
|
||||||
});
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to delete course");
|
if (!response.ok) throw new Error("Failed to delete course");
|
||||||
aethexToast.success("Course deleted");
|
aethexToast.success("Course deleted");
|
||||||
|
|
@ -197,20 +201,18 @@ export default function AdminFoundationManager() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredMentors = mentors.filter((m) =>
|
const filteredMentors = mentors.filter((m) =>
|
||||||
(m.user_name || "")
|
(m.user_name || "").toLowerCase().includes(searchMentor.toLowerCase()),
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchMentor.toLowerCase())
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredCourses = courses.filter((c) =>
|
const filteredCourses = courses.filter((c) =>
|
||||||
c.title.toLowerCase().includes(searchCourse.toLowerCase())
|
c.title.toLowerCase().includes(searchCourse.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const pendingMentors = filteredMentors.filter(
|
const pendingMentors = filteredMentors.filter(
|
||||||
(m) => m.approval_status === "pending"
|
(m) => m.approval_status === "pending",
|
||||||
);
|
);
|
||||||
const approvedMentors = filteredMentors.filter(
|
const approvedMentors = filteredMentors.filter(
|
||||||
(m) => m.approval_status === "approved"
|
(m) => m.approval_status === "approved",
|
||||||
);
|
);
|
||||||
|
|
||||||
const publishedCourses = courses.filter((c) => c.is_published).length;
|
const publishedCourses = courses.filter((c) => c.is_published).length;
|
||||||
|
|
@ -484,7 +486,7 @@ export default function AdminFoundationManager() {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handlePublishCourse(
|
handlePublishCourse(
|
||||||
course.id,
|
course.id,
|
||||||
!course.is_published
|
!course.is_published,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -534,10 +536,7 @@ export default function AdminFoundationManager() {
|
||||||
{achievement.description}
|
{achievement.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2 mt-3">
|
<div className="flex gap-2 mt-3">
|
||||||
<Badge
|
<Badge variant="secondary" className="text-xs">
|
||||||
variant="secondary"
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
{achievement.requirement_type}
|
{achievement.requirement_type}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
|
|
@ -555,10 +554,15 @@ export default function AdminFoundationManager() {
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* Approval Dialog */}
|
{/* Approval Dialog */}
|
||||||
<AlertDialog open={approvalDialogOpen} onOpenChange={setApprovalDialogOpen}>
|
<AlertDialog
|
||||||
|
open={approvalDialogOpen}
|
||||||
|
onOpenChange={setApprovalDialogOpen}
|
||||||
|
>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{approvalAction === "approve" ? "Approve Mentor?" : "Reject Mentor?"}
|
{approvalAction === "approve"
|
||||||
|
? "Approve Mentor?"
|
||||||
|
: "Reject Mentor?"}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
{approvalAction === "approve" ? (
|
{approvalAction === "approve" ? (
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export default function AdminGameForgeStudio() {
|
||||||
// Fetch builds and metrics if project selected
|
// Fetch builds and metrics if project selected
|
||||||
if (selectedProject) {
|
if (selectedProject) {
|
||||||
const buildsRes = await fetch(
|
const buildsRes = await fetch(
|
||||||
`/api/gameforge/builds?project_id=${selectedProject}`
|
`/api/gameforge/builds?project_id=${selectedProject}`,
|
||||||
);
|
);
|
||||||
if (buildsRes.ok) {
|
if (buildsRes.ok) {
|
||||||
const { data } = await buildsRes.json();
|
const { data } = await buildsRes.json();
|
||||||
|
|
@ -124,7 +124,7 @@ export default function AdminGameForgeStudio() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const metricsRes = await fetch(
|
const metricsRes = await fetch(
|
||||||
`/api/gameforge/metrics?project_id=${selectedProject}`
|
`/api/gameforge/metrics?project_id=${selectedProject}`,
|
||||||
);
|
);
|
||||||
if (metricsRes.ok) {
|
if (metricsRes.ok) {
|
||||||
const { data } = await metricsRes.json();
|
const { data } = await metricsRes.json();
|
||||||
|
|
@ -142,8 +142,9 @@ export default function AdminGameForgeStudio() {
|
||||||
|
|
||||||
// Calculate KPIs
|
// Calculate KPIs
|
||||||
const totalTeamSize = teamMembers.filter((m) => m.is_active).length;
|
const totalTeamSize = teamMembers.filter((m) => m.is_active).length;
|
||||||
const activeProjects = projects.filter((p) => p.status === "in_development")
|
const activeProjects = projects.filter(
|
||||||
.length;
|
(p) => p.status === "in_development",
|
||||||
|
).length;
|
||||||
const totalBudget = projects.reduce((sum, p) => sum + (p.budget || 0), 0);
|
const totalBudget = projects.reduce((sum, p) => sum + (p.budget || 0), 0);
|
||||||
const totalSpent = projects.reduce(
|
const totalSpent = projects.reduce(
|
||||||
(sum, p) => sum + (p.current_spend || 0),
|
(sum, p) => sum + (p.current_spend || 0),
|
||||||
|
|
@ -194,7 +195,9 @@ export default function AdminGameForgeStudio() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-3xl font-bold text-white">{activeProjects}</div>
|
<div className="text-3xl font-bold text-white">
|
||||||
|
{activeProjects}
|
||||||
|
</div>
|
||||||
<p className="text-sm text-slate-400 mt-1">in development</p>
|
<p className="text-sm text-slate-400 mt-1">in development</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -302,16 +305,16 @@ export default function AdminGameForgeStudio() {
|
||||||
return (
|
return (
|
||||||
<div key={project.id} className="flex items-center gap-3">
|
<div key={project.id} className="flex items-center gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-white font-medium">
|
<p className="text-white font-medium">{project.name}</p>
|
||||||
{project.name}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-slate-400">
|
<p className="text-xs text-slate-400">
|
||||||
{project.status}
|
{project.status}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Badge
|
<Badge
|
||||||
className={
|
className={
|
||||||
isOnSchedule ? "bg-green-500/20 text-green-400" : "bg-red-500/20 text-red-400"
|
isOnSchedule
|
||||||
|
? "bg-green-500/20 text-green-400"
|
||||||
|
: "bg-red-500/20 text-red-400"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isOnSchedule ? "On Time" : "Delayed"}
|
{isOnSchedule ? "On Time" : "Delayed"}
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,14 @@ export default function AdminNexusManager() {
|
||||||
const [loadingDisputes, setLoadingDisputes] = useState(true);
|
const [loadingDisputes, setLoadingDisputes] = useState(true);
|
||||||
const [loadingCommissions, setLoadingCommissions] = useState(true);
|
const [loadingCommissions, setLoadingCommissions] = useState(true);
|
||||||
const [searchOpp, setSearchOpp] = useState("");
|
const [searchOpp, setSearchOpp] = useState("");
|
||||||
const [disputeFilter, setDisputeFilter] = useState<"all" | "open" | "resolved">("all");
|
const [disputeFilter, setDisputeFilter] = useState<
|
||||||
|
"all" | "open" | "resolved"
|
||||||
|
>("all");
|
||||||
const [selectedDispute, setSelectedDispute] = useState<Dispute | null>(null);
|
const [selectedDispute, setSelectedDispute] = useState<Dispute | null>(null);
|
||||||
const [disputeDialogOpen, setDisputeDialogOpen] = useState(false);
|
const [disputeDialogOpen, setDisputeDialogOpen] = useState(false);
|
||||||
const [disputeResolution, setDisputeResolution] = useState("");
|
const [disputeResolution, setDisputeResolution] = useState("");
|
||||||
const [disputeAction, setDisputeAction] = useState<"resolve" | "escalate">(
|
const [disputeAction, setDisputeAction] = useState<"resolve" | "escalate">(
|
||||||
"resolve"
|
"resolve",
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -140,7 +142,7 @@ export default function AdminNexusManager() {
|
||||||
|
|
||||||
const handleModerateOpportunity = async (
|
const handleModerateOpportunity = async (
|
||||||
opportunityId: string,
|
opportunityId: string,
|
||||||
status: "open" | "filled" | "closed" | "cancelled"
|
status: "open" | "filled" | "closed" | "cancelled",
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|
@ -149,7 +151,7 @@ export default function AdminNexusManager() {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ status }),
|
body: JSON.stringify({ status }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to update opportunity");
|
if (!response.ok) throw new Error("Failed to update opportunity");
|
||||||
|
|
@ -163,7 +165,7 @@ export default function AdminNexusManager() {
|
||||||
|
|
||||||
const handleFeatureOpportunity = async (
|
const handleFeatureOpportunity = async (
|
||||||
opportunityId: string,
|
opportunityId: string,
|
||||||
featured: boolean
|
featured: boolean,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|
@ -172,12 +174,12 @@ export default function AdminNexusManager() {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ is_featured: featured }),
|
body: JSON.stringify({ is_featured: featured }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to update opportunity");
|
if (!response.ok) throw new Error("Failed to update opportunity");
|
||||||
aethexToast.success(
|
aethexToast.success(
|
||||||
`Opportunity ${featured ? "featured" : "unfeatured"}`
|
`Opportunity ${featured ? "featured" : "unfeatured"}`,
|
||||||
);
|
);
|
||||||
fetchOpportunities();
|
fetchOpportunities();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -199,12 +201,12 @@ export default function AdminNexusManager() {
|
||||||
status: disputeAction === "resolve" ? "resolved" : "escalated",
|
status: disputeAction === "resolve" ? "resolved" : "escalated",
|
||||||
resolution_notes: disputeResolution,
|
resolution_notes: disputeResolution,
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to update dispute");
|
if (!response.ok) throw new Error("Failed to update dispute");
|
||||||
aethexToast.success(
|
aethexToast.success(
|
||||||
`Dispute ${disputeAction === "resolve" ? "resolved" : "escalated"}`
|
`Dispute ${disputeAction === "resolve" ? "resolved" : "escalated"}`,
|
||||||
);
|
);
|
||||||
setDisputeDialogOpen(false);
|
setDisputeDialogOpen(false);
|
||||||
setSelectedDispute(null);
|
setSelectedDispute(null);
|
||||||
|
|
@ -217,7 +219,7 @@ export default function AdminNexusManager() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredOpportunities = opportunities.filter((o) =>
|
const filteredOpportunities = opportunities.filter((o) =>
|
||||||
o.title.toLowerCase().includes(searchOpp.toLowerCase())
|
o.title.toLowerCase().includes(searchOpp.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredDisputes = disputes.filter((d) => {
|
const filteredDisputes = disputes.filter((d) => {
|
||||||
|
|
@ -228,12 +230,12 @@ export default function AdminNexusManager() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const openOpportunities = opportunities.filter(
|
const openOpportunities = opportunities.filter(
|
||||||
(o) => o.status === "open"
|
(o) => o.status === "open",
|
||||||
).length;
|
).length;
|
||||||
const openDisputes = disputes.filter((d) => d.status === "open").length;
|
const openDisputes = disputes.filter((d) => d.status === "open").length;
|
||||||
const totalCommissionsRevenue = commissions.reduce(
|
const totalCommissionsRevenue = commissions.reduce(
|
||||||
(sum, c) => sum + c.aethex_revenue,
|
(sum, c) => sum + c.aethex_revenue,
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -261,7 +263,9 @@ export default function AdminNexusManager() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-red-500">{openDisputes}</div>
|
<div className="text-2xl font-bold text-red-500">
|
||||||
|
{openDisputes}
|
||||||
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
Requires attention
|
Requires attention
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -302,7 +306,10 @@ export default function AdminNexusManager() {
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Tabs defaultValue="opportunities" className="w-full">
|
<Tabs defaultValue="opportunities" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="opportunities" className="flex items-center gap-2">
|
<TabsTrigger
|
||||||
|
value="opportunities"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
<Briefcase className="h-4 w-4" />
|
<Briefcase className="h-4 w-4" />
|
||||||
Opportunities
|
Opportunities
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|
@ -333,7 +340,9 @@ export default function AdminNexusManager() {
|
||||||
{loadingOpp ? (
|
{loadingOpp ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<p className="text-muted-foreground">Loading opportunities...</p>
|
<p className="text-muted-foreground">
|
||||||
|
Loading opportunities...
|
||||||
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : filteredOpportunities.length === 0 ? (
|
) : filteredOpportunities.length === 0 ? (
|
||||||
|
|
@ -360,8 +369,8 @@ export default function AdminNexusManager() {
|
||||||
opp.status === "open"
|
opp.status === "open"
|
||||||
? "bg-green-50"
|
? "bg-green-50"
|
||||||
: opp.status === "filled"
|
: opp.status === "filled"
|
||||||
? "bg-blue-50"
|
? "bg-blue-50"
|
||||||
: "bg-gray-50"
|
: "bg-gray-50"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{opp.status}
|
{opp.status}
|
||||||
|
|
@ -386,7 +395,7 @@ export default function AdminNexusManager() {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleFeatureOpportunity(
|
handleFeatureOpportunity(
|
||||||
opp.id,
|
opp.id,
|
||||||
!opp.is_featured
|
!opp.is_featured,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -408,10 +417,7 @@ export default function AdminNexusManager() {
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleModerateOpportunity(
|
handleModerateOpportunity(opp.id, "cancelled")
|
||||||
opp.id,
|
|
||||||
"cancelled"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<XCircle className="h-4 w-4" />
|
<XCircle className="h-4 w-4" />
|
||||||
|
|
@ -429,7 +435,10 @@ export default function AdminNexusManager() {
|
||||||
{/* DISPUTES TAB */}
|
{/* DISPUTES TAB */}
|
||||||
<TabsContent value="disputes" className="space-y-4">
|
<TabsContent value="disputes" className="space-y-4">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Select value={disputeFilter} onValueChange={(v: any) => setDisputeFilter(v)}>
|
<Select
|
||||||
|
value={disputeFilter}
|
||||||
|
onValueChange={(v: any) => setDisputeFilter(v)}
|
||||||
|
>
|
||||||
<SelectTrigger className="w-40">
|
<SelectTrigger className="w-40">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -458,9 +467,7 @@ export default function AdminNexusManager() {
|
||||||
{filteredDisputes.map((dispute) => (
|
{filteredDisputes.map((dispute) => (
|
||||||
<Card
|
<Card
|
||||||
key={dispute.id}
|
key={dispute.id}
|
||||||
className={
|
className={dispute.status === "open" ? "border-red-300" : ""}
|
||||||
dispute.status === "open" ? "border-red-300" : ""
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
|
|
@ -474,8 +481,8 @@ export default function AdminNexusManager() {
|
||||||
dispute.status === "open"
|
dispute.status === "open"
|
||||||
? "bg-red-50"
|
? "bg-red-50"
|
||||||
: dispute.status === "escalated"
|
: dispute.status === "escalated"
|
||||||
? "bg-orange-50"
|
? "bg-orange-50"
|
||||||
: "bg-green-50"
|
: "bg-green-50"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{dispute.status}
|
{dispute.status}
|
||||||
|
|
@ -536,7 +543,7 @@ export default function AdminNexusManager() {
|
||||||
<p className="text-xs text-muted-foreground">Period</p>
|
<p className="text-xs text-muted-foreground">Period</p>
|
||||||
<p className="font-medium text-sm">
|
<p className="font-medium text-sm">
|
||||||
{new Date(
|
{new Date(
|
||||||
commission.period_start
|
commission.period_start,
|
||||||
).toLocaleDateString()}{" "}
|
).toLocaleDateString()}{" "}
|
||||||
-{" "}
|
-{" "}
|
||||||
{new Date(commission.period_end).toLocaleDateString()}
|
{new Date(commission.period_end).toLocaleDateString()}
|
||||||
|
|
@ -566,8 +573,8 @@ export default function AdminNexusManager() {
|
||||||
commission.status === "settled"
|
commission.status === "settled"
|
||||||
? "bg-green-50"
|
? "bg-green-50"
|
||||||
: commission.status === "disputed"
|
: commission.status === "disputed"
|
||||||
? "bg-red-50"
|
? "bg-red-50"
|
||||||
: "bg-yellow-50"
|
: "bg-yellow-50"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{commission.status}
|
{commission.status}
|
||||||
|
|
@ -598,13 +605,18 @@ export default function AdminNexusManager() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium">Action</label>
|
<label className="text-sm font-medium">Action</label>
|
||||||
<Select value={disputeAction} onValueChange={(v: any) => setDisputeAction(v)}>
|
<Select
|
||||||
|
value={disputeAction}
|
||||||
|
onValueChange={(v: any) => setDisputeAction(v)}
|
||||||
|
>
|
||||||
<SelectTrigger className="mt-2">
|
<SelectTrigger className="mt-2">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="resolve">Resolve & Close</SelectItem>
|
<SelectItem value="resolve">Resolve & Close</SelectItem>
|
||||||
<SelectItem value="escalate">Escalate to Senior Team</SelectItem>
|
<SelectItem value="escalate">
|
||||||
|
Escalate to Senior Team
|
||||||
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue