Skip to content

Commit

Permalink
Merge pull request #78 from YAPP-Github/feat/YW2-153
Browse files Browse the repository at this point in the history
[Feat] 홈 캘린더 api
  • Loading branch information
hayoiii authored Sep 4, 2023
2 parents 19e10e2 + 52a1367 commit 2e8b84f
Show file tree
Hide file tree
Showing 19 changed files with 220 additions and 129 deletions.
2 changes: 1 addition & 1 deletion src/api/mypage/event/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface MyBaseEvent {
startAt: string;
endAt: string;
recruitNum: number;
participantNum: number;
joiningNum: number;
waitingNum: number;
}

Expand Down
35 changes: 31 additions & 4 deletions src/api/volunteer-event/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ export type GetParams = {
from: string;
to: string;
category: VolunteerEventCategory | null;
status: EventStatus | null;
longitude: number | null;
latitude: number | null;
address: RegionOptions | null;
isFavorite: boolean | null;
isFavorite: boolean;
};

export type GetResponse = {
Expand All @@ -47,13 +46,12 @@ export const get = async (
): Promise<GetResponse> => {
const payload: GetParams = {
category: params.category === 'all' ? null : params.category || null,
status: params.status === 'all' ? null : params.status || null,
from,
to,
longitude: params.longitude || null,
latitude: params.latitude || null,
address: params.address === '내 주변' ? null : params.address || null,
isFavorite: params.isFavorite || null
isFavorite: params.isFavorite || false
};

const data = await api
Expand All @@ -65,3 +63,32 @@ export const get = async (
to
};
};

export type ShelterGetParams = {
from: string;
to: string;
category: VolunteerEventCategory[] | null;
status: EventStatus | null;
};
export const shelterGet = async (
params: HomeEventFilter,
from: string,
to: string
) => {
const payload: ShelterGetParams = {
category:
params.category === 'all' || !params.category ? null : [params.category],
from,
to,
status: params.status === 'all' || !params.status ? null : params.status
};

const data = await api
.post('shelter/admin/volunteer-event/home', { json: payload })
.json<HomeVolunteerEvent[]>();
return {
events: data,
from,
to
};
};
50 changes: 50 additions & 0 deletions src/api/volunteer-event/queryOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NUM_OF_MAX_ITERATION_MONTHS } from '@/constants/volunteerEvent';
import { getStartOfMonth, getEndOfMonth } from '@/utils/timeConvert';
import { UseInfiniteQueryOptions } from '@tanstack/react-query';
import moment, { Moment } from 'moment';
import { GetResponse } from '.';

export type UseVolunteerEventListPageParam = {
to: Moment;
from: Moment;
};

export const monthlyInfiniteOption: UseInfiniteQueryOptions<GetResponse> = {
getPreviousPageParam: (
lastPage
): UseVolunteerEventListPageParam | undefined => {
const prevDate = moment(lastPage.from).subtract(1, 'month');

// NUM_OF_MAX_ITERATION_MONTHS개월 이전 데이터는 받아오지 않는다.
const minDate = getStartOfMonth(new Date()).subtract(
NUM_OF_MAX_ITERATION_MONTHS,
'months'
);
if (prevDate.isSameOrBefore(minDate)) {
return undefined;
}

return {
from: getStartOfMonth(prevDate),
to: getEndOfMonth(prevDate)
};
},
getNextPageParam: (lastPage): UseVolunteerEventListPageParam | undefined => {
const nextDate = moment(lastPage.from).add(1, 'month');

// NUM_OF_MAX_ITERATION_MONTHS개월 이후 데이터는 받아오지 않는다.
const maxDate = getStartOfMonth(new Date()).add(
NUM_OF_MAX_ITERATION_MONTHS,
'months'
);

if (nextDate.isSameOrAfter(maxDate)) {
return undefined;
}

return {
from: getStartOfMonth(nextDate),
to: getEndOfMonth(nextDate)
};
}
};
45 changes: 0 additions & 45 deletions src/api/volunteer-event/useHomeEventList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,48 +43,3 @@ export default function useHomeEventList(
}
);
}

export type UseVolunteerEventListPageParam = {
to: Moment;
from: Moment;
};

