Skip to content
Merged
69 changes: 54 additions & 15 deletions src/components/layout/header/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const NAV_ITEMS: Record<UserRole, NavItems[]> = {
const Nav = () => {
const { role, isLogin, logout } = useAuth();
const { applications } = useUserApplications();

const [open, setOpen] = useState(false);
// 읽음 μ²˜λ¦¬ν•œ μ•Œλ¦Ό IDλ“€ (간단 둜컬 μƒνƒœ)
const [readIds, setReadIds] = useState<Set<string>>(new Set());
Expand All @@ -43,10 +44,6 @@ const Nav = () => {
}));
}, [applications, readIds]);

const unreadCount = alerts.filter(a => !a.read).length;
const bellIcon: 'notificationOn' | 'notificationOff' =
unreadCount > 0 ? 'notificationOn' : 'notificationOff';

const handleRead = (id: string) => {
setReadIds(prev => {
const next = new Set(prev);
Expand All @@ -55,9 +52,17 @@ const Nav = () => {
});
};

// βœ… role이 초기 undefined일 수 μžˆμ–΄ λ°©μ–΄
const currentRole: UserRole = (role ?? 'guest') as UserRole;

// βœ… μ•„μ΄μ½˜μ€ "νŒ¨λ„ μ—΄λ¦Ό μƒνƒœ"둜만 ν† κΈ€
const bellIcon: 'notificationOn' | 'notificationOff' = open
? 'notificationOn'
: 'notificationOff';

return (
<nav className={cn('flex shrink-0 items-center gap-4 text-body-m font-bold', 'desktop:gap-10')}>
{NAV_ITEMS[role].map(({ href, label }) => (
{(NAV_ITEMS[currentRole] ?? []).map(({ href, label }) => (
<Link key={href} href={href}>
{label}
</Link>
Expand All @@ -74,20 +79,54 @@ const Nav = () => {
>
λ‘œκ·Έμ•„μ›ƒ
</button>
<button type='button' aria-label='μ•Œλ¦Ό ν™•μΈν•˜κΈ°' onClick={() => setOpen(true)}>
<Icon iconName={bellIcon} iconSize='rg' bigScreenSize='md' ariaLabel='μ•Œλ¦Ό' />
</button>
<div className='absolute right-4 top-[64px] z-[50] w-full max-w-[420px]'>
<Notification
alerts={alerts}
onRead={handleRead}
isOpen={open}
onClose={() => setOpen(false)}

{/* μ•Œλ¦Ό λ²„νŠΌ: ν† κΈ€ */}
<button
type='button'
aria-label='μ•Œλ¦Ό ν™•μΈν•˜κΈ°'
aria-expanded={open}
aria-controls='notification-panel'
onClick={() => setOpen(prev => !prev)}
className='relative'
>
{/* 일뢀 λ©”λͺ¨μ΄μ œμ΄μ…˜ λŒ€λΉ„ κ°•μ œ λ¦¬λ Œλ” */}
<Icon
key={open ? 'bell-on' : 'bell-off'}
iconName={bellIcon}
iconSize='rg'
bigScreenSize='md'
ariaLabel='μ•Œλ¦Ό'
/>
</div>
</button>

{/* νŒ¨λ„: 열릴 λ•Œλ§Œ λ Œλ” + μ‚¬μ΄μ¦ˆ κ³ μ •(ν”Όκ·Έλ§ˆ) */}
{open && (
<div
id='notification-panel'
role='dialog'
aria-modal='true'
className={cn(
'absolute right-2 top-[64px] z-[50]',
// λͺ¨λ°”일 κΈ°λ³Έ 375 x 812
'h-[812px] w-[375px]',
// νƒœλΈ”λ¦Ώ/λ°μŠ€ν¬νƒ‘ 368 x 419
'tablet:h-[419px] tablet:w-[368px]',
'desktop:h-[419px] desktop:w-[368px]',
'overflow-auto rounded-xl'
)}
>
<Notification
alerts={alerts}
onRead={handleRead}
isOpen={open}
onClose={() => setOpen(false)} // λ‚΄λΆ€ 닫기와 연동
/>
</div>
)}
</>
)}
</nav>
);
};

export default Nav;
215 changes: 125 additions & 90 deletions src/pages/my-profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// src/pages/my-profile/index.tsx
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useMemo, useState } from 'react';
Expand Down Expand Up @@ -46,124 +47,158 @@ export default function MyProfileDetailPage() {
startsAt: a.notice.item.startsAt,
workhour: a.notice.item.workhour,
status,
// employee ν‘œμ—μ„œλŠ” λ―Έμ‚¬μš© β€” νƒ€μž…λ§Œ μΆ©μ‘±
bio: '',
phone: '',
};
});
}, [applications]);

