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

MISSION7 / 곽민준 #45

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
200 changes: 183 additions & 17 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"@actions/github": "^6.0.0",
"@slack/webhook": "^7.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"styled-components": "^6.1.11"
},
"devDependencies": {
"@types/react": "^18.2.64",
Expand Down
42 changes: 0 additions & 42 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
99 changes: 67 additions & 32 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,70 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import styled from "styled-components";
import TimeSlot from "./components/TimeSlot";
import generateTimes from "./utils/generateTimes";
import formatReservationTime from "./utils/formatReservationTime";
import useSelectedTimes from "./hooks/useSelectedTimes";

function App() {
const [count, setCount] = useState(0)
const App = () => {
const times = generateTimes(9, 20);
Copy link
Collaborator

Choose a reason for hiding this comment

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

매직넘버는 상수로 분리해주는 것이 좋을 것 같아요!

const { selectedTimes, handleSelectTime } = useSelectedTimes();

const handleReservation = () => {
if (selectedTimes.length === 0) {
alert("예약할 시간을 선택해주세요.");
return;
}

const sortedTimes = [...selectedTimes].sort((a, b) => {
return new Date(`1970/01/01 ${a}`) - new Date(`1970/01/01 ${b}`);
});

const reservationTimes = sortedTimes.map(formatReservationTime);

alert(
`예약이 완료되었습니다.\n\n[예약 시간]\n${reservationTimes.join(",\n")}`
);
};

return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}

export default App
<div>
<StyledLayout>
<h1>스터디룸 예약 서비스</h1>
<StyledTimeTable>
{times.map((time, index) => (
<TimeSlot
key={index}
time={time}
onSelectTime={handleSelectTime}
isActive={selectedTimes.includes(time)}
Comment on lines +37 to +38
Copy link
Collaborator

Choose a reason for hiding this comment

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

읽는 사람의 차이이겠지만 onSelectTime과 isActive는 잘 안읽히는 것 같습니다. onSelectTime이라고하면 select 엘리먼트가 생각이나서 여러 선택지중 하나를 고르는 듯한 느낌을 주고 isActive는 위에서는 selected라는 이름이 계속 되고 있는데 isActive가 나오는게 살짝 어색한 것 같아요!

/>
))}
</StyledTimeTable>
<StyledButton onClick={handleReservation}>예약하기</StyledButton>
</StyledLayout>
</div>
);
};

export default App;

const StyledLayout = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 5rem;
gap: 10rem;
`;

const StyledButton = styled.button`
padding: 1rem 4rem;
border: none;
font-size: 1.1rem;
border-radius: 0.4rem;
cursor: pointer;
background-color: #e2e8f0;
`;

const StyledTimeTable = styled.div`
display: flex;
justify-content: center;
`;
52 changes: 52 additions & 0 deletions src/components/TimeSlot.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import styled from "styled-components";

const TimeSlot = ({ time, onSelectTime, isActive }) => {
const isHour = time.endsWith(":00");
const isEnd = time === "20:00";
Copy link
Collaborator

Choose a reason for hiding this comment

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

isEnd를 확인해서 스타일링 하는 것이 뭔가 어색한데요. 다른 방법은 없을까요?


return (
<StyledTimeSlot onClick={() => onSelectTime(time)}>
{isHour && <StyledTimeLabel>{time}</StyledTimeLabel>}{" "}
{!isEnd && (
<>
<StyledEmptyArea></StyledEmptyArea>
<StyledColorArea $isActive={isActive}></StyledColorArea>
Copy link
Collaborator

Choose a reason for hiding this comment

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

개인적으로 굳이 달러사인을 붙이지 않아도 될 것 같아요!

</>
)}
</StyledTimeSlot>
);
};

export default TimeSlot;

const StyledTimeSlot = styled.div`
Copy link
Collaborator

Choose a reason for hiding this comment

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

clickable한 요소는 button으로 구성하는 것이 좋지 않을까요??

position: relative;
display: flex;
justify-content: center;
flex-direction: column;
border-left: 1px solid #b9b9b9;
border-top: none;
align-items: center;
`;

const StyledEmptyArea = styled.div`
width: 3.5rem;
height: 1rem;
box-sizing: border-box;
`;

const StyledColorArea = styled.div`
width: 3.5rem;
background-color: ${({ $isActive }) => ($isActive ? "#007bff" : "#f0f0f0")};
height: 2rem;
border-bottom: 1px solid #b9b9b9;
cursor: pointer;
`;

const StyledTimeLabel = styled.div`
position: absolute;
top: -1.5rem;
left: -1rem;
font-size: 0.8rem;
color: #555;
`;
17 changes: 17 additions & 0 deletions src/hooks/useSelectedTimes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useState } from "react";

const useSelectedTimes = () => {
const [selectedTimes, setSelectedTimes] = useState([]);

const handleSelectTime = (time) => {
setSelectedTimes((prevSelected) =>
prevSelected.includes(time)
? prevSelected.filter((t) => t !== time)
: [...prevSelected, time]
);
};

return { selectedTimes, handleSelectTime };
};

export default useSelectedTimes;
68 changes: 0 additions & 68 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,68 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
10 changes: 10 additions & 0 deletions src/utils/formatReservationTime.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const formatReservationTime = (time) => {
const [hours, minutes] = time.split(":").map(Number);
const endTime = new Date(1970, 0, 1, hours, minutes + 30);
const formattedEndTime = `${endTime.getHours()}:${
endTime.getMinutes() === 0 ? "00" : endTime.getMinutes()
}`;
return `${time} ~ ${formattedEndTime}`;
};

export default formatReservationTime;
12 changes: 12 additions & 0 deletions src/utils/generateTimes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const generateTimes = (startHour, endHour) => {
const times = [];
for (let hour = startHour; hour <= endHour; hour++) {
times.push(`${hour}:00`);
if (hour !== endHour) {
times.push(`${hour}:30`);
}
}
return times;
};

export default generateTimes;
Comment on lines +1 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

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

지금 로직도 훌륭하지만 현재 방식에서는 startHour를 기준으로 데이터를 다루고 있는 . 것같아요. 제 생각에는 저장하는 데이터 형식을 다르게하여 로직들을 간편하게 하면 좋을 같다는 생각인데 민준님은 어떻게 생각하시나요?