Skip to content

khg9859/database-design

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

35 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Hansung Gym Management System (ํ•œ์„ฑ ํ—ฌ์Šค์žฅ ํ†ตํ•ฉ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ)

React + MySQL ๊ธฐ๋ฐ˜์˜ ๋กœ์ปฌ ํ†ตํ•ฉ ๊ด€๋ฆฌ ์›น ์‹œ์Šคํ…œ
์šด๋™/์‹๋‹จ ๋ฃจํ‹ด ๊ฐ€์ด๋“œ์™€ ๋ฉ˜ํ† ๋ง ๋งค์นญ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•œ ํšŒ์› ์ค‘์‹ฌ ํ—ฌ์Šค์žฅ ํ”Œ๋žซํผ


๐Ÿ†• ์ตœ์‹  ์—…๋ฐ์ดํŠธ (2025-01-23)

โœจ ๋ฐฐ์น˜ ๋ณด์ƒ ์‹œ์Šคํ…œ ์™„์ „ ๊ตฌํ˜„

  • ์šด๋™ ๊ธฐ๋ก: 5ํšŒ๋งˆ๋‹ค 100P ์ž๋™ ์ง€๊ธ‰
  • ์‹๋‹จ ๊ธฐ๋ก: 3ํšŒ๋งˆ๋‹ค 50P ์ž๋™ ์ง€๊ธ‰
  • ์ถœ์„ ์ฒดํฌ: 10ํšŒ๋งˆ๋‹ค 200P ์ž๋™ ์ง€๊ธ‰
  • ๋ชฉํ‘œ ์„ค์ •: 2๊ฐœ๋งˆ๋‹ค 80P ์ž๋™ ์ง€๊ธ‰
  • SQL ํŠธ๋ฆฌ๊ฑฐ์™€ ๋™์ผํ•œ ๋กœ์ง์„ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์™„๋ฒฝ ๊ตฌํ˜„
  • achievement_id ์ž๋™ ์—ฐ๊ฒฐ ๋ฐ Toast ์•Œ๋ฆผ

๐Ÿ‹๏ธ ์‹ค์‹œ๊ฐ„ ํ—ฌ์Šค์žฅ ์ด์šฉ ํ˜„ํ™ฉ

  • Class ํŽ˜์ด์ง€์— ํ˜„์žฌ ์ด์šฉ ์ค‘์ธ ํšŒ์› ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ
  • SQL VIEW (view_current_users) ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  • Attendance ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ํ˜ผ์žก๋„ ๊ณ„์‚ฐ
  • ํ˜„์žฌ ์ธ์› / ๋‚จ์€ ์ž๋ฆฌ ์‹œ๊ฐํ™”

โœ“ ์ถœ์„ ์ฒดํฌ ๊ธฐ๋Šฅ

  • MyPage์— ์ถœ์„ ์ฒดํฌ ๋ฒ„ํŠผ ์ถ”๊ฐ€
  • ์ค‘๋ณต ์ถœ์„ ๋ฐฉ์ง€ (ํ•˜๋ฃจ 1ํšŒ)
  • ์ถœ์„ 10ํšŒ๋งˆ๋‹ค ์ž๋™ ํฌ์ธํŠธ ์ง€๊ธ‰
  • Toast ์•Œ๋ฆผ์œผ๋กœ ์ฆ‰๊ฐ ํ”ผ๋“œ๋ฐฑ

๐Ÿ“ข ๊ณต์ง€์‚ฌํ•ญ ํŽ˜์ด์ง€ ๊ฐœ์„ 

  • ํฌ์ธํŠธ ์ง€๊ธ‰ ์กฐ๊ฑด ์ƒ์„ธ ์•ˆ๋‚ด
  • ํ—ฌ์Šค์žฅ ์ด์šฉ ์•ˆ๋‚ด
  • ๋ฉ˜ํ† ๋ง ์‹œ์Šคํ…œ ์„ค๋ช…
  • ๋‹คํฌ๋ชจ๋“œ ์ง€์›

๐ŸŽฏ ๋ชฉํ‘œ ๊ด€๋ฆฌ ๋ฐฐ์น˜ ๋ณด์ƒ

  • Goal ์ปดํฌ๋„ŒํŠธ์— ๋ฐฐ์น˜ ๋ณด์ƒ ๋กœ์ง ์ถ”๊ฐ€
  • ๋ชฉํ‘œ 2๊ฐœ ์„ค์ • ์‹œ 80P ์ž๋™ ์ง€๊ธ‰
  • localStorage ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์˜์†์„ฑ

๐Ÿ“˜ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์ด ํ”„๋กœ์ ํŠธ๋Š” ํ—ฌ์Šค์žฅ ํšŒ์› ํ†ตํ•ฉ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์œผ๋กœ,
ํšŒ์›(Member)์„ ์ค‘์‹ฌ์œผ๋กœ ์šด๋™ ๋ฃจํ‹ด, ์‹๋‹จ, ๋ฉ˜ํ† ๋ง, ์ธ์„ผํ‹ฐ๋ธŒ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ํ†ตํ•ฉ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋ก ํŠธ์—”๋“œ๋Š” React ๊ธฐ๋ฐ˜์ด๋ฉฐ,
๋ฐ์ดํ„ฐ๋Š” ๋กœ์ปฌ MySQL ์—ฐ๋™ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
๋ฐฑ์—”๋“œ API ์„œ๋ฒ„๋Š” ๋ณ„๋„๋กœ ๋‘์ง€ ์•Š๊ณ , ํ”„๋ก ํŠธ ๋‚ด๋ถ€์—์„œ ์ง์ ‘ DB์™€ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.


โš™๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ

ํ•ญ๋ชฉ ๋‚ด์šฉ
Frontend React 18 (Create React App)
Styling TailwindCSS, Framer Motion
Database MySQL (๋กœ์ปฌ ์—ฐ๊ฒฐ ๋ฐฉ์‹)
State Management Context API (MatchContext.jsx)
UI Framework Responsive Design (TailwindCSS ๊ธฐ๋ฐ˜)
Animation Framer Motion
Package Manager npm

๐Ÿš€ ์‹คํ–‰ ๋ฐฉ๋ฒ•

# 1. ํŒจํ‚ค์ง€ ์„ค์น˜
npm install

# 2. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
npm start

# 3. ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด๊ธฐ
http://localhost:3000


# ๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
my-app/
โ”œโ”€โ”€ public/
โ”‚   โ”œโ”€โ”€ index.html
โ”‚   โ”œโ”€โ”€ favicon.ico
โ”‚   โ”œโ”€โ”€ logo192.png / logo512.png
โ”‚   โ”œโ”€โ”€ manifest.json / robots.txt
โ”‚   โ””โ”€โ”€ videos/
โ”‚       โ””โ”€โ”€ gym.mp4                 # ๋ฉ”์ธ ์†Œ๊ฐœ ์˜์ƒ
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ assets/
โ”‚   โ”‚   โ””โ”€โ”€ mentoring/
โ”‚   โ”‚       โ”œโ”€โ”€ mentor.png          # ๊ธฐ๋ณธ ๋ฉ˜ํ†  ์ด๋ฏธ์ง€
โ”‚   โ”‚       โ”œโ”€โ”€ mentee.png          # ๊ธฐ๋ณธ ๋ฉ˜ํ‹ฐ ์ด๋ฏธ์ง€
โ”‚   โ”‚       โ””โ”€โ”€ defaultProfile.png  # ๊ธฐ๋ณธ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ””โ”€โ”€ Navbar.jsx              # ์ƒ๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ (๊ณตํ†ต)
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ context/
โ”‚   โ”‚   โ””โ”€โ”€ ThemeContext.jsx        # ๐ŸŒ“ ์ „์—ญ ๋‹คํฌ๋ชจ๋“œ ํ…Œ๋งˆ ๊ด€๋ฆฌ
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ pages/
โ”‚   โ”‚   โ”œโ”€โ”€ guide/                  # ๐Ÿฅ— ํ—ฌ์Šค ๊ฐ€์ด๋“œ ๋ชจ๋“ˆ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Guide.jsx           # ๊ฐ€์ด๋“œ ๋ฉ”์ธ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ DietTab.jsx         # ์‹๋‹จ ๊ฐ€์ด๋“œ ํƒญ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ RoutineTab.jsx      # ์šด๋™ ๋ฃจํ‹ด ํƒญ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ PostCard.jsx        # ๊ฐ€์ด๋“œ ๊ฒŒ์‹œ๊ธ€ ์นด๋“œ
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ NewPostModal.jsx    # ๊ฐ€์ด๋“œ ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ๋ชจ๋‹ฌ
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ mentoring/              # ๐Ÿค ๋ฉ˜ํ† ๋ง ๋ชจ๋“ˆ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Mentoring.jsx       # ๋ฉ˜ํ† ๋ง ๋ฉ”์ธ ํƒญ (๋‹คํฌ๋ชจ๋“œ/ํ”„๋กœํ•„)
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ MentorRecruitTab.jsx# ๋ฉ˜ํ†  ๋ชจ์ง‘๊ธ€ ์ž‘์„ฑ + ๋ฉ˜ํ‹ฐ ์‹ ์ฒญ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ MenteeRecruitTab.jsx# ๋ฉ˜ํ‹ฐ ๋ชจ์ง‘๊ธ€ ์ž‘์„ฑ + ๋ฉ˜ํ†  ์‹ ์ฒญ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ MatchContext.jsx    # ๋ฉ˜ํ† /๋ฉ˜ํ‹ฐ/๋งค์นญ ์ƒํƒœ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ MatchModal.jsx      # ์‹ ์ฒญยท์ˆ˜๋ฝยทํŒŒ๊ธฐ ๋ชจ๋‹ฌ์ฐฝ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ MentorCard.jsx      # ๋ฉ˜ํ†  ๋ชฉ๋ก ์นด๋“œ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Home.jsx            # (์˜ˆ๋น„) ๋ฉ˜ํ† ๋ง ์†Œ๊ฐœ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ”œโ”€โ”€ Home.jsx                # ๋ฉ”์ธ ํ™ˆ ํ™”๋ฉด
โ”‚   โ”‚   โ”œโ”€โ”€ MyPage.jsx              # ๐Ÿง‘ ๊ฐœ์ธ ์ •๋ณด ๋ฐ ๋งค์นญ ํ˜„ํ™ฉ (๋‹คํฌ๋ชจ๋“œ ์ง€์›)
โ”‚   โ”‚   โ”œโ”€โ”€ Goal.jsx                # ๐ŸŽฏ ๋ชฉํ‘œ ์„ค์ • ๋ฐ ์šด๋™ ๋ฃจํ‹ด
โ”‚   โ”‚   โ”œโ”€โ”€ Class.jsx               # ๐Ÿ“š ๊ต์–‘์ˆ˜์—… ์‹œ๊ฐ„ํ‘œ ๋ฐ ํ—ฌ์Šค์žฅ ๊ฐ€์šฉ์„ฑ ํ™•์ธ
โ”‚   โ”‚   โ”œโ”€โ”€ Notice.jsx              # ๊ณต์ง€์‚ฌํ•ญ
โ”‚   โ”‚   โ””โ”€โ”€ App.css                 # ์ „์—ญ ์Šคํƒ€์ผ
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ App.js                      # ๋ผ์šฐํŒ… ๋ฐ ์ „์—ญ Provider ์„ค์ •
โ”‚   โ”œโ”€โ”€ index.js                    # ReactDOM ์ง„์ž…์ 
โ”‚   โ”œโ”€โ”€ index.css                   # ๊ณตํ†ต ์Šคํƒ€์ผ
โ”‚   โ”œโ”€โ”€ reportWebVitals.js
โ”‚   โ””โ”€โ”€ setupTests.js
โ”‚
โ”œโ”€โ”€ server/                         # ๐Ÿ”ง ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ (Express.js + MySQL)
โ”‚   โ”œโ”€โ”€ server.js                   # Express ์„œ๋ฒ„ ๋ฐ REST API
โ”‚   โ””โ”€โ”€ sql/
โ”‚       โ”œโ”€โ”€ HS_Health.sql           # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ
โ”‚       โ”œโ”€โ”€ insert_class_data.sql   # ๊ต์–‘์ˆ˜์—… ๋”๋ฏธ ๋ฐ์ดํ„ฐ
โ”‚       โ””โ”€โ”€ README.md               # SQL ์‹คํ–‰ ๊ฐ€์ด๋“œ
โ”‚
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ tailwind.config.js
โ”œโ”€โ”€ postcss.config.js
โ”œโ”€โ”€ .gitignore
โ””โ”€โ”€ README.md

---

## ๐Ÿ†• ์ตœ์‹  ์—…๋ฐ์ดํŠธ (gyu ๋ธŒ๋žœ์น˜)

### ๐Ÿ“š **Class ํŽ˜์ด์ง€ - ๊ต์–‘์ˆ˜์—… ์‹œ๊ฐ„ํ‘œ ๋ฐ ํ—ฌ์Šค์žฅ ๊ฐ€์šฉ์„ฑ ํ™•์ธ**