export const monthlyInfiniteOption: UseInfiniteQueryOptions<GetResponse> = {
getPreviousPageParam: (
lastPage
): UseVolunteerEventListPageParam | undefined => {
const prevDate = moment(lastPage.from).subtract(1, 'month');

// NUM_OF_MAX_ITERATION_MONTHS개월 이전 데이터는 받아오지 않는다.
const minDate = getStartOfMonth(new Date()).subtract(
NUM_OF_MAX_ITERATION_MONTHS,
'months'
);
if (prevDate.isSameOrBefore(minDate)) {
return undefined;
}

return {
from: getStartOfMonth(prevDate),
to: getEndOfMonth(prevDate)
};
},
getNextPageParam: (lastPage): UseVolunteerEventListPageParam | undefined => {
const nextDate = moment(lastPage.from).add(1, 'month');

// NUM_OF_MAX_ITERATION_MONTHS개월 이후 데이터는 받아오지 않는다.
const maxDate = getStartOfMonth(new Date()).add(
NUM_OF_MAX_ITERATION_MONTHS,
'months'
);

if (nextDate.isSameOrAfter(maxDate)) {
return undefined;
}

return {
from: getStartOfMonth(nextDate),
to: getEndOfMonth(nextDate)
};
}
};
45 changes: 45 additions & 0 deletions src/api/volunteer-event/useShelterHomeEventList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
UseInfiniteQueryOptions,
useInfiniteQuery
} from '@tanstack/react-query';
import { Moment } from 'moment';
import moment from 'moment';
import {
formatDatetimeForServer,
getEndOfMonth,
getStartOfMonth,
minutes
} from '@/utils/timeConvert';
import { NUM_OF_MAX_ITERATION_MONTHS } from '@/constants/volunteerEvent';
import { GetResponse, HomeEventFilter, shelterGet, queryKey } from '.';

