Skip to content
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

전체 모임 필터 작업 및 UI 수정 작업 (필터 모달 삭제) #997

Merged
merged 10 commits into from
Feb 5, 2025
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@
"workspaces": [
"packages/*"
]
}
}
28 changes: 19 additions & 9 deletions pages/list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Filter from '@components/page/list/Filter';
import Search from '@components/page/list/Filter/Search';
import GridLayout from '@components/page/list/Grid/Layout';
import { MeetingListOfAll } from '@components/page/list/Grid/List';
import NoticeSlider from '@components/page/list/Slider/NoticeSlider/NoticeSlider';
import { SSRSafeSuspense } from '@components/util/SSRSafeSuspense';
import useModal from '@hooks/useModal';
import { playgroundLink } from '@sopt-makers/playground-common';
Expand All @@ -18,12 +17,16 @@ import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { styled } from 'stitches.config';
import CrewTab from '@components/CrewTab';
import Chips from '@components/page/list/Filter/Modal/Chip';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mds chip 이 따로 있는데, mds chip 과 해당 chip 의 디자인이 동일한 게 맞을까요?? 보통의 경우엔 기존의 것이 legacy 이고 mds 컴포넌트로 교체해서요~!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 저 Chips라는 부분이 내부적으로 만든 chip 컴포넌트를 사용하고 있었는데, 해당 chip 컴포넌트를 mds로 변경했어요!
그래서 칩들을 렌더링하는 컴포넌트인 Chips는 거의 그대로 재활용하는 방식으로 구현 진행했습니다 :)

import { CATEGORY_FILTER, PART_FILTER } from '@constants/option';
import Toggle from '@components/page/list/Filter/Modal/Toggle';