ํ•™๊ต ๊ต์–‘์ˆ˜์—… ์‹œ๊ฐ„ํ‘œ๋ฅผ ํ™•์ธํ•˜๊ณ , ํ˜„์žฌ ํ—ฌ์Šค์žฅ ์ด์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณด์—ฌ์ฃผ๋Š” ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.

#### ์ฃผ์š” ๊ธฐ๋Šฅ:
- **์‹ค์‹œ๊ฐ„ ํ—ฌ์Šค์žฅ ๊ฐ€์šฉ์„ฑ ์ฒดํฌ**: ํ˜„์žฌ ๊ต์–‘์ˆ˜์—… ์ง„ํ–‰ ์ค‘์ด๋ฉด ์ธ์› ์ œํ•œ ํ‘œ์‹œ (์•ฝ 30๋ช…)
- **ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ์ˆ˜์—… ํ‘œ์‹œ**: ์ง€๊ธˆ ์ง„ํ–‰ ์ค‘์ธ ๊ต์–‘์ˆ˜์—… ์ •๋ณด ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ
- **๋‹ค์Œ ์ˆ˜์—… ๋ฏธ๋ฆฌ๋ณด๊ธฐ**: ๋‹ค์Œ ์˜ˆ์ •๋œ ๊ต์–‘์ˆ˜์—… ์‹œ๊ฐ„ ์•ˆ๋‚ด
- **์‹ค์‹œ๊ฐ„ ์‹œ๊ณ„**: 1๋ถ„๋งˆ๋‹ค ์ž๋™ ์—…๋ฐ์ดํŠธ๋˜๋Š” ํ˜„์žฌ ์‹œ๊ฐ„
- **๋‹คํฌ๋ชจ๋“œ ์ง€์›**: ํŽ˜์ด์ง€๋ณ„ ๋…๋ฆฝ์ ์ธ ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€
- **๋ฐ˜์‘ํ˜• ๋””์ž์ธ**: Framer Motion ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ํ•จ๊ป˜ ๋ถ€๋“œ๋Ÿฌ์šด UI/UX

#### ๊ธฐ์ˆ  ์Šคํƒ:
```javascript
// ์‹ค์‹œ๊ฐ„ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
useEffect(() => {
  const timer = setInterval(() => {
    setCurrentTime(new Date());
  }, 60000); // 1๋ถ„๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ
  return () => clearInterval(timer);
}, []);

// ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ์ˆ˜์—… ํ™•์ธ
const getCurrentClass = () => {
  const now = currentTime;
  const currentDay = ['์ผ', '์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† '][now.getDay()];
  const currentTimeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:00`;

  for (const cls of classes) {
    const schedules = classSchedules[cls.class_id] || [];
    for (const schedule of schedules) {
      if (schedule.day_of_week === currentDay) {
        if (currentTimeStr >= schedule.start_time && currentTimeStr < schedule.end_time) {
          return { class: cls, schedule };
        }
      }
    }
  }
  return null;
};

๐ŸŒ“ ๋‹คํฌ๋ชจ๋“œ ์‹œ์Šคํ…œ

ThemeContext (์ „์—ญ ํ…Œ๋งˆ ๊ด€๋ฆฌ)

  • Context API๋ฅผ ํ™œ์šฉํ•œ ์ „์—ญ ๋‹คํฌ๋ชจ๋“œ ์ƒํƒœ ๊ด€๋ฆฌ
  • localStorage์— ํ…Œ๋งˆ ์„ค์ • ์ €์žฅ์œผ๋กœ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ์—๋„ ์œ ์ง€
  • HTML ๋ฃจํŠธ ์š”์†Œ์— dark ํด๋ž˜์Šค ์ž๋™ ์ถ”๊ฐ€/์ œ๊ฑฐ

ํŽ˜์ด์ง€๋ณ„ ๋…๋ฆฝ ๋‹คํฌ๋ชจ๋“œ

๊ฐ ํŽ˜์ด์ง€๋งˆ๋‹ค ๋…๋ฆฝ์ ์ธ ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  1. Class ํŽ˜์ด์ง€: classPageTheme localStorage ํ‚ค ์‚ฌ์šฉ
  2. MyPage: myPageTheme localStorage ํ‚ค ์‚ฌ์šฉ
  3. Mentoring ํŽ˜์ด์ง€: ๊ธฐ์กด ๋…๋ฆฝ ๋‹คํฌ๋ชจ๋“œ ์œ ์ง€
  4. Guide ํŽ˜์ด์ง€: ๊ธฐ์กด ๋…๋ฆฝ ๋‹คํฌ๋ชจ๋“œ ์œ ์ง€
// ํŽ˜์ด์ง€๋ณ„ ๋…๋ฆฝ ๋‹คํฌ๋ชจ๋“œ ์˜ˆ์‹œ (Class.jsx)
const [isDark, setIsDark] = useState(() => {
  const saved = localStorage.getItem('classPageTheme');
  return saved ? saved === 'dark' : true;
});

useEffect(() => {
  localStorage.setItem('classPageTheme', isDark ? 'dark' : 'light');
}, [isDark]);

๐Ÿง‘ MyPage ๊ฐœ์„ ์‚ฌํ•ญ

์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ:

  • ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€: ์šฐ์ธก ์ƒ๋‹จ์— ๋…๋ฆฝ์ ์ธ ๋‹คํฌ๋ชจ๋“œ ์Šค์œ„์น˜ ์ถ”๊ฐ€
  • Goal ์ปดํฌ๋„ŒํŠธ ํ†ตํ•ฉ: ๋ชฉํ‘œ ์„ค์ • ๋ฐ ์šด๋™ ๋ฃจํ‹ด ๊ด€๋ฆฌ ๊ธฐ๋Šฅ ํ†ตํ•ฉ
  • ๋‹คํฌ๋ชจ๋“œ ํ…Œ๋งˆ ์ „๋‹ฌ: MyPage์˜ isDark ์ƒํƒœ๋ฅผ Goal ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ผ๊ด€๋œ ํ…Œ๋งˆ ์œ ์ง€

Goal.jsx (๋ชฉํ‘œ ๊ด€๋ฆฌ ์ปดํฌ๋„ŒํŠธ)

  • ๋ถ€๋ชจ(MyPage)๋กœ๋ถ€ํ„ฐ isDark prop์„ ๋ฐ›์•„ ํ…Œ๋งˆ ๋™๊ธฐํ™”
  • ์šด๋™ ๋ชฉํ‘œ ์„ค์ • ๋ฐ ํŠธ๋ž˜ํ‚น ๊ธฐ๋Šฅ
  • ๋‹คํฌ๋ชจ๋“œ ์Šคํƒ€์ผ ์ง€์›

