diff --git a/package-lock.json b/package-lock.json index 857a003..04ce88e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,11 @@ "@reduxjs/toolkit": "^1.9.5", "axios": "^1.5.0", "classnames": "^2.3.2", + "dayjs": "^1.11.10", "js-cookie": "^3.0.5", "prop-types": "^15.8.1", "react": "^18.2.0", + "react-calendar": "^4.6.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-paginate": "^8.2.0", @@ -2744,6 +2746,19 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==" + }, + "node_modules/@types/lodash.memoize": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz", + "integrity": "sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "14.18.33", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", @@ -3152,6 +3167,14 @@ "vite": "^4.2.0" } }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.0.tgz", + "integrity": "sha512-0mq88lCND6QiffnSDWp+TbOxzJSwy2V/3XN+HwWZ7S2n19QAgR5dy5hRVhlECXvQIq2r+VcblBu+S9V+yMcxXw==", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3701,6 +3724,14 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/code-block-writer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", @@ -3843,6 +3874,11 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5251,6 +5287,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-user-locale": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.0.tgz", + "integrity": "sha512-I3rQvAUwu2nauRD9YyQBSXVFJZixNouwA+eZld51Sn4Pn0N1qFbgcgOi/nPigJPQlNY519mT95fiSPRgflQiTA==", + "dependencies": { + "@types/lodash.memoize": "^4.1.7", + "lodash.memoize": "^4.1.1" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5920,6 +5968,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6577,6 +6630,31 @@ "node": ">=0.10.0" } }, + "node_modules/react-calendar": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.6.0.tgz", + "integrity": "sha512-GJ6ZipKMQmlK666t+0hgmecu6WHydEnMWJjKdEkUxW6F471hiM5DkbWXkfr8wlAg9tc9feNCBhXw3SqsPOm01A==", + "dependencies": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "prop-types": "^15.6.0", + "tiny-warning": "^1.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -7334,6 +7412,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -9639,6 +9722,19 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/lodash": { + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==" + }, + "@types/lodash.memoize": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz", + "integrity": "sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "14.18.33", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", @@ -10016,6 +10112,11 @@ "react-refresh": "^0.14.0" } }, + "@wojtekmaj/date-utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.0.tgz", + "integrity": "sha512-0mq88lCND6QiffnSDWp+TbOxzJSwy2V/3XN+HwWZ7S2n19QAgR5dy5hRVhlECXvQIq2r+VcblBu+S9V+yMcxXw==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -10415,6 +10516,11 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + }, "code-block-writer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", @@ -10532,6 +10638,11 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -11487,6 +11598,15 @@ "get-intrinsic": "^1.1.1" } }, + "get-user-locale": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.0.tgz", + "integrity": "sha512-I3rQvAUwu2nauRD9YyQBSXVFJZixNouwA+eZld51Sn4Pn0N1qFbgcgOi/nPigJPQlNY519mT95fiSPRgflQiTA==", + "requires": { + "@types/lodash.memoize": "^4.1.7", + "lodash.memoize": "^4.1.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11964,6 +12084,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12415,6 +12540,18 @@ "loose-envify": "^1.1.0" } }, + "react-calendar": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.6.0.tgz", + "integrity": "sha512-GJ6ZipKMQmlK666t+0hgmecu6WHydEnMWJjKdEkUxW6F471hiM5DkbWXkfr8wlAg9tc9feNCBhXw3SqsPOm01A==", + "requires": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "prop-types": "^15.6.0", + "tiny-warning": "^1.0.0" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -12941,6 +13078,11 @@ "convert-hrtime": "^3.0.0" } }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index 85623eb..b40f480 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "@reduxjs/toolkit": "^1.9.5", "axios": "^1.5.0", "classnames": "^2.3.2", + "dayjs": "^1.11.10", "js-cookie": "^3.0.5", "prop-types": "^15.8.1", "react": "^18.2.0", + "react-calendar": "^4.6.0", "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-paginate": "^8.2.0", diff --git a/src/App.jsx b/src/App.jsx index a054aad..b70176c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,6 +4,7 @@ import SignupPage from "./pages/SignupPage.jsx"; import MyUserPage from './pages/User/MyUserPage.jsx'; import DevelopPage from './pages/DevelopPage.jsx'; import UserUntactReservePage from './pages/User/UserUntactReservePage.jsx'; +import UserReservePage from './pages/User/UserReservePage.jsx'; import styled from "styled-components"; import "./App.scss"; @@ -22,6 +23,7 @@ function App() { } /> }/> } /> + } /> diff --git a/src/assets/icons/iconmajor.png b/src/assets/icons/iconmajor.png new file mode 100644 index 0000000..3d09d30 Binary files /dev/null and b/src/assets/icons/iconmajor.png differ diff --git a/src/assets/icons/iconx.png b/src/assets/icons/iconx.png new file mode 100644 index 0000000..b694cb1 Binary files /dev/null and b/src/assets/icons/iconx.png differ diff --git a/src/components/Button/BackButton.jsx b/src/components/Button/BackButton.jsx index f03b392..54f2627 100644 --- a/src/components/Button/BackButton.jsx +++ b/src/components/Button/BackButton.jsx @@ -12,7 +12,7 @@ const BackButtonContainer = styled.button` align-items: center; justify-content: center; padding: 0 15px; - margin-top:60px; + margin-top:40px; margin-bottom: 10px; margin-left: -580px; `; diff --git a/src/components/Calender/Calender.jsx b/src/components/Calender/Calender.jsx new file mode 100644 index 0000000..b77bca9 --- /dev/null +++ b/src/components/Calender/Calender.jsx @@ -0,0 +1,88 @@ +import { useState } from "react"; +import styled from "styled-components"; +import dayjs from 'dayjs'; +import CalenderItem from "../Calender/CalenderItem.jsx"; +import IconLeft from "../../assets/icons/Page-left.png"; +import IconRight from "../../assets/icons/Page-right.png"; + +const SelectorContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 10px; +`; + +const Icon = styled.img` + cursor: pointer; + margin: 0 10px; +`; + +const MonthLabel = styled.span` + font-size: 1.2rem; +`; + +const Container = styled.div` + width: 100%; + display: grid; + grid-template-columns: repeat(7, 50px); + grid-gap: 6px; + grid-template-rows: repeat(6, 40px); + justify-content: center; + align-items: center; +`; + +const Calender = () => { + const [selectedMonth, setSelectedMonth] = useState(8); + const [selectedDate, setSelectedDate] = useState(null); + + const prevMonth = () => { + if (selectedMonth > 8) setSelectedMonth(prev => prev - 1); + }; + + const nextMonth = () => { + if (selectedMonth < 11) setSelectedMonth(prev => prev + 1); + }; + + const daysInCurrentMonth = dayjs([2023, selectedMonth]).daysInMonth(); + const startDayOfWeek = dayjs([2023, selectedMonth, 1]).day(); + + let days = []; + for (let i = 1; i <= daysInCurrentMonth; i++) { + days.push(i); + } + + const paddingDays = startDayOfWeek; + for (let i = 0; i < paddingDays; i++) { + days.unshift(null); + } + + while (days.length < 35) { + days.push(null); + } + + return ( + <> + + + {`2023.${selectedMonth < 9 ? `${selectedMonth }` : selectedMonth }`} + + + + {days.map((day, idx) => { + let type = day ? "currentMonth" : "paddingDay"; + return ( + + ); + })} + + + ); +}; + +export default Calender; diff --git a/src/components/Calender/CalenderItem.jsx b/src/components/Calender/CalenderItem.jsx new file mode 100644 index 0000000..c5755f0 --- /dev/null +++ b/src/components/Calender/CalenderItem.jsx @@ -0,0 +1,50 @@ +import styled from "styled-components"; +import PropTypes from "prop-types"; + +const Container = styled.div` + width: 50px; + height: 40px; + aspect-ratio: 1; + border-radius: 8px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + background-color: ${({ isSelected }) => (isSelected ? "#f3f1ff" : "transparent")}; + + &:hover { + background-color: #f3f1ff; + } + &.prevMonth, + &.nextMonth { + color: rgba(21, 21, 21, 0.3); + } +`; + +const Text = styled.p` + font-size: 16px; + font-weight: 300; +`; + +const CalenderItem = ({ date, type, isSelected, onSelectDate }) => { + const handleDateClick = () => { + onSelectDate(date); + }; + + return ( + + {date} + + ); +}; + +CalenderItem.propTypes = { + date: PropTypes.number, + type: PropTypes.string.isRequired, + isSelected: PropTypes.bool.isRequired, + onSelectDate: PropTypes.func.isRequired, +}; + +export default CalenderItem; + diff --git a/src/components/Input/InputTextLong.jsx b/src/components/Input/InputTextLong.jsx new file mode 100644 index 0000000..9f713bf --- /dev/null +++ b/src/components/Input/InputTextLong.jsx @@ -0,0 +1,34 @@ +import styled from 'styled-components'; + +const InputContainer = styled.div` + display: flex; + flex-direction: column; + width: 320px; + margin-top:10px; +`; + + +const Input = styled.input` + width: 500px; + height: 100px; + border-radius: 10px; + background-color: #FAFAFA; + border: 1px solid #BBBBBB; + font-family: 'Spoqa Han Sans Neo', 'sans-serif'; + padding-left: 0px; + + &:focus { + outline: none; + } +`; + +function InputTextLong() { + return ( + + + + ); +} + +export { Input }; +export default InputTextLong; \ No newline at end of file diff --git a/src/components/UntactReserve/UntactReserveModal.jsx b/src/components/UntactReserve/UntactReserveModal.jsx new file mode 100644 index 0000000..401a70b --- /dev/null +++ b/src/components/UntactReserve/UntactReserveModal.jsx @@ -0,0 +1,151 @@ +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import XButton from '../../assets/icons/iconx.png'; +import Calendar from '../Calender/Calender.jsx'; +import { useState } from 'react'; +import InputTextLong from '../Input/InputTextLong'; + +const Overlay = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.4); + display: flex; + justify-content: center; + align-items: center; +`; + +const ModalContainer = styled.div` + width: 600px; + height: 700px; + background-color: #FFFFFF; + border-radius: 10px; + padding: 20px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); + position: relative; +`; + +const Title = styled.h1` + font-size: 28px; + font-weight: bold; + color: #333; + display: inline-block; +`; + +const CloseIcon = styled.img` + position: absolute; + right: 20px; + top: 20px; + cursor: pointer; + margin-top:10px; +`; + +const Divider = styled.hr` + width: 100%; + height: 1px; + background-color: #d9d9d9; + border: none; + margin-top: 10px; + margin-bottom: 20px; +`; + +const DateText = styled.p` + font-size: 16px; + margin-top: -10px; +`; + +const ReasonText = styled.p` + font-size: 16px; + margin-top: 10px; +`; + +const TimeButton = styled.button` + width: 90px; + height: 40px; + border-radius: 10px; + border: ${({ status }) => (status === 'selected' ? '1px solid #0064FF' : '1px solid #E8E8E8')}; + background-color: ${({ status }) => (status === 'available' ? '#FAFAFA' : status === 'selected' ? '#F3F1FF' : '#888888')}; + color: ${({ status }) => (status === 'closed' ? '#444444' : '#000000')}; + cursor: ${({ status }) => (status === 'closed' ? 'not-allowed' : 'pointer')}; + margin-right: 5px; + + &:last-child { + margin-right: 0; + } +`; + +const TimeButtonContainer = styled.div` + display: flex; + margin-top: 20px; + margin-left:8px; + gap:5px; +`; + +const Button = styled.button` + width: 140px; + height: 30px; + background-color: #3592FF; + font-weight: 300; + color: #FEFDFD; + font-size: 14px; + border: none; + border-radius: 5px; + cursor: pointer; + margin-top:20px; + display: block; + margin-left: auto; + margin-right: auto; +`; + + +export const UntactReserveModal = ({ onClose }) => { + const [selectedTime, setSelectedTime] = useState(null); + + const times = [ + { label: "18:00", status: "available" }, + { label: "18:30", status: "available" }, + { label: "19:00", status: "available" }, + { label: "19:30", status: "closed" }, + { label: "20:00", status: "closed" }, + ]; + + const handleTimeSelect = (time) => { + setSelectedTime(time); + }; + + return ( + + e.stopPropagation()}> + 예약 정보 작성 + + + 날짜 선택 * + + 시간 선택 * + + {times.map(time => ( + time.status === 'available' && handleTimeSelect(time.label)} + disabled={time.status === 'closed'} + > + {time.label} + + ))} + + 진료 희망 사유 * + + + + + ); +} + +UntactReserveModal.propTypes = { + onClose: PropTypes.func.isRequired, +}; + +export default UntactReserveModal; diff --git a/src/components/UserDashBoard/UserDoReserve.jsx b/src/components/UserDashBoard/UserDoReserve.jsx new file mode 100644 index 0000000..aa681ce --- /dev/null +++ b/src/components/UserDashBoard/UserDoReserve.jsx @@ -0,0 +1,74 @@ +import { useState, useEffect } from "react"; +import styled from "styled-components"; +import { UserSelectCard } from'./UserSelectCard'; +import { userLogin } from "../../librarys/login-api"; + +const Container = styled.div` + width: 800px; + height: 600px; + margin: 0 auto; + padding: 20px; + border: 1px solid #0064ff; + border-radius: 10px; + background-color: #ffffff; + font-family: "Spoqa Han Sans Neo", "sans-serif"; + position: relative; +`; + +const CardWrapper = styled.div` + display: flex; + justify-content: center; + margin-top: 20px; + gap: 30px; +`; + +const Title = styled.h1` + font-size: 28px; + font-weight: bold; + color: #333; + display: inline-block; +`; + +const Divider = styled.hr` + width: 100%; + height: 1px; + background-color: #d9d9d9; + border: none; + margin-top: 10px; + margin-bottom: 20px; +`; + +const Info = styled.p` + margin-top: 20px; + font-size: 18px; + font-weight: 300; + color: #000000; +` + +const UserDoReserve = () => { + const [loginData, setLoginData] = useState(null); + + useEffect(() => { + const fetchLoginData = async () => { + const data = await userLogin('HL0001', '123456'); + setLoginData(data); + }; + fetchLoginData(); + }, []); + + if (!loginData) return null; + + return ( + + 비대면 진료 예약 + + 진료를 희망하는 의료진을 선택해주세요. + + + + + + ); +}; + +export default UserDoReserve; \ No newline at end of file diff --git a/src/components/UserDashBoard/UserSelectCard.jsx b/src/components/UserDashBoard/UserSelectCard.jsx new file mode 100644 index 0000000..cd192e2 --- /dev/null +++ b/src/components/UserDashBoard/UserSelectCard.jsx @@ -0,0 +1,133 @@ +import { useState } from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; +import Icondoctor from "../../assets/icons/icondoctor.png"; +import Iconmajor from "../../assets/icons/iconmajor.png"; +import Iconhospital from "../../assets/icons/iconhospital.png"; +import DoctorImage from "../../assets/images/user/Odoctor.png"; +import TherapistImage from "../../assets/images/user/Otherapist.png"; +import { UntactReserveModal } from "../UntactReserve/UntactReserveModal"; + + +const Card = styled.div` + width: 280px; + border: 1px solid #e1e1e1; + padding: 20px; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + text-align: center; + margin: 20px; + font-family: 'Spoqa Han Sans Neo', 'sans-serif'; + background-color: #ffffff; +`; + +const Title = styled.h1` + font-size: 14px; + margin-bottom: 15px; +`; + +const Separator = styled.hr` + width: 100%; + height: 2px; + background-color: #D9D9D9; + margin-bottom: 15px; + margin-top:10px; +`; + +const ImageContainer = styled.div` + width: 96px; + height: 96px; + border-radius: 50%; + margin: 0 auto; + margin-bottom: 20px; + overflow: hidden; +`; + +const Avatar = styled.img` + width: 100%; + height: 100%; +`; + +const UserName = styled.span` + font-size: 30px; + font-weight: bold; + font-family: 'Spoqa Han Sans Neo', 'sans-serif'; +`; + + +const Info = styled.div` + font-family: 'Spoqa Han Sans Neo', 'sans-serif'; + font-size: 12px; + margin-bottom: 10px; + text-align: left; +`; + +const Button = styled.button` + width: 150px; + height: 24px; + border-radius: 10px; + background-color: #3592FF; + color: #FEFDFD; + cursor: pointer; + border: none; + align-self: center; + font-size: 12px; +`; + +const Icon = styled.img` + height: 12px; + width: 12px; + margin-right: 5px; + vertical-align: middle; +`; + +const MidSection = styled.div` + background-color: rgba(0, 100, 255, 0.03); +`; + + +export const UserSelectCard = ({ userType, userData }) => { + const [isModalOpen, setIsModalOpen] = useState(false); + + const toggleModal = () => { + setIsModalOpen(!isModalOpen); + }; + + if (!userData) return null; + + const imageUrl = userType === "admin1" ? DoctorImage : TherapistImage; + const title = userType === "admin1" ? "담당 전문의 프로필" : "담당 재활치료사 프로필"; + + return ( + <> + + {title} + + + + + + {userData.name} + + + {userType === "admin1" ? "전문의" : "재활치료사"} + {userData.workplace} + {userData.major} + + + + {isModalOpen && } + + ); +} + +UserSelectCard.propTypes = { + userType: PropTypes.string.isRequired, + userData: PropTypes.shape({ + name: PropTypes.string, + workplace: PropTypes.string, + major: PropTypes.string, + }).isRequired, +}; + +export default UserSelectCard; \ No newline at end of file diff --git a/src/librarys/context.js b/src/librarys/context.js new file mode 100644 index 0000000..3ddd569 --- /dev/null +++ b/src/librarys/context.js @@ -0,0 +1,4 @@ +import { createContext } from "react"; + +export const StateContext = createContext(); +export const DispatchContext = createContext(); \ No newline at end of file diff --git a/src/librarys/login-api.js b/src/librarys/login-api.js index 845d23a..9a9868f 100644 --- a/src/librarys/login-api.js +++ b/src/librarys/login-api.js @@ -1,87 +1,102 @@ export async function userLogin(id, password) { const accounts = [ - { - // User: 환자 - type: "user", - id: "HL0001", - password: "123456", - name: "오소현", - admin: false, - assignedDoctor: "김정원", // 배정된 전문의 - assignedTherapist: "오민혁", // 배정된 재활치료사 - recentVisitDate: "2023.09.01", // 최근 외래 진료일 - nextReservationDate: "2023.09.11" // 다음 외래 예약일 - }, - // Admin1: 전문의 - { - type: "admin1", - id: "doctor", - password: "123456", - name: "김정원", - admin: true, - }, - // Admin2: 재활치료사 - { - type: "admin2", - id: "therapist", - password: "123456", - name: "오민혁", - admin: true, - }, + { + // User: 환자 + type: "user", + id: "HL0001", + password: "123456", + name: "오소현", + admin: false, + assignedDoctor: "김정원", // 배정된 전문의 + assignedTherapist: "오민혁", // 배정된 재활치료사 + recentVisitDate: "2023.09.01", // 최근 외래 진료일 + nextReservationDate: "2023.09.11", // 다음 외래 예약일 + }, + // Admin1: 전문의 + { + type: "admin1", + id: "doctor", + password: "123456", + name: "김정원", + major: "재활의학과", + workplace: "한림대학교 춘천성심병원", + admin: true, + }, + // Admin2: 재활치료사 + { + type: "admin2", + id: "therapist", + password: "123456", + name: "오민혁", + major: "팔 재활", + workplace: "한림대학교 춘천성심병원 재활의료센터", + admin: true, + }, ]; const account = accounts.find((item) => item.id === id); + if (!account || account.password !== password) { - return null; + return null; } - // 계정 유형에 따른 다른 반환 값 - switch (account.type) { - case "user": - return { - email: account.id, - name: account.name, - access_token: "user_token1", - refresh_token: "user_token2", - admin: account.admin, - assignedDoctor: account.assignedDoctor, - assignedTherapist: account.assignedTherapist, - recentVisitDate: account.recentVisitDate, - nextReservationDate: account.nextReservationDate - }; - case "admin1": - return { - email: account.id, - name: account.name, - access_token: "admin1_token1", - refresh_token: "admin1_token2", - admin: account.admin, - }; - case "admin2": - return { - email: account.id, - name: account.name, - access_token: "admin2_token1", - refresh_token: "admin2_token2", - admin: account.admin, - }; - default: - return null; + + if (account.type === "user") { + const doctor = accounts.find(item => item.name === account.assignedDoctor && item.type === "admin1"); + const therapist = accounts.find(item => item.name === account.assignedTherapist && item.type === "admin2"); + + return { + user: { + email: account.id, + name: account.name, + access_token: "user_token1", + refresh_token: "user_token2", + admin: account.admin, + assignedDoctor: account.assignedDoctor, + assignedTherapist: account.assignedTherapist, + recentVisitDate: account.recentVisitDate, + nextReservationDate: account.nextReservationDate, + }, + doctor, + therapist, + }; + } else if (account.type === "admin1") { + return { + email: account.id, + name: account.name, + major: account.major, + workplace: account.workplace, + access_token: "admin1_token1", + refresh_token: "admin1_token2", + admin: account.admin, + }; + } else if (account.type === "admin2") { + return { + email: account.id, + name: account.name, + major: account.major, + workplace: account.workplace, + access_token: "admin2_token1", + refresh_token: "admin2_token2", + admin: account.admin, + }; } + + return null; } // 임시로 환자에게 할당된 과제 데이터를 추가 const userExercises = { - "HL0001": [ - { id: "1", name: "팔 운동 1", accuracy: "80%", judgement: "합격" }, - { id: "2", name: "팔 운동 2", accuracy: "80%", judgement: "합격" }, - { id: "3", name: "팔 운동 3", accuracy: "40%", judgement: "불합격" }, - { id: "4", name: "팔 운동 4", accuracy: "0%", judgement: "미수강" }, - { id: "5", name: "팔 운동 5", accuracy: "0%", judgement: "미수강" }, - { id: "6", name: "팔 운동 6", accuracy: "0%", judgement: "미수강" }, - { id: "7", name: "팔 운동 7", accuracy: "0%", judgement: "미수강" }, - { id: "8", name: "팔 운동 8", accuracy: "0%", judgement: "미수강" } - ] + HL0001: [ + { id: "1", name: "팔 운동 1", accuracy: "80%", judgement: "합격" }, + { id: "2", name: "팔 운동 2", accuracy: "80%", judgement: "합격" }, + { id: "3", name: "팔 운동 3", accuracy: "40%", judgement: "불합격" }, + { id: "4", name: "팔 운동 4", accuracy: "0%", judgement: "미수강" }, + { id: "5", name: "팔 운동 5", accuracy: "0%", judgement: "미수강" }, + { id: "6", name: "팔 운동 6", accuracy: "0%", judgement: "미수강" }, + { id: "7", name: "팔 운동 7", accuracy: "0%", judgement: "미수강" }, + { id: "8", name: "팔 운동 8", accuracy: "0%", judgement: "미수강" }, + ], }; // 환자의 ID를 입력받아 해당 환자에게 할당된 과제 데이터를 반환하는 함수 @@ -91,18 +106,20 @@ export function getUserExercises(userId) { // 기존 login-api.js 파일에 비대면 진료 기록 추가 const userUntactRecords = { - "HL0001": [ + HL0001: [ { date: "2023.08.31", doctorName: "김정원 전문의", - record: "환자는 3일 전부터 갑자기 시작된 기침과 가래를 주소로 내원하였음. 체온 측정 결과 38.2도로 발열 증상도 있음. 오늘 진료를 통해 급성 기관지염으로 진단하고, 관련 약물 처방하였음. 1주 후 재진을 권장함." + record: + "환자는 3일 전부터 갑자기 시작된 기침과 가래를 주소로 내원하였음. 체온 측정 결과 38.2도로 발열 증상도 있음. 오늘 진료를 통해 급성 기관지염으로 진단하고, 관련 약물 처방하였음. 1주 후 재진을 권장함.", }, { date: "2023.09.22", doctorName: "오민혁 재활치료사", - record: "환자는 3일 전부터 갑자기 시작된 기침과 가래를 주소로 내원하였음. 체온 측정 결과 38.2도로 발열 증상도 있음. 오늘 진료를 통해 급성 기관지염으로 진단하고, 관련 약물 처방하였음. 1주 후 재진을 권장함." - } - ] + record: + "환자는 3일 전부터 갑자기 시작된 기침과 가래를 주소로 내원하였음. 체온 측정 결과 38.2도로 발열 증상도 있음. 오늘 진료를 통해 급성 기관지염으로 진단하고, 관련 약물 처방하였음. 1주 후 재진을 권장함.", + }, + ], }; // 비대면 진료 기록을 반환하는 함수 diff --git a/src/pages/User/UserReservePage.jsx b/src/pages/User/UserReservePage.jsx new file mode 100644 index 0000000..393c74a --- /dev/null +++ b/src/pages/User/UserReservePage.jsx @@ -0,0 +1,34 @@ +import styled from 'styled-components'; +import Header from '../../components/Header/Header'; +import BackButton from "../../components/Button/BackButton"; +import UserDoReserve from '../../components/UserDashBoard/UserDoReserve'; + + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + height: 100vh; +`; + +const CenteredContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1; +`; + + +const UserReservePage = () => { + return ( + +
+ + + + + + ); +} + +export default UserReservePage; \ No newline at end of file