From 5faba43728b72bbcd731b9997abd9220f67f7005 Mon Sep 17 00:00:00 2001 From: Hafid Nur <73023445+hafidnrzs@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:22:47 +0700 Subject: [PATCH] base UI for Hackathon backoffice TODO: - Organize table schema for users, teams, and submissions management - Create API Contract for additional back-end endpoint --- .../(protected)/hackathon-dashboard/page.tsx | 31 ++- .../hackathon-submissions/page.tsx | 197 +++++++++++++++++- .../app/(protected)/hackathon-teams/page.tsx | 20 +- .../app/(protected)/hackathon-users/page.tsx | 18 +- .../app/(protected)/users-dimentorin/page.tsx | 69 +++--- .../backoffice-sidebar/backoffice-sidebar.tsx | 4 +- 6 files changed, 275 insertions(+), 64 deletions(-) diff --git a/apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx b/apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx index 190ff52..c9262c8 100644 --- a/apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx +++ b/apps/backoffice/src/app/(protected)/hackathon-dashboard/page.tsx @@ -1,18 +1,31 @@ +import { BackofficeWrapper } from '@imphnen-frontend-service/ui/organisms'; import { FC, ReactElement } from 'react'; export const HackathonDashboardPage: FC = (): ReactElement => { return ( -
-
-

Hackathon Dashboard

-
+ +

Dashboard

-
-

- Boilerplate page for hackathon dashboard. Add KPIs and actions here. -

+
+ {/* Participant */} +
+

+ 1261 +

+

Total Participants

+
+ {/* Team */} +
+

206

+

Total Teams

+
+ {/* Project Submitted */} +
+

0

+

Total Project Submitted

+
-
+ ); }; diff --git a/apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx b/apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx index 0951dce..1099a2c 100644 --- a/apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx +++ b/apps/backoffice/src/app/(protected)/hackathon-submissions/page.tsx @@ -1,17 +1,194 @@ -import { FC, ReactElement } from 'react'; +import { FC, ReactElement, useState } from 'react'; +import ModalUserDetail from '../../../components/modal-user-detail'; +import ModalSuspendOrBan from '../../../components/modal-suspend-or-ban'; +import ModalDeleteUser from '../../../components/modal-delete-user'; +import { + BackofficeWrapper, + DataTable, +} from '@imphnen-frontend-service/ui/organisms'; +import { + ColumnDef, + getCoreRowModel, + getPaginationRowModel, + PaginationState, + RowSelectionState, + useReactTable, +} from '@tanstack/react-table'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { cn } from '@imphnen-frontend-service/utils'; +import { SearchOutlined } from '@ant-design/icons'; + +export const HackathonUsersPage: FC = (): ReactElement => { + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [showDetailModal, setShowDetailModal] = useState(false); + const [showSuspendModal, setShowSuspendModal] = useState(false); + + const [rowSelection, setRowSelection] = useState({}); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 9, + }); + + const mockData: any[] = Array.from({ length: 90 }, (_, i) => ({ + id: i + 1, + name: i % 3 === 0 ? 'Ahmad Wijuana' : 'Sofia Wijuana', + email: 'fullname23@gmail.com', + rating: 4.5, + status: i % 2 === 0 ? 'active' : 'inactive', + })); + + type UserStatus = 'active' | 'inactive'; + + interface UserType { + id: number; + name: string; + email: string; + rating: number; + status: UserStatus; + } + + const columns: ColumnDef[] = [ + { + id: 'select', + meta: { cellClassName: cn('w-20') }, + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + id: 'name', + header: 'Name', + accessorKey: 'name', + }, + { + id: 'email', + header: 'Email', + accessorKey: 'email', + }, + { + id: 'rating', + header: 'Rating', + accessorKey: 'rating', + }, + { + id: 'status', + header: 'Status', + accessorKey: 'status', + cell: ({ row }) => { + const status = row.original.status; + const statusColors: Record = { + active: 'bg-success-200 text-success-500', + inactive: 'bg-danger-200 text-danger-500', + }; + const statusText: Record = { + active: 'Active', + inactive: 'Inactive', + }; + return ( +
+ {statusText[status]} +
+ ); + }, + }, + { + header: 'Action', + meta: { cellClassName: cn('w-72') }, + cell: ({ row }) => ( + + ), + }, + ]; + + const table = useReactTable({ + data: mockData, + columns, + state: { + pagination, + rowSelection, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + pageCount: Math.ceil(mockData.length / pagination.pageSize), + manualPagination: false, + }); -export const HackathonSubmissionsPage: FC = (): ReactElement => { return ( -
-
-

Hackathon Submissions

-
+ +

+ Project Submission +

+ {/* Filters and actions */} +
+
+ + + +
-
-

Boilerplate page for managing submissions. Add table, filters, and review workspace here.

+ {/* Table */} +
-
+ + {/* Modals extracted into shared backoffice components */} + setShowDetailModal(false)} + /> + setShowSuspendModal(false)} + /> + setShowDeleteModal(false)} + /> + ); }; -export default HackathonSubmissionsPage; +export default HackathonUsersPage; diff --git a/apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx b/apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx index afe4a71..9efec8e 100644 --- a/apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx +++ b/apps/backoffice/src/app/(protected)/hackathon-teams/page.tsx @@ -2,7 +2,10 @@ import { FC, ReactElement, useState } from 'react'; import ModalUserDetail from '../../../components/modal-user-detail'; import ModalSuspendOrBan from '../../../components/modal-suspend-or-ban'; import ModalDeleteUser from '../../../components/modal-delete-user'; -import { DataTable } from '@imphnen-frontend-service/ui/organisms'; +import { + BackofficeWrapper, + DataTable, +} from '@imphnen-frontend-service/ui/organisms'; import { ColumnDef, getCoreRowModel, @@ -219,13 +222,12 @@ export const HackathonTeamsPage: FC = (): ReactElement => { }); return ( -
-
-

Hackathon Teams

-
- + +

+ Team Management +

{/* Filters and actions */} -
+
{
- {/* Table */}
- {/* Modals extracted into shared backoffice components */} { isOpen={showDeleteModal} onClose={() => setShowDeleteModal(false)} /> -
+ ); }; diff --git a/apps/backoffice/src/app/(protected)/hackathon-users/page.tsx b/apps/backoffice/src/app/(protected)/hackathon-users/page.tsx index 3816975..8cb3ab1 100644 --- a/apps/backoffice/src/app/(protected)/hackathon-users/page.tsx +++ b/apps/backoffice/src/app/(protected)/hackathon-users/page.tsx @@ -2,7 +2,10 @@ import { FC, ReactElement, useState } from 'react'; import ModalUserDetail from '../../../components/modal-user-detail'; import ModalSuspendOrBan from '../../../components/modal-suspend-or-ban'; import ModalDeleteUser from '../../../components/modal-delete-user'; -import { DataTable } from '@imphnen-frontend-service/ui/organisms'; +import { + BackofficeWrapper, + DataTable, +} from '@imphnen-frontend-service/ui/organisms'; import { ColumnDef, getCoreRowModel, @@ -143,13 +146,12 @@ export const HackathonUsersPage: FC = (): ReactElement => { }); return ( -
-
-

Hackathon Users

-
- + +

+ User Management +

{/* Filters and actions */} -
+
{ isOpen={showDeleteModal} onClose={() => setShowDeleteModal(false)} /> -
+ ); }; diff --git a/apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx b/apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx index 071da5f..bea5c17 100644 --- a/apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx +++ b/apps/backoffice/src/app/(protected)/users-dimentorin/page.tsx @@ -1,19 +1,29 @@ -import { SearchOutlined } from "@ant-design/icons"; -import { Button, Input, Select } from "@imphnen-frontend-service/ui/atoms"; -import { BackofficeWrapper, DataTable } from "@imphnen-frontend-service/ui/organisms"; -import { cn, For } from "@imphnen-frontend-service/utils"; -import { ColumnDef, getCoreRowModel, getPaginationRowModel, PaginationState, RowSelectionState, useReactTable } from "@tanstack/react-table"; -import { ReactElement, useState } from "react"; -import { ModalDetailUser } from "./_components/modal/detail"; +import { SearchOutlined } from '@ant-design/icons'; +import { Button, Input, Select } from '@imphnen-frontend-service/ui/atoms'; +import { + BackofficeWrapper, + DataTable, +} from '@imphnen-frontend-service/ui/organisms'; +import { cn, For } from '@imphnen-frontend-service/utils'; +import { + ColumnDef, + getCoreRowModel, + getPaginationRowModel, + PaginationState, + RowSelectionState, + useReactTable, +} from '@tanstack/react-table'; +import { ReactElement, useState } from 'react'; +import { ModalDetailUser } from './_components/modal/detail'; type UserStatus = 'active' | 'inactive'; interface UserType { - id: number - name: string - email: string - rating: number - status: UserStatus + id: number; + name: string; + email: string; + rating: number; + status: UserStatus; } const mockData: UserType[] = Array.from({ length: 90 }, (_, i) => ({ @@ -22,15 +32,15 @@ const mockData: UserType[] = Array.from({ length: 90 }, (_, i) => ({ email: 'fullname23@gmail.com', rating: 4.5, status: i % 2 === 0 ? 'active' : 'inactive', -})) +})); export default function Components(): ReactElement { - const TABS = ['mentor', 'mentee'] as const - const [activeTab, setActiveTab] = useState<'mentor' | 'mentee'>('mentor') - const [showDetail, setShowDetail] = useState(false) - const [selectedUserId, setSelectedUserId] = useState(null) + const TABS = ['mentor', 'mentee'] as const; + const [activeTab, setActiveTab] = useState<'mentor' | 'mentee'>('mentor'); + const [showDetail, setShowDetail] = useState(false); + const [selectedUserId, setSelectedUserId] = useState(null); - const [rowSelection, setRowSelection] = useState({}) + const [rowSelection, setRowSelection] = useState({}); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 9, @@ -39,7 +49,7 @@ export default function Components(): ReactElement { const columns: ColumnDef[] = [ { id: 'select', - meta: { cellClassName: cn("w-20") }, + meta: { cellClassName: cn('w-20') }, header: ({ table }) => ( (