Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
653875e
🔧 chore:리팩토링하여 필요없는 파일 삭제 #51
sohyun0 Oct 14, 2025
ab58a5b
✨ feat: 공고별로 컴포넌트를 하나의 컴포넌트로 작성 #51
sohyun0 Oct 14, 2025
494e17b
✨ feat:props 제거 및 provider 사용 #51
sohyun0 Oct 14, 2025
7614ff5
✨ feat: provider value useMemo 사용 #51
sohyun0 Oct 14, 2025
3a4ad8a
🔧 chore:리팩토링하여 필요없는 파일 삭제 #51
sohyun0 Oct 14, 2025
2cd9c01
✨ feat: notice filter 컴포넌트로 분리 #51
sohyun0 Oct 14, 2025
6cb0319
✨ feat: 상단 검색기능 추가 #51
sohyun0 Oct 14, 2025
5f443aa
🔧 chore:리팩토링하여 필요없는 파일 삭제 #51
sohyun0 Oct 14, 2025
7dc5257
Merge remote-tracking branch 'upstream/develop' into feat/#51-mainpage
sohyun0 Oct 15, 2025
98a5476
Merge remote-tracking branch 'upstream/develop' into feat/#51-mainpage
sohyun0 Oct 16, 2025
db821f3
✨ feat: 공고 관련 api 작성 #52
sohyun0 Oct 16, 2025
8811ec5
✨ feat: 공고 상세페이지 관련 타입 작성 #52
sohyun0 Oct 16, 2025
db3faf1
✨ feat: 공고 조회시 과거 시간 보정 추가 #52
sohyun0 Oct 16, 2025
1951d13
✨ feat: 타입수정 #52
sohyun0 Oct 16, 2025
d5f303d
🔧 chore: eslint 주석처리 #52
sohyun0 Oct 16, 2025
e006519
✨ feat: 공고 조회 provider 작성 #52
sohyun0 Oct 16, 2025
2abeec2
✨ feat: 최근에 본 공고 기능 추가 #52
sohyun0 Oct 16, 2025
8593ef9
✨ feat: 알바 신청 및 취소 기능 추가 #52
sohyun0 Oct 16, 2025
3bb8933
💄design: 컨텐츠 전체 배경 적용 #52
sohyun0 Oct 16, 2025
0a4aa69
✨ feat: 유저의 프로필정보가 없을떄 전체노출 #52
sohyun0 Oct 16, 2025
009fbd6
✨ feat: 토스트 상단 노출 #52
sohyun0 Oct 16, 2025
acaae16
🔧 chore: 필요없는 코드 삭제 및 주석처리 #52
sohyun0 Oct 16, 2025
1e24acd
🔧 chore: 필요없는 코드 삭제 및 주석처리 #52
sohyun0 Oct 16, 2025
018aac9
✨ feat: 공고 지원 조건이 안맞을 시 early return 으로 변경 #52
sohyun0 Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/api/applications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axiosInstance from '@/lib/axios';
import type { ApiResponse } from '@/types/api';
import { ApplicationItem, ApplicationListResponse } from '@/types/applications';

// 유저의 공고 지원 내역 전체 조회
export async function getAllUserApplications({
userId,
limit = 10,
}: {
userId: string;
limit?: number;
}) {
const results: ApiResponse<ApplicationItem>[] = [];
let offset = 0;
let hasNext = true;

while (hasNext) {
const { data } = await axiosInstance.get<ApplicationListResponse>(
`/users/${userId}/applications`,
{ params: { offset, limit } }
);

results.push(...data.items);
hasNext = data.hasNext;
offset += limit;
}

return results; // 모든 페이지 합쳐 반환
}

// 가게의 특정 공고 지원 등록
export const postApplication = async (shopId: string, noticeId: string) => {
await axiosInstance.post(`/shops/${shopId}/notices/${noticeId}/applications`);
};