๐Ÿ”ง ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ (server.js)

Express.js + MySQL REST API

  • ์ž๋™ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”: ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ๊ต์–‘์ˆ˜์—… ๋ฐ์ดํ„ฐ ์ž๋™ ์‚ฝ์ž…
  • ๊ต์–‘์ˆ˜์—… API ์—”๋“œํฌ์ธํŠธ:
    • GET /api/classes: ๋ชจ๋“  ๊ต์–‘์ˆ˜์—… ์กฐํšŒ
    • GET /api/class-schedules/:classId: ํŠน์ • ์ˆ˜์—…์˜ ์‹œ๊ฐ„ํ‘œ ์กฐํšŒ
    • POST /api/classes: ์ƒˆ ๊ต์–‘์ˆ˜์—… ์ถ”๊ฐ€
    • PUT /api/classes/:id: ์ˆ˜์—… ์ •๋ณด ์ˆ˜์ •
    • DELETE /api/classes/:id: ์ˆ˜์—… ์‚ญ์ œ

์ฃผ์š” ํ…Œ์ด๋ธ”:

  • Class: ๊ต์–‘์ˆ˜์—… ์ •๋ณด (๊ณผ๋ชฉ๋ช…, ๋‹ด๋‹น๊ต์ˆ˜, ํ•™์  ๋“ฑ)
  • Class_Schedule: ์ˆ˜์—… ์‹œ๊ฐ„ํ‘œ (์š”์ผ, ์‹œ์ž‘/์ข…๋ฃŒ ์‹œ๊ฐ„)

๐Ÿ“ฆ ์˜์กด์„ฑ ์ถ”๊ฐ€

{
  "dependencies": {
    "express": "^4.21.2",
    "mysql2": "^3.11.5",
    "cors": "^2.8.5",
    "react-hot-toast": "^2.4.1"
  }
}
  • express: REST API ์„œ๋ฒ„
  • mysql2: MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ
  • cors: CORS ๋ฏธ๋“ค์›จ์–ด
  • react-hot-toast: ํ† ์ŠคํŠธ ์•Œ๋ฆผ UI

๐ŸŽจ UI/UX ๊ฐœ์„ ์‚ฌํ•ญ

  1. Framer Motion ์• ๋‹ˆ๋ฉ”์ด์…˜: ๋ถ€๋“œ๋Ÿฌ์šด ํŽ˜์ด์ง€ ์ „ํ™˜ ๋ฐ ์นด๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜
  2. ๋ฐ˜์‘ํ˜• ๊ทธ๋ผ๋””์–ธํŠธ ๋ฐฐ๊ฒฝ: ๋‹คํฌ/๋ผ์ดํŠธ ๋ชจ๋“œ๋ณ„ ์ตœ์ ํ™”๋œ ๋ฐฐ๊ฒฝ์ƒ‰
  3. ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ํ‘œ์‹œ: ํŽ„์Šค ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ํ˜„์žฌ ์ƒํƒœ ๊ฐ•์กฐ
  4. ์ผ๊ด€๋œ ๋””์ž์ธ ์‹œ์Šคํ…œ: Tailwind CSS ๊ธฐ๋ฐ˜ ํ†ต์ผ๋œ ์Šคํƒ€์ผ


๐Ÿ†• ์ตœ์‹  ์—…๋ฐ์ดํŠธ (hongrecent ๋ธŒ๋žœ์น˜)

๐Ÿ“Š ๋งˆ์ดํŽ˜์ด์ง€ ๋Œ€ํญ ๊ฐœ์„ 

ํšŒ์›์˜ ์šด๋™/์‹๋‹จ/๊ฑด๊ฐ• ๊ธฐ๋ก์„ ์‹œ๊ฐํ™”ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํ†ตํ•ฉ ๋Œ€์‹œ๋ณด๋“œ๋กœ ๊ฐœ์„ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐ŸŽฏ ์ฃผ์š” ๊ธฐ๋Šฅ

1. Chart.js ๊ธฐ๋ฐ˜ ์ฒด์ค‘ ๋ณ€ํ™” ์ฐจํŠธ
  • Line Chart๋กœ ์ฒด์ค‘ ๋ณ€ํ™” ์ถ”์ด ์‹œ๊ฐํ™”
  • ๋ถ€๋“œ๋Ÿฌ์šด ๊ณก์„ ๊ณผ ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ
  • ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„ ์‹œ ์ •ํ™•ํ•œ ๊ฐ’ ํ‘œ์‹œ
  • ๋‹คํฌ๋ชจ๋“œ ์ง€์› ๋ฐ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ
// WeightChart.jsx - Chart.js ์„ค์ •
const data = {
  labels: recentRecords.map((record, idx) => {
    if (idx === 0) return '์‹œ์ž‘';
    if (idx === recentRecords.length - 1) return 'ํ˜„์žฌ';
    return '';
  }),
  datasets: [{
    label: '์ฒด์ค‘ (kg)',
    data: recentRecords.map(r => r.weight_kg),
    borderColor: 'rgb(34, 197, 94)',
    backgroundColor: 'rgba(34, 197, 94, 0.1)',
    fill: true,
    tension: 0.4
  }]
};
2. ์‚ฌ์ด๋“œ ํŒจ๋„ ๋ชฉํ‘œ ๊ด€๋ฆฌ
  • ์˜ค๋ฅธ์ชฝ ํ•˜๋‹จ ๊ณ ์ • ๋ฒ„ํŠผ (๐ŸŽฏ)์œผ๋กœ ์ ‘๊ทผ
  • ์˜ค๋ฅธ์ชฝ์—์„œ ์Šฌ๋ผ์ด๋“œ๋˜๋Š” ์‚ฌ์ด๋“œ ํŒจ๋„
  • Spring ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜
  • ๋ฐฐ๊ฒฝ ์˜ค๋ฒ„๋ ˆ์ด๋กœ ํฌ์ปค์Šค ๊ฐ•์กฐ
  • ๋ชจ๋ฐ”์ผ: ์ „์ฒด ํ™”๋ฉด / ๋ฐ์Šคํฌํ†ฑ: 600-700px ๋„ˆ๋น„
// ์‚ฌ์ด๋“œ ํŒจ๋„ ์• ๋‹ˆ๋ฉ”์ด์…˜
<motion.div
  initial={{ x: '100%' }}
  animate={{ x: 0 }}
  exit={{ x: '100%' }}
  transition={{ type: 'spring', damping: 25, stiffness: 200 }}
