Skip to content

Commit

Permalink
[FE] 약속 조회 페이지 UI 개선 (#307)
Browse files Browse the repository at this point in the history
* refactor: Text Accent 컴포넌트 로직 수정

- children이 아닌 text를 props로 받도록 수정

* feat: 약속 날짜 범위를 페이지네이션 할 수 있는 버튼 컨테이너 구현

* feat: 약속 디테일 정보를 알려주는 텍스트 컴포넌트 구현

- 전체, 특정 참여자 분류 텍스트
- 0%-100% 분홍 그라데이션 프로그레시브 바

* feat: 로딩 상태일 때, 스피너를 보여주는 버튼 컴포넌트 스타일 추가

* refactor: 버튼 스타일 수정

- 화면 하단에 버튼을 고정하는 것으로 변경
- 아이콘 버튼 추가
- 구현해 둔 탭 버튼을 사용하는 것으로 수정

* refactor: 단일 참여자의 참여 여부를 보여주는 테이블 셀 스타일 변경

- 선택 모드의 테이블 셀 스타일과 구분

* design: 약속 조회 페이지 테이블 UI 스타일 개선

- circle icon 버튼 추가
- 화면 하단에 버튼을 고정할 수 있는 스타일 추가
- 되는/안되는 모드 구분 스타일 추가

* refactor: 시간 수정 컴포넌트 수정

- 구현해 둔 탭 버튼 컴포넌트 사용
- 분리된 날짜 페이지네이션 컴포넌트 사용
- 수정 api 요청 응답을 기다리는 경우 사용자에게 이를 알려줄 수 있는 isLoading 상태 사용

* chore: Text 공통 컴포넌트 변경사항 반영

* design: 약속 조회 페이지 디자인 개선

- 사용자 환영 메시지, 약속 소개 메시지 추가
- 하단 버튼 제거

* refactor: 약속 참여자, 주최자가 하단에서 확인할 수 있는 버튼 메시지, 클릭 이벤트 핸들러 함수 구분

* design: padding-bottom 스타일 속성 제거

* chore: 약속 시간 수정하기 버튼 svg 파일 추가

* chore: 약속 시간 등록 초기화 svg 파일 추가

* design: 배경색 수정

* design: margin-bottom 추가

* design: css 분리

* refactor: 반대로 분기 처리가 되어있던 로직 수정

* design: 중복 width 제거

* design: disabled 스타일 속성 추가
  • Loading branch information
hwinkr authored Aug 22, 2024
1 parent 8b2c8df commit c8132e8
Show file tree
Hide file tree
Showing 22 changed files with 507 additions and 227 deletions.
5 changes: 5 additions & 0 deletions frontend/src/assets/images/pen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/assets/images/rotate.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { css } from '@emotion/react';

import theme from '@styles/theme';

export const s_datesControlButtonContainer = css`
position: absolute;
top: 0;
display: flex;
justify-content: space-between;
width: 100%;
-webkit-box-pack: justify;
`;

export const s_datesControlButton = css`
cursor: pointer;
width: 2.8rem;
height: 2.8rem;
font-weight: bold;
color: ${theme.colors.primary};
background-color: ${theme.colors.white};
border: 0.1rem solid ${theme.colors.primary};
border-radius: 50%;
box-shadow: 0 0.4rem 0.4rem rgb(0 0 0 / 10%);
:disabled {
cursor: not-allowed;
opacity: 0.4;
}
&:last-of-type {
margin-right: 1rem;
}
`;
26 changes: 26 additions & 0 deletions frontend/src/components/Schedules/DateControlButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { s_datesControlButton, s_datesControlButtonContainer } from './DateControlButtons.styles';

interface DateControlButtons {
decreaseDatePage: () => void;
increaseDatePage: () => void;
isFirstPage: boolean;
isLastPage: boolean;
}

export default function DateControlButtons({
decreaseDatePage,
increaseDatePage,
isFirstPage,
isLastPage,
}: DateControlButtons) {
return (
<div css={s_datesControlButtonContainer}>
<button css={s_datesControlButton} onClick={() => decreaseDatePage()} disabled={isFirstPage}>
{'<'}
</button>
<button css={s_datesControlButton} onClick={() => increaseDatePage()} disabled={isLastPage}>
{'>'}
</button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { css } from '@emotion/react';

export const s_scheduleOverviewContainer = css`
display: flex;
flex-direction: column;
row-gap: 1.2rem;
margin-bottom: 1.2rem;
`;

export const s_infoTextContainer = css`
display: flex;
gap: 0.4rem;
align-items: center;
`;
43 changes: 43 additions & 0 deletions frontend/src/components/Schedules/ScheduleOverview/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Text from '@components/_common/Text';

import Information from '@assets/images/information.svg';

import { s_percentage, s_percentageContainer, s_pinkProgressiveBar } from '../Schedules.styles';
import { s_infoTextContainer, s_scheduleOverviewContainer } from './ScheduleOverview.styles';

interface ScheduleOverviewProps {
selectedAttendee: string;
}

export default function ScheduleOverview({ selectedAttendee }: ScheduleOverviewProps) {
return (
<div css={s_scheduleOverviewContainer}>
{selectedAttendee === '' ? (
<Text typo="bodyMedium">
<Text.Accent text="전체 " />
약속 참여자들의 일정을 확인하고 있어요
</Text>
) : (
<Text typo="bodyMedium">
<Text.Accent text={selectedAttendee} />
님의 일정을 확인하고 있어요
</Text>
)}
<div css={s_infoTextContainer}>
<Information width="12" height="12" />
<Text variant="caption" typo="captionLight">
{selectedAttendee === ''
? '시간을 클릭하여 참여할 수 있는 참여자들을 확인해 보세요'
: '나의 약속 참여 시간과 비교해 보세요'}
</Text>
</div>
<section>
<div css={s_pinkProgressiveBar}></div>
<div css={s_percentageContainer}>
<p css={s_percentage}>0%</p>
<p css={s_percentage}>100%</p>
</div>
</section>
</div>
);
}
144 changes: 75 additions & 69 deletions frontend/src/components/Schedules/SchedulePicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { css } from '@emotion/react';
import { useContext, useState } from 'react';
import { useParams } from 'react-router-dom';
import type { MeetingDateTime } from 'types/meeting';
Expand All @@ -8,23 +7,29 @@ import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStatePro

import ScheduleTimeList from '@components/Schedules/ScheduleTableFrame/ScheduleTimeList';
import { Button } from '@components/_common/Buttons/Button';
import TabButton from '@components/_common/Buttons/TabButton';
import Text from '@components/_common/Text';

import usePagedTimePick from '@hooks/usePagedTimePick/usePagedTimePick';

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

import Rotate from '@assets/images/rotate.svg';

import DateControlButtons from '../DateControlButtons';
import ScheduleDateDayList from '../ScheduleTableFrame/ScheduleDateDayList';
import {
s_baseTimeCell,
s_buttonContainer,
s_bottomFixedButtonContainer,
s_cellColorBySelected,
s_datesControlButton,
s_datesControlButtonContainer,
s_circleButton,
s_fullButtonContainer,
s_relativeContainer,
s_scheduleTable,
s_scheduleTableBody,
s_scheduleTableContainer,
s_scheduleTableRow,
s_selectModeButtonsContainer,
} from '../Schedules.styles';
import { convertToSchedule, generateSingleScheduleTable } from '../Schedules.util';

Expand Down Expand Up @@ -68,7 +73,7 @@ export default function SchedulePicker({
isLastPage,
} = usePagedTimePick(availableDates, schedules);

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

Expand All @@ -93,73 +98,74 @@ export default function SchedulePicker({
};

return (
<div css={s_relativeContainer}>
<div
css={css`
display: flex;
gap: 0.4rem;
`}
>
<button onClick={() => handleSelectModeChange('available')}>
{TIME_SELECT_MODE.available}
</button>
<p>/</p>
<button onClick={() => handleSelectModeChange('unavailable')}>
{TIME_SELECT_MODE.unavailable}
</button>
<p>시간으로 선택하기</p>
</div>
{isMultiPage && (
<div css={s_datesControlButtonContainer}>
<button
css={s_datesControlButton}
onClick={() => decreaseDatePage()}
disabled={isFirstPage}
<>
<div css={s_relativeContainer}>
<div css={s_selectModeButtonsContainer}>
<TabButton
isActive={selectMode === 'available'}
onClick={() => handleSelectModeChange('available')}
>
{'<'}
</button>
<button
css={s_datesControlButton}
onClick={() => increaseDatePage()}
disabled={isLastPage}
{TIME_SELECT_MODE.available}
</TabButton>
<p>/</p>
<TabButton
isActive={selectMode === 'unavailable'}
onClick={() => handleSelectModeChange('unavailable')}
>
{'>'}
</button>
{TIME_SELECT_MODE.unavailable}
</TabButton>
<Text typo="bodyBold">시간으로 선택하기</Text>
</div>
)}
<section css={s_scheduleTableContainer}>
<ScheduleTimeList firstTime={firstTime} lastTime={lastTime} />
<table css={s_scheduleTable} ref={tableRef} aria-label="약속 시간 수정 테이블">
<thead>
<ScheduleDateDayList availableDates={currentDates} />
</thead>
<tbody css={s_scheduleTableBody}>
{currentTableValue.map((row, rowIndex) => (
<tr key={rowIndex} css={s_scheduleTableRow}>
{row.map((isSelected, columnIndex) => {
const isHalfHour = rowIndex % 2 !== 0;
const isLastRow = rowIndex === schedules.length - 1;

return (
<td
key={columnIndex}
css={[
s_baseTimeCell(isHalfHour, isLastRow),
s_cellColorBySelected(isSelected),
]}
></td>
);
})}
</tr>
))}
</tbody>
</table>
</section>
<div css={s_buttonContainer}>
<Button onClick={handleOnToggle} size="m" variant="primary">
등록하기
</Button>
{isMultiPage && (
<DateControlButtons
decreaseDatePage={decreaseDatePage}
increaseDatePage={increaseDatePage}
isFirstPage={isFirstPage}
isLastPage={isLastPage}
/>
)}
<section css={s_scheduleTableContainer}>
<ScheduleTimeList firstTime={firstTime} lastTime={lastTime} />
<table css={s_scheduleTable} ref={tableRef} aria-label="약속 시간 수정 테이블">
<thead>
<ScheduleDateDayList availableDates={currentDates} />
</thead>
<tbody css={s_scheduleTableBody}>
{currentTableValue.map((row, rowIndex) => (
<tr key={rowIndex} css={s_scheduleTableRow}>
{row.map((isSelected, columnIndex) => {
const isHalfHour = rowIndex % 2 !== 0;
const isLastRow = rowIndex === schedules.length - 1;

return (
<td
key={columnIndex}
css={[
s_baseTimeCell(isHalfHour, isLastRow),
s_cellColorBySelected(isSelected, selectMode === 'unavailable'),
]}
></td>
);
})}
</tr>
))}
</tbody>
</table>
</section>
</div>
</div>
<footer css={s_bottomFixedButtonContainer}>
<div css={s_fullButtonContainer}>
<Button size="full" variant="primary" onClick={handleOnToggle} isLoading={isPending}>
등록하기
</Button>
</div>
<button css={s_circleButton} onClick={resetTableValue}>
<Rotate width="16" height="16" />
<Text typo="captionMedium">
<Text.Accent text="초기화" />
</Text>
</button>
</footer>
</>
);
}
Loading

0 comments on commit c8132e8

Please sign in to comment.