// 가게의 특정 공고 지원 취소
export const putApplication = async (shopId: string, noticeId: string, applicationId: string) => {
await axiosInstance.put(`/shops/${shopId}/notices/${noticeId}/applications/${applicationId}`, {
status: 'canceled',
});
};
2 changes: 1 addition & 1 deletion src/api/employer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function postShop(body: RegisterFormData) {

export async function postPresignedUrl(imageName: string) {
const { data } = await axios.post('/images', { name: imageName });
console.log(data);
// console.log(data);
return data.item.url;
}

Expand Down
Empty file removed src/api/index.ts
Empty file.
2 changes: 1 addition & 1 deletion src/components/features/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { AllNoticeList, RecentNoticeList, RecommendedNoticeList } from './noticeList';
export { NoticeListSection, RecentNoticeList } from './noticeList';
70 changes: 0 additions & 70 deletions src/components/features/noticeList/allNoticeList.tsx

This file was deleted.

57 changes: 57 additions & 0 deletions src/components/features/noticeList/hooks/useRecentNotice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { NoticeCard, RecentNotice } from '@/types/notice';
import { useCallback, useEffect, useState } from 'react';

const RECENT_KEY = 'thejulge_recent';

// 최근 본 공고 저장
export const useRecentNotice = (notice: NoticeCard) => {
const handleRecentNotice = useCallback(() => {
if (!notice) return;

const current: RecentNotice = {
id: notice.id,
shopId: notice.shopId,
name: notice.name,
address1: notice.address1,
imageUrl: notice.imageUrl,
hourlyPay: notice.hourlyPay,
startsAt: notice.startsAt,
workhour: notice.workhour,
closed: notice.closed,
originalHourlyPay: notice.originalHourlyPay,
viewedAt: new Date().toISOString(),
};

// 기존 데이터 가져오기
const stored = localStorage.getItem(RECENT_KEY);
let recentList: RecentNotice[] = stored ? JSON.parse(stored) : [];

// 중복 제거 같은 noticeId면 제거
recentList = recentList.filter(item => item.id !== current.id);

// 최신 항목 맨 앞에 추가
recentList.unshift(current);

// 최대 6개까지만 저장
if (recentList.length > 6) recentList = recentList.slice(0, 6);

localStorage.setItem(RECENT_KEY, JSON.stringify(recentList));
}, [notice]);

return { handleRecentNotice };
};

// 최근 본 공고 불러오기
export function useRecentNoticeList() {
const [recentNotices, setRecentNotices] = useState<RecentNotice[]>([]);

useEffect(() => {
const stored = localStorage.getItem(RECENT_KEY);
if (stored) {
const parsed: RecentNotice[] = JSON.parse(stored);
setRecentNotices(parsed);
}
}, []);

return { recentNotices };
}
4 changes: 2 additions & 2 deletions src/components/features/noticeList/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { default as AllNoticeList } from './allNoticeList';
export { default as NoticeListSection } from './noticeListSection';

export { default as RecentNoticeList } from './recentNoticeList';
export { default as RecommendedNoticeList } from './recommendedNoticeList';
6 changes: 0 additions & 6 deletions src/components/features/noticeList/noticeList.styles.ts

This file was deleted.

33 changes: 13 additions & 20 deletions src/components/features/noticeList/noticeList.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { Post } from '@/components/ui';
import { Pagination } from '@/components/ui/pagination';
import { NoticeQuery, PaginatedResponse } from '@/types/api';
import { type PostCard } from '@/types/notice';
import { useNotice } from '@/context/noticeProvider';

interface NoticeListProps {
notices: PostCard[];
pagination?: PaginatedResponse;
isLoading: boolean;
error: string | null;
fetchNotices?: (params?: Partial<NoticeQuery>) => Promise<void>;
interface NoticeProps {
q?: string;
}

const NoticeList = ({ notices, isLoading, error, pagination, fetchNotices }: NoticeListProps) => {

const NoticeList = ({ q }: NoticeProps) => {
const { notices, isLoading, error, pagination, fetchNotices } = useNotice();
if (error) {
return <div> {error}</div>;
}
if (notices.length === 0) {
return <div>공고가 존재하지 않습니다</div>;
return <div>{q && q + '에 대한 '}공고가 존재하지 않습니다</div>;
}

return (
Expand All @@ -31,15 +26,13 @@ const NoticeList = ({ notices, isLoading, error, pagination, fetchNotices }: Not
))}
</div>
)}
{pagination && (
<Pagination
total={pagination.count}
limit={pagination.limit}
offset={pagination.offset}
onPageChange={next => fetchNotices?.({ offset: next })}
className='mt-8 tablet:mt-10'
/>
)}
<Pagination
total={pagination.count}
limit={pagination.limit}
offset={pagination.offset}
onPageChange={next => fetchNotices({ offset: next })}
className='mt-8 tablet:mt-10'
/>
</>
);
};
Expand Down
45 changes: 45 additions & 0 deletions src/components/features/noticeList/noticeListFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Dropdown, Filter } from '@/components/ui';
import { getActiveFilterCount } from '@/components/ui/filter/getActiveFilterCount';
import { SORT_CODE, type SortCode } from '@/constants/dropdown';
import { useNotice } from '@/context/noticeProvider';
import { FilterQuery, type sort } from '@/types/api';
import { useState } from 'react';

