feat(backoffice): hackathon dashboard integration

This commit is contained in:
Hafid Nur
2025-12-09 19:51:43 +07:00
parent b74ea9307e
commit 254a2f154d
5 changed files with 161 additions and 6 deletions
@@ -1,7 +1,35 @@
import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms'; import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms';
import { FC, ReactElement } from 'react'; import { FC, ReactElement } from 'react';
import { useQuery } from '@tanstack/react-query';
import {
getAdminUsers,
getAdminTeams,
getAdminSubmissions,
} from '@imphnen-frontend-service/service';
export const HackathonDashboardPage: FC = (): ReactElement => { export const HackathonDashboardPage: FC = (): ReactElement => {
// Fetch total participants
const { data: usersData } = useQuery({
queryKey: ['admin-users-count'],
queryFn: () => getAdminUsers({ page: 1, limit: 1 }),
});
// Fetch total teams
const { data: teamsData } = useQuery({
queryKey: ['admin-teams-count'],
queryFn: () => getAdminTeams({ page: 1, limit: 1 }),
});
// Fetch total submissions
const { data: submissionsData } = useQuery({
queryKey: ['admin-submissions-count'],
queryFn: () => getAdminSubmissions({ page: 1, limit: 1 }),
});
const totalParticipants = usersData?.meta?.total_data ?? 0;
const totalTeams = teamsData?.meta?.total_data ?? 0;
const totalSubmissions = submissionsData?.meta?.total_data ?? 0;
return ( return (
<BackofficeWrapper title="IMPHNEN x Kolosal.ai Hackathon 2025"> <BackofficeWrapper title="IMPHNEN x Kolosal.ai Hackathon 2025">
<h1 className="mb-8 text-p1 font-semibold text-neutral-700">Dashboard</h1> <h1 className="mb-8 text-p1 font-semibold text-neutral-700">Dashboard</h1>
@@ -10,18 +38,22 @@ export const HackathonDashboardPage: FC = (): ReactElement => {
{/* Participant */} {/* Participant */}
<div className="bg-white px-6 py-4 rounded-md shadow"> <div className="bg-white px-6 py-4 rounded-md shadow">
<h3 className="text-primary-500 text-p2 font-semibold mb-2.5"> <h3 className="text-primary-500 text-p2 font-semibold mb-2.5">
1261 {totalParticipants}
</h3> </h3>
<p className="text-neutral-400 text-p3">Total Participants</p> <p className="text-neutral-400 text-p3">Total Participants</p>
</div> </div>
{/* Team */} {/* Team */}
<div className="bg-white px-6 py-4 rounded-md shadow"> <div className="bg-white px-6 py-4 rounded-md shadow">
<h3 className="text-primary-500 text-p2 font-semibold mb-2.5">206</h3> <h3 className="text-primary-500 text-p2 font-semibold mb-2.5">
{totalTeams}
</h3>
<p className="text-neutral-400 text-p3">Total Teams</p> <p className="text-neutral-400 text-p3">Total Teams</p>
</div> </div>
{/* Project Submitted */} {/* Project Submitted */}
<div className="bg-white px-6 py-4 rounded-md shadow"> <div className="bg-white px-6 py-4 rounded-md shadow">
<h3 className="text-primary-500 text-p2 font-semibold mb-2.5">0</h3> <h3 className="text-primary-500 text-p2 font-semibold mb-2.5">
{totalSubmissions}
</h3>
<p className="text-neutral-400 text-p3">Total Project Submitted</p> <p className="text-neutral-400 text-p3">Total Project Submitted</p>
</div> </div>
</section> </section>
+51
View File
@@ -0,0 +1,51 @@
import { api } from '../index';
import type {
TAdminUsersResponse,
TAdminTeamsResponse,
TAdminSubmissionsResponse,
} from '../../types/admin';
const ADMIN_BASE_URL = '/admin';
// Admin Users
export const getAdminUsers = async (params?: {
page?: number;
limit?: number;
search?: string;
city?: string;
}) => {
const response = await api.get<TAdminUsersResponse>(
`${ADMIN_BASE_URL}/users`,
{ params }
);
return response.data;
};
// Admin Teams
export const getAdminTeams = async (params?: {
page?: number;
limit?: number;
search?: string;
city?: string;
visibility?: string;
}) => {
const response = await api.get<TAdminTeamsResponse>(
`${ADMIN_BASE_URL}/teams`,
{ params }
);
return response.data;
};
// Admin Submissions
export const getAdminSubmissions = async (params?: {
page?: number;
limit?: number;
search?: string;
status?: string;
}) => {
const response = await api.get<TAdminSubmissionsResponse>(
`${ADMIN_BASE_URL}/submissions`,
{ params }
);
return response.data;
};
+10 -3
View File
@@ -6,6 +6,7 @@ export * from './users';
export * from './mentors'; export * from './mentors';
export * from './upload'; export * from './upload';
export * from './hackathon'; export * from './hackathon';
export * from './admin';
// Common API response wrapper interface // Common API response wrapper interface
export interface ApiResponse<T> { export interface ApiResponse<T> {
@@ -20,7 +21,9 @@ const getSessionTokenFromCookies = () => {
if (typeof document === 'undefined') return null; if (typeof document === 'undefined') return null;
const cookies = document.cookie.split(';'); const cookies = document.cookie.split(';');
const tokenCookie = cookies.find(cookie => cookie.trim().startsWith(`${TOKEN_KEY}=`)); const tokenCookie = cookies.find((cookie) =>
cookie.trim().startsWith(`${TOKEN_KEY}=`)
);
if (!tokenCookie) return null; if (!tokenCookie) return null;
@@ -32,13 +35,17 @@ const getSessionTokenFromCookies = () => {
} }
}; };
const setSessionTokenToCookies = (tokenData: { token: { access_token: string; refresh_token: string } }) => { const setSessionTokenToCookies = (tokenData: {
token: { access_token: string; refresh_token: string };
}) => {
if (typeof document === 'undefined') return; if (typeof document === 'undefined') return;
const expires = new Date(); const expires = new Date();
expires.setDate(expires.getDate() + 7); expires.setDate(expires.getDate() + 7);
document.cookie = `${TOKEN_KEY}=${encodeURIComponent(JSON.stringify(tokenData))}; expires=${expires.toUTCString()}; path=/; secure; samesite=strict`; document.cookie = `${TOKEN_KEY}=${encodeURIComponent(
JSON.stringify(tokenData)
)}; expires=${expires.toUTCString()}; path=/; secure; samesite=strict`;
}; };
const removeSessionTokenFromCookies = () => { const removeSessionTokenFromCookies = () => {
+64
View File
@@ -0,0 +1,64 @@
export type TAdminMetaResponse = {
page: number;
per_page: number;
total_data: number;
total_page: number;
};
export type TAdminListResponse<T = unknown> = {
data: T[];
meta: TAdminMetaResponse;
};
// Admin Users
export type TAdminUserItem = {
id: string;
email: string;
fullname: string;
avatar: string | null;
phone_number: string | null;
location: string | null;
bio: string;
skills: string[];
is_active: boolean;
created_at: string;
updated_at: string;
};
export type TAdminUsersResponse = TAdminListResponse<TAdminUserItem>;
// Admin Teams
export type TAdminTeamItem = {
id: string;
name: string;
description: string;
city: string;
visibility: string;
logo: string | null;
banner: string | null;
leader_id: string;
created_at: string;
updated_at: string;
};
export type TAdminTeamsResponse = TAdminListResponse<TAdminTeamItem>;
// Admin Submissions
export type TAdminSubmissionItem = {
id: string;
team_id: string;
project_name: string;
description: string;
repository_url: string;
demo_url: string | null;
presentation_url: string | null;
screenshots: string[];
status: string;
submitted_at: string;
submitted_by: string;
created_at: string;
updated_at: string;
};
export type TAdminSubmissionsResponse =
TAdminListResponse<TAdminSubmissionItem>;
+1
View File
@@ -5,3 +5,4 @@ export * from './roles';
export * from './permissions'; export * from './permissions';
export * from './mentors'; export * from './mentors';
export * from './teams'; export * from './teams';
export * from './admin';