-
Notifications
You must be signed in to change notification settings - Fork 8
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
base: main
Are you sure you want to change the base?
MISSION7 / 곽민준 #45
Changes from all commits
fb14f96
0f4feed
e071e4a
62baa24
2c15d39
5581a0e
8ff2ecc
561058b
cf1a264
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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; | ||
} | ||
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); | ||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
`; |
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 개인적으로 굳이 달러사인을 붙이지 않아도 될 것 같아요! |
||
</> | ||
)} | ||
</StyledTimeSlot> | ||
); | ||
}; | ||
|
||
export default TimeSlot; | ||
|
||
const StyledTimeSlot = styled.div` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
`; |
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; |
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; | ||
} | ||
} | ||
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; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 로직도 훌륭하지만 현재 방식에서는 startHour를 기준으로 데이터를 다루고 있는 . 것같아요. 제 생각에는 저장하는 데이터 형식을 다르게하여 로직들을 간편하게 하면 좋을 같다는 생각인데 민준님은 어떻게 생각하시나요? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
매직넘버는 상수로 분리해주는 것이 좋을 것 같아요!