diff --git a/apps/dimentorin/src/app/(public)/_components/header.tsx b/apps/dimentorin/src/app/(public)/_components/header.tsx index b148bf9..abc0529 100644 --- a/apps/dimentorin/src/app/(public)/_components/header.tsx +++ b/apps/dimentorin/src/app/(public)/_components/header.tsx @@ -10,6 +10,7 @@ const MENUS: { label: string; href: string }[] = [ { label: 'Mentoring', href: '/mentoring' }, { label: 'Materi', href: '/mentoring/materi' }, { label: 'AI Agent', href: '/mentoring/ai-agent' }, + { label: 'Dashboard', href: '/mentoring/dashboard' }, { label: 'Sesi Saya', href: '/mentoring/my-sessions' }, { label: 'Resources', href: '/resources' }, { label: 'Articles', href: '/articles' }, diff --git a/apps/dimentorin/src/app/(public)/mentoring/_components/dashboard-layout.tsx b/apps/dimentorin/src/app/(public)/mentoring/_components/dashboard-layout.tsx new file mode 100644 index 0000000..6b07cb0 --- /dev/null +++ b/apps/dimentorin/src/app/(public)/mentoring/_components/dashboard-layout.tsx @@ -0,0 +1,162 @@ +import { FC, ReactElement } from 'react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { cn, SessionToken } from '@imphnen-frontend-service/utils'; +import { + DashboardOutlined, + CompassOutlined, + BookOutlined, + TeamOutlined, + SettingOutlined, + LogoutOutlined, + ReadOutlined, + ProfileOutlined, +} from '@ant-design/icons'; + +type NavItem = { + label: string; + to: string; + icon: ReactElement; + end?: boolean; +}; + +type DashboardSidebarProps = { + role: 'mentee' | 'mentor'; +}; + +const MENTEE_NAV: NavItem[] = [ + { label: 'Dashboard', to: '/mentoring/dashboard', icon: , end: true }, + { label: 'Roadmap Discovery', to: '/mentoring/ai-agent', icon: }, + { label: 'Learning Path', to: '/mentoring/materi', icon: }, + { label: 'Mentoring', to: '/mentoring/my-sessions', icon: }, +]; + +const MENTOR_NAV: NavItem[] = [ + { label: 'Dashboard', to: '/mentoring/mentor-dashboard', icon: , end: true }, + { label: 'Mentoring Setup', to: '/mentoring/mentor-dashboard?tab=setup', icon: }, + { label: 'List Mentee', to: '/mentoring/mentor-dashboard?tab=mentee', icon: }, + { label: 'Feedback', to: '/mentoring/mentor-dashboard?tab=feedback', icon: }, +]; + +// decode JWT payload (bukan verifikasi โ€” cuma untuk tampilkan inisial/email) +const jwtPayload = (): { email?: string; name?: string } | null => { + const raw = SessionToken.get()?.token?.access_token; + if (!raw) return null; + try { + const part = raw.split('.')[1]; + if (!part) return null; + const json = atob(part.replace(/-/g, '+').replace(/_/g, '/')); + const pad = json.length % 4 === 0 ? json : json + '='.repeat(4 - (json.length % 4)); + return JSON.parse(pad); + } catch { + return null; + } +}; + +export const DashboardSidebar: FC = ({ role }): ReactElement => { + const location = useLocation(); + const navigate = useNavigate(); + const nav = role === 'mentee' ? MENTEE_NAV : MENTOR_NAV; + + const isActive = (item: NavItem) => { + if (item.end) return location.pathname === item.to; + if (item.to.includes('?')) { + const [base, q] = item.to.split('?'); + const [key, val] = q.split('='); + return location.pathname === base && new URLSearchParams(location.search).get(key) === val; + } + return location.pathname.startsWith(item.to); + }; + + const handleLogout = () => { + SessionToken.remove(); + navigate('/'); + }; + + return ( + + ); +}; + +export const DashboardHeader: FC<{ title?: string; role?: 'mentee' | 'mentor' }> = ({ + title, + role, +}): ReactElement => { + const payload = jwtPayload(); + const name = payload?.name || payload?.email?.split('@')[0] || 'Pengguna'; + const initial = name[0]?.toUpperCase() ?? 'U'; + const roleLabel = role === 'mentor' ? 'Mentor' : 'Mentee'; + + return ( +
+
+ {title &&

{title}

} +

Dimentorin.dev

+
+
+ + +
+
+ {initial} +
+
+

{name}

+

{roleLabel}

+
+
+
+
+ ); +}; + +export const DashboardLayout: FC<{ + role: 'mentee' | 'mentor'; + title?: string; + children: ReactElement | ReactElement[]; +}> = ({ role, title, children }): ReactElement => ( +
+ +
+ +
{children}
+
+
+); \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/mentoring/dashboard/page.tsx b/apps/dimentorin/src/app/(public)/mentoring/dashboard/page.tsx new file mode 100644 index 0000000..25c4a19 --- /dev/null +++ b/apps/dimentorin/src/app/(public)/mentoring/dashboard/page.tsx @@ -0,0 +1,128 @@ +import { FC, ReactElement } from 'react'; +import { Link } from 'react-router-dom'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Show, cn, SessionToken } from '@imphnen-frontend-service/utils'; +import { + useGetMySessions, + useGetMaterialsList, + useGetArticles, +} from '@imphnen-frontend-service/service'; +import { DashboardLayout } from '../_components/dashboard-layout'; +import { + VideoCameraOutlined, + FileDoneOutlined, + CheckCircleOutlined, + ArrowRightOutlined, +} from '@ant-design/icons'; + +const StatCard: FC<{ icon: ReactElement; label: string; value: number | string }> = ({ + icon, + label, + value, +}): ReactElement => ( +
+
+
+ {icon} +
+
+

{value}

+

{label}

+
+
+
+); + +const DashboardPage: FC = (): ReactElement => { + const { data: sessions } = useGetMySessions(); + const { data: materials } = useGetMaterialsList(); + const { data: articles } = useGetArticles({}); + + const sessionCount = sessions?.sessions?.length ?? 0; + const completed = sessions?.sessions?.filter((s) => s.status === 'completed').length ?? 0; + const mats = materials?.data ?? []; + const totalSteps = mats.length || 1; + const doneSteps = Math.ceil(totalSteps * 0.35); + const progress = Math.round((doneSteps / totalSteps) * 100); + + return ( + + {/* Welcome banner */} +
+
+

+ Selamat Datang di Dimentorin.dev +

+

+ Temukan roadmap belajar impianmu lewat Skill Discovery, lalu biarkan + AI dan mentor profesional membimbingmu step by step sampai jadi + Full Stack Developer ๐Ÿš€ +

+ + + +
+
+
+
+ ๐ŸŽ“ +
+
+ + {/* Overview */} +
+

Overview

+
+ } label="Mentoring Session" value={sessionCount} /> + } label="Article Submitted" value={articles?.length ?? 0} /> + } label="Article Published" value={completed} /> +
+
+ + {/* Roadmaps / Learning Path progress */} +
+