const SORT_TO_API: Record<SortCode, sort> = {
'마감 임박 순': 'time',
'시급 많은 순': 'pay',
'시간 적은 순': 'hour',
'가나다 순': 'shop',
};

const NoticeListFilter = () => {
const { fetchNotices, updateFilters, filters } = useNotice();
const [sort, setSort] = useState<SortCode>('마감 임박 순');
const appliedCount = getActiveFilterCount(filters);

const handleSort = (label: SortCode) => {
const sort = SORT_TO_API[label];
fetchNotices({ sort });
setSort(label);
};

const handleFilter = (q: FilterQuery) => {
updateFilters(q);
fetchNotices(q);
};

return (
<div className='flex gap-3'>
<Dropdown
name='sort'
ariaLabel='공고 정렬 기준'
size='sm'
values={SORT_CODE}
selected={sort}
onChange={handleSort}
/>
<Filter appliedCount={appliedCount} value={filters} onSubmit={handleFilter} align='right' />
</div>
);
};
export default NoticeListFilter;
52 changes: 52 additions & 0 deletions src/components/features/noticeList/noticeListSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import NoticeList from '@/components/features/noticeList/noticeList';
import NoticeListFilter from '@/components/features/noticeList/noticeListFilter';
import { Container } from '@/components/layout';
import { useNotice } from '@/context/noticeProvider';
import type { NoticeQuery } from '@/types/api';
import { useEffect } from 'react';

interface NoticeListSectionProps {
title?: string; // 제목
q?: string; // 검색어
showFilter?: boolean; // 우측 필터 표시 여부
initialFilters?: Partial<NoticeQuery>; // 섹션 진입 시 적용할 초기 필터
}

const NoticeListSection = ({
title = '전체 공고',
q,
showFilter,
initialFilters,
}: NoticeListSectionProps) => {
const { updateFilters, fetchNotices } = useNotice();
// 섹션 진입 및 q/initialFilters 변경 시 초기 필터 반영하여 조회
useEffect(() => {
const hasInitial = Boolean(initialFilters && Object.keys(initialFilters).length > 0);
if (hasInitial) {
updateFilters(initialFilters!);
fetchNotices(initialFilters);
} else {
fetchNotices();
}

// fetchNotices와 updateFilters 함수는 initialFilters값이 변경될때 새로만들어짐
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [q, initialFilters]);
return (
<Container as='section' isPage>
<div className='mb-4 flex items-center justify-between tablet:mb-8'>
{q ? (
<h2 className='text-heading-l font-bold'>
<span className='text-red-500'>{q}</span>에 대한 공고 목록
</h2>
) : (
<h2 className='text-heading-l font-bold'>{title}</h2>
)}
{showFilter && <NoticeListFilter />}
</div>
<NoticeList q={q} />
</Container>
);
};

export default NoticeListSection;
15 changes: 12 additions & 3 deletions src/components/features/noticeList/recentNoticeList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { noticeListLayout } from '@/components/features/noticeList/noticeList.styles';
import { Container } from '@/components/layout';
import { Post } from '@/components/ui';
import { useRecentNoticeList } from './hooks/useRecentNotice';

// @TODO 최근에 본 공고 리스트 출력
const RecentNoticeList = () => {
const { recentNotices } = useRecentNoticeList();

if (recentNotices.length === 0) return null;

return (
<Container as='section' isPage>
<h2 className={noticeListLayout.title()}>최근에 본 공고</h2>
<h2 className='mb-4 text-heading-l font-bold tablet:mb-8'>최근에 본 공고</h2>
<div className='grid gap-x-4 gap-y-8 sm:grid-cols-2 desktop:grid-cols-3'>
{recentNotices.map(notice => (
<Post key={notice.id} notice={notice} />
))}
</div>
</Container>
);
};
Expand Down
Loading