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

[FE] 해커톤 구현 내용 리팩터링 #49

Merged
merged 28 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6aabe24
chore: .vscode 세팅파일 수정
Largopie Jul 21, 2024
707d41b
refactor: TimePicker 컴포넌트에서 Button 컴포넌트 분리
Largopie Jul 21, 2024
a5c58ac
design: button 기본 색상 'red' 및 border 제거
Largopie Jul 21, 2024
f405e1b
chore: eslint jsx-a11y 일부 설정 "off"로 설정
Largopie Jul 21, 2024
7b5ccc4
refactor(TimeSlot): 시간 선택 박스 컴포넌트 분리
Largopie Jul 21, 2024
50cdb94
refactor: schedule api 컨텍스트 통합
Largopie Jul 22, 2024
1f9b183
refactor: 서버 상태 관리 로직 레이어 분리
Largopie Jul 22, 2024
958bb63
comment: uuid 관련 주석 추가
Largopie Jul 22, 2024
dec3d79
design(TimeSlot): 스타일 로직 통합
Largopie Jul 22, 2024
0c33c29
chore: `stores`, `contexts` 경로 추가
Largopie Jul 22, 2024
decd119
refactor: `isUpdate` 상태 context로 관리
Largopie Jul 22, 2024
360ba72
refactor(TimePicker): UpdateProvider 로직 연결 및 Picker, Viewer과 나누기 위한 폴…
Largopie Jul 22, 2024
518117a
style: css 선언 순서 수정
Largopie Jul 22, 2024
7b733d9
fix: post 요청 시 return 값 없도록 수정
Largopie Jul 22, 2024
cb8635f
feat: queryKey 상수 정의
Largopie Jul 22, 2024
681bc1c
refactor: mutation에 invalidateQueries 추가 및 queryKey 상수로 변경
Largopie Jul 22, 2024
96cc195
refactor: update 상태 provider 연결 및 TimePicker, TimeViewer 분리 후 구현
Largopie Jul 22, 2024
6262451
refactor: 시간 수정 낙관적 업데이트 로직 추가
hwinkr Jul 23, 2024
ab0f629
refactor: 기존 테이블을 구성할 때 사용했던 서버 상태의 '초' 단위 제거
hwinkr Jul 23, 2024
7fe63fa
refactor: context에서 관리하는 isUpdate 상태 값을 Provider 에서 내려주도록 변경
hwinkr Jul 23, 2024
a520dda
chore: 인터페이스명 PascalCase로 수정
Largopie Jul 23, 2024
eb386bc
chore: consistent-type-imports 규칙 추가 및 규칙 반영
Largopie Jul 23, 2024
7639820
refactor: 상수 정의 as const 사용
Largopie Jul 23, 2024
a444887
rename: update -> TimePickerUpdate로 네이밍 수정
Largopie Jul 23, 2024
e287ee4
rename: css prop 네이밍 <name>Style로 수정
Largopie Jul 23, 2024
ba54102
refactor: 조건부 렌더링 코드 수정
Largopie Jul 24, 2024
5725b66
refactor: 페이지명 변경사항 router파일에 적용
Largopie Jul 24, 2024
996832c
rename: css prop 네이밍 규칙에 따른 변수명 수정
Largopie Jul 24, 2024
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
3 changes: 3 additions & 0 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"plugin:storybook/recommended"
],
"rules": {
"@typescript-eslint/consistent-type-imports": "error",
Copy link
Contributor

Choose a reason for hiding this comment

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

오 이 설정 좋은데?

"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"react-refresh/only-export-components": [
"warn",
{
Expand Down
2 changes: 2 additions & 0 deletions frontend/.prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

"importOrder": [
"^@layouts/(.*)$",
"^@contexts/(.*)$",
"^@pages/(.*)$",
"^@components/(.*)$",
"^@hooks/(.*)$",
"^@apis/(.*)$",
"^@stores/(.*)$",
"^@common/(.*)$",
"^@utils/(.*)$",
"^@assets/(.*)$",
Expand Down
10 changes: 0 additions & 10 deletions frontend/.vscode/setting.json

This file was deleted.

11 changes: 11 additions & 0 deletions frontend/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"eslint.workingDirectories": ["./frontend"],
"stylelint.configFile": ".stylelintrc.json",
"stylelint.packageManager": "npm",
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.prettier": "explicit"
},
"stylelint.validate": ["typescript", "typescriptreact"]
}
7 changes: 4 additions & 3 deletions frontend/src/apis/getMeeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ export interface Schedules {
times: string[];
}

export interface getMeetingResponse {
export interface GetMeetingResponse {
meetingName: string;
startTime: string;
endTime: string;
availableDates: string[];
schedules: Schedules[];
}
Comment on lines 13 to 14
Copy link
Contributor

Choose a reason for hiding this comment

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

P1

Suggested change
schedules: Schedules[];
}
export interface GetMeetingResponse {

인터페이스 네이밍을 파스칼 케이스로 통일하면 좋을 것 같아요!

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 getMeeting = async (uuid = '550e8400') => {
// uuid 기본값은 임시 설정된 uuid
const getMeeting = async (uuid = '550e8400'): Promise<GetMeetingResponse> => {
const url = `${API_URL}/api/v1/meeting/${uuid}`;

const response = await fetch(url, {
Expand All @@ -29,7 +30,7 @@ const getMeeting = async (uuid = '550e8400') => {

const data = await response.json();

return data.data as getMeetingResponse;
return data.data;
};

export default getMeeting;
21 changes: 0 additions & 21 deletions frontend/src/apis/getSchedule.ts

This file was deleted.

28 changes: 0 additions & 28 deletions frontend/src/apis/postSchedule.ts

This file was deleted.

42 changes: 42 additions & 0 deletions frontend/src/apis/schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { API_URL } from '@constants/api';

import type { Schedules } from './getMeeting';

export const postSchedule = async (requestData: Schedules[]) => {
const url = `${API_URL}/api/v1/schedule`;

const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
meetingId: 1,
guestName: 'daon',
dateTimes: requestData,
}),
});

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
};

// 현재 쓰지 않는 API (schedule 수정 시 사용하는 API)
export const getSchedule = async () => {
const url = `${API_URL}/api/v1/schedule}`;

const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

const data = await response.json();
return data;
};
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
import { TimePickerProps } from '.';
import type { GetMeetingResponse } from '@apis/getMeeting';

type Test = Record<string, number>;
type TimeSlot = Record<string, number>;

const generateTimeSlots = (start: string, end: string) => {
const startHour = Number(start.split(':')[0]);
const endHour = Number(end.split(':')[0]);
const slots = [];
const slots: string[] = [];

for (let i = startHour; i <= endHour; i++) {
slots.push(`${i.toString().padStart(2, '0')}:00`);
slots.push(`${i.toString().padStart(2, '0') + ':00'}`);
}

return slots;
};

export const generateScheduleMatrix = (data: TimePickerProps) => {
const { startTime, endTime, availableDates, schedules } = data;

export const generateScheduleMatrix = ({
startTime,
endTime,
availableDates,
schedules,
}: GetMeetingResponse) => {
const timeSlots = generateTimeSlots(startTime, endTime);
const timeSlotIndex = timeSlots.reduce((acc, slot, idx) => {
acc[slot] = idx;
return acc;
}, {} as Test);
}, {} as TimeSlot);

const scheduleMatrix = Array.from({ length: timeSlots.length }, () =>
const scheduleMatrix: boolean[][] = Array.from({ length: timeSlots.length }, (): boolean[] =>
Array(availableDates.length).fill(false),
);

schedules.forEach((schedule) => {
const dateIndex = availableDates.indexOf(schedule.date);

if (dateIndex !== -1) {
schedule.times.forEach((time) => {
const timeIndex = timeSlotIndex[time];

if (timeIndex !== undefined) {
scheduleMatrix[timeIndex][dateIndex] = true;
}
Expand All @@ -52,16 +57,16 @@ export const convertToSchedule = (

const schedules = availableDates.map((date, colIndex) => {
const times: string[] = [];

matrix.forEach((row, rowIndex) => {
if (row[colIndex]) {
times.push(timeSlots[rowIndex] + ':00');
times.push(timeSlots[rowIndex].slice(0, 2) + ':30:00');
// 임시로 30분 단위도 추가되도록 설정
times.push(timeSlots[rowIndex]);
times.push(timeSlots[rowIndex].slice(0, 2) + ':30');
}
});
return {
date,
times,
};

return { date, times };
});

return schedules.filter((schedule) => schedule.times.length > 0);
Expand Down
74 changes: 74 additions & 0 deletions frontend/src/components/Time/Picker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useContext, useMemo } from 'react';

import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStateProvider';

import Button from '@components/_common/Button';
import TimeSlot from '@components/_common/TimeSlot';

import useTimePick from '@hooks/useTimePick/useTimePick';

import type { GetMeetingResponse } from '@apis/getMeeting';

import { usePostScheduleMutation } from '@stores/servers/meeting/mutations';

import { s_buttonContainer, s_cell, s_table, s_th } from '../Time.styles';
import { convertToSchedule, generateScheduleMatrix } from './TimePicker.util';

export interface TimePickerProps {
data: GetMeetingResponse;
}

export default function TimePicker({ data }: TimePickerProps) {
const { isTimePickerUpdate, handleToggleIsTimePickerUpdate } = useContext(
TimePickerUpdateStateContext,
);

const initialValue = useMemo(() => generateScheduleMatrix(data), [data]);
const [ref, value] = useTimePick(isTimePickerUpdate, initialValue);

const { mutate: postScheduleMutate } = usePostScheduleMutation(() =>
handleToggleIsTimePickerUpdate(),
);

const handleOnToggle = () => {
const convert = convertToSchedule(value, data.availableDates, data.startTime, data.endTime);

postScheduleMutate(convert);
};

const formattedAvailableDates = ['', ...data.availableDates];

return (
<div>
<table css={s_table} ref={ref}>
<thead>
<tr>
{formattedAvailableDates.map((date) => (
<th key={date}>{date}</th>
))}
</tr>
</thead>
<tbody>
{value.map((row, rowIndex) => (
<tr key={rowIndex}>
<th css={[s_cell, s_th]}>
{String(rowIndex + Number(data.startTime.slice(0, 2)) + ':00')}
</th>
{row.map((_, columnIndex) => (
<TimeSlot
key={rowIndex + columnIndex}
isSelected={value[rowIndex][columnIndex]}
isUpdate={isTimePickerUpdate}
Copy link
Contributor

Choose a reason for hiding this comment

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

오 훨씬 명확한 네이밍으로 바꼈네요👍🏻

/>
))}
</tr>
))}
</tbody>
</table>

<div css={s_buttonContainer}>
<Button text="등록하기" onClick={handleOnToggle} />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,44 @@ import { css } from '@emotion/react';

import theme from '@styles/theme';

export const tableStyle = css`
user-select: none;
border-collapse: collapse;
`;

export const tdStyle = css`
cursor: pointer;
padding: 8px;
text-align: center;
border-radius: 0.4rem;
`;

export const selectedStyle = css`
color: white;
background: ${theme.linear.selectedTime};
`;

export const table = css`
export const s_table = css`
cursor: pointer;
user-select: none;

border-spacing: 0.4rem 0.4rem;
border-collapse: collapse;
border-collapse: separate;

width: 100%;
`;

export const styledTd = (isSelected: boolean) => css`
export const s_td = css`
cursor: pointer;

padding: 0.4rem;

color: ${isSelected ? '#121010' : '#fff'};

background: ${isSelected ? theme.linear.selectedTime : '#ececec'};
padding: 8px;
text-align: center;
border-radius: 0.4rem;
`;

&:hover {
opacity: 0.7;
}
export const s_selected = css`
color: white;
background: ${theme.linear.selectedTime};
`;

export const styledTh = css`
export const s_th = css`
display: flex;
align-items: center;
justify-content: center;
`;

export const buttonContainer = css`
export const s_cell = css`
cursor: default;
width: 4rem;
height: 4rem;
`;

export const s_buttonContainer = css`
display: flex;
justify-content: flex-end;
width: 100%;
margin-top: 1rem;
`;

export const tableTexture = css`
cursor: default;
width: 4rem;
height: 4rem;
`;
Loading
Loading