-
Notifications
You must be signed in to change notification settings - Fork 4
my-profile 페이지네이션/알림 모달 연동 #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
1ba6250
eb7f03a
c73bbfc
6989504
7c4eccb
aee6706
8a5f2a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. table 컴포넌트를 인화님도 수정하신 상태여서 PR merge 후 재 push 부탁드립니다 |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 페이지에서는 디스코드에서 나왔던 유저 권한에 따른 리다이렉트 화면이 필요해보입니다 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,62 +7,69 @@ import Button from '@/components/ui/button/button'; | |
| import Table from '@/components/ui/table/Table'; | ||
| import type { TableRowProps } from '@/components/ui/table/TableRowProps'; | ||
| import { ICONS, ICON_SIZES } from '@/constants/icon'; | ||
| import { useUserApplications } from '@/context/userApplicationsProvider'; | ||
| import useAuth from '@/hooks/useAuth'; | ||
| import type { UserType } from '@/types/user'; // 'employee' | 'employer' | ||
| import type { ApiResponse } from '@/types/api'; | ||
| import type { ApplicationItem } from '@/types/applications'; | ||
| import type { User, UserType } from '@/types/user'; | ||
|
|
||
| export default function MyProfileDetailPage() { | ||
| const { isLogin, user } = useAuth(); | ||
| const { applications, isLoading } = useUserApplications(); | ||
|
|
||
| // 임시: 신청 내역은 아직 서버 API가 없다면 로컬 모의 데이터 사용 | ||
| const [applications, setApplications] = useState<TableRowProps[]>([]); | ||
| const [isLoadingApps, setIsLoadingApps] = useState<boolean>(true); | ||
|
|
||
| // 페이지네이션 상태(팀 Table 시그니처 맞춤) | ||
| // 테이블 페이지네이션 | ||
| const [offset, setOffset] = useState(0); | ||
| const limit = 10; | ||
|
|
||
| // (모의) 저장 키 - 나중에 서버 API로 교체 가능 | ||
| const appsKey = useMemo(() => `thejulge_apps_${user?.id ?? 'guest'}`, [user?.id]); | ||
|
|
||
| // 신청 내역 로드(모의) | ||
| useEffect(() => { | ||
| if (!isLogin) return; | ||
| setIsLoadingApps(true); | ||
| try { | ||
| const txt = localStorage.getItem(appsKey); | ||
| const parsed = txt ? (JSON.parse(txt) as TableRowProps[]) : []; | ||
| setApplications(parsed); | ||
| } catch { | ||
| setApplications([]); | ||
| } finally { | ||
| setIsLoadingApps(false); | ||
| } | ||
| }, [isLogin, appsKey]); | ||
|
|
||
| // 프로필 비었는지 판단 (user 기준) | ||
| const name = user?.name ?? ''; | ||
| const phone = user?.phone ?? ''; | ||
| // 서버 필드명이 address라고 가정 | ||
| const address = (user?.address as string) ?? ''; | ||
| const bio = user?.bio ?? ''; | ||
|
|
||
| const isProfileEmpty = !name && !phone && !address && !(bio && bio.trim()); | ||
| const limit = 5; | ||
|
|
||
| // 프로필 비었는지 판단 (User | null 안전) | ||
| function isProfileEmpty(u: User | null): boolean { | ||
| const name = u?.name?.trim() ?? ''; | ||
| const phone = u?.phone?.trim() ?? ''; | ||
| const address = (u?.address as string | undefined)?.trim() ?? ''; | ||
| const bio = u?.bio?.trim() ?? ''; | ||
| return !name && !phone && !address && !bio; | ||
| } | ||
| const profileIsEmpty = useMemo(() => isProfileEmpty(user), [user]); | ||
|
|
||
| const headers: string[] = ['가게명', '근무일시', '시급', '상태']; | ||
| const userType: UserType = 'employee'; | ||
|
|
||
| // 현재 페이지 조각 | ||
| const paged = useMemo(() => applications.slice(offset, offset + limit), [applications, offset]); | ||
| // 서버 응답 → TableRowProps 매핑 | ||
| const rows: TableRowProps[] = useMemo(() => { | ||
| return applications.map((app: ApiResponse<ApplicationItem>) => { | ||
| const a = app.item; | ||
| const status = | ||
| a.status === 'accepted' ? 'approved' : a.status === 'rejected' ? 'rejected' : 'pending'; | ||
| return { | ||
| id: a.id, | ||
| name: a.shop.item.name, | ||
| hourlyPay: `${a.notice.item.hourlyPay.toLocaleString()}원`, | ||
| 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]); | ||
|
|
||
| // rows 변화 시 첫 페이지로 리셋 (페이지네이션 UX 보강) | ||
| 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> | ||
|
|
||
| {/* 프로필이 없으면 등록 프레임 */} | ||
| {isProfileEmpty ? ( | ||
| {profileIsEmpty ? ( | ||
| <Frame | ||
| title='내 프로필' | ||
| title='' | ||
| content='내 프로필을 등록하고 원하는 가게에 지원해 보세요.' | ||
| buttonText='내 프로필 등록하기' | ||
| href='/my-profile/register' | ||
|
|
@@ -77,7 +84,7 @@ export default function MyProfileDetailPage() { | |
| <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)]'> | ||
| {name || '—'} | ||
| {user?.name || '—'} | ||
| </p> | ||
|
|
||
| {/* 연락처 */} | ||
|
|
@@ -90,7 +97,7 @@ export default function MyProfileDetailPage() { | |
| className={ICON_SIZES.md} | ||
| priority | ||
| /> | ||
| <span className='text-body-m'>{phone || '—'}</span> | ||
| <span className='text-body-m'>{user?.phone || '—'}</span> | ||
| </div> | ||
|
|
||
| {/* 선호 지역 */} | ||
|
|
@@ -103,13 +110,13 @@ export default function MyProfileDetailPage() { | |
| className={ICON_SIZES.md} | ||
| priority | ||
| /> | ||
| <span className='text-body-m'>선호 지역: {address || '—'}</span> | ||
| <span className='text-body-m'>선호 지역: {(user?.address as string) || '—'}</span> | ||
| </div> | ||
|
|
||
| {/* 소개 */} | ||
| {bio && ( | ||
| {user?.bio && ( | ||
| <p className='mt-6 whitespace-pre-wrap text-body-m text-[var(--gray-900)]'> | ||
| {bio} | ||
| {user.bio} | ||
| </p> | ||
| )} | ||
| </div> | ||
|
|
@@ -131,12 +138,12 @@ export default function MyProfileDetailPage() { | |
| )} | ||
| </div> | ||
|
|
||
| {/* 신청 내역 — 프로필 있을 때만 노출 */} | ||
| {!isProfileEmpty && ( | ||
| {/* 신청 내역 — 프로필 있고 로그인 상태일 때만 */} | ||
| {!profileIsEmpty && isLogin && ( | ||
| <section className='mt-8'> | ||
| {isLoadingApps ? ( | ||
| {isLoading ? ( | ||
| <div className='text-body-m text-[var(--gray-500)]'>불러오는 중…</div> | ||
| ) : applications.length === 0 ? ( | ||
| ) : rows.length === 0 ? ( | ||
| <div className='mx-auto w-full desktop:max-w-[964px]'> | ||
| <Frame | ||
| title='신청 내역' | ||
|
|
@@ -147,16 +154,14 @@ export default function MyProfileDetailPage() { | |
| </div> | ||
| ) : ( | ||
| <div className='mx-auto w-full desktop:max-w-[964px]'> | ||
| <h2 className='mb-4 text-heading-s font-semibold'>신청 내역</h2> | ||
| {/* 팀 Table이 요구하는 pagination props 전달 */} | ||
| <Table | ||
| headers={headers} | ||
| data={paged} | ||
| data={pagedRows} | ||
|
||
| userType={userType} | ||
| total={applications.length} | ||
| total={rows.length} | ||
| limit={limit} | ||
| offset={offset} | ||
| onPageChange={setOffset} // 팀 Table이 pageIndex를 요구하면 (p)=>setOffset(p*limit) 로 바꾸세요. | ||
| onPageChange={setOffset} | ||
| /> | ||
| </div> | ||
| )} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 컴포넌트의 컬러를 var-- 형태로 변환하신 이유가 있으실까요 ?
왜냐면 기존과 동일한 문법인데 바꾸신 이유가 있을까 해서요!