Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ build/
# OpenAPI auto-generated files
src/api/openapi

**/*.json
**/*.json

# CodeRabbit config
.coderabbit.yaml
5 changes: 3 additions & 2 deletions src/components/filtering/study-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,16 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) {
type: [],
targetRoles: [],
method: [],
recruiting: false,
recruiting: true, // 기본값: 모집 중만 보기
});
}, [onChange]);

// recruiting은 기본값이므로 필터로 간주하지 않음
const hasAnyFilter =
values.type.length > 0 ||
values.targetRoles.length > 0 ||
values.method.length > 0 ||
values.recruiting;
!values.recruiting; // recruiting이 false면 필터 적용 중

return (
<div className="flex items-center gap-100">
Expand Down
31 changes: 26 additions & 5 deletions src/components/pages/group-study-list-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useGetStudies } from '@/hooks/queries/study-query';
import GroupStudyFormModal from '../../features/study/group/ui/group-study-form-modal';
import GroupStudyPagination from '../../features/study/group/ui/group-study-pagination';
import GroupStudyList from '../lists/group-study-list';
import MyParticipatingStudiesSection from '../section/my-participating-studies-section';

// Carousel이 클라이언트 전용이므로 dynamic import로 로드
const Banner = dynamic(() => import('@/widgets/home/banner'), {
Expand All @@ -38,12 +39,18 @@ export default function GroupStudyListPage() {
const [searchQuery, setSearchQuery] = useState('');

// URL에서 필터 값 읽기
// 기본값: recruiting = true (모집 중만 보기)
// 사용자가 토글을 조작하면 URL에 명시적으로 저장됨
const filterValues = useMemo<StudyFilterValues>(() => {
const type = searchParams.get('type')?.split(',').filter(Boolean) ?? [];
const targetRoles =
searchParams.get('targetRoles')?.split(',').filter(Boolean) ?? [];
const method = searchParams.get('method')?.split(',').filter(Boolean) ?? [];
const recruiting = searchParams.get('recruiting') === 'true';
// URL에 recruiting 파라미터가 없으면 기본값 true (모집 중만 보기)
// 파라미터가 있으면 그 값 사용 (명시적 사용자 선택)
const recruitingParam = searchParams.get('recruiting');
const recruiting =
recruitingParam === null ? true : recruitingParam === 'true';

return { type, targetRoles, method, recruiting };
}, [searchParams]);
Expand All @@ -67,7 +74,8 @@ export default function GroupStudyListPage() {
filterValues.method.length > 0
? (filterValues.method as GetGroupStudiesMethodEnum[])
: undefined,
recruiting: filterValues.recruiting || undefined,
// 기본값: true (모집 중만), false면 전체 조회
recruiting: filterValues.recruiting ? true : undefined,
});

const allStudies = useMemo(() => data?.content ?? [], [data?.content]);
Expand All @@ -78,10 +86,16 @@ export default function GroupStudyListPage() {
const params = new URLSearchParams(searchParams.toString());

Object.entries(updates).forEach(([key, value]) => {
if (value === undefined || value === '' || value === 'false') {
if (value === undefined || value === '') {
params.delete(key);
} else {
params.set(key, value);
// recruiting의 경우 'false'도 명시적으로 저장 (기본값이 true이므로)
// 다른 필터는 'false'일 때 파라미터 제거
if (key === 'recruiting' || value !== 'false') {
params.set(key, value);
} else {
params.delete(key);
}
}
});

Expand All @@ -97,6 +111,8 @@ export default function GroupStudyListPage() {
);

// 필터 변경 핸들러
// recruiting 토글: true면 'true' 저장, false면 'false' 명시적으로 저장
// (기본값이 true이므로 false일 때도 명시적으로 저장해야 토글이 정상 작동)
const handleFilterChange = useCallback(
(values: StudyFilterValues) => {
updateSearchParams({
Expand All @@ -106,7 +122,9 @@ export default function GroupStudyListPage() {
? values.targetRoles.join(',')
: undefined,
method: values.method.length > 0 ? values.method.join(',') : undefined,
recruiting: values.recruiting ? 'true' : undefined,
// recruiting: true면 'true', false면 'false' 명시적으로 저장
// (기본값이 true이므로 false일 때도 URL에 저장해야 토글 해제 가능)
recruiting: values.recruiting ? 'true' : 'false',
});
},
[updateSearchParams],
Expand Down Expand Up @@ -158,6 +176,9 @@ export default function GroupStudyListPage() {
<Banner />
</div>

{/* 내가 참여중인 스터디 섹션 */}
<MyParticipatingStudiesSection classification="GROUP_STUDY" />

{/* 헤더 */}
<div className="mb-400 flex items-center justify-between">
<h1 className="font-designer-24b text-text-default">
Expand Down
30 changes: 24 additions & 6 deletions src/components/pages/premium-study-list-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Button from '@/components/ui/button';
import GroupStudyFormModal from '@/features/study/group/ui/group-study-form-modal';
import { useAuth } from '@/hooks/common/use-auth';
import { useGetStudies } from '@/hooks/queries/study-query';
import MyParticipatingStudiesSection from '../section/my-participating-studies-section';

// Carousel이 클라이언트 전용이므로 dynamic import로 로드
const Banner = dynamic(() => import('@/widgets/home/banner'), {
Expand All @@ -37,13 +38,16 @@ export default function PremiumStudyListPage() {
// 로컬 검색 상태
const [searchQuery, setSearchQuery] = useState('');

// URL에서 필터 값 읽기
// URL에서 필터 값 읽기 (기본값: recruiting = true, 모집 중만 보기)
const filterValues = useMemo<StudyFilterValues>(() => {
const type = searchParams.get('type')?.split(',').filter(Boolean) ?? [];
const targetRoles =
searchParams.get('targetRoles')?.split(',').filter(Boolean) ?? [];
const method = searchParams.get('method')?.split(',').filter(Boolean) ?? [];
const recruiting = searchParams.get('recruiting') === 'true';
// URL에 recruiting 파라미터가 없으면 기본값 true (모집 중만 보기)
const recruitingParam = searchParams.get('recruiting');
const recruiting =
recruitingParam === null ? true : recruitingParam === 'true';

return { type, targetRoles, method, recruiting };
}, [searchParams]);
Expand All @@ -67,7 +71,8 @@ export default function PremiumStudyListPage() {
filterValues.method.length > 0
? (filterValues.method as GetGroupStudiesMethodEnum[])
: undefined,
recruiting: filterValues.recruiting || undefined,
// 기본값: true (모집 중만), false면 전체 조회
recruiting: filterValues.recruiting ? true : undefined,
});

const allStudies = useMemo(() => data?.content ?? [], [data?.content]);
Expand All @@ -78,10 +83,16 @@ export default function PremiumStudyListPage() {
const params = new URLSearchParams(searchParams.toString());

Object.entries(updates).forEach(([key, value]) => {
if (value === undefined || value === '' || value === 'false') {
if (value === undefined || value === '') {
params.delete(key);
} else {
params.set(key, value);
// recruiting의 경우 'false'도 명시적으로 저장 (기본값이 true이므로)
// 다른 필터는 'false'일 때 파라미터 제거
if (key === 'recruiting' || value !== 'false') {
params.set(key, value);
} else {
params.delete(key);
}
}
});

Expand All @@ -97,6 +108,8 @@ export default function PremiumStudyListPage() {
);

// 필터 변경 핸들러
// recruiting 토글: true면 'true' 저장, false면 'false' 명시적으로 저장
// (기본값이 true이므로 false일 때도 명시적으로 저장해야 토글이 정상 작동)
const handleFilterChange = useCallback(
(values: StudyFilterValues) => {
updateSearchParams({
Expand All @@ -106,7 +119,9 @@ export default function PremiumStudyListPage() {
? values.targetRoles.join(',')
: undefined,
method: values.method.length > 0 ? values.method.join(',') : undefined,
recruiting: values.recruiting ? 'true' : undefined,
// recruiting: true면 'true', false면 'false' 명시적으로 저장
// (기본값이 true이므로 false일 때도 URL에 저장해야 토글 해제 가능)
recruiting: values.recruiting ? 'true' : 'false',
});
},
[updateSearchParams],
Expand Down Expand Up @@ -158,6 +173,9 @@ export default function PremiumStudyListPage() {
<Banner />
</div>

{/* 내가 참여중인 스터디 섹션 */}
<MyParticipatingStudiesSection classification="PREMIUM_STUDY" />

{/* 헤더 */}
<div className="mb-400 flex items-center justify-between">
<h1 className="font-designer-24b text-text-default">
Expand Down
Loading
Loading