export default function useShelterHomeEventList(
filter: HomeEventFilter,
from: Moment,
to: Moment,
options?: UseInfiniteQueryOptions<GetResponse>
) {
const filterForKey = {
...filter
};
delete filterForKey.latitude;
delete filterForKey.longitude;
return useInfiniteQuery<GetResponse>(
queryKey.list(filterForKey),
({
pageParam = {
from,
to
}
}) =>
shelterGet(
filter,
formatDatetimeForServer(pageParam.from, 'DATE'),
formatDatetimeForServer(pageParam.to, 'DATE')
),
{
cacheTime: minutes(10),
...options
}
);
}
9 changes: 9 additions & 0 deletions src/components/common/Button/Button.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ export const ButtonWrapper = recipe({
size: 'middle'
},
style: variants.button2
},
{
variants: {
variant: 'line',
size: 'xsmall'
},
style: {
backgroundColor: palette.white
}
}
]
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/Filter/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ const Filter = forwardRef<FilterRef, FilterProps>(
const [pickOption, setPickOption] = useState('');

useEffect(() => {
const initial =
typeof options[0] === 'string' ? options[0] : options[0]?.label;
setPickOption(initial);
setPickOption(
typeof options[0] === 'string' ? options[0] : options[0]?.label
);
}, [options]);

useImperativeHandle(ref, () => ({
Expand Down
5 changes: 4 additions & 1 deletion src/components/common/Header/MainHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ export default function MainHeader({ initRole, shelterId }: MainHeaderProps) {
</a>

<div className={styles.rightSide}>
<Body3 style={{ cursor: 'pointer' }} onClick={moveToLogin}>
<Body3
style={{ cursor: role === 'NONE' ? 'pointer' : 'default' }}
onClick={moveToLogin}
>
{content}
</Body3>
{(role === 'SHELTER' || role === 'VOLUNTEER') && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/home/Banner/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function Banner({ name, shelterId }: BannerProps) {
<section id={DOM_ID_BANNER}>
<div className={styles.container}>
<div className={styles.titleWrapper}>
<h1>안녕하세요! {name && <span>{name}.</span>}</h1>
<h1>안녕하세요! {name && <span>{name}</span>}</h1>
<h1>더 나은 세상을 만들어봐요</h1>
</div>
<a className={styles.infoLink} href="" onClick={handleClick}>
Expand Down
76 changes: 41 additions & 35 deletions src/components/home/CalendarSection/CalendarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ import getUserGeolocation from './utils/getUserGeolocation';
import useBooleanState from '@/hooks/useBooleanState';
import { HEADER_HEIGHT } from '@/components/common/Header/Header.css';
import VolunteerEventList from '@/components/volunteer-schedule/VolunteerEventList/VolunteerEventList';
import useHomeEventList, {
monthlyInfiniteOption
} from '@/api/volunteer-event/useHomeEventList';
import useHomeEventList from '@/api/volunteer-event/useHomeEventList';
import { HomeEventFilter } from '@/api/volunteer-event';
import { getEndOfMonth, getStartOfMonth } from '@/utils/timeConvert';
import SkeletonList from '@/components/common/Skeleton/SkeletonList';
import { homeEventsMock } from './mock';
import clsx from 'clsx';
import { monthlyInfiniteOption } from '@/api/volunteer-event/queryOptions';
import useShelterHomeEventList from '@/api/volunteer-event/useShelterHomeEventList';

export default function CalendarSection() {
const { dangle_role } = useAuthContext();
Expand All @@ -51,27 +50,31 @@ export default function CalendarSection() {
return {
...filterInput,
longitude: geolocation.coords.longitude,
latitude: geolocation.coords.latitude
latitude: geolocation.coords.latitude,
address: undefined
};
}
return { ...filterInput, address: undefined };
return { ...filterInput, longitude: undefined, latitude: undefined };
}, [dangle_role, filterInput, geolocation]);

const query = useHomeEventList(
filterForQuery,
getStartOfMonth(new Date()),
getEndOfMonth(new Date()),
{ ...monthlyInfiniteOption, enabled: !loading }
{ ...monthlyInfiniteOption, enabled: !loading && dangle_role !== 'SHELTER' }
);

const shelterQuery = useShelterHomeEventList(
filterForQuery,
getStartOfMonth(new Date()),
getEndOfMonth(new Date()),
{ ...monthlyInfiniteOption, enabled: dangle_role === 'SHELTER' }
);

const volunteerEvents = useMemo(() => {
// TODO: mock data 제거
if (!query.data) {
return homeEventsMock;
}
const pages = query.data?.pages;
const pages = query.data?.pages || shelterQuery.data?.pages;
return pages?.flatMap(page => page.events);
}, [query.data]);
}, [query.data, shelterQuery.data]);

const handleChangeFilter = useCallback(
(name: string, value: string | boolean) => {
Expand Down Expand Up @@ -121,7 +124,7 @@ export default function CalendarSection() {
}, []);

const scrollToTarget = (eventCardEl: HTMLElement) => {
const calendarEl = document.getElementById(CALENDAR_ID);
const calendarEl = document.getElementById(CALENDAR_ID)?.parentElement;
if (!calendarEl) return;

const calendarBottom = calendarEl.getBoundingClientRect().bottom;
Expand All @@ -132,9 +135,11 @@ export default function CalendarSection() {
};

const fetchNextEvents = useCallback(async () => {
const result = await query.fetchNextPage();
let result;
if (dangle_role === 'SHELTER') result = await shelterQuery.fetchNextPage();
else result = await query.fetchNextPage();
return { hasNext: Boolean(result.hasNextPage) };
}, [query]);
}, [dangle_role, query, shelterQuery]);

return (
<div>
Expand Down Expand Up @@ -179,26 +184,27 @@ export default function CalendarSection() {
handleChangeFilter('isFavorite', !filterInput.isFavorite)
}
/>
{dangle_role === 'NONE' && filterInput.isFavorite && (
<div className={styles.empty}>
<Body3 color="gray400">
보호소 즐겨찾기 기능을 사용하려면 <br />
로그인이 필요합니다
</Body3>
</div>
)}
</div>
<div style={{ marginTop: '16px' }}>
{!volunteerEvents && <SkeletonList />}
{volunteerEvents && (
<VolunteerEventList
selectedDate={selectedDate}
events={volunteerEvents}
scrollTo={scrollToTarget}
fetchNextEvents={fetchNextEvents}
/>
)}
</div>
{dangle_role === 'NONE' && filterInput.isFavorite ? (
<div className={styles.empty}>
<Body3 color="gray400">
보호소 즐겨찾기 기능을 사용하려면 <br />
로그인이 필요합니다
</Body3>
</div>
) : (
<div style={{ marginTop: '16px' }}>
{!volunteerEvents && <SkeletonList />}
{volunteerEvents && (
<VolunteerEventList
selectedDate={selectedDate}
events={volunteerEvents}
scrollTo={scrollToTarget}
fetchNextEvents={fetchNextEvents}
/>
)}
</div>
)}
</div>
);
}
Loading

0 comments on commit 2e8b84f

Please sign in to comment.