>
  <Goal isDark={isDark} />
</motion.div>
3. ๋‚˜์˜ ์š”์•ฝ ์„น์…˜

6๊ฐœ์˜ ์ธ์‚ฌ์ดํŠธ ์นด๋“œ๋กœ ๊ตฌ์„ฑ๋œ ๋Œ€์‹œ๋ณด๋“œ:

  • ๐Ÿ”ฅ ์ตœ๊ทผ ํ™œ๋™ ์š”์•ฝ: ์š”์ผ๋ณ„ ์šด๋™ ๋นˆ๋„ ์ฐจํŠธ
  • โค๏ธ ๋งŽ์ด ์ˆ˜ํ–‰ํ•œ ์šด๋™ TOP 3: ๊ฐ€์žฅ ๋งŽ์ด ํ•œ ์šด๋™ ์ˆœ์œ„
  • ๐Ÿ“ˆ ๋งŽ์ด ์„ฑ์žฅํ•œ ์šด๋™ TOP 3: ์„ฑ์žฅ๋ฅ  ๊ธฐ์ค€ ์ˆœ์œ„ (307.8% ์ฆ๊ฐ€ ๋“ฑ)
  • ๐Ÿ” ๋ถ€์œ„๋ณ„ ์šด๋™ ๋ถ„์„: ๋„๋„› ์ฐจํŠธ๋กœ ์šด๋™ ๋ถ€์œ„ ๋ถ„ํฌ ์‹œ๊ฐํ™”
  • ๐Ÿ’ช ์ด๋ฒˆ๋‹ฌ ์š”์•ฝ: ๊ฐœ์ธํ™”๋œ ํƒ€์ดํ‹€ ๋ฐ ์„ฑ์ทจ ์š”์•ฝ
  • โš–๏ธ ์ฒด์ค‘ ๋ณ€ํ™”: Chart.js ๋ผ์ธ ์ฐจํŠธ๋กœ ์ถ”์ด ํ‘œ์‹œ
4. ๊ฐœ์„ ๋œ ๊ธฐ๋ก ํ‘œ์‹œ (DailyRecordCard)

๋‚ ์งœ ํด๋ฆญ ์‹œ ํ•ด๋‹น ๋‚ ์งœ์˜ ๋ชจ๋“  ๊ธฐ๋ก์„ ์นด๋“œ ํ˜•์‹์œผ๋กœ ํ‘œ์‹œ:

  • ์šด๋™ ๊ธฐ๋ก: ํŒŒ๋ž€์ƒ‰ ๊ทธ๋ผ๋ฐ์ด์…˜, ์‹œ๊ฐ„ ํ‘œ์‹œ, ๋ณด์ƒ ํš๋“ ์—ฌ๋ถ€
  • ์‹๋‹จ ๊ธฐ๋ก: ์ดˆ๋ก์ƒ‰ ๊ทธ๋ผ๋ฐ์ด์…˜, ์‹์‚ฌ ์‹œ๊ฐ„๋ณ„ ๋ฐฐ์ง€ (์•„์นจ/์ ์‹ฌ/์ €๋…/๊ฐ„์‹)
  • ๊ฑด๊ฐ• ๊ธฐ๋ก: ๋นจ๊ฐ„์ƒ‰ ๊ทธ๋ผ๋ฐ์ด์…˜, ์ฒด์ค‘/๊ทผ์œก๋Ÿ‰/์ฒด์ง€๋ฐฉ/BMI ํ‘œ์‹œ
  • ๊ฐ ์นด๋“œ์— ํ˜ธ๋ฒ„ ํšจ๊ณผ ๋ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ ์šฉ
  • ๋นˆ ์ƒํƒœ UI ๊ฐœ์„  (์ด๋ชจ์ง€ + ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€)
// ์‹์‚ฌ ์‹œ๊ฐ„๋ณ„ ๋ฐฐ์ง€ ์ƒ‰์ƒ
const mealColors = {
  '์•„์นจ': 'bg-yellow-500/20 text-yellow-400',
  '์ ์‹ฌ': 'bg-orange-500/20 text-orange-400',
  '์ €๋…': 'bg-purple-500/20 text-purple-400',
  '๊ฐ„์‹': 'bg-pink-500/20 text-pink-400'
};
5. ์บ˜๋ฆฐ๋” ๊ฐœ์„ 
  • ํ˜„์žฌ ๋‚ ์งœ ๊ฐ•์กฐ: ์˜ค๋ Œ์ง€-๋ ˆ๋“œ ๊ทธ๋ผ๋ฐ์ด์…˜ + ๋…ธ๋ž€์ƒ‰ ๋ง
  • ์ถœ์„ ๊ธฐ๋ก: ํŒŒ๋ž€์ƒ‰-๋ณด๋ผ์ƒ‰ ๊ทธ๋ผ๋ฐ์ด์…˜ + ์ดˆ๋ก ์ 
  • ์„ ํƒ๋œ ๋‚ ์งœ: ํŒŒ๋ž€์ƒ‰ ๋ง์œผ๋กœ ๊ฐ•์กฐ
  • ๋‚ ์งœ ํด๋ฆญ ์‹œ ํ•ด๋‹น ๋‚ ์งœ์˜ ๋ชจ๋“  ๊ธฐ๋ก ํ‘œ์‹œ
6. ๋ฑƒ์ง€ ์‹œ์Šคํ…œ
  • 8๊ฐœ ๋ฑƒ์ง€ ์ข…๋ฅ˜: ํ—ฌ์Šค ์ž…๋ฌธ์ž, ์‹๋‹จ ๊ด€๋ฆฌ์ž, ์ถœ์„์™•, ๊ทผ์œก ๋นŒ๋” ๋“ฑ
  • ํš๋“ํ•œ ๋ฑƒ์ง€: ์ปฌ๋Ÿฌํ’€ + ํš๋“ ๋‚ ์งœ ํ‘œ์‹œ
  • ๋ฏธํš๋“ ๋ฑƒ์ง€: ํ‘๋ฐฑ + ์ž ๊ธˆ ์•„์ด์ฝ˜
  • ๋ฑƒ์ง€ ๋ชจ๋‹ฌ์—์„œ ์ „์ฒด ์ปฌ๋ ‰์…˜ ํ™•์ธ
7. ๊ธฐ๋ก ์ถ”๊ฐ€ ๊ธฐ๋Šฅ

