feat(dimentorin): integrate mentors API - service layer + public list/detail pages
This commit is contained in:
@@ -1,12 +1,20 @@
|
||||
import { StarFilled } from "@ant-design/icons"
|
||||
import { Button } from "@imphnen-frontend-service/ui/atoms"
|
||||
import { cn, For } from "@imphnen-frontend-service/utils"
|
||||
import { TMentorDetail } from "@imphnen-frontend-service/service"
|
||||
|
||||
type Props = {
|
||||
onBook: () => void
|
||||
mentor?: TMentorDetail
|
||||
}
|
||||
|
||||
export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
export const ProfileSection: React.FC<Props> = ({ onBook, mentor }) => {
|
||||
const name = mentor?.fullname || mentor?.legal_name || "Mentor"
|
||||
const role = mentor?.current_role || "Mentor"
|
||||
const company = mentor?.current_company || "IMPHNEN"
|
||||
const expertise = mentor?.expertise ?? []
|
||||
const softSkills = mentor?.languages ?? []
|
||||
|
||||
return (
|
||||
<div className="bg-white px-4 py-5 space-y-6 md:px-8 md:pt-6 md:pb-0 xl:space-y-0 xl:py-[30px] xl:flex xl:gap-x-9 xl:justify-between">
|
||||
<div
|
||||
@@ -33,10 +41,10 @@ export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
"md:text-[19px] md:mb-2 md:text-start xl:text-[23px]",
|
||||
)}
|
||||
>
|
||||
Muhammad Firdaus Oi Oi Oi, S.H., M.H.
|
||||
{name}
|
||||
</h1>
|
||||
<p className="text-xs mb-4 text-neutral-600 md:mb-5 md:text-[15px] xl:text-[19px] xl:mb-5">
|
||||
UI Designer at Oray orayan Studios
|
||||
{role} at {company}
|
||||
</p>
|
||||
<div className="flex items-center gap-x-2.5 w-full max-w-max border border-primary-50 p-1.5 rounded-md mx-auto md:ms-0">
|
||||
<div
|
||||
@@ -56,7 +64,7 @@ export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
<div>
|
||||
<h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Expertise</h2>
|
||||
<div className="p-4 bg-primary-50 border border-primary-100 rounded-md flex flex-wrap gap-3">
|
||||
<For data={['UI Design', 'UX Reseacrh']}>
|
||||
<For data={expertise}>
|
||||
{(item) => (
|
||||
<div key={item} className="bg-primary-300 text-primary-600 px-3 py-2 rounded-md text-[10px] md:font-medium xl:text-xs xl:font-semibold">
|
||||
{item}
|
||||
@@ -68,7 +76,7 @@ export const ProfileSection: React.FC<Props> = ({ onBook }) => {
|
||||
<div>
|
||||
<h2 className="text-xs font-semibold mb-3 md:text-[15px] xl:text-[19px]">Soft Skills</h2>
|
||||
<div className="p-4 bg-primary-50 border border-primary-100 rounded-md flex flex-wrap gap-3">
|
||||
<For data={['Design Thinking', 'Communication', 'Problem Solving', '19:00 WIB']}>
|
||||
<For data={softSkills}>
|
||||
{(item) => (
|
||||
<div key={item} className="bg-primary-300 text-primary-600 px-3 py-2 rounded-md text-[10px] md:font-medium xl:text-xs xl:font-semibold">
|
||||
{item}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { ProfileSection } from './_components/sections/profile'
|
||||
import { StatisticsSection } from './_components/sections/senpai-statistics'
|
||||
import { TopicsSection } from './_components/sections/topics'
|
||||
@@ -7,44 +8,61 @@ import { EducationSection } from './_components/sections/education'
|
||||
import { SenpaiScheduleSection } from './_components/sections/senpai-schedule'
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms'
|
||||
import { AppointmentModal } from './_components/modals/appointment'
|
||||
import { useGetMentorDetail } from '@imphnen-frontend-service/service'
|
||||
import { Show } from '@imphnen-frontend-service/utils'
|
||||
|
||||
export const Components: FC = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { id } = useParams()
|
||||
const { data: mentor, isLoading, isError } = useGetMentorDetail(id ?? '')
|
||||
|
||||
const resume = mentor?.bio ?? ''
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20">
|
||||
<div className="max-w-7xl mx-auto space-y-8 md:bg-white xl:bg-transparent">
|
||||
<ProfileSection onBook={() => setOpen(true)} />
|
||||
<Show
|
||||
condition={!isLoading && !isError}
|
||||
fallback={
|
||||
<section className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20">
|
||||
<div className="max-w-7xl mx-auto text-center text-neutral-500 py-10">
|
||||
{isError ? 'Mentor tidak ditemukan' : 'Memuat profil mentor...'}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
>
|
||||
<section className="w-full p-8 md:py-14 md:px-[60px] lg:py-16 lg:px-20">
|
||||
<div className="max-w-7xl mx-auto space-y-8 md:bg-white xl:bg-transparent">
|
||||
<ProfileSection onBook={() => setOpen(true)} mentor={mentor} />
|
||||
|
||||
<Button type="button" size="sm" className="w-full md:hidden" onClick={() => setOpen(true)}>
|
||||
Book Your Senpai!
|
||||
</Button>
|
||||
<Button type="button" size="sm" className="w-full md:hidden" onClick={() => setOpen(true)}>
|
||||
Book Your Senpai!
|
||||
</Button>
|
||||
|
||||
<div className="bg-white px-4 py-5 md:px-8 md:pb-6 md:pt-0 xl:py-7 xl:flex xl:gap-x-10">
|
||||
<div className="space-y-10 md:space-y-7 xl:flex-1">
|
||||
<StatisticsSection />
|
||||
<TopicsSection />
|
||||
<div className="bg-white px-4 py-5 md:px-8 md:pb-6 md:pt-0 xl:py-7 xl:flex xl:gap-x-10">
|
||||
<div className="space-y-10 md:space-y-7 xl:flex-1">
|
||||
<StatisticsSection />
|
||||
<TopicsSection />
|
||||
|
||||
<div className="px-6 py-8 rounded-md shadow-md">
|
||||
<h2 className="text-xs text-neutral-800 font-semibold mb-5 md:text-[15px] xl:text-[19px]">Senpai Resume</h2>
|
||||
<p className="text-[10px] font-medium text-neutral-600 text-pretty md:text-[15px]">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi. Aliquam in hendrerit urna. Pellentesque sit amet sapien fringilla, mattis ligula consectetur, ultrices mauris. Maecenas vitae mattis tellus. Nullam quis imperdiet augue. Vestibulum auctor ornare leo, non suscipit magna interdum eu. Curabitur pellentesque nibh nibh, at maximus ante fermentum sit amet. Pellentesque commodo lacus at sodales sodales. Quisque sagittis orci ut diam condimentum, vel euismod erat placerat. In iaculis arcu eros, eget tempus orci facilisis id.
|
||||
</p>
|
||||
<div className="px-6 py-8 rounded-md shadow-md">
|
||||
<h2 className="text-xs text-neutral-800 font-semibold mb-5 md:text-[15px] xl:text-[19px]">Senpai Resume</h2>
|
||||
<p className="text-[10px] font-medium text-neutral-600 text-pretty md:text-[15px]">
|
||||
{resume || 'Belum ada deskripsi dari senpai ini.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ExperienceSection />
|
||||
<EducationSection />
|
||||
</div>
|
||||
|
||||
<ExperienceSection />
|
||||
<EducationSection />
|
||||
</div>
|
||||
|
||||
<div className="hidden xl:block xl:w-[400px]">
|
||||
<SenpaiScheduleSection onBook={() => setOpen(true)} />
|
||||
<div className="hidden xl:block xl:w-[400px]">
|
||||
<SenpaiScheduleSection onBook={() => setOpen(true)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<AppointmentModal open={open} setOpen={setOpen} />
|
||||
<AppointmentModal open={open} setOpen={setOpen} />
|
||||
</Show>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,40 +1,70 @@
|
||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { For } from '@imphnen-frontend-service/utils';
|
||||
import { TMentorDetail } from '@imphnen-frontend-service/service';
|
||||
import { FC } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const MentorCard: FC = () => {
|
||||
type TMentorCardProps = {
|
||||
mentor: TMentorDetail;
|
||||
};
|
||||
|
||||
export const MentorCard: FC<TMentorCardProps> = ({ mentor }) => {
|
||||
const experience =
|
||||
mentor.years_of_experience > 0
|
||||
? `${mentor.years_of_experience} Years Experience`
|
||||
: 'New Mentor';
|
||||
const role = mentor.current_role || 'Mentor';
|
||||
const company = mentor.current_company || 'IMPHNEN';
|
||||
const softSkills = mentor.expertise || [];
|
||||
const shownSkills = softSkills.slice(0, 2);
|
||||
const extraCount = Math.max(0, softSkills.length - shownSkills.length);
|
||||
|
||||
return (
|
||||
<div className="p-2.5 rounded-md bg-white shadow flex gap-x-4 items-start md:p-4 md:flex-col md:rounded-lg md:gap-y-4">
|
||||
<div className="size-[60px] rounded-md overflow-hidden md:w-full md:h-auto md:aspect-square">
|
||||
<img src="/image/testimonial.webp" alt="Mentor" className="w-full object-cover" />
|
||||
<img src="/image/testimonial.webp" alt={mentor.fullname ?? 'Mentor'} className="w-full object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="text" size="sm" className="bg-primary-100 h-auto px-1.5 py-1 text-[8px] font-normal mb-2 hover:bg-primary-100 md:text-[10px] md:font-medium md:mb-4">
|
||||
3-5 Years Experience
|
||||
{experience}
|
||||
</Button>
|
||||
<h2 className="mb-1">
|
||||
<Link to="/mentoring/detail" className="text-xs font-semibold text-primary-500 md:text-[15px] md:font-semibold lg:text-[19px]">
|
||||
Fullname
|
||||
<Link to={`/mentoring/${mentor.id}`} className="text-xs font-semibold text-primary-500 md:text-[15px] md:font-semibold lg:text-[19px]">
|
||||
{mentor.fullname || mentor.legal_name || 'Mentor'}
|
||||
</Link>
|
||||
</h2>
|
||||
<p className="text-[8px] text-neutral-500 mb-3 md:text-[10px] md:font-medium md:mb-4 lg:text-xs">
|
||||
Full Stack Enjoyer at name company
|
||||
{role} at {company}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<p className="text-[8px] text-neutral-500 mb-1 md:mb-2 lg:text-[10px]">Soft Skills :</p>
|
||||
<div className="space-x-2">
|
||||
<Button variant="text" size="sm" className="bg-primary-200 h-auto px-1.5 py-1 text-[8px] font-normal hover:bg-primary-200 lg:text-[10px]">
|
||||
Communication
|
||||
</Button>
|
||||
<Button variant="text" size="sm" className="bg-primary-200 h-auto px-1.5 py-1 text-[8px] font-normal hidden hover:bg-primary-200 md:inline-block lg:text-[10px]">
|
||||
Communication
|
||||
</Button>
|
||||
<Button variant="text" size="sm" className="h-auto px-1.5 py-1 text-[8px] font-normal text-neutral-500 hover:bg-transparent lg:text-[10px]">
|
||||
+2
|
||||
</Button>
|
||||
{softSkills.length > 0 && (
|
||||
<div>
|
||||
<p className="text-[8px] text-neutral-500 mb-1 md:mb-2 lg:text-[10px]">Soft Skills :</p>
|
||||
<div className="space-x-2">
|
||||
<For data={shownSkills}>
|
||||
{(skill) => (
|
||||
<Button
|
||||
key={skill}
|
||||
variant="text"
|
||||
size="sm"
|
||||
className="bg-primary-200 h-auto px-1.5 py-1 text-[8px] font-normal hover:bg-primary-200 lg:text-[10px]"
|
||||
>
|
||||
{skill}
|
||||
</Button>
|
||||
)}
|
||||
</For>
|
||||
{extraCount > 0 && (
|
||||
<Button
|
||||
variant="text"
|
||||
size="sm"
|
||||
className="h-auto px-1.5 py-1 text-[8px] font-normal text-neutral-500 hover:bg-transparent lg:text-[10px]"
|
||||
>
|
||||
+{extraCount}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,27 +3,24 @@ import { BannerSection } from './_components/banner-section';
|
||||
import { Topics } from './_components/topics';
|
||||
import { Input } from '@imphnen-frontend-service/ui/atoms';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { For } from '@imphnen-frontend-service/utils';
|
||||
import { For, Show } from '@imphnen-frontend-service/utils';
|
||||
import { MentorCard } from './_components/mentor-card';
|
||||
import { Pagination } from '@imphnen-frontend-service/ui/molecules';
|
||||
import { useGetMentors } from '@imphnen-frontend-service/service';
|
||||
import { getCoreRowModel, getPaginationRowModel, PaginationState, useReactTable } from '@tanstack/react-table';
|
||||
import { motion, useInView, Variants } from 'framer-motion';
|
||||
|
||||
const TEMP_DATA = [
|
||||
{ id: 1, name: 'John Doe' },
|
||||
{ id: 2, name: 'John Doe' },
|
||||
{ id: 3, name: 'John Doe' },
|
||||
{ id: 4, name: 'John Doe' },
|
||||
]
|
||||
|
||||
export const Components: FC = (): ReactElement => {
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 3,
|
||||
pageSize: 8,
|
||||
});
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const { data: mentors, isLoading } = useGetMentors({ search });
|
||||
|
||||
const table = useReactTable({
|
||||
data: TEMP_DATA,
|
||||
data: mentors ?? [],
|
||||
columns: [],
|
||||
state: { pagination },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
@@ -31,6 +28,8 @@ export const Components: FC = (): ReactElement => {
|
||||
onPaginationChange: setPagination,
|
||||
});
|
||||
|
||||
const pageMentors = table.getRowModel().rows.map((row) => row.original);
|
||||
|
||||
const ref = useRef(null)
|
||||
const isInView = useInView(ref, { once: true, amount: 0.2 })
|
||||
|
||||
@@ -91,6 +90,8 @@ export const Components: FC = (): ReactElement => {
|
||||
<Input
|
||||
placeholder="Cari berdasarkan nama, posisi/peran"
|
||||
className="relative min-w-full w-full"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<SearchOutlined className="absolute right-2.5 top-1/2 -translate-y-1/2 text-primary-500 size-2.5 cursor-text md:me-12 lg:me-0" />
|
||||
</motion.div>
|
||||
@@ -101,13 +102,31 @@ export const Components: FC = (): ReactElement => {
|
||||
initial="hidden"
|
||||
animate={isInView ? 'visible' : 'hidden'}
|
||||
>
|
||||
<For data={Array.from({ length: 8 })}>
|
||||
{(_, index) => (
|
||||
<motion.div key={index} variants={childVariants}>
|
||||
<MentorCard />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
<Show
|
||||
condition={!isLoading}
|
||||
fallback={
|
||||
<motion.p variants={childVariants} className="text-center text-neutral-500 col-span-full py-10">
|
||||
Memuat data mentor...
|
||||
</motion.p>
|
||||
}
|
||||
>
|
||||
<Show
|
||||
condition={pageMentors.length > 0}
|
||||
fallback={
|
||||
<motion.p variants={childVariants} className="text-center text-neutral-500 col-span-full py-10">
|
||||
Tidak ada mentor ditemukan
|
||||
</motion.p>
|
||||
}
|
||||
>
|
||||
<For data={pageMentors}>
|
||||
{(mentor, index) => (
|
||||
<motion.div key={mentor.id ?? index} variants={childVariants}>
|
||||
<MentorCard mentor={mentor} />
|
||||
</motion.div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</Show>
|
||||
</motion.div>
|
||||
|
||||
<Pagination table={table} />
|
||||
|
||||
Reference in New Issue
Block a user