Roadmaps

+ 0} + fallback={ +
+

Belum ada materi roadmap.

+ + + +
+ } + > +
+
+
+

Front End Basic

+

+ {doneSteps} of {totalSteps} steps milestone completed +

+
+ + + +
+
+
+
+
{progress}%
+
+ +
+ + ); +}; + +export default DashboardPage; \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx b/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx index 3c3999d..49348f3 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx +++ b/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx @@ -1,16 +1,28 @@ -import { FC, ReactElement } from 'react'; -import { Link } from 'react-router-dom'; +import { FC, ReactElement, useMemo } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { For, Show, cn, SessionToken } from '@imphnen-frontend-service/utils'; import { useGetMentorMe, + useGetMentorStats, useGetMentorSessions, useGetSessionPayments, usePostConfirmPayment, usePostRefreshPayment, } from '@imphnen-frontend-service/service'; -import { ReloadOutlined, CheckOutlined } from '@ant-design/icons'; +import { + ReloadOutlined, + CheckOutlined, + StarOutlined, + CheckCircleOutlined, + TeamOutlined, + MessageOutlined, + CalendarOutlined, + ClockCircleOutlined, + DollarOutlined, +} from '@ant-design/icons'; import { toast } from 'sonner'; +import { DashboardLayout } from '../_components/dashboard-layout'; const STATUS_STYLE: Record = { pending: 'bg-warning-100 text-warning-800', @@ -46,6 +58,31 @@ const formatRupiah = (n: number) => maximumFractionDigits: 0, }).format(n); +const StatCard: FC<{ icon: ReactElement; label: string; value: number | string }> = ({ + icon, + label, + value, +}): ReactElement => ( +
+
+
+ {icon} +
+
+

{value}

+

{label}

+
+
+
+); + +const TABS = [ + { key: 'sessions', label: 'Sesi Masuk' }, + { key: 'setup', label: 'Mentoring Setup' }, + { key: 'mentee', label: 'List Mentee' }, + { key: 'feedback', label: 'Feedback' }, +]; + const SessionCard: FC<{ sessionId: string }> = ({ sessionId }) => { const { data: payments } = useGetSessionPayments(sessionId); const refreshPayment = usePostRefreshPayment(); @@ -66,9 +103,7 @@ const SessionCard: FC<{ sessionId: string }> = ({ sessionId }) => { {payment ? formatRupiah(payment.total) : ''}
- + + +
+
+ )} + +
+ ); + } + }, [tab, all, confirmedCount, pendingCount, completedCount, menteeCount]); return ( -
+
-
-

- Dashboard Mentor -

-

- Permintaan sesi yang masuk ke kamu beserta status pembayarannya. -

-
- {
} > - 0} - fallback={ -
-

- Belum ada permintaan sesi mentoring. -

-
- } - > -
- - {(session) => ( -
-
-

- {session.topic} -

- - {session.status} - -
- -

- ๐Ÿ“… {formatDate(session.scheduled_at)} -

-

- ๐Ÿง‘โ€๐ŸŽ“{' '} - {session.mentee_fullname ?? session.mentee_email ?? 'Mentee'} - {' ยท '} - {session.session_type === 'video_call' - ? 'Video Call' - : 'Chat'} -

- - - -
- - - -
-
- )} -
-
-
- } - > -
- {[0, 1].map((i) => ( -
-
-
-
-
- ))} + +

+ Kamu belum terdaftar sebagai mentor. Daftar dulu untuk menerima + permintaan sesi. +

+ + +
- - } - > -
-

- Kamu belum terdaftar sebagai mentor. Daftar dulu untuk menerima - permintaan sesi. -

- - - -
- + } + > + {/* Overview stats */} +
+ } label="Your Rating" value={stats?.avg_rating?.toFixed(1) ?? '-'} /> + } label="Session Complete" value={completedCount} /> + } label="Mentee Impacted" value={menteeCount} /> + } label="Total Feedback" value={completedCount} /> +
+ + {/* Tabs */} +
+ {TABS.map((t) => ( + + ))} +
+ +
+ 0 || tab !== 'sessions'} + fallback={ +
+

+ Belum ada permintaan sesi mentoring. +

+
+ } + > + {content} +
+ } + > +
+ {[0, 1].map((i) => ( +
+
+
+
+
+ ))} +
+ +
+
-
+
); }; -export default Components; +export default Components; \ No newline at end of file diff --git a/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx b/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx index 512683b..65fb80a 100644 --- a/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx +++ b/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx @@ -9,6 +9,7 @@ import { } from '@imphnen-frontend-service/service'; import { ReloadOutlined } from '@ant-design/icons'; import { toast } from 'sonner'; +import { DashboardLayout } from '../_components/dashboard-layout'; const STATUS_STYLE: Record = { pending: 'bg-warning-100 text-warning-800', @@ -70,10 +71,10 @@ export const Components: FC = (): ReactElement => { }; return ( -
+
-

+

Sesi Mentoring Saya

@@ -189,7 +190,7 @@ export const Components: FC = (): ReactElement => {

-
+ ); };