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/*"
]
}
}
27 changes: 18 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,15 @@ 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 } from '@constants/option';

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 +63,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 +138,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 +152,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 { useDisplay } from '@hooks/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 } = useDisplay();
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
19 changes: 9 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,13 @@ function Filter() {
export default Filter;

const SGuideButton = styled('a', {
height: '$48',
flexType: 'verticalCenter',
gap: '$8',
color: '$gray10',
padding: '$18 $20',
border: '1px solid $gray10',
borderRadius: '14px',
fontAg: '18_medium_100',
fontAg: '18_semibold_100',
boxSizing: 'border-box',
'@tablet': {
padding: '$14 $12 $14 $16',
Expand Down
5 changes: 3 additions & 2 deletions src/constants/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const APPROVAL_STATUS_KOREAN_TO_ENGLISH: StringKeyObject = {
거절: 'REJECT',
};
export const APPLICATION_TYPE = ['신청', '초대'];
export const CATEGORY_OPTIONS = ['번쩍', '스터디', '세미나', '행사'];
export const CATEGORY_OPTIONS = ['스터디', '세미나', '행사', '번쩍'];
export const CATEGORY_FILTER_OPTIONS = ['전체', ...CATEGORY_OPTIONS];
export const PART_OPTIONS = ['기획', '디자인', 'Android', 'iOS', '웹', '서버'];
export const PART_VALUES = ['PM', 'DESIGN', 'ANDROID', 'IOS', 'WEB', 'SERVER'];
export const ACTION_STATUS = ['모집 전', '모집 중', '모집 마감', '활동 중', '활동 종료'];
Expand Down Expand Up @@ -53,7 +54,7 @@ export interface FilterType {
export const CATEGORY_FILTER = {
label: '카테고리',
subject: 'category',
options: CATEGORY_OPTIONS,
options: CATEGORY_FILTER_OPTIONS,
};
export const STATUS_FILTER = {
label: '모집 상태',
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,
};
}
1 change: 1 addition & 0 deletions src/hooks/useDisplay.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from 'react';
import { useMediaQuery } from 'react-responsive';
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';

export function useDisplay() {
const [isMobile, setIsMobile] = useState(false);
const [isTablet, setIsTable] = useState(false);
Expand Down