const Home: NextPage = () => {
const router = useRouter();
const { data: me } = useQueryMyProfile();
const { isModalOpened, handleModalOpen, handleModalClose } = useModal();
const { data: notices } = useNotices();

const categoryFilterStyle = { display: 'flex', alignItems: 'flex-start', gap: '$12', alignSelf: 'stretch' };

const handleMakeMeeting = () => {
if (!me?.hasActivities) {
Expand Down Expand Up @@ -61,12 +64,12 @@ const Home: NextPage = () => {
</SMakeMeetingButton>
</CrewTab>

{/*Notice 슬라이더*/}
<SNoticeWrapper>
<NoticeSlider notices={notices} />
</SNoticeWrapper>
{/*카테고리 필터 칩*/}
<SChipWrapper>
<Chips css={categoryFilterStyle} filter={CATEGORY_FILTER} />
</SChipWrapper>

{/*필터 - 필터, 모임 검색, 모임 신청 가이드, 필터 적용 후 생기는 FLEX 박스(chip 모임)*/}
{/*필터 - 모임 검색, 드롭다운, 토글, 모임 신청 가이드*/}
<SFilterWrapper>
<Filter />
</SFilterWrapper>
Expand Down Expand Up @@ -136,10 +139,10 @@ const SMobileButtonContainer = styled('div', {
});

const SFilterWrapper = styled('div', {
mt: '$40',
mt: '$20',
mb: '$64',
'@tablet': {
mt: '$32',
mt: '$16',
mb: '$24',
},
});
Expand All @@ -150,3 +153,10 @@ const SNoticeWrapper = styled('div', {
mt: '$28',
},
});

const SChipWrapper = styled('div', {
mt: '$45',
'@tablet': {
mt: '$32',
},
});
4 changes: 4 additions & 0 deletions public/assets/svg/arrow_right_circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions src/components/page/list/Filter/DropDown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ampli } from '@/ampli';
import { FilterType } from '@constants/option';
import { useQueryString } from '@hooks/queryString';
import { SelectV2 } from '@sopt-makers/ui';
import { useRouter } from 'next/router';
import React from 'react';
import { styled } from 'stitches.config';

interface DropDownFilterProps {
filter: FilterType;
}

function DropDownFilter({ filter }: DropDownFilterProps) {
const router = useRouter();
const selectedPartQuery = router.query.part as string;
const defaultValue = selectedPartQuery ? { label: selectedPartQuery, value: selectedPartQuery } : undefined;

const { subject, options } = filter;
const { value: selectedValue, setValue, deleteKey } = useQueryString(subject);

const setPartQuery = (value: string) => {
ampli.clickFilterPart({ group_part: value });

if (selectedValue === value) return deleteKey();
return setValue(value);
};

return (
<SDropDownContainer>
<SelectV2.Root type="text" visibleOptions={6} defaultValue={defaultValue} onChange={setPartQuery}>
<SelectV2.Trigger>
<SelectV2.TriggerContent placeholder={'대상 파트'} />
</SelectV2.Trigger>
<SelectV2.Menu>
{options.map(option => (
<SelectV2.MenuItem key={option} option={{ label: option, value: option }} />
))}
</SelectV2.Menu>
</SelectV2.Root>
</SDropDownContainer>
);
}

export default DropDownFilter;

const SDropDownContainer = styled('div', {
ml: '$16',
});
15 changes: 12 additions & 3 deletions src/components/page/list/Filter/Modal/Chip/ChipItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ampli } from '@/ampli';
import { CATEGORY_FILTER, PART_FILTER, STATUS_FILTER } from '@constants/option';
import { useMediaQuery } from '@hooks/useMediaQuery';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

라이브러리 사용하고 있으니 직접 구현한 것을 사용할 필요가 없을 것 같다는 생각이 드는데 ..! 다른 의도가 있으신지 궁금합니다 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에서 이유는 설명드렸으니 여기선 생략하겠습니당
useDisplay로 변경하겠습니다..!!

import { Chip } from '@sopt-makers/ui';
import { styled } from 'stitches.config';

interface ChipItemProps {
Expand All @@ -8,10 +10,17 @@ interface ChipItemProps {
isSelected: boolean;
addValue: (val: string) => void;
deleteValue: (val: string) => void;
resetQuery: () => void;
}

function ChipItem({ label, value, isSelected, addValue, deleteValue }: ChipItemProps) {
function ChipItem({ label, value, isSelected, addValue, deleteValue, resetQuery }: ChipItemProps) {
const isTablet = useMediaQuery('(max-width: 768px)');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우리 useDisplay 라는 커스텀 훅을 활용하고 있어!! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알려주셔서 감사합니다 !! 확인하고 반영하겠습니다 ~!

const toggle = () => {
if (value === '전체') {
resetQuery();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체 클릭시 모든 파라미터를 reset 시키는 방식으로 한다면 다른 필터링 조건들도 함께 날아갈텐데 의도하신 부분이 맞을까요 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 맞습니다 !
url 쿼리키로 필터링을 하다보니 새로고침을 하더라도 필터링이 유지되던데, 그럼 초기화를 위해 칩을 일일히 다시 선택하며 해제하는게 불편하게 느껴져서 전체 칩 버튼을 클릭하면 전부 리셋되도록 구현했습니다.

return;
}

switch (label) {
case CATEGORY_FILTER.label:
ampli.clickFilterCategory({ group_category: value });
Expand All @@ -28,9 +37,9 @@ function ChipItem({ label, value, isSelected, addValue, deleteValue }: ChipItemP
return addValue(value);
};
return (
<SOption isSelected={isSelected} onClick={toggle}>
<Chip size={isTablet ? 'sm' : 'md'} active={isSelected} onClick={toggle}>
{value}
</SOption>
</Chip>
);
}

Expand Down
16 changes: 10 additions & 6 deletions src/components/page/list/Filter/Modal/Chip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,37 @@ import { useMultiQueryString } from '@hooks/queryString';
import { CSSType, styled } from 'stitches.config';
import ChipItem from './ChipItem';

interface ChipProps {
interface ChipsProps {
isLabel?: boolean;
css?: CSSType;
filter: FilterType;
}

function Chip({ css, filter }: ChipProps) {
function Chips({ isLabel, css, filter }: ChipsProps) {
//해당 Chip 선택시 Chip의 filter로 전달된 subject를 이용하여 쿼리 세팅
const { label, subject, options } = filter;
const { value: selectedValues, addValue, deleteValue } = useMultiQueryString(subject, true);
const { value: selectedValues, addValue, deleteValue, resetQuery } = useMultiQueryString(subject, true);

const isEntire = !selectedValues.length;
return (
<SChipWrapper css={{ ...css }}>
{label && <SLabel>{label}</SLabel>}
{isLabel && <SLabel>{label}</SLabel>}
{options.map(option => (
<ChipItem
key={option}
isSelected={selectedValues.includes(option)}
isSelected={selectedValues.includes(option) || (isEntire && option === '전체')}
label={label}
value={option}
addValue={addValue}
deleteValue={deleteValue}
resetQuery={resetQuery}
/>
))}
</SChipWrapper>
);
}

export default Chip;
export default Chips;

const SChipWrapper = styled('div', {});
const SLabel = styled('p', {
Expand Down
2 changes: 1 addition & 1 deletion src/components/page/list/Filter/Modal/Toggle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Switch as HeadlessSwitch } from '@headlessui/react';
import { ampli } from '@/ampli';
interface ToggleProps {
css?: CSSType;
label: string;
label?: string;
}

function Toggle({ css, label }: ToggleProps) {
Expand Down
11 changes: 6 additions & 5 deletions src/components/page/list/Filter/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import DefaultModal from '@components/modal/DefaultModal';
import { CATEGORY_FILTER, PART_FILTER, STATUS_FILTER } from '@constants/option';
import { styled } from 'stitches.config';
import InitializationButton from '../Result/InitializationButton';
import Chip from './Chip';
import Chips from './Chip';
import Toggle from './Toggle';

interface FilterSelectModalProps {
isModalOpened: boolean;
handleModalClose: () => void;
}

//Notice: 현재 사용 중이지 않습니다.
function FilterSelectModal({ isModalOpened, handleModalClose }: FilterSelectModalProps) {
const filterSectionStyle = { mb: '$48', '@tablet': { mb: '$40' } };
return (
Expand All @@ -19,10 +20,10 @@ function FilterSelectModal({ isModalOpened, handleModalClose }: FilterSelectModa
titleLeft={<InitializationButton withText={false} size={24} />}
>
<SSelectWrapper>
<Chip css={filterSectionStyle} filter={CATEGORY_FILTER} />
<Chip css={filterSectionStyle} filter={STATUS_FILTER} />
<Chips css={filterSectionStyle} filter={CATEGORY_FILTER} isLabel={true} />
<Chips css={filterSectionStyle} filter={STATUS_FILTER} isLabel={true} />
<Toggle css={filterSectionStyle} label="대상 기수" />
<Chip css={filterSectionStyle} filter={PART_FILTER} />
<Chips css={filterSectionStyle} filter={PART_FILTER} isLabel={true} />
</SSelectWrapper>
</DefaultModal>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/page/list/Filter/Result/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import InitializationButton from './InitializationButton';
import { styled } from 'stitches.config';
import { parseBool } from '@utils/parseBool';

//Notice: 현재 사용 중이지 않습니다.
function Result() {
//설정된 쿼리 파라미터의 값을 가져와서 OR 연산으로 필터링한 모임 렌더링
const { value: category, deleteValue: deleteCategoryValue } = useCategoryParams();
Expand Down
2 changes: 1 addition & 1 deletion src/components/page/list/Filter/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ function Search() {

export default Search;
const SSearchWrapper = styled(Flex, {
height: '$48',
py: '$15',
px: '$20',
border: '1px solid $gray600',
borderRadius: '14px',
ml: '$12',
'@tablet': {
display: 'none',
},
Expand Down
20 changes: 10 additions & 10 deletions src/components/page/list/Filter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { styled } from 'stitches.config';
import { Flex } from '@components/util/layout/Flex';
import { useSearchParams } from '@hooks/queryString/custom';
import Search from './Search';
import Result from './Result';
import ArrowSmallRightIcon from '@assets/svg/arrow_small_right.svg';
import FilterModalOpenButton from './Modal/OpenButton';
import ArrowRightCircleIcon from '@assets/svg/arrow_right_circle.svg';
import Toggle from './Modal/Toggle';
import { PART_FILTER } from '@constants/option';
import DropDownFilter from './DropDown';

function Filter() {
const { value: search } = useSearchParams();
Expand All @@ -13,8 +14,9 @@ function Filter() {
<>
<Flex align="center" justify="between">
<Flex>
<FilterModalOpenButton />
<Search />
<DropDownFilter filter={PART_FILTER} />
<Toggle />
</Flex>

<SGuideButton
Expand All @@ -23,13 +25,10 @@ function Filter() {
rel="noreferrer noopener"
>
모임 신청 가이드
<ArrowSmallRightIcon />
<ArrowRightCircleIcon />
</SGuideButton>
</Flex>

{/*필터 적용 결과 박스 (chip 모임)*/}
<Result />

{!!search && <SearchResultMessage>"{search}"에 대한 검색결과입니다.</SearchResultMessage>}
</>
);
Expand All @@ -38,13 +37,14 @@ function Filter() {
export default Filter;

const SGuideButton = styled('a', {
height: '$48',
flexType: 'verticalCenter',
gap: '$8',
color: '$gray10',
padding: '$18 $20',
border: '1px solid $gray10',
//border: '1px solid $gray10',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 발견! 레거시가 되지 않도록 제거 부탁드립니당

borderRadius: '14px',
fontAg: '18_medium_100',
fontAg: '18_semibold_100',
boxSizing: 'border-box',
'@tablet': {
padding: '$14 $12 $14 $16',
Expand Down
2 changes: 1 addition & 1 deletion src/constants/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const APPROVAL_STATUS_KOREAN_TO_ENGLISH: StringKeyObject = {
거절: 'REJECT',
};
export const APPLICATION_TYPE = ['신청', '초대'];
export const CATEGORY_OPTIONS = ['번쩍', '스터디', '세미나', '행사'];
export const CATEGORY_OPTIONS = ['전체', '스터디', '세미나', '행사', '번쩍'];
Copy link
Member

@j-nary j-nary Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CATEGORY_OPTIONS'전체' 를 넣으면 다른 부분과 충돌이 생길 거에요..!
필터에서만 사용하는 부분이 아닌 걸로 기억하는데, 해당 상수를 사용하는 곳 모두 확인 후에 추가된 걸까요 ?
필터에서 '전체' 가 필요하다면 FILTER_OPTIONS = ['전체', ...CATEGORY_OPTIONS]; 로 새로 생성하는 것은 어떨까요 ?

번쩍 요소의 순서를 변경한 이유도 궁금합니당

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 의견 감사합니다!

확인해보니, 다른 부분들에서는 style 적용하는 용도로만 사용하고 있더라구요 ('스터디'일 경우 빨간 글씨가 나타나도록 함)
그래서 단순하게 CATEGORY_OPTIONS[0] -> CATEGORY_OPTIONS[1] 로 변경하려다가
말씀 주신대로 필터 옵션은 필터 옵션에 맞게 분리하는 게 맞는 것 같아서
CATEGORY_FILTER_OPTIONS 을 새로 만들고 적용시키겠습니다!

번쩍 요소의 순서는 기획 의도 상 번쩍 칩이 가장 오른쪽에 가길 원하셨고, 이미 구현되어 있는 로직에서는 CATEGORY_OPTIONS 을 순서대로 map 돌리면서 렌더링하고 있어서, 기존의 코드를 최대한 활용하기 위해 간단하게 '번쩍' 요소의 순서를 변경하는 방식으로 코드를 구현했습니다!

export const PART_OPTIONS = ['기획', '디자인', 'Android', 'iOS', '웹', '서버'];
export const PART_VALUES = ['PM', 'DESIGN', 'ANDROID', 'IOS', 'WEB', 'SERVER'];
export const ACTION_STATUS = ['모집 전', '모집 중', '모집 마감', '활동 중', '활동 종료'];
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/queryString/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,24 @@ export function useMultiQueryString(key: string, withPage?: boolean) {
delete query[key];
pushQuery(query);
};

const resetQuery = () => {
router.replace(
{
pathname: router.pathname,
query: {},
},
undefined,
{ shallow: true }
);
};

return {
value: splitQueryOfKey, // , 로 구분된 해당 key의 value들을 배열 형태로 반환
setValue,
addValue,
deleteValue,
deleteKey,
resetQuery,
};
}
19 changes: 19 additions & 0 deletions src/hooks/useMediaQuery/index.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희가 react-responsive 라이브러리를 사용하고 있는데, 새로 구현해준 이유가 있을까요 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-responsive 라이브러리를 사용해서 만든 useDisplay라는 훅이 있는 걸 몰랐어요,,
좋은 지적 감사합니다! 추후 팀원분들이 헷갈리지 않도록 아예 삭제할게요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from 'react';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useDisplay 훅을 활용한다면 useMediaQuery 는 동일한 기능을 하는 훅인 것 같으니 삭제해줘도 괜찮겠다!!


export function useMediaQuery(query: string) {
const [matches, setMatches] = useState(false);

useEffect(() => {
const mediaQuery = window.matchMedia(query);
setMatches(mediaQuery.matches);

const handleChange = (event: MediaQueryListEvent) => {
setMatches(event.matches);
};

mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [query]);

return matches;
}