์บ˜๋ฆฐ๋” ์•„๋ž˜ 3๊ฐœ ๋ฒ„ํŠผ์œผ๋กœ ๋น ๋ฅธ ๊ธฐ๋ก ์ถ”๊ฐ€:

  • ๐Ÿ’ช ์šด๋™ ๊ธฐ๋ก: ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•„ํ„ฐ๋ง (๊ฐ€์Šด/๋“ฑ/ํ•˜์ฒด/์–ด๊นจ/ํŒ”/๋ณต๊ทผ/์œ ์‚ฐ์†Œ)
  • ๐Ÿฝ๏ธ ์‹๋‹จ ๊ธฐ๋ก: ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•„ํ„ฐ๋ง (๋‹จ๋ฐฑ์งˆ/ํƒ„์ˆ˜ํ™”๋ฌผ/์ฑ„์†Œ/๊ณผ์ผ/์œ ์ œํ’ˆ/๋ณด์ถฉ์ œ/ํ•œ์‹)
  • โค๏ธ ๊ฑด๊ฐ• ๊ธฐ๋ก: ํ‚ค/์ฒด์ค‘/๊ทผ์œก๋Ÿ‰/์ฒด์ง€๋ฐฉ ์ž…๋ ฅ, BMI ์ž๋™ ๊ณ„์‚ฐ

๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐœ์„ 

์Šคํ‚ค๋งˆ ์—…๋ฐ์ดํŠธ (HS_Health.sql)

-- ํฌ์ธํŠธ ์บ์‹ฑ ์ถ”๊ฐ€
ALTER TABLE Member ADD COLUMN total_points INT DEFAULT 0;

-- ๋ณด์ƒ ์—ฐ๊ฒฐ ์ถ”๊ฐ€
ALTER TABLE ExerciseLog ADD COLUMN achievement_id INT;
ALTER TABLE DietLog ADD COLUMN achievement_id INT;
ALTER TABLE Attendance ADD COLUMN achievement_id INT;
ALTER TABLE Goal ADD COLUMN achievement_id INT;

-- ํŠธ๋ฆฌ๊ฑฐ ์ถ”๊ฐ€: ์ž๋™ ํฌ์ธํŠธ ์ฆ๊ฐ
CREATE TRIGGER TRG_Achievement_Point_Earn
AFTER INSERT ON AchievementLog
FOR EACH ROW
BEGIN
    UPDATE Member SET total_points = total_points + NEW.points_earned
    WHERE member_id = NEW.member_id;
END;

๋”๋ฏธ ๋ฐ์ดํ„ฐ ๋Œ€ํญ ํ™•์žฅ (insert_dummy_data.sql)

  • ์šด๋™ ๋ฆฌ์ŠคํŠธ: 40๊ฐœ (์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ถ„๋ฅ˜)
    • ๊ฐ€์Šด 6๊ฐœ, ๋“ฑ 6๊ฐœ, ํ•˜์ฒด 6๊ฐœ, ์–ด๊นจ 5๊ฐœ, ํŒ” 5๊ฐœ, ๋ณต๊ทผ 5๊ฐœ, ์œ ์‚ฐ์†Œ 7๊ฐœ
  • ์Œ์‹ ๋ฆฌ์ŠคํŠธ: 45๊ฐœ (์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ถ„๋ฅ˜)
    • ๋‹จ๋ฐฑ์งˆ 8๊ฐœ, ํƒ„์ˆ˜ํ™”๋ฌผ 7๊ฐœ, ์ฑ„์†Œ 7๊ฐœ, ๊ณผ์ผ 6๊ฐœ, ์œ ์ œํ’ˆ 4๊ฐœ, ๋ณด์ถฉ์ œ 4๊ฐœ, ํ•œ์‹ 6๊ฐœ, ๊ฐ„์‹ 3๊ฐœ
  • ์šด๋™ ๋กœ๊ทธ: 40๊ฐœ (์ตœ๊ทผ 30์ผ)
  • ์‹๋‹จ ๋กœ๊ทธ: 50๊ฐœ (์ตœ๊ทผ 30์ผ)
  • ๊ฑด๊ฐ• ๊ธฐ๋ก: 7๊ฐœ (์ฃผ๊ฐ„ ์ธก์ •)
  • ์ถœ์„ ๊ธฐ๋ก: 7๊ฐœ
  • ๋ฑƒ์ง€: 8๊ฐœ
  • ํšŒ์› ๋ฑƒ์ง€: 4๊ฐœ ํš๋“

๐ŸŽจ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ ๊ฐœ์„ 

์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ

src/components/
โ”œโ”€โ”€ WeightChart.jsx          # Chart.js ์ฒด์ค‘ ๋ณ€ํ™” ์ฐจํŠธ
โ”œโ”€โ”€ DailyRecordCard.jsx      # ์ผ์ผ ๊ธฐ๋ก ์นด๋“œ (์šด๋™/์‹๋‹จ/๊ฑด๊ฐ•)
โ””โ”€โ”€ Navbar.jsx               # ๊ธฐ์กด ๋„ค๋น„๊ฒŒ์ด์…˜

์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ์ด์ 

  • ์žฌ์‚ฌ์šฉ์„ฑ: WeightChart๋Š” ๋‹ค๋ฅธ ํŽ˜์ด์ง€์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ์œ ์ง€๋ณด์ˆ˜์„ฑ: ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌ๋จ
  • ์„ฑ๋Šฅ: ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ Œ๋”๋ง
  • ํ…Œ์ŠคํŠธ: ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์šฉ์ด

๐Ÿ“ฆ ์ƒˆ๋กœ์šด ์˜์กด์„ฑ

{
  "dependencies": {
    "chart.js": "^4.4.1",
    "react-chartjs-2": "^5.2.0"
  }
}

Chart.js ์„ค์ •

import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
  Filler
} from 'chart.js';

ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
  Filler
);

๐ŸŽฏ UI/UX ๊ฐœ์„ ์‚ฌํ•ญ

1. ์• ๋‹ˆ๋ฉ”์ด์…˜

  • Framer Motion์œผ๋กœ ๋ชจ๋“  ์นด๋“œ์— ๋ถ€๋“œ๋Ÿฌ์šด ๋“ฑ์žฅ ์• ๋‹ˆ๋ฉ”์ด์…˜
  • ํ˜ธ๋ฒ„ ์‹œ scale, rotate ํšจ๊ณผ
  • ์‚ฌ์ด๋“œ ํŒจ๋„ ์Šฌ๋ผ์ด๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜ (Spring)
  • ๋ชจ๋‹ฌ fade in/out ํšจ๊ณผ