const pagedRows = useMemo(() => rows.slice(offset, offset + limit), [rows, offset]);
// λ‘œλ”© 쀑에도 λ§ˆμ§€λ§‰ 성곡 데이터λ₯Ό μœ μ§€ (ν™”λ©΄ 흔듀림 λ°©μ§€)
const [stableRows, setStableRows] = useState<TableRowProps[]>([]);
const [stableTotal, setStableTotal] = useState<number>(0);
useEffect(() => {
if (rows.length > 0) {
setStableRows(rows);
setStableTotal(applications.length);
}
}, [rows, applications.length]);

// rows λ³€ν™” μ‹œ 첫 νŽ˜μ΄μ§€λ‘œ 리셋 (νŽ˜μ΄μ§€λ„€μ΄μ…˜ UX 보강)
// rows λ³€ν™” μ‹œ 첫 νŽ˜μ΄μ§€λ‘œ 리셋
useEffect(() => {
setOffset(0);
}, [rows.length]);

return (
<main className='mx-auto w-full max-w-[1440px] px-4 py-6 tablet:py-8'>
<div className='mx-auto w-full desktop:max-w-[957px]'>
<h1 className='mb-6 text-heading-l font-semibold'>λ‚΄ ν”„λ‘œν•„</h1>
const currentRows = rows.length > 0 ? rows : stableRows;
const currentTotal = rows.length > 0 ? applications.length : stableTotal;

const pagedRows = useMemo(() => currentRows.slice(offset, offset + limit), [currentRows, offset]);

{/* ν”„λ‘œν•„μ΄ μ—†μœΌλ©΄ 등둝 ν”„λ ˆμž„ */}
return (
<main className='mx-auto w-full max-w-[1440px] py-6 tablet:py-8'>
{/* 곡톡 μ»¨ν…Œμ΄λ„ˆ: Tableκ³Ό μ’ŒμΈ‘μ„ /폭 동일 */}
<div className='mx-auto w-full max-w-full px-8 md:px-10 lg:mx-auto lg:max-w-[1000px] lg:px-0'>
{profileIsEmpty ? (
<Frame
title=''
content='λ‚΄ ν”„λ‘œν•„μ„ λ“±λ‘ν•˜κ³  μ›ν•˜λŠ” κ°€κ²Œμ— 지원해 λ³΄μ„Έμš”.'
buttonText='λ‚΄ ν”„λ‘œν•„ λ“±λ‘ν•˜κΈ°'
href='/my-profile/register'
/>
<>
<h1 className='mb-6 text-heading-l font-semibold'>λ‚΄ ν”„λ‘œν•„</h1>
<div className='mx-auto w-full'>
<Frame
title=''
content='λ‚΄ ν”„λ‘œν•„μ„ λ“±λ‘ν•˜κ³  μ›ν•˜λŠ” κ°€κ²Œμ— 지원해 λ³΄μ„Έμš”.'
buttonText='λ‚΄ ν”„λ‘œν•„ λ“±λ‘ν•˜κΈ°'
href='/my-profile/register'
/>
</div>
</>
) : (
// ν”„λ‘œν•„ μΉ΄λ“œ(ν”Όκ·Έλ§ˆ μŠ€νƒ€μΌ)
<section
className='w-full rounded-[24px] border border-[var(--red-300)] bg-[var(--red-100)] p-5 tablet:p-7 desktop:p-8'
aria-label='λ‚΄ ν”„λ‘œν•„ μš”μ•½'
>
<div className='flex items-start justify-between gap-4'>
<div className='flex-1'>
<p className='mb-1 text-body-m font-semibold text-[var(--red-500)]'>이름</p>
<p className='text-heading-m font-extrabold leading-tight text-[var(--gray-900)]'>
{user?.name || 'β€”'}
</p>

{/* μ—°λ½μ²˜ */}
<div className='mt-4 flex items-center gap-2 text-[var(--gray-600)]'>
<Image
src={ICONS.phone}
alt='μ „ν™”'
width={24}
height={24}
className={ICON_SIZES.md}
priority
/>
<span className='text-body-m'>{user?.phone || 'β€”'}</span>
</div>

{/* μ„ ν˜Έ μ§€μ—­ */}
<div className='mt-3 flex items-center gap-2 text-[var(--gray-600)]'>
<Image
src={ICONS.map}
alt='지도'
width={24}
height={24}
className={ICON_SIZES.md}
priority
/>
<span className='text-body-m'>μ„ ν˜Έ μ§€μ—­: {(user?.address as string) || 'β€”'}</span>
</div>
// βœ… λ°μŠ€ν¬νƒ‘μ—μ„œ 제λͺ©κ³Ό μΉ΄λ“œκ°€ 같은 flex 라인에 놓이도둝
<div className='desktop:flex desktop:items-start desktop:gap-8'>
<h1 className='mb-6 text-heading-l font-semibold desktop:mb-0 desktop:w-[200px] desktop:shrink-0 desktop:pt-2'>
λ‚΄ ν”„λ‘œν•„
</h1>

{/* μ†Œκ°œ */}
{user?.bio && (
<p className='mt-6 whitespace-pre-wrap text-body-m text-[var(--gray-900)]'>
{user.bio}
<section
className='flex-1 rounded-[24px] bg-[var(--red-100)] p-4 tablet:p-6 desktop:max-w-[780px] desktop:p-7'
aria-label='λ‚΄ ν”„λ‘œν•„ μš”μ•½'
>
<div className='flex items-start justify-between gap-3 tablet:gap-4 desktop:gap-6'>
<div className='flex-1'>
<p className='mb-1 text-body-m font-semibold text-[var(--red-500)]'>이름</p>
<p className='text-heading-m font-extrabold leading-tight text-[var(--gray-900)]'>
{user?.name || 'β€”'}
</p>
)}
</div>

{/* μš°μƒλ‹¨ νŽΈμ§‘ λ²„νŠΌ */}
<div className='ml-4 shrink-0'>
<Button
variant='secondary'
size='lgFixed'
className='w-[169px]'
as={Link}
href='/my-profile/register'
>
νŽΈμ§‘ν•˜κΈ°
</Button>
{/* μ—°λ½μ²˜ */}
<div className='mt-3 flex items-center gap-2 text-[var(--gray-600)] tablet:mt-4'>
<Image
src={ICONS.phone}
alt='μ „ν™”'
width={24}
height={24}
className={ICON_SIZES.md}
priority
/>
<span className='text-body-m'>{user?.phone || 'β€”'}</span>
</div>

{/* μ„ ν˜Έ μ§€μ—­ */}
<div className='mt-2 flex items-center gap-2 text-[var(--gray-600)] tablet:mt-3'>
<Image
src={ICONS.map}
alt='지도'
width={24}
height={24}
className={ICON_SIZES.md}
priority
/>
<span className='text-body-m'>
μ„ ν˜Έ μ§€μ—­: {(user?.address as string) || 'β€”'}
</span>
</div>

{/* μ†Œκ°œ */}
{user?.bio && (
<p className='mt-5 whitespace-pre-wrap text-body-m text-[var(--gray-900)] tablet:mt-6'>
{user.bio}
</p>
)}
</div>

{/* μš°μƒλ‹¨ νŽΈμ§‘ λ²„νŠΌ */}
<div className='ml-3 shrink-0 tablet:ml-4'>
<Button
variant='secondary'
size='lgFixed'
className='w-[169px]'
as={Link}
href='/my-profile/register'
>
νŽΈμ§‘ν•˜κΈ°
</Button>
</div>
</div>
</div>
</section>
</section>
</div>
)}
</div>

{/* μ‹ μ²­ λ‚΄μ—­ β€” ν”„λ‘œν•„ 있고 둜그인 μƒνƒœμΌ λ•Œλ§Œ */}
{/* ν•˜λ‹¨: μ‹ μ²­ λ‚΄μ—­ β€” ν”„λ‘œν•„ 있고 둜그인 μƒνƒœμΌ λ•Œλ§Œ */}
{!profileIsEmpty && isLogin && (
<section className='mt-8'>
{isLoading ? (
<div className='text-body-m text-[var(--gray-500)]'>λΆˆλŸ¬μ˜€λŠ” 쀑…</div>
) : rows.length === 0 ? (
<div className='mx-auto w-full desktop:max-w-[964px]'>
<section className='mt-8 bg-[var(--gray-50)] py-8'>
<div className='mx-auto w-full max-w-full px-8 md:px-10 lg:mx-auto lg:max-w-[1000px] lg:px-0'>
{isLoading && currentRows.length === 0 ? (
<>
<div className='px-0 text-xl font-bold'>
<h2 className='text-heading-l font-semibold'>μ‹ μ²­ λ‚΄μ—­</h2>
</div>
<div className='m-7 overflow-hidden rounded-lg border bg-white lg:mx-auto lg:max-w-[1000px]'>
<div className='h-[48px] bg-[var(--red-100)]' />
{[...Array(5)].map((_, i) => (
<div key={i} className='h-[56px] border-t bg-white last:border-b' />
))}
<div className='flex justify-center px-3 py-2' />
</div>
</>
) : currentRows.length === 0 ? (
<Frame
title='μ‹ μ²­ λ‚΄μ—­'
content='λ§ˆμŒμ— λ“œλŠ” 곡고λ₯Ό μ°Ύμ•„ 지원해 λ³΄μ„Έμš”.'
buttonText='곡고 λ³΄λŸ¬κ°€κΈ°'
href='/notices'
/>
</div>
) : (
<div className='mx-auto w-full desktop:max-w-[964px]'>
<Table
headers={headers}
tableData={pagedRows}
userRole={userType}
total={applications.length}
limit={limit}
offset={offset}
onPageChange={setOffset}
/>
</div>
)}
) : (
<div className='mx-auto w-full lg:mx-auto lg:max-w-[1000px]'>
<Table
headers={headers}
tableData={pagedRows}
userRole={userType}
total={currentTotal}
limit={limit}
offset={offset}
onPageChange={setOffset}
/>
</div>
)}
</div>
</section>
)}
</main>
Expand Down
Loading
Loading