feat(backoffice): hackathon dashboard integration
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 './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 = () => {
|
||||||
|
|||||||
@@ -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 './permissions';
|
||||||
export * from './mentors';
|
export * from './mentors';
|
||||||
export * from './teams';
|
export * from './teams';
|
||||||
|
export * from './admin';
|
||||||
|
|||||||
Reference in New Issue
Block a user