2. ์ƒ‰์ƒ ์‹œ์Šคํ…œ

// ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ƒ‰์ƒ ๊ตฌ๋ถ„
const categoryColors = {
  exercise: 'from-blue-500 to-purple-600',    // ์šด๋™: ํŒŒ๋ž‘-๋ณด๋ผ
  diet: 'from-green-500 to-emerald-600',      // ์‹๋‹จ: ์ดˆ๋ก
  health: 'from-red-500 to-pink-600',         // ๊ฑด๊ฐ•: ๋นจ๊ฐ•-ํ•‘ํฌ
  badge: 'from-yellow-500 to-orange-600',     // ๋ฑƒ์ง€: ๋…ธ๋ž‘-์ฃผํ™ฉ
  point: 'from-blue-600 to-pink-600'          // ํฌ์ธํŠธ: ํŒŒ๋ž‘-ํ•‘ํฌ
};

3. ๋ฐ˜์‘ํ˜• ๋””์ž์ธ

  • ๋ชจ๋ฐ”์ผ: 1์—ด ๊ทธ๋ฆฌ๋“œ
  • ํƒœ๋ธ”๋ฆฟ: 2์—ด ๊ทธ๋ฆฌ๋“œ
  • ๋ฐ์Šคํฌํ†ฑ: 3์—ด ๊ทธ๋ฆฌ๋“œ
  • ์‚ฌ์ด๋“œ ํŒจ๋„: ๋ชจ๋ฐ”์ผ ์ „์ฒด ํ™”๋ฉด, ๋ฐ์Šคํฌํ†ฑ 600-700px

4. ๋‹คํฌ๋ชจ๋“œ ์ตœ์ ํ™”

  • ๋ชจ๋“  ์นด๋“œ์— backdrop-blur ํšจ๊ณผ
  • ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ์œผ๋กœ ๊นŠ์ด๊ฐ ํ‘œํ˜„
  • ํ…์ŠคํŠธ ๊ฐ€๋…์„ฑ ์ตœ์ ํ™”
  • ์ฐจํŠธ ์ƒ‰์ƒ ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘

๐Ÿ“Š ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”

1. ์ฒด์ค‘ ๋ณ€ํ™” ์ฐจํŠธ

  • Line Chart with gradient fill
  • 8๊ฐœ ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ (์ตœ๊ทผ ๊ธฐ๋ก)
  • ์‹œ์ž‘์ ๊ณผ ํ˜„์žฌ์  ๋ผ๋ฒจ ํ‘œ์‹œ
  • ํˆดํŒ์œผ๋กœ ์ •ํ™•ํ•œ ๊ฐ’ ํ™•์ธ

2. ์š”์ผ๋ณ„ ํ™œ๋™ ์ฐจํŠธ

  • ๋ฐ” ์ฐจํŠธ ํ˜•์‹
  • ํ™”์š”์ผ, ๋ชฉ์š”์ผ ๊ฐ•์กฐ (๊ฐ€์žฅ ๋งŽ์ด ์šด๋™)
  • ๋†’์ด๋กœ ๋นˆ๋„ ํ‘œํ˜„

3. ๋ถ€์œ„๋ณ„ ์šด๋™ ๋ถ„์„

  • ๋„๋„› ์ฐจํŠธ (SVG)
  • ๋“ฑ 24%, ํ•˜์ฒด 20%, ํŒ” 14%, ๋ณต๊ทผ 12%, ๊ธฐํƒ€ 31%
  • ๋ฒ”๋ก€์™€ ํ•จ๊ป˜ ํ‘œ์‹œ

๐Ÿ”ง ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ€์ด๋“œ

์„ค์ • ๋ฐฉ๋ฒ•

# 1. MySQL ์ ‘์†
mysql -u root -p

# 2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์„ฑ
CREATE DATABASE hs_health CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE hs_health;

# 3. ์Šคํ‚ค๋งˆ ์ ์šฉ
source my-app/server/sql/HS_Health.sql;

# 4. ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์‚ฝ์ž…
source my-app/server/sql/insert_dummy_data.sql;

์ฃผ์š” ํ…Œ์ด๋ธ”

  • Member: ํšŒ์› ์ •๋ณด + ํฌ์ธํŠธ ์บ์‹ฑ
  • ExerciseList: ์šด๋™ ๋ชฉ๋ก (40๊ฐœ, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„)
  • FoodList: ์Œ์‹ ๋ชฉ๋ก (45๊ฐœ, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„)
  • ExerciseLog: ์šด๋™ ๊ธฐ๋ก + achievement_id
  • DietLog: ์‹๋‹จ ๊ธฐ๋ก + achievement_id
  • HealthRecord: ๊ฑด๊ฐ• ๊ธฐ๋ก (์ฒด์ค‘/๊ทผ์œก๋Ÿ‰/์ฒด์ง€๋ฐฉ/BMI)
  • Attendance: ์ถœ์„ ๊ธฐ๋ก + achievement_id
  • AchievementLog: ์„ฑ์ทจ ๋กœ๊ทธ (ํฌ์ธํŠธ ํš๋“)
  • Badge: ๋ฑƒ์ง€ ๋ชฉ๋ก
  • MemberBadge: ํšŒ์› ๋ฑƒ์ง€ ํš๋“ ๊ธฐ๋ก

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™”

1. ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ

  • ํฐ MyPage.jsx๋ฅผ ์ž‘์€ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌ
  • ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋ฆฌ๋ Œ๋”๋ง

2. ๋ฉ”๋ชจ์ด์ œ์ด์…˜

// ํ•„ํ„ฐ๋ง๋œ ๋ชฉ๋ก ์บ์‹ฑ
const filteredExercises = useMemo(() => 
  exerciseCategory === '์ „์ฒด' 
    ? exerciseList.filter(e => e.status === 'APPROVED')
    : exerciseList.filter(e => e.status === 'APPROVED' && e.category === exerciseCategory),
  [exerciseCategory, exerciseList]
);

3. ์ง€์—ฐ ๋กœ๋”ฉ

  • ๋ชจ๋‹ฌ์€ ์—ด๋ฆด ๋•Œ๋งŒ ๋ Œ๋”๋ง (AnimatePresence)
  • ์ฐจํŠธ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ๋ Œ๋”๋ง

๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ์ตœ์ ํ™”

  • ํ„ฐ์น˜ ์ œ์Šค์ฒ˜ ์ง€์› (Framer Motion)
  • ํฐ ํ„ฐ์น˜ ์˜์—ญ (์ตœ์†Œ 44x44px)
  • ์Šค์™€์ดํ”„๋กœ ์‚ฌ์ด๋“œ ํŒจ๋„ ๋‹ซ๊ธฐ
  • ๋ฐ˜์‘ํ˜• ํฐํŠธ ํฌ๊ธฐ
  • ๋ชจ๋ฐ”์ผ ๋„ค๋น„๊ฒŒ์ด์…˜ ์ตœ์ ํ™”

๐Ÿ”„ ๋ธŒ๋žœ์น˜ ์ •๋ณด

  • main: ํ”„๋กœ๋•์…˜ ๋ธŒ๋žœ์น˜
  • gyu: Class ํŽ˜์ด์ง€, ๋‹คํฌ๋ชจ๋“œ, ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ๊ฐœ๋ฐœ
  • hong: gyu ๋ธŒ๋žœ์น˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋จธ์ง€ ์™„๋ฃŒ
  • hongrecent: ๋งˆ์ดํŽ˜์ด์ง€ ๋Œ€ํญ ๊ฐœ์„ , Chart.js ์ ์šฉ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ™•์žฅ โญ NEW

๐Ÿ“ ์ปค๋ฐ‹ ํžˆ์Šคํ† ๋ฆฌ

hongrecent ๋ธŒ๋žœ์น˜ (2025-01-23)

feat: ๋งˆ์ดํŽ˜์ด์ง€ ๊ฐœ์„  ๋ฐ Chart.js ์ ์šฉ

- Chart.js๋ฅผ ์‚ฌ์šฉํ•œ ์ฒด์ค‘ ๋ณ€ํ™” ์ฐจํŠธ ์ถ”๊ฐ€
- ๋ชฉํ‘œ ๊ด€๋ฆฌ๋ฅผ ์‚ฌ์ด๋“œ ํŒจ๋„๋กœ ๋ณ€๊ฒฝ (์ ‘์—ˆ๋‹ค ํŽผ์น  ์ˆ˜ ์žˆ์Œ)
- DailyRecordCard ์ปดํฌ๋„ŒํŠธ๋กœ ๊ธฐ๋ก ํ‘œ์‹œ ๊ฐœ์„ 
- ์šด๋™/์‹๋‹จ/๊ฑด๊ฐ• ๊ธฐ๋ก UI ๊ฐœ์„  (์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ƒ‰์ƒ, ์• ๋‹ˆ๋ฉ”์ด์…˜)
- ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ (insert_dummy_data.sql)
- ์šด๋™ ๋ฆฌ์ŠคํŠธ 40๊ฐœ, ์Œ์‹ ๋ฆฌ์ŠคํŠธ 45๊ฐœ๋กœ ํ™•์žฅ
- ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ์ถ”๊ฐ€
- ๋‚˜์˜ ์š”์•ฝ ์„น์…˜ ์ถ”๊ฐ€ (ํ™œ๋™ ์š”์•ฝ, TOP 3, ๋ถ€์œ„๋ณ„ ๋ถ„์„ ๋“ฑ)
- ํ˜„์žฌ ๋‚ ์งœ ๊ฐ•์กฐ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
- ๋ฑƒ์ง€ ์‹œ์Šคํ…œ ๊ตฌํ˜„
- WeightChart ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ

๐ŸŽ“ ํ•™์Šต ํฌ์ธํŠธ

1. Chart.js ํ†ตํ•ฉ

  • React์—์„œ Chart.js ์‚ฌ์šฉ๋ฒ•
  • ๋ฐ˜์‘ํ˜• ์ฐจํŠธ ๊ตฌํ˜„
  • ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘ ์ฐจํŠธ ์Šคํƒ€์ผ๋ง

2. ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„

  • ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ
  • Props๋ฅผ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
  • ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ํ†ต์‹ 

3. ์• ๋‹ˆ๋ฉ”์ด์…˜

  • Framer Motion ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•
  • Spring ์• ๋‹ˆ๋ฉ”์ด์…˜
  • ์‚ฌ์ด๋“œ ํŒจ๋„ ๊ตฌํ˜„

4. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„

  • ์ •๊ทœํ™”์™€ ๋น„์ •๊ทœํ™” (ํฌ์ธํŠธ ์บ์‹ฑ)
  • ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ํ†ตํ•œ ์ž๋™ํ™”
  • ์™ธ๋ž˜ํ‚ค ๊ด€๊ณ„ ์„ค์ •

5. UI/UX ํŒจํ„ด

  • ์นด๋“œ ๊ธฐ๋ฐ˜ ๋ ˆ์ด์•„์›ƒ
  • ๋ชจ๋‹ฌ๊ณผ ์‚ฌ์ด๋“œ ํŒจ๋„
  • ๋ฐ˜์‘ํ˜• ๊ทธ๋ฆฌ๋“œ ์‹œ์Šคํ…œ
  • ์ƒ‰์ƒ ์‹œ์Šคํ…œ ์„ค๊ณ„

๐Ÿ› ์•Œ๋ ค์ง„ ์ด์Šˆ

  1. ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ๋ฏธ๊ตฌํ˜„: ํ˜„์žฌ๋Š” ๋”๋ฏธ ๋ฐ์ดํ„ฐ๋งŒ ์‚ฌ์šฉ
  2. ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ: WebSocket ๋ฏธ๊ตฌํ˜„
  3. ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ: ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ ์—†์Œ
  4. ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ: ์šด๋™/์Œ์‹ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๋ฏธ๊ตฌํ˜„

๐Ÿ”ฎ ํ–ฅํ›„ ๊ณ„ํš

  • ๋ฐฑ์—”๋“œ API ์„œ๋ฒ„ ๊ตฌํ˜„ (Express.js)
  • ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ (WebSocket)
  • ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ
  • ์šด๋™/์Œ์‹ ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ๋ง ๊ณ ๋„ํ™”
  • ์†Œ์…œ ๊ธฐ๋Šฅ (์นœ๊ตฌ, ๋žญํ‚น)
  • PWA ์ง€์›
  • ๋ชจ๋ฐ”์ผ ์•ฑ (React Native)

๐Ÿ‘ฅ ๊ธฐ์—ฌ์ž

  • gyu: Class ํŽ˜์ด์ง€, ๋ฐฑ์—”๋“œ ์„œ๋ฒ„
  • hong: ๋งˆ์ดํŽ˜์ด์ง€ ๊ฐœ์„ , Chart.js ํ†ตํ•ฉ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ™•์žฅ

๐Ÿ“„ ๋ผ์ด์„ ์Šค

์ด ํ”„๋กœ์ ํŠธ๋Š” ๊ต์œก ๋ชฉ์ ์œผ๋กœ ์ œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages