From c992467305a2ec170f0c4641587ce734b615f9ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Tue, 9 Apr 2024 17:34:36 +0900
Subject: [PATCH 1/8] feat(member): add ArrowButton Component (#91)
---
.../common/ArrowButton/ArrowButton.tsx | 26 +++++++++++++++++
.../common/Pagination/Pagination.tsx | 29 +++++++------------
2 files changed, 37 insertions(+), 18 deletions(-)
create mode 100644 apps/member/src/components/common/ArrowButton/ArrowButton.tsx
diff --git a/apps/member/src/components/common/ArrowButton/ArrowButton.tsx b/apps/member/src/components/common/ArrowButton/ArrowButton.tsx
new file mode 100644
index 00000000..e8315f03
--- /dev/null
+++ b/apps/member/src/components/common/ArrowButton/ArrowButton.tsx
@@ -0,0 +1,26 @@
+import { cn } from '@utils/string';
+import { ComponentPropsWithRef, forwardRef } from 'react';
+import { MdOutlineNavigateNext } from 'react-icons/md';
+
+interface ArrowButtonProps extends ComponentPropsWithRef<'button'> {
+ direction?: 'prev' | 'next';
+}
+
+const ArrowButton = forwardRef(
+ ({ direction = 'prev', className, ...rest }, ref) => {
+ return (
+
+ );
+ },
+);
+
+export default ArrowButton;
diff --git a/apps/member/src/components/common/Pagination/Pagination.tsx b/apps/member/src/components/common/Pagination/Pagination.tsx
index 4eb11bdb..242a1073 100644
--- a/apps/member/src/components/common/Pagination/Pagination.tsx
+++ b/apps/member/src/components/common/Pagination/Pagination.tsx
@@ -1,5 +1,5 @@
import { cn } from '@utils/string';
-import { MdOutlineNavigateNext } from 'react-icons/md';
+import ArrowButton from '../ArrowButton/ArrowButton';
import type { PaginationOnChange } from '@type/component';
interface PaginationProps {
@@ -19,12 +19,12 @@ const Pagination = ({
}: PaginationProps) => {
const totalPages = Math.ceil(totalItems / postLimit);
- const pageNumber = [];
- const startPage = Math.max(1, page - 2);
+ const startPage = Math.max(1, page - 1);
const endPage = Math.min(totalPages, startPage + 4);
- for (let i = startPage; i <= endPage; i++) {
- pageNumber.push(i);
- }
+ const pageNumber = Array.from(
+ { length: endPage - startPage + 1 },
+ (_, index) => startPage + index,
+ );
const handleCurrentPageClick = (page: number) => {
onChange(Math.max(1, Math.min(page, totalPages)));
@@ -32,12 +32,7 @@ const Pagination = ({
return (
-
+
handleCurrentPageClick(-postLimit)} />
{pageNumber.length === 0 ? (
1
) : (
@@ -53,12 +48,10 @@ const Pagination = ({
))
)}
-
+ handleCurrentPageClick(+postLimit)}
+ />
);
};
From 34d7816880e6216ad22606cbb60224bdca826bf6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Tue, 9 Apr 2024 17:36:15 +0900
Subject: [PATCH 2/8] feat(member): add StatusCard Component (#91)
---
.../common/StatusCard/StatusCard.tsx | 13 +++++++
.../common/StatusCard/StatusCard.types.ts | 5 +++
.../src/components/common/StatusCard/index.ts | 2 +
.../SupportProcedureSection.tsx | 38 ++++++++++---------
4 files changed, 41 insertions(+), 17 deletions(-)
create mode 100644 apps/member/src/components/common/StatusCard/StatusCard.tsx
create mode 100644 apps/member/src/components/common/StatusCard/StatusCard.types.ts
create mode 100644 apps/member/src/components/common/StatusCard/index.ts
diff --git a/apps/member/src/components/common/StatusCard/StatusCard.tsx b/apps/member/src/components/common/StatusCard/StatusCard.tsx
new file mode 100644
index 00000000..d596d387
--- /dev/null
+++ b/apps/member/src/components/common/StatusCard/StatusCard.tsx
@@ -0,0 +1,13 @@
+import type { StatusCardProps } from './StatusCard.types';
+
+const StatusCard = ({ icon, label, description }: StatusCardProps) => {
+ return (
+
+
{icon}
+
{label}
+
{description}
+
+ );
+};
+
+export default StatusCard;
diff --git a/apps/member/src/components/common/StatusCard/StatusCard.types.ts b/apps/member/src/components/common/StatusCard/StatusCard.types.ts
new file mode 100644
index 00000000..ac3b0d85
--- /dev/null
+++ b/apps/member/src/components/common/StatusCard/StatusCard.types.ts
@@ -0,0 +1,5 @@
+export interface StatusCardProps {
+ icon: React.ReactNode;
+ label: string;
+ description: string;
+}
diff --git a/apps/member/src/components/common/StatusCard/index.ts b/apps/member/src/components/common/StatusCard/index.ts
new file mode 100644
index 00000000..6e3a03be
--- /dev/null
+++ b/apps/member/src/components/common/StatusCard/index.ts
@@ -0,0 +1,2 @@
+export { default as StatusCard } from './StatusCard.tsx';
+export type { StatusCardProps } from './StatusCard.types.ts';
diff --git a/apps/member/src/components/support/SupportProcedureSection/SupportProcedureSection.tsx b/apps/member/src/components/support/SupportProcedureSection/SupportProcedureSection.tsx
index 20a57d73..32cb4ce6 100644
--- a/apps/member/src/components/support/SupportProcedureSection/SupportProcedureSection.tsx
+++ b/apps/member/src/components/support/SupportProcedureSection/SupportProcedureSection.tsx
@@ -1,4 +1,9 @@
import Section from '@components/common/Section/Section';
+import {
+ StatusCard,
+ type StatusCardProps,
+} from '@components/common/StatusCard';
+
import {
FcSurvey,
FcReading,
@@ -6,28 +11,28 @@ import {
FcFilingCabinet,
} from 'react-icons/fc';
-const stepContents = [
+const stepContents: StatusCardProps[] = [
{
- image: ,
- title: '신청서 작성',
+ icon: ,
+ label: '신청서 작성',
description:
'회비 사용과 관련된 정보를 입력하여 제출해 주세요. 누락된 정보가 있을 경우 승인이 반려될 수 있어요.',
},
{
- image: ,
- title: '운영진 검토',
+ icon: ,
+ label: '운영진 검토',
description:
'운영진이 신청서를 면밀히 검토하여 승인 여부를 결정해요. 24시간 이내에 처리가 되지 않는다면 별도로 문의해 주세요.',
},
{
- image: ,
- title: '직접 결제',
+ icon: ,
+ label: '직접 결제',
description:
'승인이 되면 결제 대금을 이체 받아요. 이체 받은 결제 대금을 사용하여 직접 결제를 진행해 주세요.',
},
{
- image: ,
- title: '사용 후 반납',
+ icon: ,
+ label: '사용 후 반납',
description:
'대여기간이 지나면 운영진이 사용 내역을 확인하고 반납 여부를 확인해요.',
},
@@ -38,14 +43,13 @@ const SupportProcedureSection = () => {
- {stepContents.map((stepContent, index) => (
-
-
- {stepContent.image}
-
-
{stepContent.title}
-
{stepContent.description}
-
+ {stepContents.map(({ icon, label, description }) => (
+
))}
From b8aca54e381ab7361f1ce6e957355ded3d5356ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Tue, 9 Apr 2024 23:34:06 +0900
Subject: [PATCH 3/8] =?UTF-8?q?refactor(member):=20=EC=BA=98=EB=A6=B0?=
=?UTF-8?q?=EB=8D=94=20UI=20=EA=B0=9C=EC=84=A0=20(#91)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/calendar/Calendar/Calendar.tsx | 77 -------------
.../CalendarSchedule/CalendarSchedule.tsx | 38 +++---
.../CalendarSection/CalendarSection.tsx | 99 ++++++++++++++++
apps/member/src/utils/date.ts | 108 ++++++++++++------
4 files changed, 195 insertions(+), 127 deletions(-)
delete mode 100644 apps/member/src/components/calendar/Calendar/Calendar.tsx
create mode 100644 apps/member/src/components/calendar/CalendarSection/CalendarSection.tsx
diff --git a/apps/member/src/components/calendar/Calendar/Calendar.tsx b/apps/member/src/components/calendar/Calendar/Calendar.tsx
deleted file mode 100644
index c463e3a5..00000000
--- a/apps/member/src/components/calendar/Calendar/Calendar.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import dayjs from 'dayjs';
-import classNames from 'classnames';
-import CalendarSchedule from '../CalendarSchedule/CalendarSchedule';
-import { now, transformEvents } from '@utils/date';
-import { useSchedule } from '@hooks/queries/useSchedule';
-import { DATE_FORMAT } from '@constants/state';
-
-interface CalendarProps {
- date: dayjs.Dayjs;
-}
-
-const Calendar = ({ date }: CalendarProps) => {
- const dateElements = [];
- const year = date.year();
- const month = date.month() + 1;
- const prevDaysInMonth = date.subtract(1, 'month').daysInMonth();
- const toDaysInMonth = date.daysInMonth();
-
- const { data } = useSchedule({
- startDate: date.startOf('month').format(DATE_FORMAT.WITH_TIME),
- endDate: date.endOf('month').format(DATE_FORMAT.WITH_TIME),
- });
-
- const events = transformEvents(data.items);
-
- //전 달 날짜
- for (let i = date.day(); i >= 0; i--) {
- const day = date.subtract(1, 'month');
- dateElements.push(
-
-
{prevDaysInMonth - i}
-
,
- );
- }
-
- //이번 달 날짜
- for (let i = 1; i <= toDaysInMonth; i++) {
- const day = dayjs(`${year}-${month}-${i}`);
- const element = day.format('YYYY-MM-DD');
- const event = events[element];
- dateElements.push(
- ,
- );
- }
-
- const nextDaysInMonth = 7 - (dateElements.length % 7);
-
- //다음 달 날짜
- for (let i = 1; i <= nextDaysInMonth; i++) {
- const day = date.add(1, 'month');
- dateElements.push(
- ,
- );
- }
-
- return dateElements;
-};
-
-export default Calendar;
diff --git a/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx b/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx
index 8eab441a..486f1227 100644
--- a/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx
+++ b/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx
@@ -1,6 +1,9 @@
import useModal from '@hooks/common/useModal';
import { formattedDate } from '@utils/date';
import type { ScheduleItem } from '@type/schedule';
+import { useCallback } from 'react';
+import { cn } from '@utils/string';
+import dayjs from 'dayjs';
const CalendarSchedule = ({
title,
@@ -9,28 +12,33 @@ const CalendarSchedule = ({
endDate,
}: ScheduleItem) => {
const { openModal } = useModal();
+ const isSameDate = dayjs(startDate).isSame(endDate, 'date');
- const onClickSchedule = (detail: string, start: string, end: string) => {
- let date = `${formattedDate(start)} ~ ${formattedDate(end)}`;
-
- if (start === end) {
+ const handleScheduleClick = useCallback(
+ (detail: string, start: string, end: string) => {
// 시작일과 종료일이 같은 경우, 종료일은 표시하지 않는다.
- date = `${formattedDate(start)}`;
- }
+ const date =
+ start === end
+ ? `${formattedDate(start)}`
+ : `${formattedDate(start)} ~ ${formattedDate(end)}`;
- openModal({
- title: '📆 일정',
- content: `내용: ${detail}\n일시: ${date}`,
- });
- };
+ openModal({
+ title: '📆 일정',
+ content: `일시: ${date}\n내용: ${detail}`,
+ });
+ },
+ [openModal],
+ );
return (
- onClickSchedule(detail, startDate, endDate)}
+
+
);
};
diff --git a/apps/member/src/components/calendar/CalendarSection/CalendarSection.tsx b/apps/member/src/components/calendar/CalendarSection/CalendarSection.tsx
new file mode 100644
index 00000000..1f47b18b
--- /dev/null
+++ b/apps/member/src/components/calendar/CalendarSection/CalendarSection.tsx
@@ -0,0 +1,99 @@
+import { startTransition, useCallback, useState } from 'react';
+import ArrowButton from '@components/common/ArrowButton/ArrowButton';
+import Section from '@components/common/Section/Section';
+import { useSchedule } from '@hooks/queries';
+import { now, transformEvents } from '@utils/date';
+import { cn } from '@utils/string';
+import CalendarSchedule from '../CalendarSchedule/CalendarSchedule';
+
+const today = now();
+
+const CalendarSection = () => {
+ const [date, setDate] = useState(today);
+ const { data } = useSchedule({
+ startDate: date.startOf('month').format('YYYY-MM-DD'),
+ endDate: date.endOf('month').format('YYYY-MM-DD'),
+ });
+
+ const handleDateClick = useCallback((action: 'prev' | 'next' | 'today') => {
+ startTransition(() => {
+ setDate((current) => {
+ switch (action) {
+ case 'prev':
+ return current.subtract(1, 'month');
+ case 'next':
+ return current.add(1, 'month');
+ case 'today':
+ return today;
+ }
+ });
+ });
+ }, []);
+
+ const events = transformEvents(data.items);
+ const startDay = date.startOf('month').startOf('week'); // 현재 월의 첫 날짜의 주의 시작일
+ const endDay = date.endOf('month').endOf('week'); // 현재 월의 마지막 날짜의 주의 마지막일
+
+ const days = [];
+ let day = startDay;
+
+ while (day.isBefore(endDay)) {
+ const isToday = day.isSame(today, 'day');
+
+ days.push(
+
+
+
+ {day.format('D')}
+
+ {events[day.format('YYYY-MM-DD')]?.map((event) => (
+
+ ))}
+
+ | ,
+ );
+ day = day.add(1, 'day');
+ }
+
+ const weeks = [];
+ for (let i = 0; i < days.length; i += 7) {
+ weeks.push({days.slice(i, i + 7)}
);
+ }
+
+ return (
+
+
+ handleDateClick('prev')} />
+
+ handleDateClick('next')} />
+
+
+
+
+
+ 일 |
+ 월 |
+ 화 |
+ 수 |
+ 목 |
+ 금 |
+ 토 |
+
+
+ {weeks}
+
+
+
+ );
+};
+
+export default CalendarSection;
diff --git a/apps/member/src/utils/date.ts b/apps/member/src/utils/date.ts
index 62b11933..34db7d6e 100644
--- a/apps/member/src/utils/date.ts
+++ b/apps/member/src/utils/date.ts
@@ -1,16 +1,15 @@
import type { ScheduleItem } from '@type/schedule';
import dayjs from 'dayjs';
import 'dayjs/locale/ko';
-
dayjs.locale('ko');
-
/**
* 오늘 날짜를 dayjs 객체로 반환합니다.
*
* @returns {dayjs.Dayjs} dayjs 객체로 반환합니다.
*/
-export const now = (): dayjs.Dayjs => dayjs();
-
+export function now(): dayjs.Dayjs {
+ return dayjs();
+}
/**
* 주어진 날짜 문자열을 'YY.MM.DD' 형식으로 변환합니다.
* `dayjs` 라이브러리의 `format` 메서드를 사용하여 날짜 형식을 지정합니다.
@@ -19,70 +18,109 @@ export const now = (): dayjs.Dayjs => dayjs();
* @param date 날짜를 나타내는 문자열입니다. 'YYYY-MM-DD', 'YYYY/MM/DD' 등의 형식이 가능합니다.
* @returns 변환된 날짜 문자열을 'YY.MM.DD' 형식으로 반환합니다.
*/
-export const toYYMMDD = (date: string) => dayjs(date).format('YY.MM.DD');
-
+export function toYYMMDD(date: string) {
+ return dayjs(date).format('YY.MM.DD');
+}
/**
* 주어진 날짜와 오늘 날짜의 차이를 일(day) 단위로 계산하여 반환합니다.
*
* @param {string} date - 비교할 날짜 (YYYY-MM-DD 형식)
* @return {number} 오늘부터 주어진 날짜까지의 일수 차이, 절대값으로 반환됩니다.
*/
-export const calculateDDay = (date: string): number =>
- Math.abs(dayjs(date).diff(dayjs(), 'day'));
-
+export function calculateDDay(date: string): number {
+ return Math.abs(dayjs(date).diff(dayjs(), 'day'));
+}
/**
* 주어진 날짜를 'YY.MM.DD(dd) HH:mm' 형식으로 포맷합니다.
*
* @param {Date|string|number} date - 포맷할 날짜. Date 객체, 문자열 또는 타임스탬프일 수 있습니다.
* @returns {string} 포맷된 날짜 문자열.
*/
-export const formattedDate = (date: string | undefined): string =>
- dayjs(date).format('YY.MM.DD(dd) HH:mm');
-
+export function formattedDate(date: string | undefined): string {
+ return dayjs(date).format('YY.MM.DD(dd) HH:mm');
+}
/**
* 주어진 초를 'mm:ss' 형식으로 포맷합니다.
*
* @param {number} seconds - 초 단위의 시간.
* @returns {string} 포맷된 시간 문자열.
*/
-export const formattedTime = (seconds: number): string =>
- dayjs()
+export function formattedTime(seconds: number): string {
+ return dayjs()
.startOf('day')
.add(seconds * 1000)
.format('mm:ss');
-
+}
/**
* 주어진 이벤트 배열을 받아서, 각 이벤트의 시작 날짜를 기준으로 객체로 변환합니다.
* 변환된 객체의 키는 이벤트의 `startDate`에서 추출한 날짜이며, 값은 해당 이벤트 객체입니다.
*
* @param {ScheduleItem[]} events - 변환할 이벤트 객체 배열. 각 이벤트는 `ScheduleItem` 인터페이스를 따릅니다.
- * @returns {Record} - 키가 이벤트의 시작 날짜이고 값이 이벤트 객체인 객체.
+ * @returns {Record} - 키가 이벤트의 시작 날짜이고 값이 이벤트 객체인 객체.
*/
export const transformEvents = (
events: ScheduleItem[],
-): Record => {
+): Record => {
return events.reduce(
(acc, current) => {
- const dateKey = current.startDate.split('T')[0];
- acc[dateKey] = current;
+ const startDate = new Date(current.startDate.split('T')[0]); // 시작 날짜 객체 생성
+ const endDate = new Date(current.endDate.split('T')[0]); // 종료 날짜 객체 생성
+
+ for (
+ let date = new Date(startDate);
+ date <= endDate;
+ date.setDate(date.getDate() + 1)
+ ) {
+ const dateKey = date.toISOString().split('T')[0];
+
+ if (!acc[dateKey]) {
+ acc[dateKey] = [];
+ }
+
+ acc[dateKey].push(current);
+ }
+
return acc;
},
- {} as Record,
+ {} as Record,
);
};
+/**
+ * 가장 가까운 이벤트를 찾습니다.
+ *
+ * @param events - 일정 항목 배열 (transformEvents 함수로 변환된 객체)
+ * @returns 가장 가까운 이벤트 항목
+ */
+export function findClosestEvent(
+ events: Record,
+): ScheduleItem | null {
+ const today = dayjs();
+ let closestEvent: ScheduleItem | null = null;
+ let smallestDiff = Number.MAX_SAFE_INTEGER;
+
+ Object.entries(events).forEach(([date, eventArray]) => {
+ const eventDate = dayjs(date);
+ const diff = eventDate.diff(today, 'day');
+
+ if (diff >= 0 && diff < smallestDiff) {
+ smallestDiff = diff;
+ closestEvent = eventArray[0];
+ }
+ });
+ return closestEvent;
+}
/**
* 주어진 날짜의 년도와 학기를 반환합니다.
*
* @param {string} createdAt - 주어진 날짜.
* @return {string} 주어진 날짜를 YY년도 1/2학기로 반환.
*/
-export const getDateSemester = (createdAt: string): string => {
+export function getDateSemester(createdAt: string): string {
const year = dayjs(createdAt).format('YY');
const semester = dayjs(createdAt).get('month') <= 5 ? 1 : 2;
return `${year}년도 ${semester}학기`;
-};
-
+}
/**
* 주어진 참조 날짜와 확인하고자 하는 날짜를 비교하여 유효성을 평가합니다.
*
@@ -90,26 +128,24 @@ export const getDateSemester = (createdAt: string): string => {
* @param {string | undefined} referenceDateStr - 유효성 판단의 기준이 되는 날짜를 나타내는 문자열입니다. 'YYYY-MM-DD' 형식이어야 합니다.
* @returns {boolean} 확인하고자 하는 날짜가 참조 날짜 이전이거나 같으면 true, 그렇지 않으면 false를 반환합니다.
*/
-export const isDateValid = (
+export function isDateValid(
checkDateStr: string | undefined,
referenceDateStr: string | undefined,
-): boolean => {
+): boolean {
const checkDate = dayjs(checkDateStr);
const referenceDate = dayjs(referenceDateStr);
return checkDate.isBefore(referenceDate) || checkDate.isSame(referenceDate);
-};
-
+}
/**
* 14일 기간 기반으로 작업 또는 이벤트의 진행 상황을 백분율로 계산합니다.
*
* @param {string} date - 작업 또는 이벤트의 시작 날짜 (Day.js와 호환 가능한 형식).
* @returns {number} 14일 기간 내 진행 상황을 나타내는 백분율 (0-100).
*/
-export const checkProgress = (date: string) => {
+export function checkProgress(date: string): number {
const end = dayjs(date).add(14, 'd');
return (end.diff(now(), 'd') * 100) % 14;
-};
-
+}
/**
* 시작 날짜와 종료 날짜 사이의 맞춤 시간 기간 내 진행 상황을 백분율로 계산합니다.
* 14일 기간을 넘어서는 작업이나 기간 연장에 유용합니다.
@@ -118,20 +154,22 @@ export const checkProgress = (date: string) => {
* @param {string} endDate - 작업 또는 이벤트의 종료 날짜.
* @returns {number} 지정된 기간 내 진행 상황을 나타내는 백분율 (0-100).
*/
-export const checkExtendProgress = (startDate: string, endDate: string) => {
+export function checkExtendProgress(
+ startDate: string,
+ endDate: string,
+): number {
const end = dayjs(endDate);
const start = dayjs(startDate);
const gap = end.diff(start, 'd'); // 총 기간 (일 단위)
return (end.diff(now(), 'd') * 100) % gap;
-};
-
+}
/**
* 기본 14일 유예 기간을 포함한 지정된 마감 날짜까지 남은 일 수를 계산합니다.
*
* @param {string} date - 작업 또는 이벤트의 마감 날짜.
* @returns {number} 남은 일 수. 마감 날짜가 지났으면 음수 값이 반환됩니다.
*/
-export const checkDueDate = (date: string) => {
+export function checkDueDate(date: string): number {
const end = dayjs(date).add(14, 'd');
return end.diff(now(), 'd');
-};
+}
From 5da0b44d167347b2fae1ba155d798b664de1428c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Tue, 9 Apr 2024 23:34:53 +0900
Subject: [PATCH 4/8] =?UTF-8?q?refactor(member):=20useSchedule=20=EA=B8=B0?=
=?UTF-8?q?=EB=B3=B8=EA=B0=92=20=EB=B3=80=EA=B2=BD=20(#91)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/member/src/hooks/queries/useSchedule.ts | 16 ++++++++--------
apps/member/src/types/api.ts | 7 +++++++
2 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/apps/member/src/hooks/queries/useSchedule.ts b/apps/member/src/hooks/queries/useSchedule.ts
index 0a8d2680..f6c3303f 100644
--- a/apps/member/src/hooks/queries/useSchedule.ts
+++ b/apps/member/src/hooks/queries/useSchedule.ts
@@ -2,17 +2,17 @@ import { getSchedule } from '@api/schedule';
import { QUERY_KEY } from '@constants/key';
import { DATE_FORMAT } from '@constants/state';
import { useSuspenseQuery } from '@tanstack/react-query';
-import type { PaginationPramsType } from '@type/api';
+import type { WithPaginationPrams } from '@type/api';
import dayjs from 'dayjs';
-interface UseMainScheduleArgs extends PaginationPramsType {
+interface UseMainSchedulePrams extends WithPaginationPrams {
startDate?: string;
endDate?: string;
}
-// 오늘 날짜와 3개월 후 날짜를 기본값으로 설정
-const defaultStartDate = dayjs().format(DATE_FORMAT.WITH_TIME);
-const defaultEndDate = dayjs().add(3, 'month').format(DATE_FORMAT.WITH_TIME);
+// 이번달을 기본값으로 설정
+const defaultStartDate = dayjs().startOf('month').format(DATE_FORMAT.WITH_TIME);
+const defaultEndDate = dayjs().endOf('month').format(DATE_FORMAT.WITH_TIME);
/**
* 동아리 일정을 조회합니다.
@@ -21,10 +21,10 @@ export const useSchedule = ({
startDate = defaultStartDate,
endDate = defaultEndDate,
page = 0,
- size = 62,
-}: UseMainScheduleArgs) => {
+ size = 100,
+}: UseMainSchedulePrams = {}) => {
return useSuspenseQuery({
- queryKey: [QUERY_KEY.SCHEDULE, startDate, endDate, page, size],
+ queryKey: [QUERY_KEY.SCHEDULE, startDate, endDate],
queryFn: () => getSchedule({ startDate, endDate, page, size }),
});
};
diff --git a/apps/member/src/types/api.ts b/apps/member/src/types/api.ts
index 38eaa20d..a21dcbdd 100644
--- a/apps/member/src/types/api.ts
+++ b/apps/member/src/types/api.ts
@@ -20,6 +20,13 @@ export interface PaginationPramsType {
page?: number;
size?: number;
}
+/**
+ * `PaginationPramsType`를 대체하기 위한 페이지네이션 파라미터 타입입니다.
+ */
+export interface WithPaginationPrams {
+ page?: number;
+ size?: number;
+}
export type IDType = string | number;
From 63b98d4b8db875550ed865f95cc2e39cc4c460ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Wed, 10 Apr 2024 00:44:34 +0900
Subject: [PATCH 5/8] =?UTF-8?q?refactor(member):=20=EC=BA=98=EB=A6=B0?=
=?UTF-8?q?=EB=8D=94=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20UI=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0=20(#91)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CalendarSchedule/CalendarSchedule.tsx | 30 +++++++++++++++----
.../CalendarSection/CalendarSection.tsx | 2 +-
2 files changed, 26 insertions(+), 6 deletions(-)
diff --git a/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx b/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx
index 486f1227..50ea169a 100644
--- a/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx
+++ b/apps/member/src/components/calendar/CalendarSchedule/CalendarSchedule.tsx
@@ -5,14 +5,19 @@ import { useCallback } from 'react';
import { cn } from '@utils/string';
import dayjs from 'dayjs';
+interface CalendarScheduleProps extends ScheduleItem {
+ day: dayjs.Dayjs;
+}
+
const CalendarSchedule = ({
+ day,
title,
detail,
startDate,
endDate,
-}: ScheduleItem) => {
+}: CalendarScheduleProps) => {
const { openModal } = useModal();
- const isSameDate = dayjs(startDate).isSame(endDate, 'date');
+ const isDateDiff = dayjs(startDate).diff(endDate, 'd');
const handleScheduleClick = useCallback(
(detail: string, start: string, end: string) => {
@@ -32,9 +37,24 @@ const CalendarSchedule = ({
return (
{events[day.format('YYYY-MM-DD')]?.map((event) => (
-
+
))}
,
From 126b6c6dd624bab01cb892a7aa2c5353be66a002 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Wed, 10 Apr 2024 01:17:14 +0900
Subject: [PATCH 6/8] =?UTF-8?q?feat(member):=20=EC=9D=BC=EC=A0=95=20?=
=?UTF-8?q?=EB=AA=A8=EC=95=84=EB=B3=B4=EA=B8=B0=20API=20=EC=97=B0=EB=8F=99?=
=?UTF-8?q?=20(#91)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/member/src/api/schedule.ts | 16 +++++-
.../CalendarStatusSection.tsx | 49 ++++++++++++++++++
.../common/StatusCard/StatusCard.types.ts | 2 +-
apps/member/src/constants/api.ts | 3 ++
apps/member/src/constants/key.ts | 1 +
.../src/hooks/queries/useScheduleCollect.ts | 13 +++++
.../src/pages/CalendarPage/CalendarPage.tsx | 51 ++++---------------
apps/member/src/types/schedule.ts | 8 +++
8 files changed, 99 insertions(+), 44 deletions(-)
create mode 100644 apps/member/src/components/calendar/CalendarStatusSection/CalendarStatusSection.tsx
create mode 100644 apps/member/src/hooks/queries/useScheduleCollect.ts
diff --git a/apps/member/src/api/schedule.ts b/apps/member/src/api/schedule.ts
index fc544b42..ac153804 100644
--- a/apps/member/src/api/schedule.ts
+++ b/apps/member/src/api/schedule.ts
@@ -6,7 +6,11 @@ import type {
PaginationPramsType,
PaginationType,
} from '@type/api';
-import type { ScheduleItem, ScheduleRegisterItem } from '@type/schedule';
+import type {
+ ScheduleCollect,
+ ScheduleItem,
+ ScheduleRegisterItem,
+} from '@type/schedule';
interface GetScheduleParams extends PaginationPramsType {
startDate: string;
@@ -30,6 +34,16 @@ export const getSchedule = async ({
return data;
};
+/**
+ * 일정 모아보기
+ */
+export const getScheduleCollect = async () => {
+ const { data } = await server.get>({
+ url: END_POINT.SCHEDULE_COLLECT,
+ });
+
+ return data;
+};
/**
* 일정 등록
*/
diff --git a/apps/member/src/components/calendar/CalendarStatusSection/CalendarStatusSection.tsx b/apps/member/src/components/calendar/CalendarStatusSection/CalendarStatusSection.tsx
new file mode 100644
index 00000000..5db6c04d
--- /dev/null
+++ b/apps/member/src/components/calendar/CalendarStatusSection/CalendarStatusSection.tsx
@@ -0,0 +1,49 @@
+import Section from '@components/common/Section/Section';
+import StatusCard from '@components/common/StatusCard/StatusCard';
+import { useSchedule } from '@hooks/queries';
+import { calculateDDay, findClosestEvent, transformEvents } from '@utils/date';
+import { FcCalendar, FcLeave, FcOvertime, FcAlarmClock } from 'react-icons/fc';
+import { useScheduleCollect } from '@hooks/queries/useScheduleCollect';
+
+const CalendarStatusSection = () => {
+ const { data: yearData } = useScheduleCollect();
+ const { data: monthData } = useSchedule();
+
+ const closestEvent = findClosestEvent(transformEvents(monthData.items));
+ const closestDDay = closestEvent?.startDate
+ ? `D-${calculateDDay(closestEvent.startDate)}`
+ : '이번 달에 남은 일정이 없어요';
+
+ return (
+
+
+
+ }
+ label={`${yearData.totalScheduleCount}회`}
+ description="이번 연도 동아리의 모든 일정 횟수에요."
+ />
+ }
+ label={`${yearData.totalEventCount}회`}
+ description="이번 연도 총회, MT 등 중요도가 높은 행사 횟수에요."
+ />
+ }
+ label={`${monthData.totalItems}회`}
+ description="이번 달 동아리 일정 횟수에요."
+ />
+ }
+ label={closestDDay}
+ description="가장 가까운 일정까지 남은 일수에요."
+ />
+
+
+ );
+};
+
+export default CalendarStatusSection;
diff --git a/apps/member/src/components/common/StatusCard/StatusCard.types.ts b/apps/member/src/components/common/StatusCard/StatusCard.types.ts
index ac3b0d85..e7b8f806 100644
--- a/apps/member/src/components/common/StatusCard/StatusCard.types.ts
+++ b/apps/member/src/components/common/StatusCard/StatusCard.types.ts
@@ -1,5 +1,5 @@
export interface StatusCardProps {
icon: React.ReactNode;
- label: string;
+ label: string | number;
description: string;
}
diff --git a/apps/member/src/constants/api.ts b/apps/member/src/constants/api.ts
index 4730e37c..6df04f7d 100644
--- a/apps/member/src/constants/api.ts
+++ b/apps/member/src/constants/api.ts
@@ -5,8 +5,10 @@ export const API_BASE_URL: string = import.meta.env.VITE_API_BASE_URL;
export const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN';
export const REFRESH_TOKEN_KEY = 'REFRESH_TOKEN';
+
export const FORM_DATA_KEY = 'multipartFile';
export const STORAGE_PERIOD = (period: number) => `?storagePeriod=${period}`;
+export const MAX_PAGINATION_SIZE = 99999;
export const END_POINT = {
LOGIN_REISSUE: '/v1/login/reissue',
@@ -35,6 +37,7 @@ export const END_POINT = {
MY_BIRTHDAY: `/v1/members/birthday`,
MY_ACTIVITY: `/v1/schedule/activity`,
MAIN_SCHEDULE: `/v1/schedule`,
+ SCHEDULE_COLLECT: `/v1/schedule/collect`,
MAIN_ACTIVITY_PHOTO: `/v1/activity-photos`,
MEMBERSHIP_FEE: `/v1/membership-fees`,
SHARED_ACCOUNT: `/v1/shared-accounts`,
diff --git a/apps/member/src/constants/key.ts b/apps/member/src/constants/key.ts
index 56fbade4..0ce8a96d 100644
--- a/apps/member/src/constants/key.ts
+++ b/apps/member/src/constants/key.ts
@@ -24,6 +24,7 @@ export const QUERY_KEY = {
HIRE: 'Hire',
BIRTHDAY: 'Birthday',
SCHEDULE: 'Schedule',
+ SCHEDULE_COLLECT: 'ScheduleCollect',
MAIN_ACTIVITY_PHOTO: 'MainActivityPhoto',
COMMENTS: 'Comments',
MEMBERSHIP_FEE: 'MembershipFee',
diff --git a/apps/member/src/hooks/queries/useScheduleCollect.ts b/apps/member/src/hooks/queries/useScheduleCollect.ts
new file mode 100644
index 00000000..08b93337
--- /dev/null
+++ b/apps/member/src/hooks/queries/useScheduleCollect.ts
@@ -0,0 +1,13 @@
+import { getScheduleCollect } from '@api/schedule';
+import { QUERY_KEY } from '@constants/key';
+import { useSuspenseQuery } from '@tanstack/react-query';
+
+/**
+ * 일정 모아보기를 조회합니다.
+ */
+export const useScheduleCollect = () => {
+ return useSuspenseQuery({
+ queryKey: [QUERY_KEY.SCHEDULE_COLLECT],
+ queryFn: getScheduleCollect,
+ });
+};
diff --git a/apps/member/src/pages/CalendarPage/CalendarPage.tsx b/apps/member/src/pages/CalendarPage/CalendarPage.tsx
index 557dd4a4..1f03acaf 100644
--- a/apps/member/src/pages/CalendarPage/CalendarPage.tsx
+++ b/apps/member/src/pages/CalendarPage/CalendarPage.tsx
@@ -1,52 +1,19 @@
-import { startTransition, useState } from 'react';
+import { Suspense } from 'react';
import Content from '@components/common/Content/Content';
import Header from '@components/common/Header/Header';
-import Section from '@components/common/Section/Section';
-import Calendar from '@components/calendar/Calendar/Calendar';
-import { now } from '@utils/date';
-import { Dayjs } from 'dayjs';
-
-const today = now();
+import CalendarSection from '@components/calendar/CalendarSection/CalendarSection';
+import CalendarStatusSection from '@components/calendar/CalendarStatusSection/CalendarStatusSection';
const CalendarPage = () => {
- const [date, setDate] = useState(today);
- const onClickPrev = () => {
- startTransition(() => {
- setDate((prev) => prev.subtract(1, 'month'));
- });
- };
-
- const onClickNext = () => {
- startTransition(() => {
- setDate((prev) => prev.add(1, 'month'));
- });
- };
-
- const onClickToday = () => {
- startTransition(() => {
- setDate(today);
- });
- };
-
return (
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
);
};
diff --git a/apps/member/src/types/schedule.ts b/apps/member/src/types/schedule.ts
index fefb463b..ca3b039b 100644
--- a/apps/member/src/types/schedule.ts
+++ b/apps/member/src/types/schedule.ts
@@ -1,3 +1,5 @@
+type SchedulePriority = 'HIGH' | 'MIDDLE' | 'LOW';
+
export interface ScheduleItem {
id: number;
title: string;
@@ -5,6 +7,7 @@ export interface ScheduleItem {
activityName: string | null;
startDate: string;
endDate: string;
+ priority: SchedulePriority;
}
export interface ScheduleRegisterItem {
@@ -15,3 +18,8 @@ export interface ScheduleRegisterItem {
endDateTime: string;
activityGroupId?: number;
}
+
+export interface ScheduleCollect {
+ totalScheduleCount: number;
+ totalEventCount: number;
+}
From f74f638265f7f37428d79eed73d9a639a7750e0f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Wed, 10 Apr 2024 01:18:21 +0900
Subject: [PATCH 7/8] refactor(member): linker component to make target prop
optional (#91)
---
apps/member/src/components/common/Linker/Linker.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/member/src/components/common/Linker/Linker.tsx b/apps/member/src/components/common/Linker/Linker.tsx
index b87625b4..14ce4933 100644
--- a/apps/member/src/components/common/Linker/Linker.tsx
+++ b/apps/member/src/components/common/Linker/Linker.tsx
@@ -5,7 +5,7 @@ import { Link } from 'react-router-dom';
interface LinkerProps extends PropsWithChildren {
to: string | Partial;
className?: string;
- target: React.HTMLAttributeAnchorTarget;
+ target?: React.HTMLAttributeAnchorTarget;
}
const Linker = ({ to, target, className, children }: LinkerProps) => {
From dd31e47b19f5be4d7ef716037397eb4d8e245fb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B4=80=EC=8B=9D?=
<39869096+gwansikk@users.noreply.github.com>
Date: Wed, 10 Apr 2024 01:46:26 +0900
Subject: [PATCH 8/8] =?UTF-8?q?fix(member):=20useBoardList=20queryOptions?=
=?UTF-8?q?=20=ED=83=80=EC=9E=85=20=EC=B6=94=EB=A1=A0=20=EC=97=90=EB=9F=AC?=
=?UTF-8?q?=20(#91)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/member/src/hooks/queries/useBoardsList.ts | 12 ++++++++++--
apps/member/src/types/api.ts | 2 +-
apps/member/src/types/hire.ts | 3 +--
apps/member/src/types/news.ts | 2 +-
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/apps/member/src/hooks/queries/useBoardsList.ts b/apps/member/src/hooks/queries/useBoardsList.ts
index 1e9c12e1..615474da 100644
--- a/apps/member/src/hooks/queries/useBoardsList.ts
+++ b/apps/member/src/hooks/queries/useBoardsList.ts
@@ -3,8 +3,16 @@ import { getNews } from '@api/news';
import { QUERY_KEY } from '@constants/key';
import { useSuspenseQuery } from '@tanstack/react-query';
import { getBoardsList } from '@api/board';
-import { PaginationPramsType } from '@type/api';
+import type { Pagination, PaginationPramsType } from '@type/api';
import type { CommunityCategoryType } from '@type/community';
+import type { PostItem } from '@type/post';
+import type { NewsItem } from '@type/news';
+import type { HireItem } from '@type/hire';
+
+interface QueryOptions {
+ queryKey: string;
+ queryFn: () => Promise>;
+}
interface UseBoardsListParams extends PaginationPramsType {
category: CommunityCategoryType;
@@ -18,7 +26,7 @@ export const useBoardsList = ({
page = 0,
size = 6,
}: UseBoardsListParams) => {
- const queryOptions = {
+ const queryOptions: QueryOptions = {
notice: {
queryKey: QUERY_KEY.BORDER_NOTICE,
queryFn: () => getBoardsList('notice', page, size),
diff --git a/apps/member/src/types/api.ts b/apps/member/src/types/api.ts
index a21dcbdd..8fcc8c98 100644
--- a/apps/member/src/types/api.ts
+++ b/apps/member/src/types/api.ts
@@ -3,7 +3,7 @@ export interface BaseResponse {
data: T;
}
-interface Pagination {
+export interface Pagination {
currentPage: number;
hasPrevious: boolean;
hasNext: boolean;
diff --git a/apps/member/src/types/hire.ts b/apps/member/src/types/hire.ts
index c385206e..f705c0d0 100644
--- a/apps/member/src/types/hire.ts
+++ b/apps/member/src/types/hire.ts
@@ -16,8 +16,7 @@ export type EmploymentType =
| 'ASSISTANT'
| 'PART_TIME';
-export interface HireItem
- extends Omit {
+export interface HireItem extends Omit {
careerLevel: CareerLevel;
recruitmentPeriod: string;
jobPostingUrl: string;
diff --git a/apps/member/src/types/news.ts b/apps/member/src/types/news.ts
index 511c51d9..2956317f 100644
--- a/apps/member/src/types/news.ts
+++ b/apps/member/src/types/news.ts
@@ -1,6 +1,6 @@
import { PostItem } from './post';
-export interface NewsItem extends Omit {
+export interface NewsItem extends PostItem {
articleUrl: string;
date: string;
source: string;