feat(backoffice): hackathon dashboard integration
This commit is contained in:
@@ -1,7 +1,35 @@
|
||||
import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms';
|
||||
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 => {
|
||||
// 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 (
|
||||
<BackofficeWrapper title="IMPHNEN x Kolosal.ai Hackathon 2025">
|
||||
<h1 className="mb-8 text-p1 font-semibold text-neutral-700">Dashboard</h1>
|
||||
@@ -10,18 +38,22 @@ export const HackathonDashboardPage: FC = (): ReactElement => {
|
||||
{/* Participant */}
|
||||
<div className="bg-white px-6 py-4 rounded-md shadow">
|
||||
<h3 className="text-primary-500 text-p2 font-semibold mb-2.5">
|
||||
1261
|
||||
{totalParticipants}
|
||||
</h3>
|
||||
<p className="text-neutral-400 text-p3">Total Participants</p>
|
||||
</div>
|
||||
{/* Team */}
|
||||
<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>
|
||||
</div>
|
||||
{/* Project Submitted */}
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -6,6 +6,7 @@ export * from './users';
|
||||
export * from './mentors';
|
||||
export * from './upload';
|
||||
export * from './hackathon';
|
||||
export * from './admin';
|
||||
|
||||
// Common API response wrapper interface
|
||||
export interface ApiResponse<T> {
|
||||
@@ -20,7 +21,9 @@ const getSessionTokenFromCookies = () => {
|
||||
if (typeof document === 'undefined') return null;
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
const expires = new Date();
|
||||
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 = () => {
|
||||
|
||||
@@ -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>;
|
||||
@@ -5,3 +5,4 @@ export * from './roles';
|
||||
export * from './permissions';
|
||||
export * from './mentors';
|
||||
export * from './teams';
|
||||
export * from './admin';
|
||||
|
||||
Reference in New Issue
Block a user