commit 111590104e30e32c3b98e3363e6845b9f8bb7622 Author: AWESOMEDEV Date: Thu Jul 16 08:18:12 2026 +0900 feat: 수습 학습 플랫폼 최초 구축 (React + Spring Boot + PostgreSQL) - 로그인/회원가입/아이디찾기/비밀번호재설정 (세션 기반) - 대시보드(주차별 진도 체크리스트), 학습 문서 뷰어(17종), 과제 제출/멘토 피드백 - 코딩 기초·개발 환경 설치 가이드 학습 페이지 - 전 소스 한국어 학습 주석 — 수습생 교육용 저장소 - Docker 배포 구성 (Caddy + Spring Boot + PG + Gitea) Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0e6703 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# 빌드 결과물 +backend/target/ +frontend/dist/ +frontend/node_modules/ + +# 비밀값 — 절대 커밋 금지 +deploy/.env +*.pem + +# IDE·OS +.idea/ +.vscode/ +*.iml +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..96cbc03 --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +# 미림 앱 (mirim-app) — 어썸데브 수습 학습 플랫폼 + +> **이 저장소는 여러분(수습생)의 것입니다.** +> 미림마이스터고 수습생 4명이 직접 보고, 배우고, 고치는 학습용 웹 앱입니다. +> 처음에는 문서를 읽는 곳이지만, 5~6주차부터는 여러분이 직접 기능을 붙이고 버그를 고치는 실전 연습장이 됩니다. +> 코드를 읽는 법이 궁금하면 [STUDY.md](./STUDY.md)부터 열어 보세요. + +## 무엇을 하는 앱인가? + +- 수습 기간(6주) 동안 필요한 **학습 문서**(가이드·커리큘럼·수업자료·과제)를 한곳에서 봅니다. +- 주차별 **체크리스트**로 오늘 할 일을 확인하고 체크합니다. +- **과제를 제출**하고, 멘토에게 **피드백**을 받습니다. +- 멘토는 전체 제출물을 한 화면에서 검토합니다. + +## 기술 스택 + +| 영역 | 기술 | 비고 | +|---|---|---| +| 프론트엔드 | React + Vite | `frontend/` — 브라우저에서 돌아가는 화면 | +| 백엔드 | Spring Boot 3 + Java 21 | `backend/` — API 서버 | +| 데이터베이스 | PostgreSQL 17 | Docker 컨테이너로 실행 | +| 인프라 | Docker Compose | 로컬에서 DB를 띄우는 용도 | + +## 폴더 구조 + +``` +mirim-app/ +├── README.md ← 지금 읽는 파일 (프로젝트 소개·실행법) +├── STUDY.md ← 수습생용 "이 코드로 배우는 법" +├── docker-compose.yml ← PostgreSQL을 띄우는 설정 +├── backend/ ← Spring Boot API 서버 +│ ├── pom.xml ← 자바 의존성(라이브러리) 목록 +│ └── src/main/ +│ ├── java/dev/awesomedev/mirim/ +│ │ ├── MirimApplication.java ← 앱 시작점 +│ │ ├── domain/ ← 엔티티(DB 테이블과 짝이 되는 클래스) +│ │ ├── repository/ ← DB를 읽고 쓰는 계층 +│ │ ├── service/ ← 비즈니스 로직(규칙) 계층 +│ │ ├── controller/ ← HTTP 요청을 받는 계층 +│ │ └── config/ ← 보안·시드 등 설정 +│ └── resources/ +│ ├── application.yml ← 서버 설정(DB 접속 정보 등) +│ └── seed/ ← 초기 데이터 JSON (문서·체크리스트·과제) +└── frontend/ ← React 화면 + ├── package.json ← JS 의존성 목록 + ├── index.html ← 브라우저가 처음 여는 파일 + ├── public/docs/ ← 학습 문서 HTML 17개 (앱이 iframe으로 보여줌) + └── src/ + ├── pages/ ← 화면 단위 컴포넌트 (로그인, 대시보드, 문서, 과제…) + ├── components/ ← 여러 화면에서 재사용하는 조각 컴포넌트 + └── api/ ← 백엔드 API를 부르는 함수 모음 +``` + +## 로컬 실행법 (3단계) + +### 0. 준비물 +- Docker Desktop +- Java 21 (JDK) +- Node.js 20 이상 + +### 1. 데이터베이스 켜기 +```bash +# 프로젝트 루트(mirim-app/)에서 +docker compose up -d +``` +PostgreSQL이 컨테이너로 뜹니다. `docker ps`로 확인하세요. + +### 2. 백엔드 켜기 +```bash +cd backend +./mvnw spring-boot:run # mvnw가 안 되면: mvn spring-boot:run +``` +`http://localhost:8080` 에서 API 서버가 돌아갑니다. 첫 실행 시 시드 데이터(문서·체크리스트·과제·계정)가 자동으로 들어갑니다. + +### 3. 프론트엔드 켜기 +```bash +cd frontend +npm install # 최초 1회 +npm run dev +``` +브라우저에서 **http://localhost:5173** 을 열면 로그인 화면이 보입니다. + +## 초기 계정 + +| 아이디 | 비밀번호 | 역할 | +|---|---|---| +| student1 ~ student4 | `mirim2026!` | 수습생 (STUDENT) | +| mentor1 | `mirim2026!` | 멘토 (MENTOR) | + +> ⚠️ **실서비스로 쓰기 전에는 반드시 비밀번호를 변경하세요.** 이 계정들은 로컬 학습용입니다. + +## 환경변수 + +기본값이 로컬 개발에 맞춰져 있어서, 그냥 실행하면 대부분 그대로 동작합니다. + +| 변수 | 기본값 | 설명 | +|---|---|---| +| `DB_HOST` | `localhost` | PostgreSQL 호스트 | +| `DB_PORT` | `5432` | PostgreSQL 포트 | +| `DB_NAME` | `mirim` | 데이터베이스 이름 | +| `DB_USER` | `mirim` | DB 사용자 | +| `DB_PASSWORD` | (docker-compose.yml 참고) | DB 비밀번호 | +| `SERVER_PORT` | `8080` | 백엔드 포트 | + +> 🔒 **시크릿(비밀번호·키)은 절대 커밋하지 마세요.** +> 로컬 기본값은 학습 편의를 위한 것이고, 실제 배포용 비밀번호·키는 `.env` 같은 별도 파일에 두고 `.gitignore`에 등록합니다. "일단 코드에 박아 두고 나중에 지우지"는 통하지 않습니다 — git 이력에 영원히 남습니다. + +## 자주 겪는 문제 + +- **백엔드가 DB에 못 붙어요** → `docker compose up -d`를 먼저 했는지, `docker ps`에 postgres가 있는지 확인. +- **5173 화면은 뜨는데 로그인이 안 돼요** → 백엔드(8080)가 켜져 있는지 확인. 프론트는 API를 백엔드로 보냅니다. +- **포트가 이미 사용 중이래요** → 이전에 켜 둔 서버가 남아 있을 수 있어요. 터미널을 확인하고 종료하세요. + +--- + +AWESOMEDEV (어썸데브) · 수습 온보딩 프로그램 학습용 저장소 diff --git a/STUDY.md b/STUDY.md new file mode 100644 index 0000000..a57d770 --- /dev/null +++ b/STUDY.md @@ -0,0 +1,108 @@ +# STUDY.md — 이 코드로 배우는 법 (수습생용) + +> 이 문서는 "코드를 어디서부터 어떻게 읽어야 하는지" 알려주는 지도입니다. +> 처음부터 다 이해하려고 하지 마세요. 하나의 요청이 어디를 거쳐 가는지 **한 줄기**만 따라가면, 나머지는 전부 같은 패턴의 반복입니다. + +--- + +## 1. 요청 하나의 여행 — 로그인 버튼을 눌렀을 때 무슨 일이 일어나나 + +2주차 수업(웹의 동작 원리, 요청과 응답)에서 배운 내용을 실제 코드로 확인하는 코스입니다. +아래 순서대로 파일을 **직접 열어서** 읽어 보세요. 각 단계마다 "여기서 다음 단계로 어떻게 넘어가는지"를 찾는 게 목표입니다. + +``` +[브라우저] [서버] [DB] +LoginPage.jsx → api/client.js → (HTTP) → AuthController → AuthService → UserRepository → PostgreSQL +``` + +### ① `frontend/src/pages/LoginPage.jsx` — 출발점 +- 로그인 버튼을 누르면 실행되는 함수(예: `handleSubmit`)를 찾으세요. +- 여기서 화면은 "아이디·비밀번호를 모아서 API 함수를 부르는 것"까지만 합니다. 검증 규칙 같은 진짜 판단은 서버가 합니다. +- **관찰 포인트**: 입력값이 어떻게 state로 관리되는지, 실패했을 때 에러 메시지를 어떻게 보여주는지. + +### ② `frontend/src/api/client.js` — 프론트의 우체국 +- 모든 페이지가 이 파일을 거쳐 서버와 통신합니다. `fetch`로 `POST /api/auth/login`을 보내는 부분을 찾으세요. +- **관찰 포인트**: 왜 페이지마다 fetch를 직접 쓰지 않고 한 파일에 모았을까? (주소가 바뀌면 한 곳만 고치면 됨, 에러 처리를 한 번만 작성하면 됨.) +- 여기서 브라우저를 떠나 HTTP 요청이 네트워크를 타고 백엔드(8080)로 갑니다. 개발자도구(F12) → Network 탭에서 이 요청을 직접 눈으로 확인해 보세요. + +### ③ `backend/.../controller/AuthController.java` — 서버의 현관문 +- `@PostMapping("/api/auth/login")`이 붙은 메서드를 찾으세요. +- 컨트롤러는 얇습니다: JSON을 받아서 → 서비스에 넘기고 → 결과를 JSON으로 돌려줄 뿐. 판단하지 않습니다. +- **관찰 포인트**: 요청 JSON이 어떻게 자바 객체로 변하는지(`@RequestBody`). + +### ④ `backend/.../service/AuthService.java` — 진짜 일하는 곳 +- "이 아이디의 사용자가 있나? 비밀번호가 맞나? 틀리면 어떻게 하나?" — 규칙(비즈니스 로직)은 전부 여기 있습니다. +- **관찰 포인트**: 비밀번호를 평문 비교하지 않고 해시로 비교하는 부분. 왜 DB에 비밀번호 원문을 저장하면 안 되는지 생각해 보세요. + +### ⑤ `backend/.../repository/UserRepository.java` — DB로 가는 문 +- 파일을 열면 놀랄 만큼 짧습니다. `findByUsername` 같은 메서드 **선언만** 있고 구현이 없습니다. +- **학습 포인트**: Spring Data JPA가 메서드 이름을 읽고 SQL을 대신 만들어 줍니다. `findByUsername` → `SELECT * FROM users WHERE username = ?`. + +### ⑥ `backend/.../domain/User.java` — DB 테이블의 자바 버전 +- 엔티티 클래스 하나가 DB 테이블 하나와 짝을 이룹니다. 필드 하나가 컬럼 하나입니다. +- 여기까지 오면 여행 끝. 응답은 왔던 길을 **거꾸로** 타고 브라우저까지 돌아가고, LoginPage는 받은 사용자 정보로 화면을 바꿉니다. + +> ✅ **확인 과제**: 같은 방식으로 "체크리스트에 체크했을 때"의 여행 경로를 종이에 그려 보세요. +> 힌트: `POST /api/progress/{itemId}/toggle`에서 출발해서, ChecklistPage → client.js → ProgressController → ProgressService → ProgressRepository 순서로 찾으면 됩니다. + +--- + +## 2. 왜 이렇게 나눠 놨을까? + +### 프론트엔드: pages / components / api +- **pages/** — "화면 한 장" 단위. 주소(URL) 하나에 페이지 하나가 붙습니다. (로그인 페이지, 대시보드, 문서 목록…) +- **components/** — 여러 페이지에서 반복해서 쓰는 조각. 버튼, 카드, 헤더 같은 것들. 한 번 만들어 두면 복붙하지 않고 재사용합니다. +- **api/** — 서버와 통신하는 코드만 모은 곳. 화면 코드와 통신 코드를 섞지 않으면, "화면이 이상한 건지 서버 응답이 이상한 건지"를 나눠서 디버깅할 수 있습니다. + +한 문장으로: **pages는 조립, components는 부품, api는 배달.** + +### 백엔드: controller / service / repository / domain +- **controller/** — HTTP를 아는 유일한 계층. 요청을 받고 응답을 돌려주는 현관문. +- **service/** — 규칙과 판단. "멘토만 피드백을 쓸 수 있다", "같은 과제를 다시 제출하면 덮어쓴다" 같은 우리 서비스의 법. +- **repository/** — DB를 읽고 쓰는 일만. SQL 걱정은 여기(와 JPA)가 다 합니다. +- **domain/** — 데이터의 모양 정의. User, Document, Assignment 같은 "명사"들. + +왜 나누냐면: **한 파일에 다 쓰면 처음엔 빠르지만, 고칠 때 지옥이 됩니다.** "비밀번호 규칙을 바꿔라"라는 요청이 오면 service만 보면 되고, "응답 JSON에 필드를 추가해라"면 controller 근처만 보면 됩니다. 계층은 "어디를 고쳐야 하는지"를 알려주는 주소 체계입니다. + +--- + +## 3. 직접 해보기 — 미니 과제 5개 + +티켓 풀 문서(문서함의 실전 티켓 가이드)에 나오는 것과 같은 형식입니다. 순서대로 안 해도 되고, 막히면 멘토에게 "어디까지 해봤는지"와 함께 물어보세요. + +### 과제 1. 대시보드에 오늘 날짜 표시하기 (난이도 ★) +- **목표**: 대시보드 상단에 "2026년 7월 16일 (목)" 형태로 오늘 날짜를 보여준다. +- **건드릴 파일**: `frontend/src/pages/DashboardPage.jsx` +- **힌트**: 자바스크립트 `new Date()`와 `toLocaleDateString('ko-KR', {...})`을 검색해 보세요. 서버는 건드릴 필요가 없습니다. + +### 과제 2. 문서 카드에 즐겨찾기 버튼 달기 — 프론트만 (난이도 ★★) +- **목표**: 문서 목록의 각 카드에 ☆ 버튼을 달고, 누르면 ★로 바뀐다. 새로고침하면 사라져도 됩니다(서버 저장 없음). +- **건드릴 파일**: `frontend/src/pages/DocumentsPage.jsx` (카드가 별도 컴포넌트라면 `frontend/src/components/` 안의 카드 파일) +- **힌트**: `useState`로 즐겨찾기된 문서 id 배열을 들고, 버튼 클릭 시 배열에 넣거나 빼면 됩니다. 도전 과제: `localStorage`에 저장해서 새로고침을 버텨 보세요. + +### 과제 3. 체크리스트 항목 검색 (난이도 ★★) +- **목표**: 체크리스트 페이지에 검색창을 달아서, 입력한 글자가 포함된 항목만 보여준다. +- **건드릴 파일**: `frontend/src/pages/ChecklistPage.jsx` +- **힌트**: 검색어를 `useState`로 들고, 화면에 그리기 직전에 `items.filter(item => item.label.includes(검색어))`. 서버 API를 바꿀 필요가 없다는 것 자체가 학습 포인트입니다 — 이미 받아 온 데이터는 프론트에서 거를 수 있습니다. + +### 과제 4. API 응답에 필드 추가해 보기 (난이도 ★★★, 백엔드 첫 수정) +- **목표**: `GET /api/auth/me` 응답에 `createdAt`(가입 시각)을 추가하고, 화면 어딘가에 "함께한 지 N일째"를 표시한다. +- **건드릴 파일**: `backend/.../controller/AuthController.java`(응답 DTO에 필드 추가) → `frontend/src/api/client.js`는 그대로 → 표시할 페이지 컴포넌트 +- **힌트**: User 엔티티에는 `createdAt`이 이미 있습니다. 응답으로 내보내는 record/DTO에 한 필드만 추가하면 됩니다. 백엔드를 재시작한 뒤 브라우저 Network 탭에서 응답 JSON에 필드가 생겼는지 먼저 확인하고, 그다음 화면을 고치세요. **한 번에 한 층씩 확인하는 습관**이 이 과제의 진짜 목표입니다. + +### 과제 5. 빈 상태(empty state) 화면 넣기 (난이도 ★★) +- **목표**: 제출한 과제가 하나도 없을 때, 휑한 빈 목록 대신 "아직 제출한 과제가 없어요. 이번 주 과제부터 시작해 볼까요?" 같은 안내를 보여준다. +- **건드릴 파일**: `frontend/src/pages/SubmissionsPage.jsx` (내 제출물 페이지) +- **힌트**: `submissions.length === 0`일 때 다른 JSX를 그리면 됩니다. 좋은 서비스는 "데이터가 없을 때"를 항상 설계합니다 — 실무에서 정말 자주 하는 일입니다. + +--- + +## 4. 이 저장소는 여러분의 연습장입니다 + +5~6주차 실전 티켓 기간에는 이 앱 자체가 작업 대상이 됩니다. 지금 문서를 보고 과제를 제출하는 데 쓰는 바로 이 코드에, 여러분이 만든 기능이 붙습니다. + +- 위 미니 과제 5개는 실전 티켓의 축소판입니다. 티켓도 결국 "어느 파일을, 왜, 어떻게 고칠지"를 찾는 일입니다. +- 브랜치를 파서 마음껏 실험하세요. 망가뜨려도 `git checkout`으로 돌아올 수 있고, 망가뜨려 본 만큼 빨리 늡니다. +- 모르는 게 나오면 정상입니다. "이 파일이 왜 있는지 모르겠어요"는 좋은 질문이고, 멘토는 그런 질문을 기다립니다. + +여러분이 고친 코드가 다음 기수 수습생의 교재가 됩니다. 즐겁게 부수고, 정직하게 고치세요. 🚀 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..4c7e598 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,19 @@ +# 백엔드 도커 이미지 (멀티스테이지 빌드) +# 학습 포인트: 1단계에서 Maven으로 빌드하고, 2단계에는 실행에 필요한 JRE+JAR만 담아요. +# 이렇게 하면 최종 이미지가 작아지고(빌드 도구 미포함), 서버에 Java를 설치할 필요도 없어요. + +# ── 1단계: 빌드 ── +FROM maven:3.9-eclipse-temurin-21 AS build +WORKDIR /build +# pom.xml만 먼저 복사해 의존성을 캐시 — 코드만 바뀌면 라이브러리 다운로드를 다시 안 해요 +COPY pom.xml . +RUN mvn -q dependency:go-offline +COPY src ./src +RUN mvn -q -DskipTests package + +# ── 2단계: 실행 ── +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +COPY --from=build /build/target/mirim-backend-*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..03ec8af --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,69 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.4.2 + + + + dev.awesomedev + mirim-backend + 0.1.0 + mirim-backend + 어썸데브 수습 학습 플랫폼 API 서버 + + + 21 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.postgresql + postgresql + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/backend/src/main/java/dev/awesomedev/mirim/MirimApplication.java b/backend/src/main/java/dev/awesomedev/mirim/MirimApplication.java new file mode 100644 index 0000000..ec08458 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/MirimApplication.java @@ -0,0 +1,23 @@ +package dev.awesomedev.mirim; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 어썸데브 수습 학습 플랫폼 — 백엔드 시작점. + * 학습 포인트: 스프링부트 앱은 이 main 메서드 하나로 서버 전체가 뜹니다. + * + * @SpringBootApplication 한 줄이 하는 일 세 가지: + * 1) @Configuration — 이 클래스 자체가 설정 클래스가 된다. + * 2) @EnableAutoConfiguration — 클래스패스를 보고 필요한 설정을 자동으로 잡는다. + * (spring-boot-starter-web이 있으니 내장 톰캣을 띄우고, JPA가 있으니 DB 연결을 만든다) + * 3) @ComponentScan — 이 패키지(dev.awesomedev.mirim) 아래의 @Service, @RestController, + * @Component 등을 전부 찾아 스프링 빈으로 등록한다. + * 그래서 컨트롤러·서비스를 어디에도 "등록"하는 코드 없이 애노테이션만 붙이면 동작한다. + */ +@SpringBootApplication +public class MirimApplication { + public static void main(String[] args) { + SpringApplication.run(MirimApplication.class, args); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/config/SecurityConfig.java b/backend/src/main/java/dev/awesomedev/mirim/config/SecurityConfig.java new file mode 100644 index 0000000..7830f48 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/config/SecurityConfig.java @@ -0,0 +1,93 @@ +package dev.awesomedev.mirim.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextRepository; + +import java.nio.charset.StandardCharsets; + +/** + * 이 파일이 하는 일: + * Spring Security 설정 — "어떤 URL에 어떤 권한이 필요한가"를 한곳에 선언한다. + * + * 인증 방식: 세션 쿠키. + * 로그인 성공 시 서버가 세션을 만들고 브라우저에 JSESSIONID 쿠키를 준다. + * 이후 요청마다 브라우저가 쿠키를 보내면 Spring Security가 세션에서 인증 정보를 복원한다. + * + * 학습 포인트: 세션 vs 토큰(JWT) — + * 세션은 "로그인 상태"를 서버 메모리에 두고 브라우저엔 열쇠(세션 ID)만 준다. + * 서버가 상태를 쥐고 있으므로 로그아웃 즉시 무효화가 쉽고 구현이 단순하다. + * 대신 서버가 여러 대로 늘어나면 세션을 공유할 방법(Redis 등)이 필요해진다. + * 서버 1대짜리 사내 학습 플랫폼에는 세션 방식이 가장 알맞다. + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + /** + * 학습 포인트: BCrypt가 뭔가? — + * 1) 비밀번호 전용 단방향 해시 함수다. 해시에서 원문을 되돌리는 것은 불가능하다. + * 2) 일부러 느리게 설계되어 있어(기본 2^10회 반복) 해커가 초당 수십억 번씩 + * 대입해 보는 무차별 대입 공격의 비용을 크게 올린다. SHA-256이 부적합한 이유다. + * 3) 같은 비밀번호라도 매번 다른 해시를 만든다(해시 문자열 안에 무작위 salt가 포함). + * 그래서 해시끼리 == 비교는 불가능하고 반드시 matches()로 검증해야 한다. + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * 인증 정보를 HTTP 세션에 저장/복원하는 저장소. + * AuthController가 로그인 성공 시 이 저장소로 SecurityContext를 세션에 넣는다. + */ + @Bean + public SecurityContextRepository securityContextRepository() { + return new HttpSessionSecurityContextRepository(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http, + SecurityContextRepository securityContextRepository) throws Exception { + http + // 학습 포인트: CSRF 보호를 끈 이유 — CSRF 토큰을 프론트와 주고받는 절차가 + // 학습 초기엔 큰 진입장벽이라 이 프로젝트에선 단순화를 위해 비활성화했다. + // 실서비스에서 세션 쿠키 인증을 쓴다면 CSRF 보호는 반드시 켜야 한다. + .csrf(csrf -> csrf.disable()) + + // URL별 접근 규칙. 위에서부터 순서대로 매칭되므로 구체적인 규칙을 먼저 쓴다. + .authorizeHttpRequests(auth -> auth + // 학습 포인트: 이 네 API는 "아직 로그인하지 못한 사람"이 쓰는 기능이라 + // permitAll이어야 한다. 로그인해야만 가입/비밀번호 찾기가 가능하다면 모순이다. + .requestMatchers("/api/auth/login").permitAll() // 로그인 + .requestMatchers("/api/auth/signup").permitAll() // 회원가입 + .requestMatchers("/api/auth/find-id").permitAll() // 아이디 찾기 + .requestMatchers("/api/auth/reset-password").permitAll() // 비밀번호 재설정 + .requestMatchers("/api/mentor/**").hasRole("MENTOR") // 멘토 전용 + .requestMatchers("/api/**").authenticated() // 나머지 API는 로그인 필요 + .anyRequest().permitAll()) // 그 외(정적 파일 등)는 공개 + + // 로그인 안 한 요청이 보호된 API를 부르면: 로그인 페이지로 리다이렉트하는 대신 + // JSON 401을 돌려준다. (프론트가 SPA라 리다이렉트는 처리하기 어렵다) + .exceptionHandling(handling -> handling + .authenticationEntryPoint((request, response, authException) -> { + response.setStatus(401); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.getWriter().write("{\"error\":\"로그인이 필요합니다.\"}"); + })) + + // AuthController가 저장한 세션의 인증 정보를 매 요청마다 읽어오게 한다. + .securityContext(context -> context + .securityContextRepository(securityContextRepository)); + + return http.build(); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/config/SeedDataLoader.java b/backend/src/main/java/dev/awesomedev/mirim/config/SeedDataLoader.java new file mode 100644 index 0000000..fb1b947 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/config/SeedDataLoader.java @@ -0,0 +1,169 @@ +package dev.awesomedev.mirim.config; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.awesomedev.mirim.domain.Assignment; +import dev.awesomedev.mirim.domain.ChecklistItem; +import dev.awesomedev.mirim.domain.Document; +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.repository.AssignmentRepository; +import dev.awesomedev.mirim.repository.ChecklistItemRepository; +import dev.awesomedev.mirim.repository.DocumentRepository; +import dev.awesomedev.mirim.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.util.List; + +/** + * 이 파일이 하는 일: + * 앱이 시작될 때 한 번 실행되어, 테이블이 비어 있으면 초기 데이터를 넣는다. + * - 사용자 5명 (수습생 4명 + 멘토 1명) + * - classpath의 seed/documents.json, seed/checklist.json, seed/assignments.json + * + * 학습 포인트: ApplicationRunner는 스프링 컨테이너가 완전히 뜬 뒤 run()을 호출해 주는 + * 인터페이스다. "서버 시작 직후 한 번 할 일"을 넣기에 알맞다. + * count() == 0 조건 덕분에 서버를 여러 번 재시작해도 데이터가 중복으로 쌓이지 않는다. + */ +@Component +public class SeedDataLoader implements ApplicationRunner { + + private static final Logger log = LoggerFactory.getLogger(SeedDataLoader.class); + + private final UserRepository userRepository; + private final DocumentRepository documentRepository; + private final ChecklistItemRepository checklistItemRepository; + private final AssignmentRepository assignmentRepository; + private final PasswordEncoder passwordEncoder; + private final ObjectMapper objectMapper; + + public SeedDataLoader(UserRepository userRepository, + DocumentRepository documentRepository, + ChecklistItemRepository checklistItemRepository, + AssignmentRepository assignmentRepository, + PasswordEncoder passwordEncoder, + ObjectMapper objectMapper) { + this.userRepository = userRepository; + this.documentRepository = documentRepository; + this.checklistItemRepository = checklistItemRepository; + this.assignmentRepository = assignmentRepository; + this.passwordEncoder = passwordEncoder; + this.objectMapper = objectMapper; + } + + @Override + public void run(ApplicationArguments args) { + seedUsers(); + seedDocuments(); + seedChecklist(); + seedAssignments(); + } + + /** + * 사용자 5명 생성. + * 초기 비밀번호는 전원 "mirim2026!" — 첫 로그인 후 변경을 권장한다. + * 학습 포인트: DB엔 BCrypt 해시만 저장된다. 원문 비밀번호는 어디에도 남지 않는다. + */ + private void seedUsers() { + if (userRepository.count() > 0) { + return; // 이미 사용자가 있으면 건너뛴다 + } + // 학습 포인트: encode()는 일부러 느린 연산(BCrypt)이라 한 번만 호출해서 재사용한다. + // 다섯 명이 같은 해시를 공유해도 문제없다 — 어차피 같은 초기 비밀번호이고, + // 각자 비밀번호를 바꾸는 순간 서로 다른 해시가 된다. + String initialPasswordHash = passwordEncoder.encode("mirim2026!"); + + userRepository.save(new User("student1", initialPasswordHash, "수습생1", "STUDENT", "DEV")); + userRepository.save(new User("student2", initialPasswordHash, "수습생2", "STUDENT", "DEV")); + userRepository.save(new User("student3", initialPasswordHash, "수습생3", "STUDENT", "DEV")); + userRepository.save(new User("student4", initialPasswordHash, "수습생4", "STUDENT", "DESIGN")); + userRepository.save(new User("mentor1", initialPasswordHash, "멘토", "MENTOR", "NONE")); + + log.info("초기 사용자 5명을 생성했습니다 (student1~4, mentor1)."); + } + + private void seedDocuments() { + if (documentRepository.count() > 0) { + return; + } + List seeds = readSeedFile("seed/documents.json", new TypeReference<>() {}); + if (seeds == null) { + return; + } + for (DocumentSeed seed : seeds) { + documentRepository.save(new Document(seed.slug(), seed.title(), seed.category(), + seed.week(), seed.audience(), seed.filePath(), seed.sortOrder())); + } + log.info("문서 시드 {}건을 저장했습니다.", seeds.size()); + } + + private void seedChecklist() { + if (checklistItemRepository.count() > 0) { + return; + } + List seeds = readSeedFile("seed/checklist.json", new TypeReference<>() {}); + if (seeds == null) { + return; + } + for (ChecklistSeed seed : seeds) { + checklistItemRepository.save(new ChecklistItem(seed.week(), seed.day(), + seed.label(), seed.sortOrder())); + } + log.info("체크리스트 시드 {}건을 저장했습니다.", seeds.size()); + } + + private void seedAssignments() { + if (assignmentRepository.count() > 0) { + return; + } + List seeds = readSeedFile("seed/assignments.json", new TypeReference<>() {}); + if (seeds == null) { + return; + } + for (AssignmentSeed seed : seeds) { + assignmentRepository.save(new Assignment(seed.week(), seed.day(), seed.kind(), + seed.title(), seed.summary())); + } + log.info("과제 시드 {}건을 저장했습니다.", seeds.size()); + } + + /** + * classpath에서 시드 JSON 파일을 읽는다. + * 학습 포인트: 파일이 없어도 앱이 죽지 않고 경고 로그만 남기고 넘어간다. + * 시드 파일을 만드는 작업과 이 코드를 만드는 작업이 서로를 기다리지 않아도 되게 하기 위해서다. + */ + private List readSeedFile(String classpathLocation, TypeReference> type) { + ClassPathResource resource = new ClassPathResource(classpathLocation); + if (!resource.exists()) { + log.warn("시드 파일이 없어 건너뜁니다: {}", classpathLocation); + return null; + } + try (InputStream inputStream = resource.getInputStream()) { + return objectMapper.readValue(inputStream, type); + } catch (Exception e) { + log.error("시드 파일을 읽는 중 오류가 발생해 건너뜁니다: {}", classpathLocation, e); + return null; + } + } + + // --- 시드 JSON의 한 줄을 담는 record들 (JSON 필드명과 record 컴포넌트명이 그대로 대응된다) --- + + /** documents.json 한 건 */ + record DocumentSeed(String slug, String title, String category, Integer week, + String audience, String filePath, Integer sortOrder) { + } + + /** checklist.json 한 건 */ + record ChecklistSeed(Integer week, Integer day, String label, Integer sortOrder) { + } + + /** assignments.json 한 건 */ + record AssignmentSeed(Integer week, Integer day, String kind, String title, String summary) { + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/domain/Assignment.java b/backend/src/main/java/dev/awesomedev/mirim/domain/Assignment.java new file mode 100644 index 0000000..4a6bf1c --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/domain/Assignment.java @@ -0,0 +1,80 @@ +package dev.awesomedev.mirim.domain; + +import jakarta.persistence.*; + +/** + * 이 파일이 하는 일: + * 주차·일차별 과제를 표현하는 JPA 엔티티다. + * 과제의 종류(kind)로 필수 과제(MAIN), 심화 과제(EXTRA), 디자인 과제(DESIGN)를 구분한다. + */ +@Entity +@Table(name = "assignments") +public class Assignment { + + /** + * 학습 포인트: GenerationType.IDENTITY — 기본키(id) 값을 자바가 아니라 + * DB의 자동 증가 기능(PostgreSQL의 identity 컬럼)이 만들게 한다. + * 그래서 save() 전에는 id가 null이고, INSERT가 실행된 뒤에야 값이 채워진다. + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 몇 주차 과제인지 */ + @Column(nullable = false) + private Integer week; + + /** 몇 일차 과제인지 */ + @Column(nullable = false) + private Integer day; + + /** 과제 종류: MAIN | EXTRA | DESIGN */ + @Column(nullable = false) + private String kind; + + /** 과제 제목 */ + @Column(nullable = false) + private String title; + + /** + * 과제 설명. + * 학습 포인트: 기본 VARCHAR(255)로는 긴 설명이 잘리므로 text 타입으로 지정한다. + */ + @Column(columnDefinition = "text") + private String summary; + + protected Assignment() { + } + + public Assignment(Integer week, Integer day, String kind, String title, String summary) { + this.week = week; + this.day = day; + this.kind = kind; + this.title = title; + this.summary = summary; + } + + public Long getId() { + return id; + } + + public Integer getWeek() { + return week; + } + + public Integer getDay() { + return day; + } + + public String getKind() { + return kind; + } + + public String getTitle() { + return title; + } + + public String getSummary() { + return summary; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/domain/ChecklistItem.java b/backend/src/main/java/dev/awesomedev/mirim/domain/ChecklistItem.java new file mode 100644 index 0000000..2ac7efd --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/domain/ChecklistItem.java @@ -0,0 +1,67 @@ +package dev.awesomedev.mirim.domain; + +import jakarta.persistence.*; + +/** + * 이 파일이 하는 일: + * 주차별 체크리스트의 "항목 하나"를 표현하는 JPA 엔티티다. + * 예: 1주차 1일차 "개발 환경 설치 완료". + * 누가 체크했는지는 여기 저장하지 않고 Progress 엔티티가 따로 기록한다. + */ +@Entity +@Table(name = "checklist_items") +public class ChecklistItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 몇 주차 항목인지 */ + @Column(nullable = false) + private Integer week; + + /** + * 몇 일차 항목인지. 주 전체에 걸친 항목이면 null. + * 학습 포인트: 그래서 int가 아닌 Integer를 쓴다 — 원시 타입 int는 null을 담을 수 없다. + * "값이 없을 수 있는" 컬럼은 반드시 래퍼 타입(Integer, Long)으로 선언해야 한다. + */ + private Integer day; + + /** 체크리스트에 표시되는 문구 */ + @Column(nullable = false) + private String label; + + /** 같은 주차 안에서의 정렬 순서 */ + @Column(nullable = false) + private Integer sortOrder; + + protected ChecklistItem() { + } + + public ChecklistItem(Integer week, Integer day, String label, Integer sortOrder) { + this.week = week; + this.day = day; + this.label = label; + this.sortOrder = sortOrder; + } + + public Long getId() { + return id; + } + + public Integer getWeek() { + return week; + } + + public Integer getDay() { + return day; + } + + public String getLabel() { + return label; + } + + public Integer getSortOrder() { + return sortOrder; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/domain/Document.java b/backend/src/main/java/dev/awesomedev/mirim/domain/Document.java new file mode 100644 index 0000000..19dc64b --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/domain/Document.java @@ -0,0 +1,99 @@ +package dev.awesomedev.mirim.domain; + +import jakarta.persistence.*; + +/** + * 이 파일이 하는 일: + * 학습 문서(가이드, 커리큘럼, 주차 계획, 채점 기준 등)의 메타데이터를 담는 JPA 엔티티다. + * 실제 문서 본문은 frontend/public/docs/ 아래 HTML 파일이고, + * 이 엔티티는 "어떤 문서가 어디에 있고 누가 볼 수 있는지"만 기록한다. + */ +@Entity +@Table(name = "documents") +public class Document { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * URL에 쓰이는 고유 식별 문자열 (예: "week1-plan"). + * 학습 포인트: 숫자 id 대신 slug를 URL에 쓰는 이유 — 주소만 봐도 무슨 문서인지 + * 알 수 있고, DB를 다시 만들어 id가 바뀌어도 링크가 깨지지 않는다. + * unique = true 덕분에 DB가 중복 slug를 거부한다(유니크 인덱스 생성). + */ + @Column(nullable = false, unique = true) + private String slug; + + /** 문서 제목 */ + @Column(nullable = false) + private String title; + + /** 분류: GUIDE | CURRICULUM | PLAN | RUBRIC | LESSON | ASSIGNMENT | TICKET */ + @Column(nullable = false) + private String category; + + /** 몇 주차 문서인지. 주차와 무관한 문서(전체 가이드 등)는 null. */ + private Integer week; + + /** + * 누구에게 보여줄 문서인지: STUDENT | MENTOR | ALL + * 학습 포인트: MENTOR 전용 문서(채점 기준 등)는 API 레벨에서 걸러서 수습생에게 노출하지 않는다. + */ + @Column(nullable = false) + private String audience; + + /** 프론트엔드에서 접근할 파일 경로 (예: "/docs/week1-plan.html") */ + @Column(nullable = false) + private String filePath; + + /** 목록에서 보여줄 정렬 순서 */ + @Column(nullable = false) + private Integer sortOrder; + + protected Document() { + } + + public Document(String slug, String title, String category, Integer week, + String audience, String filePath, Integer sortOrder) { + this.slug = slug; + this.title = title; + this.category = category; + this.week = week; + this.audience = audience; + this.filePath = filePath; + this.sortOrder = sortOrder; + } + + public Long getId() { + return id; + } + + public String getSlug() { + return slug; + } + + public String getTitle() { + return title; + } + + public String getCategory() { + return category; + } + + public Integer getWeek() { + return week; + } + + public String getAudience() { + return audience; + } + + public String getFilePath() { + return filePath; + } + + public Integer getSortOrder() { + return sortOrder; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/domain/Progress.java b/backend/src/main/java/dev/awesomedev/mirim/domain/Progress.java new file mode 100644 index 0000000..7a70caf --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/domain/Progress.java @@ -0,0 +1,74 @@ +package dev.awesomedev.mirim.domain; + +import jakarta.persistence.*; + +import java.time.Instant; + +/** + * 이 파일이 하는 일: + * "어떤 사용자가 어떤 체크리스트 항목을 체크했다"는 사실 하나를 기록하는 JPA 엔티티다. + * 행이 존재하면 체크된 것, 없으면 체크 안 된 것 — 체크 해제는 행 삭제로 표현한다. + * + * 학습 포인트: (user, item) 조합에 유니크 제약을 걸어 + * 같은 사용자가 같은 항목을 두 번 체크한 행이 생기는 것을 DB 차원에서 막는다. + */ +@Entity +@Table( + name = "progress", + uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "item_id"}) +) +public class Progress { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 체크한 사용자. + * + * 학습 포인트: @ManyToOne이 DB에서 뭐가 되나? — + * progress 테이블에 user_id 라는 숫자 컬럼(외래키, FK)이 생기고, + * 그 값이 users 테이블의 id를 가리킨다. 자바에선 객체 참조지만 DB에선 그냥 숫자다. + * + * fetch = LAZY(지연 로딩): Progress를 조회할 때 User를 즉시 JOIN해서 가져오지 않고, + * 실제로 getUser()의 내용을 쓰는 순간에 SELECT가 나간다. + * "체크한 항목 id 목록"처럼 User가 필요 없는 조회에서 불필요한 JOIN을 아끼기 위해서다. + */ + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id") + private User user; + + /** 체크된 체크리스트 항목. 위와 마찬가지로 DB에는 item_id 외래키 컬럼이 된다. */ + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "item_id") + private ChecklistItem item; + + /** 체크한 시각 */ + @Column(nullable = false) + private Instant checkedAt; + + protected Progress() { + } + + public Progress(User user, ChecklistItem item) { + this.user = user; + this.item = item; + this.checkedAt = Instant.now(); + } + + public Long getId() { + return id; + } + + public User getUser() { + return user; + } + + public ChecklistItem getItem() { + return item; + } + + public Instant getCheckedAt() { + return checkedAt; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/domain/Submission.java b/backend/src/main/java/dev/awesomedev/mirim/domain/Submission.java new file mode 100644 index 0000000..2726e6e --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/domain/Submission.java @@ -0,0 +1,115 @@ +package dev.awesomedev.mirim.domain; + +import jakarta.persistence.*; + +import java.time.Instant; + +/** + * 이 파일이 하는 일: + * 수습생이 과제에 대해 제출한 결과물 하나를 표현하는 JPA 엔티티다. + * 같은 과제를 다시 제출하면 새 행을 만들지 않고 기존 행을 갱신한다(서비스 계층에서 처리). + * 멘토가 피드백을 남기면 status가 SUBMITTED → REVIEWED로 바뀐다. + */ +@Entity +@Table(name = "submissions") +public class Submission { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 어떤 과제에 대한 제출인지. + * DB에서는 submissions 테이블에 assignment_id 외래키(FK) 컬럼이 생긴다. + * + * 학습 포인트: @ManyToOne의 fetch 기본값은 EAGER(즉시 로딩) — Submission을 읽을 때 + * Assignment도 함께 JOIN해서 가져온다. 제출물은 항상 과제 제목·학생 이름과 함께 + * 화면에 보여주므로 EAGER를 그대로 둔다. 목록이 커지면 fetch join으로 바꾸는 게 다음 단계다. + * (Progress 엔티티는 반대로 LAZY를 쓴다 — 두 파일을 비교해 보자.) + */ + @ManyToOne(optional = false) + @JoinColumn(name = "assignment_id") + private Assignment assignment; + + /** 제출한 수습생. DB에는 user_id 외래키 컬럼이 된다. */ + @ManyToOne(optional = false) + @JoinColumn(name = "user_id") + private User user; + + /** 제출 내용 (긴 글이 될 수 있어 text 타입) */ + @Column(columnDefinition = "text", nullable = false) + private String content; + + /** 결과물 링크 (깃 저장소, 배포 URL 등). 없으면 null. */ + private String link; + + /** 제출(또는 재제출) 시각 */ + @Column(nullable = false) + private Instant submittedAt; + + /** 멘토의 피드백. 아직 리뷰 전이면 null. */ + @Column(columnDefinition = "text") + private String feedback; + + /** 상태: SUBMITTED(제출됨) | REVIEWED(피드백 완료) */ + @Column(nullable = false) + private String status; + + protected Submission() { + } + + public Submission(Assignment assignment, User user, String content, String link) { + this.assignment = assignment; + this.user = user; + this.content = content; + this.link = link; + this.submittedAt = Instant.now(); + this.status = "SUBMITTED"; + } + + /** 재제출: 내용과 링크를 바꾸고 제출 시각을 갱신, 상태를 다시 SUBMITTED로 되돌린다. */ + public void resubmit(String content, String link) { + this.content = content; + this.link = link; + this.submittedAt = Instant.now(); + this.status = "SUBMITTED"; + } + + /** 멘토 피드백 등록: 피드백을 저장하고 상태를 REVIEWED로 바꾼다. */ + public void review(String feedback) { + this.feedback = feedback; + this.status = "REVIEWED"; + } + + public Long getId() { + return id; + } + + public Assignment getAssignment() { + return assignment; + } + + public User getUser() { + return user; + } + + public String getContent() { + return content; + } + + public String getLink() { + return link; + } + + public Instant getSubmittedAt() { + return submittedAt; + } + + public String getFeedback() { + return feedback; + } + + public String getStatus() { + return status; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/domain/User.java b/backend/src/main/java/dev/awesomedev/mirim/domain/User.java new file mode 100644 index 0000000..9eda75d --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/domain/User.java @@ -0,0 +1,99 @@ +package dev.awesomedev.mirim.domain; + +import jakarta.persistence.*; + +import java.time.Instant; + +/** + * 이 파일이 하는 일: + * 미림 수습 학습 플랫폼의 "사용자" 테이블을 표현하는 JPA 엔티티다. + * 수습생(STUDENT)과 멘토(MENTOR) 모두 이 하나의 테이블에 저장된다. + */ +@Entity +@Table(name = "users") // 학습 포인트: "user"는 일부 DB(H2, Postgres)에서 예약어라 테이블명을 "users"로 피해간다. +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 로그인 아이디. 중복될 수 없다. */ + @Column(nullable = false, unique = true) + private String username; + + /** + * 학습 포인트: 비밀번호는 절대 원문으로 저장하지 않는다. + * BCrypt로 해시된 문자열만 저장하고, 로그인 시 PasswordEncoder.matches()로 비교한다. + */ + @Column(nullable = false) + private String passwordHash; + + /** 화면에 보여줄 이름 (예: 수습생1, 멘토) */ + @Column(nullable = false) + private String name; + + /** 역할: "STUDENT" 또는 "MENTOR" */ + @Column(nullable = false) + private String role; + + /** 트랙: "DEV" | "DESIGN" | "NONE" (멘토는 NONE) */ + @Column(nullable = false) + private String track; + + /** 계정 생성 시각 */ + @Column(nullable = false) + private Instant createdAt; + + /** 학습 포인트: JPA는 리플렉션으로 객체를 만들기 때문에 기본 생성자가 반드시 필요하다. */ + protected User() { + } + + public User(String username, String passwordHash, String name, String role, String track) { + this.username = username; + this.passwordHash = passwordHash; + this.name = name; + this.role = role; + this.track = track; + this.createdAt = Instant.now(); + } + + /** + * 비밀번호 변경 (비밀번호 재설정 기능에서 사용). + * + * 학습 포인트: 무분별한 setter를 다 열어두는 대신, "비밀번호를 바꾼다"는 + * 의미가 드러나는 메서드 하나만 연다. username이나 role을 바꾸는 setter가 + * 아예 없으면, 실수로라도 그런 코드를 쓸 수 없다(엔티티가 스스로 규칙을 지킨다). + * 여기에는 반드시 "이미 해시된 값"만 들어와야 한다 — 인코딩은 서비스 계층의 책임이다. + */ + public void changePassword(String newPasswordHash) { + this.passwordHash = newPasswordHash; + } + + public Long getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getPasswordHash() { + return passwordHash; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + public String getTrack() { + return track; + } + + public Instant getCreatedAt() { + return createdAt; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/repository/AssignmentRepository.java b/backend/src/main/java/dev/awesomedev/mirim/repository/AssignmentRepository.java new file mode 100644 index 0000000..8184643 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/repository/AssignmentRepository.java @@ -0,0 +1,16 @@ +package dev.awesomedev.mirim.repository; + +import dev.awesomedev.mirim.domain.Assignment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * Assignment(과제) 엔티티의 DB 접근을 담당하는 Spring Data JPA 리포지토리다. + */ +public interface AssignmentRepository extends JpaRepository { + + /** 특정 주차의 과제를 일차(day) 오름차순으로 조회 */ + List findByWeekOrderByDayAsc(Integer week); +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/repository/ChecklistItemRepository.java b/backend/src/main/java/dev/awesomedev/mirim/repository/ChecklistItemRepository.java new file mode 100644 index 0000000..19b7c35 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/repository/ChecklistItemRepository.java @@ -0,0 +1,16 @@ +package dev.awesomedev.mirim.repository; + +import dev.awesomedev.mirim.domain.ChecklistItem; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * ChecklistItem 엔티티의 DB 접근을 담당하는 Spring Data JPA 리포지토리다. + */ +public interface ChecklistItemRepository extends JpaRepository { + + /** 특정 주차의 체크리스트 항목을 정렬 순서대로 조회 */ + List findByWeekOrderBySortOrderAsc(Integer week); +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/repository/DocumentRepository.java b/backend/src/main/java/dev/awesomedev/mirim/repository/DocumentRepository.java new file mode 100644 index 0000000..d093290 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/repository/DocumentRepository.java @@ -0,0 +1,16 @@ +package dev.awesomedev.mirim.repository; + +import dev.awesomedev.mirim.domain.Document; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * Document 엔티티의 DB 접근을 담당하는 Spring Data JPA 리포지토리다. + */ +public interface DocumentRepository extends JpaRepository { + + /** 문서 목록을 정렬 순서(sortOrder) 오름차순으로 전부 조회 */ + List findAllByOrderBySortOrderAsc(); +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/repository/ProgressRepository.java b/backend/src/main/java/dev/awesomedev/mirim/repository/ProgressRepository.java new file mode 100644 index 0000000..968c3f5 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/repository/ProgressRepository.java @@ -0,0 +1,22 @@ +package dev.awesomedev.mirim.repository; + +import dev.awesomedev.mirim.domain.ChecklistItem; +import dev.awesomedev.mirim.domain.Progress; +import dev.awesomedev.mirim.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +/** + * 이 파일이 하는 일: + * Progress(체크 기록) 엔티티의 DB 접근을 담당하는 Spring Data JPA 리포지토리다. + */ +public interface ProgressRepository extends JpaRepository { + + /** 특정 사용자가 특정 항목을 체크했는지 조회. 있으면 체크된 것. */ + Optional findByUserAndItem(User user, ChecklistItem item); + + /** 특정 사용자의 전체 체크 기록 조회 */ + List findByUser(User user); +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/repository/SubmissionRepository.java b/backend/src/main/java/dev/awesomedev/mirim/repository/SubmissionRepository.java new file mode 100644 index 0000000..5159f65 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/repository/SubmissionRepository.java @@ -0,0 +1,29 @@ +package dev.awesomedev.mirim.repository; + +import dev.awesomedev.mirim.domain.Assignment; +import dev.awesomedev.mirim.domain.Submission; +import dev.awesomedev.mirim.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +/** + * 이 파일이 하는 일: + * Submission(과제 제출물) 엔티티의 DB 접근을 담당하는 Spring Data JPA 리포지토리다. + */ +public interface SubmissionRepository extends JpaRepository { + + /** 특정 사용자의 제출물을 최신 제출 순으로 조회 (내 제출물 화면용) */ + List findByUserOrderBySubmittedAtDesc(User user); + + /** + * 특정 과제에 대한 특정 사용자의 제출물 조회. + * 학습 포인트: 재제출 정책(같은 과제엔 제출물 1개, 다시 내면 갱신) 덕분에 + * 결과가 최대 1건이라 Optional로 받을 수 있다. + */ + Optional findByAssignmentAndUser(Assignment assignment, User user); + + /** 전체 제출물을 최신 제출 순으로 조회 (멘토 리뷰 화면용) */ + List findAllByOrderBySubmittedAtDesc(); +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/repository/UserRepository.java b/backend/src/main/java/dev/awesomedev/mirim/repository/UserRepository.java new file mode 100644 index 0000000..fed187c --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/repository/UserRepository.java @@ -0,0 +1,34 @@ +package dev.awesomedev.mirim.repository; + +import dev.awesomedev.mirim.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +/** + * 이 파일이 하는 일: + * User 엔티티의 DB 접근을 담당하는 Spring Data JPA 리포지토리다. + * + * 학습 포인트: 인터페이스만 선언하면 Spring Data JPA가 구현체를 자동으로 만들어 준다. + * findByUsername처럼 메서드 이름 규칙만 지키면 SQL을 직접 쓰지 않아도 된다. + * (예: existsByUsername → SELECT EXISTS(... WHERE username = ?) 같은 쿼리가 자동 생성된다) + */ +public interface UserRepository extends JpaRepository { + + /** 로그인 아이디로 사용자 조회. 없을 수 있으므로 Optional로 감싼다. */ + Optional findByUsername(String username); + + /** + * 아이디 중복 확인 (회원가입용). + * 학습 포인트: findByUsername(...).isPresent()로도 되지만, exists 쿼리는 + * 행 전체를 가져오지 않고 "있는지 없는지"만 물어서 DB 입장에서 더 가볍다. + */ + boolean existsByUsername(String username); + + /** 이름으로 사용자 목록 조회 (아이디 찾기용). 동명이인이 있을 수 있어 List로 받는다. */ + List findByName(String name); + + /** 아이디와 이름이 모두 일치하는 사용자 조회 (비밀번호 재설정의 본인 확인용). */ + Optional findByUsernameAndName(String username, String name); +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/service/AuthService.java b/backend/src/main/java/dev/awesomedev/mirim/service/AuthService.java new file mode 100644 index 0000000..89f5376 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/service/AuthService.java @@ -0,0 +1,133 @@ +package dev.awesomedev.mirim.service; + +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.repository.UserRepository; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.util.ArrayList; +import java.util.List; + +/** + * 이 파일이 하는 일: + * 로그인 검증(아이디+비밀번호 확인), 회원가입, 아이디 찾기, 비밀번호 재설정, + * 그리고 "현재 로그인한 사용자 찾기"를 담당하는 서비스다. + */ +@Service +public class AuthService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + public AuthService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + /** + * 아이디와 비밀번호를 검증한다. 틀리면 401을 던진다. + * + * 학습 포인트: "아이디가 없음"과 "비밀번호가 틀림"을 구분해서 알려주지 않는다. + * 구분해서 알려주면 공격자가 어떤 아이디가 존재하는지 알아낼 수 있기 때문이다. + */ + public User authenticate(String username, String rawPassword) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.UNAUTHORIZED, "아이디 또는 비밀번호가 올바르지 않습니다.")); + + // 학습 포인트: BCrypt 해시는 복호화가 불가능하다. + // 원문을 다시 해시해서 비교하는 게 아니라 matches()가 해시 내부의 salt를 이용해 검증한다. + if (!passwordEncoder.matches(rawPassword, user.getPasswordHash())) { + throw new ResponseStatusException( + HttpStatus.UNAUTHORIZED, "아이디 또는 비밀번호가 올바르지 않습니다."); + } + return user; + } + + /** + * 회원가입: 새 수습생 계정을 만든다. 아이디가 이미 사용 중이면 409 Conflict를 던진다. + * + * role은 항상 "STUDENT"로 고정한다. + * 이유: 역할을 클라이언트 입력으로 받으면, 프론트 화면을 우회해서 API를 직접 호출하는 + * 것만으로 누구나 멘토 권한(전체 제출물 열람, 피드백 작성)을 가질 수 있게 된다. + * 권한은 "요청자가 달라고 하는 값"이 아니라 "서버가 부여하는 값"이어야 한다. + * 멘토 계정이 필요하면 관리자가 별도 절차(현재는 시드 데이터)로 만든다. + */ + @Transactional + public User signup(String username, String rawPassword, String name, String track) { + // 학습 포인트: 중복 검사와 저장 사이에 다른 요청이 끼어들 수도 있지만(경쟁 상태), + // username 컬럼에 unique 제약이 걸려 있어 최악의 경우에도 DB가 중복을 막아 준다. + // 이 사전 검사는 사용자에게 친절한 에러 메시지를 주기 위한 것이다. + if (userRepository.existsByUsername(username)) { + throw new ResponseStatusException( + HttpStatus.CONFLICT, "이미 사용 중인 아이디입니다: " + username); + } + + // 비밀번호는 반드시 BCrypt로 해시해서 저장한다. 원문은 DB 어디에도 남지 않는다. + String passwordHash = passwordEncoder.encode(rawPassword); + + User user = new User(username, passwordHash, name, "STUDENT", track); + return userRepository.save(user); + } + + /** + * 아이디 찾기: 이름이 일치하는 계정들의 아이디 목록을 돌려준다. + * + * 학습 포인트: 실무에서는 이름만으로 아이디를 알려주면 개인정보 유출 위험이 있다 — + * 남의 이름을 넣어 보는 것만으로 그 사람의 계정 아이디를 수집할 수 있기 때문이다. + * 그래서 실서비스는 "가입 시 등록한 이메일로 인증 메일을 보내는" 방식을 쓴다. + * 여기서는 사내 4명이 쓰는 학습용 플랫폼이라 단순한 방식으로 구현했다. + */ + @Transactional(readOnly = true) + public List findUsernamesByName(String name) { + List users = userRepository.findByName(name); + + // 스트림 체이닝 대신 for문으로 풀어쓴다 — 무슨 일이 일어나는지 한 줄씩 보이도록. + List usernames = new ArrayList<>(); + for (User user : users) { + usernames.add(user.getUsername()); + } + return usernames; + } + + /** + * 비밀번호 재설정: 아이디와 이름이 모두 일치하면 새 비밀번호로 바꾼다. 불일치면 404. + * + * 학습 포인트: 실무에서는 "이메일로 재설정 링크 + 만료 토큰" 방식을 쓴다. 이유: + * 1) 이름은 비밀이 아니다 — 동료·지인이면 누구나 알고 있어 본인 확인 수단이 못 된다. + * 2) 이메일 링크는 "그 메일함에 접근할 수 있는 사람 = 본인"이라는 훨씬 강한 증거다. + * 3) 토큰에 만료 시간을 두면, 링크가 유출되어도 짧은 시간이 지나면 쓸 수 없게 된다. + * 여기서는 사내 4명용 학습 구현이라 아이디+이름 대조로 단순화했다. + */ + @Transactional + public void resetPassword(String username, String name, String newRawPassword) { + // 학습 포인트: "아이디는 맞는데 이름이 틀림"을 구분해 주지 않고 똑같이 404를 준다. + // 구분해 주면 공격자가 아이디의 존재 여부를 하나씩 확인해 볼 수 있기 때문이다. + User user = userRepository.findByUsernameAndName(username, name) + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, "일치하는 계정을 찾을 수 없습니다.")); + + // 새 비밀번호도 로그인 비교가 가능하도록 같은 방식(BCrypt)으로 해시해서 저장한다. + user.changePassword(passwordEncoder.encode(newRawPassword)); + // 학습 포인트: save()를 부르지 않아도 된다. @Transactional 안에서 조회한 엔티티는 + // JPA가 변경을 감지해(더티 체킹) 트랜잭션 커밋 시점에 자동으로 UPDATE 한다. + } + + /** + * 현재 요청의 인증 정보(Authentication)로부터 User 엔티티를 찾아온다. + * 인증 정보가 없거나 사용자를 찾을 수 없으면 401을 던진다. + * 컨트롤러들이 "지금 누가 요청했는지" 알아낼 때 공통으로 사용한다. + */ + public User requireUser(Authentication authentication) { + if (authentication == null || authentication.getName() == null) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다."); + } + return userRepository.findByUsername(authentication.getName()) + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/service/ProgressService.java b/backend/src/main/java/dev/awesomedev/mirim/service/ProgressService.java new file mode 100644 index 0000000..35a6641 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/service/ProgressService.java @@ -0,0 +1,71 @@ +package dev.awesomedev.mirim.service; + +import dev.awesomedev.mirim.domain.ChecklistItem; +import dev.awesomedev.mirim.domain.Progress; +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.repository.ChecklistItemRepository; +import dev.awesomedev.mirim.repository.ProgressRepository; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +/** + * 이 파일이 하는 일: + * 체크리스트 진행 상황(Progress)을 다루는 서비스다. + * "체크 토글"과 "내가 체크한 항목 id 목록 조회" 두 가지 일을 한다. + */ +@Service +public class ProgressService { + + private final ProgressRepository progressRepository; + private final ChecklistItemRepository checklistItemRepository; + + public ProgressService(ProgressRepository progressRepository, + ChecklistItemRepository checklistItemRepository) { + this.progressRepository = progressRepository; + this.checklistItemRepository = checklistItemRepository; + } + + /** + * 체크 상태를 토글한다. + * 이미 체크되어 있으면 → 체크 기록을 삭제하고 false(체크 해제됨) 반환. + * 체크 안 되어 있으면 → 체크 기록을 새로 만들고 true(체크됨) 반환. + * + * 학습 포인트: 조회와 삭제/저장이 한 트랜잭션 안에서 일어나야 + * 중간에 끊겨 어중간한 상태가 남는 일이 없다. @Transactional이 그 경계를 만든다. + */ + @Transactional + public boolean toggle(User user, Long itemId) { + ChecklistItem item = checklistItemRepository.findById(itemId) + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, "체크리스트 항목을 찾을 수 없습니다: " + itemId)); + + Optional existing = progressRepository.findByUserAndItem(user, item); + if (existing.isPresent()) { + progressRepository.delete(existing.get()); + return false; // 체크 해제됨 + } + progressRepository.save(new Progress(user, item)); + return true; // 체크됨 + } + + /** + * 내가 체크한 체크리스트 항목의 id 목록을 반환한다. + * + * 학습 포인트: readOnly = true — "이 트랜잭션은 읽기만 한다"고 선언하면 + * JPA가 변경 감지(더티 체킹) 준비를 생략해 조회 성능이 좋아지고, + * 실수로 이 안에서 데이터를 바꾸는 코드를 막는 안전장치도 된다. + * 또한 Progress.user/item이 LAZY라 getItem()을 트랜잭션 안에서 호출해야 + * 하는데, 이 @Transactional이 그 경계를 만들어 준다. + */ + @Transactional(readOnly = true) + public List checkedItemIds(User user) { + return progressRepository.findByUser(user).stream() + .map(progress -> progress.getItem().getId()) + .toList(); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/service/SubmissionService.java b/backend/src/main/java/dev/awesomedev/mirim/service/SubmissionService.java new file mode 100644 index 0000000..daa44c4 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/service/SubmissionService.java @@ -0,0 +1,81 @@ +package dev.awesomedev.mirim.service; + +import dev.awesomedev.mirim.domain.Assignment; +import dev.awesomedev.mirim.domain.Submission; +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.repository.AssignmentRepository; +import dev.awesomedev.mirim.repository.SubmissionRepository; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +/** + * 이 파일이 하는 일: + * 과제 제출물(Submission)을 다루는 서비스다. + * 제출/재제출, 내 제출물 조회, 멘토 피드백 등록을 담당한다. + */ +@Service +public class SubmissionService { + + private final SubmissionRepository submissionRepository; + private final AssignmentRepository assignmentRepository; + + public SubmissionService(SubmissionRepository submissionRepository, + AssignmentRepository assignmentRepository) { + this.submissionRepository = submissionRepository; + this.assignmentRepository = assignmentRepository; + } + + /** + * 과제를 제출한다. 같은 과제를 이미 제출했다면 새 행을 만들지 않고 기존 제출물을 갱신한다. + * + * 학습 포인트: "제출물은 과제당 1개" 규칙을 INSERT 전에 SELECT로 확인해서 지킨다. + * 재제출하면 상태가 다시 SUBMITTED로 돌아가 멘토가 다시 리뷰해야 함을 알 수 있다. + */ + @Transactional + public Submission submitOrUpdate(User user, Long assignmentId, String content, String link) { + if (content == null || content.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "제출 내용(content)은 비울 수 없습니다."); + } + Assignment assignment = assignmentRepository.findById(assignmentId) + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, "과제를 찾을 수 없습니다: " + assignmentId)); + + Optional existing = submissionRepository.findByAssignmentAndUser(assignment, user); + if (existing.isPresent()) { + Submission submission = existing.get(); + submission.resubmit(content, link); + return submission; // 학습 포인트: 트랜잭션 안에서 엔티티를 바꾸면 JPA가 커밋 시 자동으로 UPDATE 한다(더티 체킹). + } + return submissionRepository.save(new Submission(assignment, user, content, link)); + } + + /** 내 제출물을 최신순으로 조회한다. */ + @Transactional(readOnly = true) + public List mySubmissions(User user) { + return submissionRepository.findByUserOrderBySubmittedAtDesc(user); + } + + /** 전체 제출물을 최신순으로 조회한다 (멘토용). */ + @Transactional(readOnly = true) + public List allSubmissions() { + return submissionRepository.findAllByOrderBySubmittedAtDesc(); + } + + /** 멘토가 제출물에 피드백을 남긴다. 상태는 REVIEWED로 바뀐다. */ + @Transactional + public Submission giveFeedback(Long submissionId, String feedback) { + if (feedback == null || feedback.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "피드백 내용은 비울 수 없습니다."); + } + Submission submission = submissionRepository.findById(submissionId) + .orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, "제출물을 찾을 수 없습니다: " + submissionId)); + submission.review(feedback); + return submission; + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/AssignmentController.java b/backend/src/main/java/dev/awesomedev/mirim/web/AssignmentController.java new file mode 100644 index 0000000..3e9553b --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/AssignmentController.java @@ -0,0 +1,33 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.repository.AssignmentRepository; +import dev.awesomedev.mirim.web.dto.AssignmentResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 주차별 과제 조회 API를 제공하는 컨트롤러다. + */ +@RestController +@RequestMapping("/api/assignments") +public class AssignmentController { + + private final AssignmentRepository assignmentRepository; + + public AssignmentController(AssignmentRepository assignmentRepository) { + this.assignmentRepository = assignmentRepository; + } + + /** GET /api/assignments?week=N — 해당 주차의 과제 목록 (일차순) */ + @GetMapping + public List list(@RequestParam("week") Integer week) { + return assignmentRepository.findByWeekOrderByDayAsc(week).stream() + .map(AssignmentResponse::from) + .toList(); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/AuthController.java b/backend/src/main/java/dev/awesomedev/mirim/web/AuthController.java new file mode 100644 index 0000000..4ae2ea4 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/AuthController.java @@ -0,0 +1,138 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.service.AuthService; +import dev.awesomedev.mirim.web.dto.FindIdRequest; +import dev.awesomedev.mirim.web.dto.FindIdResponse; +import dev.awesomedev.mirim.web.dto.LoginRequest; +import dev.awesomedev.mirim.web.dto.ResetPasswordRequest; +import dev.awesomedev.mirim.web.dto.SignupRequest; +import dev.awesomedev.mirim.web.dto.SignupResponse; +import dev.awesomedev.mirim.web.dto.UserResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.context.SecurityContextRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 로그인 / 로그아웃 / 내 정보 조회 / 회원가입 / 아이디 찾기 / 비밀번호 재설정 + * API를 제공하는 컨트롤러다. + * + * 학습 포인트: 보통 Spring Security 예제는 AuthenticationManager와 formLogin을 쓰지만, + * 여기서는 "무슨 일이 일어나는지" 눈에 다 보이도록 직접 구현한다: + * 1) UserRepository로 사용자를 찾고 PasswordEncoder로 비밀번호를 검증한다 (AuthService). + * 2) 검증에 성공하면 Authentication 객체를 직접 만든다. + * 3) SecurityContextRepository로 그 인증 정보를 세션에 저장한다. + * 이후 요청부터는 Spring Security가 세션에서 인증 정보를 꺼내 "로그인된 상태"로 처리한다. + */ +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + private final AuthService authService; + private final SecurityContextRepository securityContextRepository; + + public AuthController(AuthService authService, + SecurityContextRepository securityContextRepository) { + this.authService = authService; + this.securityContextRepository = securityContextRepository; + } + + /** POST /api/auth/login — 로그인. 성공 시 세션이 만들어지고 사용자 정보를 돌려준다. */ + @PostMapping("/login") + public UserResponse login(@RequestBody LoginRequest loginRequest, + HttpServletRequest request, + HttpServletResponse response) { + // 1) 아이디·비밀번호 검증 (틀리면 AuthService가 401을 던진다) + User user = authService.authenticate(loginRequest.username(), loginRequest.password()); + + // 2) 인증 토큰 생성. principal은 username, 권한은 "ROLE_" + 역할. + // 학습 포인트: Spring Security의 hasRole("MENTOR")는 내부적으로 "ROLE_MENTOR" 권한을 찾는다. + // 그래서 권한 이름 앞에 반드시 "ROLE_" 접두어를 붙여야 한다. + Authentication authentication = UsernamePasswordAuthenticationToken.authenticated( + user.getUsername(), + null, // 비밀번호는 인증이 끝났으니 보관하지 않는다 + List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole()))); + + // 3) SecurityContext를 만들어 세션에 저장 — 이 한 줄이 "로그인 상태 유지"의 핵심이다. + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authentication); + SecurityContextHolder.setContext(context); + securityContextRepository.saveContext(context, request, response); + + return UserResponse.from(user); + } + + /** POST /api/auth/logout — 로그아웃. 세션을 통째로 무효화하고 204를 돌려준다. */ + @PostMapping("/logout") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void logout(HttpServletRequest request) { + HttpSession session = request.getSession(false); // false: 세션이 없으면 새로 만들지 않는다 + if (session != null) { + session.invalidate(); + } + SecurityContextHolder.clearContext(); + } + + /** GET /api/auth/me — 지금 로그인한 사용자 정보. 로그인 안 했으면 401(SecurityConfig가 처리). */ + @GetMapping("/me") + public UserResponse me(Authentication authentication) { + User user = authService.requireUser(authentication); + return UserResponse.from(user); + } + + /** + * POST /api/auth/signup — 회원가입. 성공하면 201 Created와 함께 만들어진 계정 정보를 준다. + * + * 학습 포인트: @Valid가 붙어 있으면 SignupRequest의 검증 애노테이션 + * (@NotBlank, @Size, @Pattern)이 이 메서드가 실행되기 전에 먼저 검사된다. + * 검사에 실패하면 스프링이 알아서 400 Bad Request를 돌려주므로, + * 컨트롤러 안에는 "규칙을 통과한 값"만 들어온다고 믿고 코드를 쓸 수 있다. + * 가입 후 자동 로그인은 하지 않는다 — 로그인 화면에서 직접 로그인해 보는 것도 학습의 일부다. + */ + @PostMapping("/signup") + @ResponseStatus(HttpStatus.CREATED) // 자원(계정)이 새로 생겼으므로 REST 관례대로 201 + public SignupResponse signup(@Valid @RequestBody SignupRequest signupRequest) { + User user = authService.signup( + signupRequest.username(), + signupRequest.password(), + signupRequest.name(), + signupRequest.track()); + return SignupResponse.from(user); + } + + /** + * POST /api/auth/find-id — 이름으로 아이디 찾기. + * 이름이 일치하는 계정이 없으면 빈 배열을 돌려준다(에러가 아니다). + * 개인정보 관련 주의사항은 AuthService.findUsernamesByName 주석 참고. + */ + @PostMapping("/find-id") + public FindIdResponse findId(@Valid @RequestBody FindIdRequest findIdRequest) { + return new FindIdResponse(authService.findUsernamesByName(findIdRequest.name())); + } + + /** + * POST /api/auth/reset-password — 비밀번호 재설정. + * 아이디+이름이 일치하면 새 비밀번호를 저장하고 200, 불일치하면 404. + * 왜 이 방식이 실무에선 부족한지는 AuthService.resetPassword 주석 참고. + */ + @PostMapping("/reset-password") + public void resetPassword(@Valid @RequestBody ResetPasswordRequest resetPasswordRequest) { + authService.resetPassword( + resetPasswordRequest.username(), + resetPasswordRequest.name(), + resetPasswordRequest.newPassword()); + // 반환할 내용이 없으므로 본문 없이 200 OK만 내려간다. + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/ChecklistController.java b/backend/src/main/java/dev/awesomedev/mirim/web/ChecklistController.java new file mode 100644 index 0000000..cf9121b --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/ChecklistController.java @@ -0,0 +1,33 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.repository.ChecklistItemRepository; +import dev.awesomedev.mirim.web.dto.ChecklistItemResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 주차별 체크리스트 항목 조회 API를 제공하는 컨트롤러다. + */ +@RestController +@RequestMapping("/api/checklist") +public class ChecklistController { + + private final ChecklistItemRepository checklistItemRepository; + + public ChecklistController(ChecklistItemRepository checklistItemRepository) { + this.checklistItemRepository = checklistItemRepository; + } + + /** GET /api/checklist?week=N — 해당 주차의 체크리스트 항목 목록 */ + @GetMapping + public List list(@RequestParam("week") Integer week) { + return checklistItemRepository.findByWeekOrderBySortOrderAsc(week).stream() + .map(ChecklistItemResponse::from) + .toList(); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/DocumentController.java b/backend/src/main/java/dev/awesomedev/mirim/web/DocumentController.java new file mode 100644 index 0000000..e888fa3 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/DocumentController.java @@ -0,0 +1,44 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.repository.DocumentRepository; +import dev.awesomedev.mirim.service.AuthService; +import dev.awesomedev.mirim.web.dto.DocumentResponse; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 학습 문서 목록 API를 제공하는 컨트롤러다. + * 멘토 전용 문서(채점 기준 등)는 수습생에게 보이지 않도록 걸러낸다. + */ +@RestController +@RequestMapping("/api/documents") +public class DocumentController { + + private final DocumentRepository documentRepository; + private final AuthService authService; + + public DocumentController(DocumentRepository documentRepository, AuthService authService) { + this.documentRepository = documentRepository; + this.authService = authService; + } + + /** GET /api/documents — 내 역할로 볼 수 있는 문서 목록 */ + @GetMapping + public List list(Authentication authentication) { + User user = authService.requireUser(authentication); + boolean isMentor = "MENTOR".equals(user.getRole()); + + // 학습 포인트: 권한 필터링을 프론트엔드에 맡기면 안 된다. + // 프론트에서 숨겨도 API를 직접 호출하면 다 보이기 때문에, 서버가 걸러서 내려줘야 한다. + return documentRepository.findAllByOrderBySortOrderAsc().stream() + .filter(doc -> isMentor || !"MENTOR".equals(doc.getAudience())) + .map(DocumentResponse::from) + .toList(); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/MentorController.java b/backend/src/main/java/dev/awesomedev/mirim/web/MentorController.java new file mode 100644 index 0000000..ecb7e9a --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/MentorController.java @@ -0,0 +1,46 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.domain.Submission; +import dev.awesomedev.mirim.service.SubmissionService; +import dev.awesomedev.mirim.web.dto.FeedbackRequest; +import dev.awesomedev.mirim.web.dto.MentorSubmissionResponse; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 멘토 전용 API(전체 제출물 조회, 피드백 등록)를 제공하는 컨트롤러다. + * + * 학습 포인트: 이 컨트롤러엔 권한 검사 코드가 없다. + * SecurityConfig에서 "/api/mentor/** 는 MENTOR 권한 필요"로 선언했기 때문에, + * 권한이 없는 요청은 컨트롤러에 도달하기 전에 403으로 차단된다. + * 이렇게 URL 패턴으로 권한을 선언하면, 멘토 API가 늘어나도 검사 코드를 + * 메서드마다 복사할 필요가 없고 "권한 규칙이 어디 있는지"가 한곳에 모인다. + */ +@RestController +@RequestMapping("/api/mentor") +public class MentorController { + + private final SubmissionService submissionService; + + public MentorController(SubmissionService submissionService) { + this.submissionService = submissionService; + } + + /** GET /api/mentor/submissions — 전체 제출물 + 학생 이름 + 과제 제목 (최신순) */ + @GetMapping("/submissions") + public List allSubmissions() { + return submissionService.allSubmissions().stream() + .map(MentorSubmissionResponse::from) + .toList(); + } + + /** POST /api/mentor/submissions/{id}/feedback — 피드백 등록, 상태는 REVIEWED로 변경 */ + @PostMapping("/submissions/{id}/feedback") + public MentorSubmissionResponse giveFeedback(@PathVariable Long id, + @RequestBody FeedbackRequest feedbackRequest) { + Submission submission = submissionService.giveFeedback(id, feedbackRequest.feedback()); + return MentorSubmissionResponse.from(submission); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/ProgressController.java b/backend/src/main/java/dev/awesomedev/mirim/web/ProgressController.java new file mode 100644 index 0000000..21c9a66 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/ProgressController.java @@ -0,0 +1,42 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.service.AuthService; +import dev.awesomedev.mirim.service.ProgressService; +import dev.awesomedev.mirim.web.dto.ToggleResponse; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 체크리스트 진행 상황(내 체크 목록 조회, 체크 토글) API를 제공하는 컨트롤러다. + */ +@RestController +@RequestMapping("/api/progress") +public class ProgressController { + + private final ProgressService progressService; + private final AuthService authService; + + public ProgressController(ProgressService progressService, AuthService authService) { + this.progressService = progressService; + this.authService = authService; + } + + /** GET /api/progress/me — 내가 체크한 항목 id 배열 */ + @GetMapping("/me") + public List myProgress(Authentication authentication) { + User user = authService.requireUser(authentication); + return progressService.checkedItemIds(user); + } + + /** POST /api/progress/{itemId}/toggle — 체크 상태 뒤집기 */ + @PostMapping("/{itemId}/toggle") + public ToggleResponse toggle(@PathVariable Long itemId, Authentication authentication) { + User user = authService.requireUser(authentication); + boolean checked = progressService.toggle(user, itemId); + return new ToggleResponse(itemId, checked); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/SubmissionController.java b/backend/src/main/java/dev/awesomedev/mirim/web/SubmissionController.java new file mode 100644 index 0000000..2831c41 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/SubmissionController.java @@ -0,0 +1,54 @@ +package dev.awesomedev.mirim.web; + +import dev.awesomedev.mirim.domain.Submission; +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.service.AuthService; +import dev.awesomedev.mirim.service.SubmissionService; +import dev.awesomedev.mirim.web.dto.SubmissionRequest; +import dev.awesomedev.mirim.web.dto.SubmissionResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 과제 제출(생성/재제출)과 내 제출물 조회 API를 제공하는 컨트롤러다. + */ +@RestController +@RequestMapping("/api/submissions") +public class SubmissionController { + + private final SubmissionService submissionService; + private final AuthService authService; + + public SubmissionController(SubmissionService submissionService, AuthService authService) { + this.submissionService = submissionService; + this.authService = authService; + } + + /** GET /api/submissions/me — 내 제출물 목록 (최신순) */ + @GetMapping("/me") + public List mySubmissions(Authentication authentication) { + User user = authService.requireUser(authentication); + return submissionService.mySubmissions(user).stream() + .map(SubmissionResponse::from) + .toList(); + } + + /** + * POST /api/submissions — 과제 제출. 이미 낸 과제면 내용을 갱신한다. + * 학습 포인트: "자원 생성"이라 REST 관례대로 201 Created를 돌려준다. + */ + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public SubmissionResponse submit(@RequestBody SubmissionRequest submissionRequest, + Authentication authentication) { + User user = authService.requireUser(authentication); + Submission submission = submissionService.submitOrUpdate( + user, submissionRequest.assignmentId(), + submissionRequest.content(), submissionRequest.link()); + return SubmissionResponse.from(submission); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/AssignmentResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/AssignmentResponse.java new file mode 100644 index 0000000..606bf98 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/AssignmentResponse.java @@ -0,0 +1,16 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.Assignment; + +/** + * 이 파일이 하는 일: + * 과제 조회 API의 응답 한 건을 담는 DTO다. + */ +public record AssignmentResponse(Long id, Integer week, Integer day, String kind, + String title, String summary) { + + public static AssignmentResponse from(Assignment assignment) { + return new AssignmentResponse(assignment.getId(), assignment.getWeek(), assignment.getDay(), + assignment.getKind(), assignment.getTitle(), assignment.getSummary()); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/ChecklistItemResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/ChecklistItemResponse.java new file mode 100644 index 0000000..e798bb3 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/ChecklistItemResponse.java @@ -0,0 +1,15 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.ChecklistItem; + +/** + * 이 파일이 하는 일: + * 체크리스트 조회 API의 응답 한 건을 담는 DTO다. + */ +public record ChecklistItemResponse(Long id, Integer week, Integer day, String label, Integer sortOrder) { + + public static ChecklistItemResponse from(ChecklistItem item) { + return new ChecklistItemResponse(item.getId(), item.getWeek(), item.getDay(), + item.getLabel(), item.getSortOrder()); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/DocumentResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/DocumentResponse.java new file mode 100644 index 0000000..3e2e169 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/DocumentResponse.java @@ -0,0 +1,16 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.Document; + +/** + * 이 파일이 하는 일: + * 문서 목록 API의 응답 한 건을 담는 DTO다. + */ +public record DocumentResponse(Long id, String slug, String title, String category, + Integer week, String audience, String filePath, Integer sortOrder) { + + public static DocumentResponse from(Document doc) { + return new DocumentResponse(doc.getId(), doc.getSlug(), doc.getTitle(), doc.getCategory(), + doc.getWeek(), doc.getAudience(), doc.getFilePath(), doc.getSortOrder()); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/FeedbackRequest.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/FeedbackRequest.java new file mode 100644 index 0000000..bc699cd --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/FeedbackRequest.java @@ -0,0 +1,8 @@ +package dev.awesomedev.mirim.web.dto; + +/** + * 이 파일이 하는 일: + * 멘토 피드백 등록 요청 본문 {feedback}을 담는 DTO다. + */ +public record FeedbackRequest(String feedback) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/FindIdRequest.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/FindIdRequest.java new file mode 100644 index 0000000..1e360f4 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/FindIdRequest.java @@ -0,0 +1,14 @@ +package dev.awesomedev.mirim.web.dto; + +import jakarta.validation.constraints.NotBlank; + +/** + * 이 파일이 하는 일: + * 아이디 찾기 요청 본문 {name}을 담는 DTO다. + */ +public record FindIdRequest( + + @NotBlank(message = "이름은 필수입니다.") + String name +) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/FindIdResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/FindIdResponse.java new file mode 100644 index 0000000..7cc999d --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/FindIdResponse.java @@ -0,0 +1,13 @@ +package dev.awesomedev.mirim.web.dto; + +import java.util.List; + +/** + * 이 파일이 하는 일: + * 아이디 찾기 응답 {usernames: [...]}을 담는 DTO다. + * + * 학습 포인트: 결과를 단일 값이 아니라 목록으로 내려주는 이유 — + * 동명이인이 있을 수 있기 때문이다. "이름 → 아이디"는 1:1이라고 가정하면 안 된다. + */ +public record FindIdResponse(List usernames) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/LoginRequest.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/LoginRequest.java new file mode 100644 index 0000000..2dda2bf --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/LoginRequest.java @@ -0,0 +1,11 @@ +package dev.awesomedev.mirim.web.dto; + +/** + * 이 파일이 하는 일: + * 로그인 요청 본문 {username, password}를 담는 DTO다. + * + * 학습 포인트: record는 필드·생성자·getter를 자동으로 만들어 주는 자바 문법으로, + * "데이터를 담기만 하는 그릇"인 DTO에 딱 맞는다. + */ +public record LoginRequest(String username, String password) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/MentorSubmissionResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/MentorSubmissionResponse.java new file mode 100644 index 0000000..650c183 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/MentorSubmissionResponse.java @@ -0,0 +1,31 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.Submission; + +import java.time.Instant; + +/** + * 이 파일이 하는 일: + * 멘토용 제출물 목록 API의 응답 한 건을 담는 DTO다. + * 멘토는 누가(studentName) 어떤 과제(assignmentTitle)를 냈는지 한눈에 봐야 하므로 + * 학생 이름과 과제 제목을 함께 담는다. + */ +public record MentorSubmissionResponse(Long id, Long assignmentId, String assignmentTitle, + Long studentId, String studentName, + String content, String link, Instant submittedAt, + String feedback, String status) { + + public static MentorSubmissionResponse from(Submission submission) { + return new MentorSubmissionResponse( + submission.getId(), + submission.getAssignment().getId(), + submission.getAssignment().getTitle(), + submission.getUser().getId(), + submission.getUser().getName(), + submission.getContent(), + submission.getLink(), + submission.getSubmittedAt(), + submission.getFeedback(), + submission.getStatus()); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/ResetPasswordRequest.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/ResetPasswordRequest.java new file mode 100644 index 0000000..892570a --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/ResetPasswordRequest.java @@ -0,0 +1,25 @@ +package dev.awesomedev.mirim.web.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 이 파일이 하는 일: + * 비밀번호 재설정 요청 본문 {username, name, newPassword}를 담는 DTO다. + * "아이디 + 이름"이 모두 일치해야 재설정을 허용한다. + */ +public record ResetPasswordRequest( + + @NotBlank(message = "아이디는 필수입니다.") + String username, + + // 본인 확인용 이름. 아이디만으로 재설정을 허용하면 아이디를 아는 누구나 계정을 뺏을 수 있다. + @NotBlank(message = "이름은 필수입니다.") + String name, + + // 새 비밀번호도 가입 때와 같은 규칙(8자 이상)을 적용한다. + @NotBlank(message = "새 비밀번호는 필수입니다.") + @Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다.") + String newPassword +) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/SignupRequest.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SignupRequest.java new file mode 100644 index 0000000..da89e5b --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SignupRequest.java @@ -0,0 +1,47 @@ +package dev.awesomedev.mirim.web.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** + * 이 파일이 하는 일: + * 회원가입 요청 본문 {username, password, name, track}을 담는 DTO다. + * + * 학습 포인트: 검증 애노테이션(@NotBlank, @Size, @Pattern)을 필드에 붙여 두면, + * 컨트롤러에서 @Valid를 만나는 순간 스프링이 자동으로 검사해 준다. + * 검사에 실패하면 컨트롤러 코드가 실행되기도 전에 400 Bad Request가 반환된다. + * "검증 규칙은 데이터 옆에 선언한다" — if문을 컨트롤러마다 반복해서 쓰지 않아도 되는 이유다. + */ +public record SignupRequest( + + /* + * 아이디: 4자 이상, 영문/숫자만. + * 학습 포인트: @Pattern의 정규식 ^[a-zA-Z0-9]+$ 은 + * "처음(^)부터 끝($)까지 영문 대소문자와 숫자만 1개 이상(+)"이라는 뜻이다. + * 공백·한글·특수문자가 하나라도 섞이면 매칭에 실패한다. + */ + @NotBlank(message = "아이디는 필수입니다.") + @Size(min = 4, message = "아이디는 4자 이상이어야 합니다.") + @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "아이디는 영문과 숫자만 사용할 수 있습니다.") + String username, + + // 비밀번호: 8자 이상. (원문은 서버에 저장되지 않고 BCrypt 해시만 저장된다) + @NotBlank(message = "비밀번호는 필수입니다.") + @Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다.") + String password, + + // 화면에 표시될 이름 + @NotBlank(message = "이름은 필수입니다.") + String name, + + /* + * 트랙: DEV(개발) 또는 DESIGN(디자인)만 허용. + * 학습 포인트: 허용 값을 서버가 강제하지 않으면 프론트를 우회한 요청이 + * "HACKER" 같은 임의 값을 넣을 수 있다. 서버 검증이 항상 최종 방어선이다. + */ + @NotBlank(message = "트랙은 필수입니다.") + @Pattern(regexp = "^(DEV|DESIGN)$", message = "트랙은 DEV 또는 DESIGN만 선택할 수 있습니다.") + String track +) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/SignupResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SignupResponse.java new file mode 100644 index 0000000..e6956d7 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SignupResponse.java @@ -0,0 +1,19 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.User; + +/** + * 이 파일이 하는 일: + * 회원가입 성공 응답 {id, username, name}을 담는 DTO다. + * + * 학습 포인트: UserResponse(role·track 포함)를 재사용하지 않고 따로 만든 이유 — + * 가입 응답은 "계정이 만들어졌다"는 확인만 하면 되고, + * 응답마다 전용 DTO를 두면 나중에 한쪽 화면의 요구가 바뀌어도 다른 API가 흔들리지 않는다. + */ +public record SignupResponse(Long id, String username, String name) { + + /** User 엔티티 → 가입 응답 DTO 변환 */ + public static SignupResponse from(User user) { + return new SignupResponse(user.getId(), user.getUsername(), user.getName()); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/SubmissionRequest.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SubmissionRequest.java new file mode 100644 index 0000000..32f331a --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SubmissionRequest.java @@ -0,0 +1,9 @@ +package dev.awesomedev.mirim.web.dto; + +/** + * 이 파일이 하는 일: + * 과제 제출 요청 본문 {assignmentId, content, link}를 담는 DTO다. + * link는 없어도 된다(null 허용). + */ +public record SubmissionRequest(Long assignmentId, String content, String link) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/SubmissionResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SubmissionResponse.java new file mode 100644 index 0000000..0fefc55 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/SubmissionResponse.java @@ -0,0 +1,19 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.Submission; + +import java.time.Instant; + +/** + * 이 파일이 하는 일: + * 내 제출물 조회 API의 응답 한 건을 담는 DTO다. + */ +public record SubmissionResponse(Long id, Long assignmentId, String content, String link, + Instant submittedAt, String feedback, String status) { + + public static SubmissionResponse from(Submission submission) { + return new SubmissionResponse(submission.getId(), submission.getAssignment().getId(), + submission.getContent(), submission.getLink(), submission.getSubmittedAt(), + submission.getFeedback(), submission.getStatus()); + } +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/ToggleResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/ToggleResponse.java new file mode 100644 index 0000000..644c51c --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/ToggleResponse.java @@ -0,0 +1,9 @@ +package dev.awesomedev.mirim.web.dto; + +/** + * 이 파일이 하는 일: + * 체크 토글 API의 응답 {itemId, checked}를 담는 DTO다. + * checked=true면 방금 체크됨, false면 방금 체크 해제됨을 뜻한다. + */ +public record ToggleResponse(Long itemId, boolean checked) { +} diff --git a/backend/src/main/java/dev/awesomedev/mirim/web/dto/UserResponse.java b/backend/src/main/java/dev/awesomedev/mirim/web/dto/UserResponse.java new file mode 100644 index 0000000..625ffe3 --- /dev/null +++ b/backend/src/main/java/dev/awesomedev/mirim/web/dto/UserResponse.java @@ -0,0 +1,19 @@ +package dev.awesomedev.mirim.web.dto; + +import dev.awesomedev.mirim.domain.User; + +/** + * 이 파일이 하는 일: + * 로그인/내 정보 응답으로 내려줄 사용자 정보 DTO다. + * + * 학습 포인트: 엔티티(User)를 그대로 응답하지 않고 DTO로 변환하는 이유 — + * passwordHash 같은 민감한 필드가 실수로 밖에 나가는 것을 막기 위해서다. + */ +public record UserResponse(Long id, String username, String name, String role, String track) { + + /** User 엔티티 → 응답 DTO 변환 */ + public static UserResponse from(User user) { + return new UserResponse(user.getId(), user.getUsername(), user.getName(), + user.getRole(), user.getTrack()); + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..f9916a4 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,36 @@ +# 미림 수습 학습 플랫폼 설정 +# 학습 포인트: 설정값은 코드에 안 박고 여기(그리고 환경변수)에 둡니다. +# ${환경변수:기본값} — 환경변수가 있으면 그 값을, 없으면 기본값(로컬 개발용)을 씁니다. +spring: + application: + name: mirim-backend + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:mirim} + username: ${DB_USER:mirim} + password: ${DB_PASSWORD:mirim123} + jpa: + hibernate: + # 학습 포인트: ddl-auto: update의 의미 — + # 앱이 뜰 때 Hibernate가 엔티티(@Entity 클래스)와 실제 테이블을 비교해서 + # 없는 테이블/컬럼을 자동으로 만들어 준다(CREATE/ALTER를 대신 실행). + # 개발 단계에선 편하지만 운영에선 위험하다: + # - 컬럼 삭제/이름 변경은 반영하지 못해 엔티티와 테이블이 조용히 어긋난다. + # - 어떤 DDL이 언제 실행됐는지 기록이 남지 않아 되돌리기가 불가능하다. + # 운영에선 validate(검증만)로 바꾸고 Flyway 같은 마이그레이션 도구로 스키마를 관리한다. + ddl-auto: update # 개발 단계: 엔티티에 맞춰 테이블 자동 생성/수정 + # open-in-view: 화면 렌더링이 끝날 때까지 DB 커넥션을 물고 있는 기능. REST API 서버에선 + # 커넥션 낭비일 뿐이라 끈다. (끄면 LAZY 로딩은 반드시 @Transactional 안에서 끝내야 한다) + open-in-view: false + properties: + hibernate: + format_sql: true + +server: + port: 8080 + servlet: + session: + timeout: 8h # 하루 근무 동안 로그인 유지 + +logging: + level: + dev.awesomedev.mirim: DEBUG diff --git a/backend/src/main/resources/seed/assignments.json b/backend/src/main/resources/seed/assignments.json new file mode 100644 index 0000000..67c5036 --- /dev/null +++ b/backend/src/main/resources/seed/assignments.json @@ -0,0 +1,81 @@ +[ + { "week": 1, "day": 1, "kind": "MAIN", "title": "회사 탐색 미션 — 우리 회사 지도 만들기", "summary": "사내 위키와 팀원 인터뷰 3명을 바탕으로 우리 회사 지도 1장을 그립니다. 팀 구성, 기술 분야별 담당 팀, 막히면 찾아갈 사람 3명을 채워 제출하고, 리뷰 시간에 지도를 보며 1분 회사 소개를 발표합니다." }, + { "week": 1, "day": 1, "kind": "EXTRA", "title": "사규 요약 카드 만들기", "summary": "사규·가이드 문서에서 수습 기간의 나에게 직접 해당되는 규칙 10개를 골라 내 말로 한 줄씩 다시 씁니다. 지키기 어려울 것 같은 2개에 별표를 치고 이유를 적어 본 과제와 함께 제출합니다." }, + { "week": 1, "day": 1, "kind": "DESIGN", "title": "회사 지도 만들기 — 디자이너 인터뷰 포함", "summary": "개발자와 동일하게 회사 지도와 인터뷰를 진행하되, 인터뷰 상대 3명 중 1명은 반드시 디자이너(또는 디자인 협업이 많은 분)를 포함합니다. 질문 하나를 '신입 디자이너가 첫 달에 익힐 것'으로 바꿔 묻고 지도와 인터뷰 메모를 제출합니다." }, + + { "week": 1, "day": 2, "kind": "MAIN", "title": "예절 워크북 — 상황 판단 문제 10제 + 중간보고 대본 3편", "summary": "호렌소(보고·연락·상담) 기준으로 상황 판단 문제 10개를 O/X와 이유 2문장 이상으로 풀고, 3개 상황의 중간보고 대본을 각 40초 이내 분량으로 씁니다. 리뷰 시간에 대본 1편을 멘토 앞에서 실제로 말해 봅니다." }, + { "week": 1, "day": 2, "kind": "EXTRA", "title": "나의 태도 점검표 만들기", "summary": "오전 강의에서 배운 태도 항목 중 내가 약한 7개를 골라 아침마다 스스로 물을 수 있는 질문으로 바꿉니다. 항목 7개 × 월~금 체크표를 만들어 오늘 것부터 체크하고 제출합니다." }, + { "week": 1, "day": 2, "kind": "DESIGN", "title": "예절 워크북 — 디자인 상황 중간보고 대본", "summary": "판단 문제와 대본 과제는 개발자와 동일하게 진행합니다. 단, 중간보고 대본의 상황 B를 '시안 작업 중 참고 자료가 부족해 방향을 잡기 어렵다'는 디자인 상황으로 바꿔 작성해 제출합니다." }, + + { "week": 1, "day": 3, "kind": "MAIN", "title": "소통 3종 세트 — 이메일 · 메신저 · 전화", "summary": "멘토에게 인사 메일과 진행보고 메일 2통을 실제로 발송하고, '상황→시도→질문' 틀에 맞춘 메신저 질문 1건을 보냅니다. 가상 통화 시나리오 2건을 전화 메모 양식으로 정리해 함께 제출합니다." }, + { "week": 1, "day": 3, "kind": "EXTRA", "title": "나쁜 메일 고쳐쓰기", "summary": "제시된 나쁜 메일에서 문제점을 5개 이상 찾아 번호를 매기고, 업무 이메일 기본 틀에 맞춰 전체를 다시 씁니다. 원본과 수정본을 비교해 무엇이 좋아졌는지 3줄로 정리해 제출합니다." }, + { "week": 1, "day": 3, "kind": "DESIGN", "title": "소통 3종 세트 — 디자인 진행보고 버전", "summary": "이메일·메신저·전화 메모 과제를 동일하게 진행하되, 진행보고 메일은 '시안 2개 방향을 잡았고 내일 오전까지 다듬어 공유하겠다'는 디자인 진행보고 내용으로 바꿔 작성해 발송합니다." }, + + { "week": 1, "day": 4, "kind": "MAIN", "title": "우리 서비스 탐험 — 화면 15장 리포트", "summary": "담당 예정 서비스를 사용자 눈으로 훑으며 이동 순서대로 화면 15개를 캡처하고, 각 화면에서 '무엇을 할 수 있는지'를 한 줄로 설명합니다. 화면 흐름도와 궁금한 점 TOP 5, 서비스 존재 이유 3줄을 담은 리포트를 제출합니다." }, + { "week": 1, "day": 4, "kind": "EXTRA", "title": "2진수 변환 연습 12제", "summary": "계산기 없이 자릿값 표를 그려 두고 10진수↔2진수 변환 12문제를 풀이 과정과 함께 풉니다. 다 풀면 검산하고 틀린 문제는 어디서 틀렸는지 한 줄씩 적어 본 과제와 함께 제출합니다." }, + { "week": 1, "day": 4, "kind": "DESIGN", "title": "서비스 화면 15개 — 디자인 관점 탐험", "summary": "같은 서비스의 화면 15장을 캡처하되 색·글자·간격 세 가지 디자인 관찰로 메모를 채웁니다. 베스트/워스트 화면 각 1개와 이유, 개선 제안 3가지, 디자인 관점 궁금한 점 5개를 담은 리포트를 제출합니다." }, + + { "week": 1, "day": 5, "kind": "MAIN", "title": "Git 사이클 3회전 + 온보딩 회고", "summary": "연습 저장소에서 브랜치→커밋→푸시→PR 사이클을 feat·docs·fix 세 번 반복하고, 커밋 컨벤션과 PR 양식을 지켜 PR 3개 링크를 제출합니다. Keep·Problem·Try 양식의 온보딩 회고 1장도 함께 냅니다." }, + { "week": 1, "day": 5, "kind": "EXTRA", "title": "JavaScript 워밍업 10제 (콘솔에서)", "summary": "브라우저 콘솔에서 변수·조건문·배열·반복문 10문제를 직접 입력해 풀고, 입력한 코드와 출력 결과(에러 포함)를 그대로 기록합니다. 기록 문서 1부를 본 과제와 함께 제출합니다." }, + { "week": 1, "day": 5, "kind": "DESIGN", "title": "Figma 파일 정리 연습 + 온보딩 회고", "summary": "어질러진 연습용 Figma 파일을 복제해 페이지 재구성, 프레임·레이어 네이밍, 색 스타일 등록으로 정리합니다. Before→After 캡처와 정리 규칙 5가지를 담은 파일 링크, 온보딩 회고 1장을 제출합니다." }, + + { "week": 2, "day": 1, "kind": "MAIN", "title": "코드 지도 그리기 — React · Spring Boot 구조도", "summary": "연습용 React·Spring Boot 프로젝트를 열어 각각 폴더 10개의 역할을 한 줄씩 적은 구조도 2장을 그립니다. 두 프로젝트의 공통점·차이점 4문장과 모르는 파일 질문 3개, JS 기초 10제를 푼 day1.js를 함께 제출합니다." }, + { "week": 2, "day": 1, "kind": "EXTRA", "title": "package.json 탐험", "summary": "React의 package.json에서 scripts 명령어의 역할을 추측해 적고, dependencies 5개와 Spring Boot의 pom.xml 의존성 5개의 용도를 조사합니다. 메모 1장으로 정리해 본 과제와 함께 제출합니다." }, + { "week": 2, "day": 1, "kind": "DESIGN", "title": "UI 인벤토리 ① — 색상 팔레트 추출", "summary": "서비스 화면 5개 이상에서 실제 쓰인 색을 전부 HEX로 추출해 Figma에 색상 칩으로 나열하고 주색·보조색·회색·경고색으로 그룹핑합니다. '거의 같은데 미묘하게 다른 색' 쌍과 색 관리에 대한 생각 3문장을 함께 제출합니다." }, + + { "week": 2, "day": 2, "kind": "MAIN", "title": "요청 추적 리포트 — API 요청 5개 추적하기", "summary": "Network 탭으로 서로 다른 동작의 API 요청 5개(POST·PUT·DELETE 최소 1개 포함)를 추적표에 기록하고, 각 요청이 무슨 일을 하는지 근거와 함께 추측합니다. 실패 실험 기록과 배열 15제를 푼 day2.js를 함께 제출합니다." }, + { "week": 2, "day": 2, "kind": "EXTRA", "title": "상태 코드 카드 만들기", "summary": "200·201·301·400·401·403·404·500 여덟 개 HTTP 상태 코드를 조사해 공식 의미, 내 언어 설명, 만날 것 같은 예시를 한 줄씩 적습니다. 오늘 Network 탭에서 실제로 본 코드에 체크 표시를 해 제출합니다." }, + { "week": 2, "day": 2, "kind": "DESIGN", "title": "UI 인벤토리 ② — 타이포그래피", "summary": "개발자도구 Elements 탭으로 제목·본문·버튼 라벨 등의 폰트 속성을 기록해 Figma에 타입 스케일 표를 만듭니다. 같은 역할인데 값이 다른 불일치 사례 목록과 글자 크기 개수에 대한 생각 3문장을 제출합니다." }, + + { "week": 2, "day": 3, "kind": "MAIN", "title": "기능 해부 보고서 — 기능 하나를 끝까지 해부하기", "summary": "로그인이나 목록 조회 같은 기능 하나를 골라 화면 관찰→프론트 코드→백엔드 컨트롤러·서비스·리포지토리까지 파일·줄 단위로 추적합니다. 이해 못 한 지점 2개 이상을 솔직히 적은 보고서와 객체·Map 10제를 푼 day3.js를 제출합니다." }, + { "week": 2, "day": 3, "kind": "EXTRA", "title": "두 번째 기능 미니 해부", "summary": "본 과제에서 고르지 않은 기능 하나를 골라 화면 관찰과 전체 흐름 한 문장만 채운 미니 보고서를 작성합니다. 본 과제 대비 얼마나 빨라졌는지 소요 시간 비교 한 줄을 덧붙여 제출합니다." }, + { "week": 2, "day": 3, "kind": "DESIGN", "title": "UI 인벤토리 ③ — 컴포넌트 변형 수집", "summary": "서비스 전체에서 버튼·입력 필드·카드의 모든 변형을 스크린샷으로 수집해 Figma에 세 그룹으로 나열하고 변형마다 이름을 붙입니다. '사실 하나여야 하지 않나' 싶은 변형 쌍 후보 목록을 함께 제출합니다." }, + + { "week": 2, "day": 4, "kind": "MAIN", "title": "PR 실전 연습 — 브랜치→커밋→PR 3회전 + 동료 리뷰", "summary": "연습 저장소에서 README 오타 수정, 주석 추가, 간단 함수 작성으로 PR 3개를 템플릿에 맞춰 만듭니다. 동료 PR에 좋은 점과 질문을 담은 리뷰 코멘트를 남기고, 나만의 Git 치트시트 8줄 이상을 함께 제출합니다." }, + { "week": 2, "day": 4, "kind": "EXTRA", "title": "커밋 메시지 고쳐 쓰기", "summary": "'수정', 'asdf' 같은 나쁜 커밋 메시지 6개를 무엇을 왜 했는지 드러나는 좋은 메시지로 고쳐 씁니다. 각 메시지의 원래 문제점을 한 줄씩 적어 문서로 제출합니다." }, + { "week": 2, "day": 4, "kind": "DESIGN", "title": "UI 인벤토리 ④ — 불일치 지점 10개 찾기", "summary": "월~수에 모은 색·타이포·컴포넌트 인벤토리를 증거로 불일치 지점 10개를 골라 비교 스크린샷과 함께 정리합니다. 각 항목에 무엇이 다른지, 사용자 혼란, 통일 방향 의견과 심각도(상·중·하)를 매겨 제출합니다." }, + + { "week": 2, "day": 5, "kind": "MAIN", "title": "주간 통합 미니 챌린지 — 스택·큐 직접 구현", "summary": "Stack·Queue 클래스를 직접 구현해 빈 경우까지 검증하는 stack.js와 queue.js를 작성하고, 스택/큐 판단문제 5개를 풉니다. 서비스에서 목록이 그려지는 화면 3개를 찾아 기록하고 주간 회고 5줄을 담은 챌린지 리포트를 제출합니다." }, + { "week": 2, "day": 5, "kind": "EXTRA", "title": "스택으로 괄호 검사기 만들기", "summary": "본 과제의 Stack 클래스를 재사용해 소괄호 짝이 맞는지 판별하는 isBalanced(str) 함수를 만듭니다. 제시된 5개 입력으로 검증한 출력을 포함한 balance.js를 본 과제와 함께 제출합니다." }, + { "week": 2, "day": 5, "kind": "DESIGN", "title": "UI 개선점 5개 리포트 완성", "summary": "이번 주 불일치 10개 중 심각도가 높고 고치기 쉬운 것 위주로 개선점 5개를 골라 As-Is 스크린샷과 To-Be 시안으로 구성합니다. 문제·개선안·기대 효과 3줄씩과 표지·요약을 붙인 리포트를 제출하고 리뷰 시간에 5분 발표합니다." }, + + { "week": 3, "day": 1, "kind": "MAIN", "title": "소과제 착수 계획서 1장 쓰기", "summary": "배정받은 첫 소과제를 3번 읽고 화면에서 확인한 뒤, 무엇을·어떻게·예상 소요·불확실한 점·완료 기준을 담은 착수 계획서(plan.md)를 작성합니다. 불확실한 점을 최소 2개 적고 멘토 확인 코멘트를 반영해 제출합니다." }, + { "week": 3, "day": 1, "kind": "EXTRA", "title": "내 과제 주변 코드 탐험 노트", "summary": "계획서에 적은 파일 후보를 하나씩 열어 파일별 역할 한 줄과 호출 관계 화살표 그림을 노트에 정리합니다. 모르는 함수·문법 목록과 '내일 제일 먼저 열 파일 1개'를 적어 계획서 뒤에 붙여 제출합니다." }, + { "week": 3, "day": 1, "kind": "DESIGN", "title": "개선 과제 착수 계획 — 대상 화면과 문제 정의", "summary": "배정받은 개선 대상 화면의 모든 상태를 캡처하고 불편한 점 5개 이상에서 핵심 문제 1~2개를 골라 누가·언제·왜 불편한지 정의합니다. 참고 서비스 2개와 화·수·목 일정을 담은 디자인 착수 계획서를 멘토 확인 후 제출합니다." }, + + { "week": 3, "day": 2, "kind": "MAIN", "title": "소과제 구현 1일차 + 퇴근 전 중간보고", "summary": "작업 브랜치를 만들어 계획서의 화요일 오전 덩어리부터 구현하고, 동작하는 단위마다 커밋해 하루 3커밋 이상 푸시합니다. 사실→판단→다음 행동 틀의 중간보고 메시지를 작성해 커밋과 함께 제출합니다." }, + { "week": 3, "day": 2, "kind": "EXTRA", "title": "JS 함수 연습 10제 — 손을 멈추지 않기", "summary": "sum, isEven, reverseWords 등 함수 작성 문제 10개를 practice-w3-tue.js에 순서대로 풀고 예시 입력 2개 이상으로 확인합니다. 푸시된 파일 링크와 어디까지 풀었는지 한 줄을 중간보고에 덧붙여 제출합니다." }, + { "week": 3, "day": 2, "kind": "DESIGN", "title": "시안 1차 — 핵심 문제 하나를 화면으로", "summary": "핵심 문제 1개에 대해 현재 화면 복제 프레임과 최소 수정·과감한 수정 두 방향의 개선안을 그립니다. 비어 있음·로딩·오류 상태 중 1개 이상을 함께 그리고, 시안 1차 링크와 중간보고 메시지를 제출합니다." }, + + { "week": 3, "day": 3, "kind": "MAIN", "title": "구현 마무리 + 첫 PR 올리기", "summary": "완료 기준을 하나씩 검증하며 구현을 마무리하고, 무엇을/왜/스크린샷/확인 방법 4개 섹션을 채운 첫 PR을 올립니다. 변경 전·후 스크린샷을 붙이고 셀프 리뷰 코멘트를 1개 이상 단 PR 링크를 제출합니다." }, + { "week": 3, "day": 3, "kind": "EXTRA", "title": "내 코드 설명 문서 1장", "summary": "PR의 핵심 파일 1개를 골라 처음 보는 동료에게 설명하듯 줄 단위로 무슨 일을 하는지 쓰는 explain.md를 작성합니다. 정확히 모르는 부분은 솔직히 표시하고, 고장 난다면 어디부터 볼지 1문단을 덧붙여 제출합니다." }, + { "week": 3, "day": 3, "kind": "DESIGN", "title": "시안 2차 + 개발자 피드백 받기", "summary": "멘토와 고른 방향 1개를 다듬어 기본·비어 있음·로딩·오류 4개 상태와 버튼·입력창의 눌림/비활성 상태까지 그립니다. 개발 동료 1명에게 구현 가능 여부를 물어 피드백 3개와 반영 여부 메모를 시안 링크와 함께 제출합니다." }, + + { "week": 3, "day": 4, "kind": "MAIN", "title": "리뷰 코멘트 반영 → 머지 → 배포 확인", "summary": "리뷰 코멘트를 전부 읽고 하나 고칠 때마다 커밋 하나씩 반영하며, 모든 코멘트에 답글을 답니다. 무엇을 왜 고쳤는지 담은 반영 기록표와 머지된 PR 링크, 개발 서버에서 직접 확인한 배포 스크린샷을 제출합니다." }, + { "week": 3, "day": 4, "kind": "EXTRA", "title": "동료 PR 읽기 — 배운 점 3개", "summary": "동료의 PR 1개를 골라 본문과 변경 파일, 리뷰 대화를 처음부터 끝까지 읽고 배운 점 3개를 메모합니다. 1문장 요약과 이해 못 한 줄 개수를 적고, 가능하면 칭찬 코멘트를 남긴 뒤 메모를 제출합니다." }, + { "week": 3, "day": 4, "kind": "DESIGN", "title": "핸드오프 문서 — 시안을 만들 수 있는 명세로", "summary": "최종 시안의 색·글자를 이름 붙은 스타일로 정돈하고 간격을 8곳 이상 숫자로 표기하며, 상태별 명세표와 인터랙션 명세 3개를 씁니다. 개발자에게 '이제 만들 수 있겠어?'라는 확인을 받아 문서 링크와 함께 제출합니다." }, + + { "week": 3, "day": 5, "kind": "MAIN", "title": "첫 실무 회고 — 계획 대비 실제 1장", "summary": "월요일 계획서의 예상과 실제 결과를 비교표로 정리하고, 예상 못 한 문제마다 미리 알 수 있었는지 표시합니다. 행동으로 검증 가능한 '다음에 다르게 할 것 3가지'를 담은 retro-w3.md를 제출하고 1:1 면담에서 함께 읽습니다." }, + { "week": 3, "day": 5, "kind": "EXTRA", "title": "「프로그램의 변화」 그림 한 장 정리", "summary": "이번 주 아침 기본기 코너에서 배운 상태 변화 개념을 내 소과제 기능을 예로 '사용자 행동→상태 변화→화면 변화' 흐름 그림 한 장으로 그립니다. 가장 새로웠던 개념 1개를 2~3문장으로 설명해 회고와 함께 제출합니다." }, + { "week": 3, "day": 5, "kind": "DESIGN", "title": "디자인 회고 — 시안의 계획 대비 실제", "summary": "회고 양식의 비교표 항목을 시안 1차 완료·개발자 피드백 개수·핸드오프 완료 시점으로 바꿔 채웁니다. 개발자가 애매하다고 한 부분을 예상 못 한 문제 표에 넣고, 다음 시안 때 다르게 할 행동 문장 3가지를 적어 제출합니다." }, + + { "week": 4, "day": 1, "kind": "MAIN", "title": "전체 흐름도 완성 — 버튼 클릭에서 화면 갱신까지", "summary": "버튼 클릭→React→HTTP 요청→Spring Boot→DB→응답→화면 갱신의 7단계 흐름도를 그리고, 1~3주차 개념 10개 이상과 에러 흐름 1갈래를 연결합니다. 내 3주차 소과제 파일명 3개 이상을 상자 옆에 적고 2분 설명 리허설까지 마쳐 제출합니다." }, + { "week": 4, "day": 1, "kind": "EXTRA", "title": "가장 자신 없는 단계 1개 파고들기", "summary": "흐름도 7단계 중 설명이 가장 막혔던 단계를 골라 스스로 질문 3개를 만들고 문서·필기에서 답을 찾아 3~5줄씩 적습니다. 알게 된 내용을 흐름도에 반영해 질문·답 문서와 업데이트된 흐름도를 함께 제출합니다." }, + { "week": 4, "day": 1, "kind": "DESIGN", "title": "같은 흐름을 사용자 여정으로 그리기", "summary": "같은 시나리오를 시간축 여정 지도로 그리되 각 시점의 화면·사용자 감정·뒤에서 일어나는 일을 채웁니다. 로딩 순간의 대안 2가지와 실패 화면 1장을 스케치하고, 개발 동료의 기술 검증을 받아 제출합니다." }, + + { "week": 4, "day": 2, "kind": "MAIN", "title": "두 번째 소과제 1일차 — 계획서 작성 + 구현 시작 + 중간보고", "summary": "한 단계 90분 이내로 쪼갠 착수 계획서를 스스로 작성하고 3가지 질문으로 계획의 구멍을 먼저 공격해 고칩니다. 브랜치를 만들어 계획 단계와 연결된 커밋 2개 이상을 남기고, 1일차 중간보고를 채워 제출합니다." }, + { "week": 4, "day": 2, "kind": "EXTRA", "title": "JavaScript 근육 유지 — 배열·객체 연습 10제", "summary": "map·filter·reduce·스프레드·fetch까지 배열·객체 연습 10문제를 week4-practice.js에 풀고 console.log로 결과를 확인합니다. 7문제 이상 실행 확인을 목표로 하고 가장 어려웠던 문제와 이유를 주석으로 남겨 제출합니다." }, + { "week": 4, "day": 2, "kind": "DESIGN", "title": "내가 다시 디자인한다면 — 1일차: 문제 정의와 러프 시안", "summary": "1~3주차에 팀이 만든 화면 하나를 골라 근거 있는 문제 3개를 정의하고 개선 계획서를 씁니다. 배치와 흐름에 집중한 러프 시안 2안을 그리고 개발 동료 5분 인터뷰 기록과 함께 제출합니다." }, + + { "week": 4, "day": 3, "kind": "MAIN", "title": "두 번째 소과제 2일차 — 마무리 + PR + 셀프 리뷰 3개", "summary": "완료 조건 3개를 직접 확인하며 구현을 마무리하고, 필요하면 13시에 축소 계획을 실행한 뒤 PR을 올립니다. 읽기 쉬움·깨질 수 있음·중복 정리 세 관점의 셀프 리뷰 코멘트 3개를 코드 줄을 지정해 달아 제출합니다." }, + { "week": 4, "day": 3, "kind": "EXTRA", "title": "코드 판단 문제 8제 — 어느 쪽이 나을까?", "summary": "변수 이름, 함수 분리, 로딩 표시, 서버 검증 등 8개 판단 문제에 선택과 이유 2줄씩을 적습니다. 내 어제·오늘 코드에서 같은 상황 1개를 찾아 표시하고 문서로 제출합니다. 이유 없는 선택만으로는 인정되지 않습니다." }, + { "week": 4, "day": 3, "kind": "DESIGN", "title": "내가 다시 디자인한다면 — 2일차: 완성과 전·후 비교", "summary": "고른 대안을 실제 문구까지 채운 완성 시안으로 다듬고, 기존 화면과 나란히 놓은 전·후 비교 문서에서 바뀐 지점 3곳을 설명합니다. 내 시안의 약점 셀프 리뷰 3개와 개발 동료의 구현 난이도 확인 메모를 함께 제출합니다." }, + + { "week": 4, "day": 4, "kind": "MAIN", "title": "10분 발표 슬라이드 초안 + 소리 내어 리허설 1회", "summary": "도입 1장·개념 3장·시스템 연결 2장·배운 점 1장의 7장 구성으로 발표 자료 초안을 만들고, 이번 주 산출물을 재사용해 그림 위주로 채웁니다. 타이머를 켜고 소리 내어 리허설한 기록과 1:1 코칭 질문 2개를 함께 제출합니다." }, + { "week": 4, "day": 4, "kind": "EXTRA", "title": "예상 질문 5개 만들고 답 준비하기", "summary": "내 발표를 처음 듣는 사람이 할 법한 질문 5개(가장 받기 싫은 질문 1개 포함)를 적고 각 3~4문장 답을 준비합니다. 답을 소리 내어 말해 본 뒤 질문·답 문서를 발표 자료와 함께 제출합니다." }, + { "week": 4, "day": 4, "kind": "DESIGN", "title": "디자이너의 10분 발표 — 같은 구성, 다른 재료", "summary": "7장 구성과 리허설은 개발 과제와 동일하되 개념 3장을 사용자 여정·화면 설계·피드백 반영 같은 디자인 개념으로 채웁니다. 최소 1장은 개발 팀과 협업하며 알게 된 기술 이야기를 넣어 초안과 리허설 기록을 제출합니다." }, + + { "week": 4, "day": 5, "kind": "MAIN", "title": "미니 발표회 — 최종 점검 + 발표 + 자기평가서", "summary": "코칭 피드백을 반영해 최종 리허설과 발표 환경 점검을 마친 뒤 미니 발표회에서 10분 발표를 합니다. 발표 후 4개 항목 전부에 점수와 산출물 근거를 단 자기평가서를 작성해 최종 발표자료와 함께 제출합니다." }, + { "week": 4, "day": 5, "kind": "EXTRA", "title": "4주 산출물 아카이브 — 내 포트폴리오의 씨앗", "summary": "주차별 폴더 4개에 계획서·흐름도·PR 링크·발표자료 등 대표 산출물을 모으고, 무엇을 만들었고 뭘 배웠는지 주차별 2줄씩 적은 README를 만듭니다. 이 아카이브 폴더는 중간평가 면담 자료로도 쓰입니다." }, + { "week": 4, "day": 5, "kind": "DESIGN", "title": "발표 + 자기평가서 — 개발 팀과 동일 진행", "summary": "발표와 자기평가서는 개발 과제와 완전히 동일하게 진행하되, 구현 능력 항목만 '시안 완성도'로 바꿔 화·수 개선안 산출물을 근거로 평가합니다. 최종 발표자료와 자기평가서를 같은 파일명 규칙으로 제출합니다." } +] diff --git a/backend/src/main/resources/seed/checklist.json b/backend/src/main/resources/seed/checklist.json new file mode 100644 index 0000000..ed1e999 --- /dev/null +++ b/backend/src/main/resources/seed/checklist.json @@ -0,0 +1,55 @@ +[ + { "week": 1, "day": 1, "label": "1주차 월: 우리 회사 지도 1장 + 팀원 인터뷰 메모 3장 제출", "sortOrder": 1 }, + { "week": 1, "day": 2, "label": "1주차 화: 상황 판단 문제 10제 + 중간보고 대본 3편 제출", "sortOrder": 2 }, + { "week": 1, "day": 3, "label": "1주차 수: 이메일 2통 발송 + 메신저 질문 1건 + 전화 메모 2장 제출", "sortOrder": 3 }, + { "week": 1, "day": 4, "label": "1주차 목: 서비스 화면 탐험 리포트(화면 15장 + 궁금한 점 5개) 제출", "sortOrder": 4 }, + { "week": 1, "day": 5, "label": "1주차 금: 연습 저장소 PR 3개(feat·docs·fix) 링크 제출", "sortOrder": 5 }, + { "week": 1, "day": 5, "label": "1주차 금: 온보딩 회고 1장(Keep·Problem·Try) 제출", "sortOrder": 6 }, + + { "week": 2, "day": 1, "label": "2주차 월: React·Spring Boot 구조도 2장 + day1.js 제출", "sortOrder": 7 }, + { "week": 2, "day": 2, "label": "2주차 화: API 요청 추적표 5건 + 실패 실험 기록 + day2.js 제출", "sortOrder": 8 }, + { "week": 2, "day": 3, "label": "2주차 수: 기능 해부 보고서 1건(프론트→백엔드 흐름) + day3.js 제출", "sortOrder": 9 }, + { "week": 2, "day": 4, "label": "2주차 목: PR 3개 + 동료 리뷰 코멘트 + 나만의 Git 치트시트 제출", "sortOrder": 10 }, + { "week": 2, "day": 5, "label": "2주차 금: stack.js·queue.js 구현(빈 경우 검증 포함) 제출", "sortOrder": 11 }, + { "week": 2, "day": 5, "label": "2주차 금: 주간 챌린지 리포트(판단문제 5개 + 주간 회고 5줄) 제출", "sortOrder": 12 }, + + { "week": 3, "day": 1, "label": "3주차 월: 소과제 착수 계획서(plan.md) 멘토 확인 받아 제출", "sortOrder": 13 }, + { "week": 3, "day": 2, "label": "3주차 화: 구현 1일차 — 작업 브랜치에 커밋 3개 이상 푸시", "sortOrder": 14 }, + { "week": 3, "day": 2, "label": "3주차 화: 퇴근 전 중간보고 메시지(사실→판단→다음 행동) 발송", "sortOrder": 15 }, + { "week": 3, "day": 3, "label": "3주차 수: 첫 PR 올리기(전·후 스크린샷 + 셀프 리뷰 코멘트 포함)", "sortOrder": 16 }, + { "week": 3, "day": 4, "label": "3주차 목: 리뷰 반영 기록표 작성 후 머지 + 배포 확인 스크린샷 제출", "sortOrder": 17 }, + { "week": 3, "day": 5, "label": "3주차 금: 계획 대비 실제 회고(retro-w3.md) 제출 + 1:1 면담", "sortOrder": 18 }, + + { "week": 4, "day": 1, "label": "4주차 월: 전체 흐름도 1장(7단계 + 에러 흐름) 제출 + 2분 설명", "sortOrder": 19 }, + { "week": 4, "day": 2, "label": "4주차 화: 두 번째 소과제 착수 계획서 + 진행 커밋 2개 이상 제출", "sortOrder": 20 }, + { "week": 4, "day": 3, "label": "4주차 수: 구현 마무리 PR + 셀프 리뷰 코멘트 3개 제출", "sortOrder": 21 }, + { "week": 4, "day": 4, "label": "4주차 목: 발표 슬라이드 초안 7장 + 소리 내어 리허설 1회 기록 제출", "sortOrder": 22 }, + { "week": 4, "day": 5, "label": "4주차 금: 미니 발표회 10분 발표 완료(청중 메모 포함)", "sortOrder": 23 }, + { "week": 4, "day": 5, "label": "4주차 금: 자기평가서(항목별 점수 + 근거) 제출", "sortOrder": 24 }, + + { "week": 5, "day": null, "label": "5주차: 첫 실전 티켓 배정받고 설계 합의 완료", "sortOrder": 25 }, + { "week": 5, "day": null, "label": "5주차: 1단계 구현 완료 + 첫 30초 중간보고", "sortOrder": 26 }, + { "week": 5, "day": null, "label": "5주차: 첫 실전 티켓 PR 올리고 리뷰 코멘트 반영", "sortOrder": 27 }, + { "week": 5, "day": null, "label": "5주차: 완료 조건 채우고 배포 — 내 티켓이 실제 화면에서 동작 확인", "sortOrder": 28 }, + { "week": 5, "day": null, "label": "5주차: 주간 회고 작성 + 1:1 개인 피드백 면담 참여", "sortOrder": 29 }, + + { "week": 6, "day": null, "label": "6주차: 두 번째 티켓의 설계를 스스로 제안하고 합의", "sortOrder": 30 }, + { "week": 6, "day": null, "label": "6주차: 독립 구현 — 30분 룰로 막힘 해결 기록 남기기", "sortOrder": 31 }, + { "week": 6, "day": null, "label": "6주차: 셀프 코드리뷰 후 PR 리뷰 반영 완료", "sortOrder": 32 }, + { "week": 6, "day": null, "label": "6주차: 티켓 완료 기준 충족 — 두 번째 티켓 마무리", "sortOrder": 33 }, + { "week": 6, "day": null, "label": "6주차: 종합 프로젝트 팀 구성 · 역할과 PM 롤 확정", "sortOrder": 34 }, + + { "week": 7, "day": null, "label": "7주차: 프로젝트 주제 확정 · 역할 분담 회의 참여", "sortOrder": 35 }, + { "week": 7, "day": null, "label": "7주차: 프로젝트 기획서 작성 참여", "sortOrder": 36 }, + { "week": 7, "day": null, "label": "7주차: 화면 설계(와이어프레임) 완성", "sortOrder": 37 }, + { "week": 7, "day": null, "label": "7주차: API 설계 워크숍 + 태스크 보드 구성, 멘토 설계 리뷰 통과", "sortOrder": 38 }, + { "week": 7, "day": null, "label": "7주차: 개발 착수 — 내 담당 태스크 첫 커밋 푸시", "sortOrder": 39 }, + { "week": 7, "day": null, "label": "7주차: 중간 진행률 리뷰 · 팀 회고 참여", "sortOrder": 40 }, + + { "week": 8, "day": null, "label": "8주차: 담당 기능 완성 — 동작하는 상태까지 구현", "sortOrder": 41 }, + { "week": 8, "day": null, "label": "8주차: 프론트-백엔드 통합 작업 완료", "sortOrder": 42 }, + { "week": 8, "day": null, "label": "8주차: 버그 목록 정리 + 안정화 — 발표 흐름 끝까지 돌려 보기", "sortOrder": 43 }, + { "week": 8, "day": null, "label": "8주차: 데모 시나리오 확정 + 발표 리허설 2회 참여", "sortOrder": 44 }, + { "week": 8, "day": null, "label": "8주차: 자기평가서 제출", "sortOrder": 45 }, + { "week": 8, "day": null, "label": "8주차: 데모 발표(15분) 완료 + 최종 전환 면담 참여", "sortOrder": 46 } +] diff --git a/backend/src/main/resources/seed/documents.json b/backend/src/main/resources/seed/documents.json new file mode 100644 index 0000000..c363ba4 --- /dev/null +++ b/backend/src/main/resources/seed/documents.json @@ -0,0 +1,19 @@ +[ + { "slug": "intern-guide", "title": "AWESOMEDEV 수습 가이드", "category": "GUIDE", "week": null, "audience": "STUDENT", "filePath": "intern-guide.html", "sortOrder": 1 }, + { "slug": "cs-curriculum", "title": "CS 기본기 4주 커리큘럼", "category": "CURRICULUM", "week": null, "audience": "ALL", "filePath": "cs-curriculum.html", "sortOrder": 2 }, + { "slug": "weekly-plan", "title": "어썸데브 수습 8주 상세 교육안", "category": "PLAN", "week": null, "audience": "MENTOR", "filePath": "weekly-plan.html", "sortOrder": 3 }, + { "slug": "evaluation-rubric", "title": "어썸데브 수습 평가 루브릭", "category": "RUBRIC", "week": null, "audience": "MENTOR", "filePath": "evaluation-rubric.html", "sortOrder": 4 }, + { "slug": "week1-detailed", "title": "1주차 상세 강의안 · 온보딩 & 예절", "category": "LESSON", "week": 1, "audience": "MENTOR", "filePath": "week1-detailed.html", "sortOrder": 5 }, + { "slug": "week2-detailed", "title": "2주차 상세 강의안 — 기초 다지기", "category": "LESSON", "week": 2, "audience": "MENTOR", "filePath": "week2-detailed.html", "sortOrder": 6 }, + { "slug": "week3-detailed", "title": "3주차 상세 강의안 — 첫 소과제 실전", "category": "LESSON", "week": 3, "audience": "MENTOR", "filePath": "week3-detailed.html", "sortOrder": 7 }, + { "slug": "week4-detailed", "title": "4주차 상세 강의안 · 기본기 마무리 · 중간평가", "category": "LESSON", "week": 4, "audience": "MENTOR", "filePath": "week4-detailed.html", "sortOrder": 8 }, + { "slug": "week5-detailed", "title": "5주차 상세 강의안 · 실전 티켓 ①", "category": "LESSON", "week": 5, "audience": "MENTOR", "filePath": "week5-detailed.html", "sortOrder": 9 }, + { "slug": "week6-detailed", "title": "6주차 상세 강의안 — 실전 티켓 ② · 더 독립적으로", "category": "LESSON", "week": 6, "audience": "MENTOR", "filePath": "week6-detailed.html", "sortOrder": 10 }, + { "slug": "week7-detailed", "title": "7주차 상세 강의안 — 종합 프로젝트 기획·설계", "category": "LESSON", "week": 7, "audience": "MENTOR", "filePath": "week7-detailed.html", "sortOrder": 11 }, + { "slug": "week8-detailed", "title": "8주차 상세 강의안 · 구현 · 발표 · 최종평가", "category": "LESSON", "week": 8, "audience": "MENTOR", "filePath": "week8-detailed.html", "sortOrder": 12 }, + { "slug": "week1-assignments", "title": "1주차 일일 과제집 — 온보딩 & 예절 적용", "category": "ASSIGNMENT", "week": 1, "audience": "STUDENT", "filePath": "week1-assignments.html", "sortOrder": 13 }, + { "slug": "week2-assignments", "title": "2주차 일일 과제집 — 코드 읽기 & 자료구조 적용", "category": "ASSIGNMENT", "week": 2, "audience": "STUDENT", "filePath": "week2-assignments.html", "sortOrder": 14 }, + { "slug": "week3-assignments", "title": "3주차 일일 과제집 — 첫 소과제 실전", "category": "ASSIGNMENT", "week": 3, "audience": "STUDENT", "filePath": "week3-assignments.html", "sortOrder": 15 }, + { "slug": "week4-assignments", "title": "4주차 일일 과제집 — 통합·발표 준비", "category": "ASSIGNMENT", "week": 4, "audience": "STUDENT", "filePath": "week4-assignments.html", "sortOrder": 16 }, + { "slug": "ticket-pool", "title": "수습용 실전 티켓 풀 20", "category": "TICKET", "week": null, "audience": "MENTOR", "filePath": "ticket-pool.html", "sortOrder": 17 } +] diff --git a/backend/src/test/java/dev/awesomedev/mirim/service/ProgressServiceTest.java b/backend/src/test/java/dev/awesomedev/mirim/service/ProgressServiceTest.java new file mode 100644 index 0000000..8785bc8 --- /dev/null +++ b/backend/src/test/java/dev/awesomedev/mirim/service/ProgressServiceTest.java @@ -0,0 +1,83 @@ +package dev.awesomedev.mirim.service; + +import dev.awesomedev.mirim.domain.ChecklistItem; +import dev.awesomedev.mirim.domain.Progress; +import dev.awesomedev.mirim.domain.User; +import dev.awesomedev.mirim.repository.ChecklistItemRepository; +import dev.awesomedev.mirim.repository.ProgressRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * 이 파일이 하는 일: + * ProgressService의 토글 로직을 검증하는 단위 테스트다. + * + * 학습 포인트: 단위 테스트는 DB 없이 돌아간다. + * Mockito로 리포지토리를 "가짜(mock)"로 바꿔치기해서 + * 서비스의 분기 로직(체크/해제/404)만 빠르게 검증한다. + */ +class ProgressServiceTest { + + private ProgressRepository progressRepository; + private ChecklistItemRepository checklistItemRepository; + private ProgressService progressService; + + private User student; + private ChecklistItem item; + + @BeforeEach + void setUp() { + progressRepository = mock(ProgressRepository.class); + checklistItemRepository = mock(ChecklistItemRepository.class); + progressService = new ProgressService(progressRepository, checklistItemRepository); + + student = new User("student1", "해시값", "수습생1", "STUDENT", "DEV"); + item = new ChecklistItem(1, 1, "개발 환경 설치 완료", 1); + } + + @Test + @DisplayName("체크 안 된 항목을 토글하면 체크 기록이 저장되고 true를 반환한다") + void toggle_checks_when_not_checked() { + when(checklistItemRepository.findById(10L)).thenReturn(Optional.of(item)); + when(progressRepository.findByUserAndItem(student, item)).thenReturn(Optional.empty()); + + boolean checked = progressService.toggle(student, 10L); + + assertTrue(checked); + verify(progressRepository).save(any(Progress.class)); // 새 체크 기록이 저장됐는지 확인 + verify(progressRepository, never()).delete(any()); + } + + @Test + @DisplayName("이미 체크된 항목을 토글하면 체크 기록이 삭제되고 false를 반환한다") + void toggle_unchecks_when_already_checked() { + Progress existing = new Progress(student, item); + when(checklistItemRepository.findById(10L)).thenReturn(Optional.of(item)); + when(progressRepository.findByUserAndItem(student, item)).thenReturn(Optional.of(existing)); + + boolean checked = progressService.toggle(student, 10L); + + assertFalse(checked); + verify(progressRepository).delete(existing); // 기존 기록이 삭제됐는지 확인 + verify(progressRepository, never()).save(any()); + } + + @Test + @DisplayName("존재하지 않는 항목을 토글하면 404 예외가 발생한다") + void toggle_throws_404_when_item_missing() { + when(checklistItemRepository.findById(999L)).thenReturn(Optional.empty()); + + ResponseStatusException exception = assertThrows(ResponseStatusException.class, + () -> progressService.toggle(student, 999L)); + + assertEquals(404, exception.getStatusCode().value()); + } +} diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..bf750d1 --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,3 @@ +# 운영 환경 비밀값 — 이 파일을 .env 로 복사한 뒤 실제 값으로 바꾸세요. +# 학습 포인트: .env 는 절대 git에 커밋하지 않아요 (.gitignore에 포함). +DB_PASSWORD=여기에_강한_비밀번호 diff --git a/deploy/Caddyfile b/deploy/Caddyfile new file mode 100644 index 0000000..f44705b --- /dev/null +++ b/deploy/Caddyfile @@ -0,0 +1,35 @@ +# Caddy 설정 — 웹서버 + 리버스 프록시 + 자동 HTTPS +# 학습 포인트: 운영에서 사용자는 Caddy(80/443)만 만나요. +# - 정적 파일(React 빌드 결과)은 Caddy가 직접 서빙 +# - /api/* 요청은 백엔드 컨테이너(backend:8080)로 넘김 — 개발 때 vite 프록시와 같은 역할 +# - 도메인이 연결되면 HTTPS 인증서를 자동으로 받아요 (Let's Encrypt) + +# 도메인 접속 (DNS 연결 후 자동 HTTPS) +mirim.awesomedev.dev { + encode gzip + + handle /api/* { + reverse_proxy backend:8080 + } + + handle { + root * /srv + try_files {path} /index.html # 새로고침해도 React 라우터가 동작하게 하는 설정 + file_server + } +} + +# IP 직접 접속용 (DNS 연결 전 확인용, HTTP) +:80 { + encode gzip + + handle /api/* { + reverse_proxy backend:8080 + } + + handle { + root * /srv + try_files {path} /index.html + file_server + } +} diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml new file mode 100644 index 0000000..9b7b5ad --- /dev/null +++ b/deploy/docker-compose.prod.yml @@ -0,0 +1,83 @@ +# 운영 배포용 컴포즈 — EC2에서 이 파일 하나로 전체 스택이 뜹니다. +# 사용법: cd deploy && cp .env.example .env (비밀번호 수정) && docker compose -f docker-compose.prod.yml up -d --build +# 학습 포인트: 개발(로컬)과 운영의 차이 — +# - DB 포트를 외부에 안 엽니다(컨테이너끼리 내부 네트워크로만 통신) +# - 비밀번호는 .env 파일로 주입 (코드/이미지에 안 넣음) +# - 재부팅해도 자동 시작(restart: unless-stopped) + +services: + postgres: + image: postgres:17-alpine + container_name: mirim-postgres + environment: + POSTGRES_DB: mirim + POSTGRES_USER: mirim + POSTGRES_PASSWORD: ${DB_PASSWORD:?deploy/.env에 DB_PASSWORD를 설정하세요} + volumes: + - pgdata:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mirim"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: ../backend + container_name: mirim-backend + environment: + DB_HOST: postgres # 학습 포인트: 컨테이너끼리는 서비스 이름이 곧 주소예요 + DB_PORT: 5432 + DB_NAME: mirim + DB_USER: mirim + DB_PASSWORD: ${DB_PASSWORD} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + + # 프론트 빌드 결과물을 공유 볼륨(webroot)에 복사해 주고 종료하는 일회성 컨테이너 + frontend-build: + build: ../frontend + container_name: mirim-frontend-build + volumes: + - webroot:/out + restart: "no" + + caddy: + image: caddy:2-alpine + container_name: mirim-caddy + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - webroot:/srv:ro + - caddy-data:/data # HTTPS 인증서 보관 (재시작해도 유지) + depends_on: + - backend + - frontend-build + restart: unless-stopped + + # Gitea — 가벼운 사내 Git 서버. 학생들이 clone/commit/push를 연습하는 곳. + # 학습 포인트: GitHub 같은 서비스를 우리 서버 안에 직접 띄운 것. 웹 UI는 :3000. + gitea: + image: gitea/gitea:1.23 + container_name: mirim-gitea + environment: + GITEA__server__ROOT_URL: http://${GIT_HOST:-3.36.160.246}:3000/ + GITEA__server__SSH_PORT: "222" + GITEA__service__DISABLE_REGISTRATION: "false" # 학생 스스로 가입 가능 + GITEA__security__INSTALL_LOCK: "true" # 웹 설치 마법사 건너뛰기(설정은 env로 완료) + ports: + - "3000:3000" # 웹 UI + HTTP clone/push + - "222:22" # SSH clone/push (선택) + volumes: + - gitea-data:/data + restart: unless-stopped + +volumes: + pgdata: + webroot: + caddy-data: + gitea-data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1a6c483 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +# 로컬 개발용: DB만 도커로 띄웁니다. +# 사용법: docker compose up -d → 백엔드/프론트는 각자 실행 +# 학습 포인트: 개발 DB는 이렇게 컨테이너로 띄우면 내 PC를 어지럽히지 않아요. +services: + postgres: + image: postgres:17-alpine + container_name: mirim-postgres + environment: + POSTGRES_DB: mirim + POSTGRES_USER: mirim + POSTGRES_PASSWORD: mirim123 + ports: + - "5432:5432" + volumes: + - mirim-pgdata:/var/lib/postgresql/data + +volumes: + mirim-pgdata: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..0ba9686 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,17 @@ +# 프론트엔드 도커 이미지 (멀티스테이지 빌드) +# 학습 포인트: React 앱은 빌드하면 결국 정적 파일(html/js/css)이 돼요. +# 1단계에서 Node로 빌드하고, 2단계에선 가벼운 웹서버(여기선 Caddy가 대신 서빙하므로 +# 파일만 담는 컨테이너)로 결과물을 전달합니다. + +FROM node:22-alpine AS build +WORKDIR /build +COPY package.json package-lock.json* ./ +RUN npm install --no-audit --no-fund +COPY . . +RUN npm run build +# public/docs 의 학습 문서 17개도 dist/docs 로 함께 복사됩니다 (Vite가 public/을 그대로 포함) + +# 결과물만 담는 단계 — deploy/docker-compose.prod.yml 이 이 컨테이너에서 dist를 꺼내 씁니다 +FROM alpine:3.21 +COPY --from=build /build/dist /dist +CMD ["sh", "-c", "cp -r /dist/* /out/ && echo 'frontend dist copied' && sleep 1"] diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..e231559 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + AWESOMEDEV 수습 학습 플랫폼 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..6558504 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2183 @@ +{ + "name": "mirim-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mirim-frontend", + "version": "0.1.0", + "dependencies": { + "axios": "^1.7.9", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.1.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..0e7d3ee --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "mirim-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.7.9", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.1.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7" + } +} diff --git a/frontend/public/docs/cs-curriculum.html b/frontend/public/docs/cs-curriculum.html new file mode 100644 index 0000000..41ce2a6 --- /dev/null +++ b/frontend/public/docs/cs-curriculum.html @@ -0,0 +1,465 @@ +CS 기본기 4주 커리큘럼 + + +
+ +
+
AWESOMEDEV · 수습 교육 · Layer B
+

컴퓨터 기본기
4주 커리큘럼

+

개발 수습생 3명이 매일 아침 1시간씩, 4주 동안 컴퓨터의 뼈대를 잡는 과정이에요. 도구 사용법이 아니라 "컴퓨터가 어떻게 돌아가는가"를 배웁니다. 모든 주제는 마지막에 우리 회사 시스템으로 연결돼요.

+
+ 매일 아침 09:30–10:30 · 20일 + 강의 20분 + 실습 40분 +
+
+ + +
+
운영 방식
+

이렇게 진행해요

+

지루한 이론 통독은 안 해요. 짧게 배우고, 바로 손으로 확인하고, 우리 것에 붙입니다.

+
+
+
시간
매일 아침 1시간. 하루를 가볍게 여는 루틴으로.
+
구성
강의 20분 → 실습 40분. 듣기보다 해보기 위주.
+
실습 언어
JavaScript(React에서 쓰는 언어)로 개념 확인. 잡히면 Java(Spring Boot)로도 확인.
+
마무리
매일 3줄 회고, 금요일 미니 퀴즈로 이해도 확인.
+
+
+
+

💡 멘토를 위한 한 가지 원칙

+

모든 개념 끝에 꼭 "그래서 우리 회사에선?"을 붙여주세요. 학생이 배운 걸 우리 실제 스택(React 웹 · Spring Boot · 클라우드 · React Native · Flutter)에서 직접 보면, 죽은 지식이 살아 있는 이해가 됩니다.

+
+
+ + +
+
Week 1
+
+
+
WEEK1
+
+

컴퓨터 구조 — 컴퓨터는 결국 뭘 하는 기계인가

+
데이터가 0과 1로 저장되고, 부품들이 어떻게 협력해 프로그램을 실행하는지 이해해요.
+
+
+
+
+
1일차
+
+

2진수 · 비트 · 바이트 — 모든 건 0과 1

+
+
실습10진수 ↔ 2진수 손으로 변환. 글자가 숫자로 바뀌는 것 보기(ASCII), 색이 숫자로: #15715A 가 곧 RGB 숫자라는 것 확인.
+ +
+
+
+
+
2일차
+
+

CPU · 메모리(RAM) · 저장장치 · 입출력

+
+
실습내 PC 작업 관리자로 CPU·메모리 사용량 관찰. 비유로 정리: 책상=RAM(작업 공간), 책장=디스크(보관).
+ +
+
+
+
+
3일차
+
+

프로그램이 실행되면 벌어지는 일

+
+
실습소스코드 → 실행 과정 따라가기. 간단한 JavaScript(Node) 프로그램을 돌리며 프로세스가 메모리에 올라가는 것 관찰.
+ +
+
+
+
+
4일차
+
+

네트워크 기초 — 컴퓨터끼리 대화하는 법

+
+
실습브라우저 개발자도구 → Network 탭으로 우리 사이트가 서버에 보내는 요청/응답 직접 보기. IP·포트·HTTP 개념.
+ +
+
+
+
+
5일차
+
+

정리 & 미니 퀴즈 & 우리 시스템 세미나

+
+
확인한 주 개념 퀴즈(10문항). "왜 메모리가 부족하면 서버가 느려지나?"를 스스로 설명해보기.
+
+
+
+
+
+
🎨 디자이너 대체 트랙 (같은 시간)
+

디자인 기본기 ① — 색(색상·명도·채도, RGB/HEX), 타이포그래피 기초. + 컴퓨터가 색·이미지를 숫자로 다루는 원리는 개발자와 함께 들어요(협업 기반).

+
+
+
+ + +
+
Week 2
+
+
+
WEEK2
+
+

자료구조 — 데이터를 어떻게 담는가

+
상황마다 왜 다른 그릇(구조)을 쓰는지 감을 잡고, 직접 만들어봐요.
+
+
+
+
+
6일차
+
+

배열 · 리스트 — 순서대로 담기

+
+
실습JavaScript 배열로 할 일 목록·상품 목록 만들기 — 추가·삭제·인덱스로 꺼내기.
+ +
+
+
+
+
7일차
+
+

스택 · 큐 — 쌓기(LIFO)와 줄서기(FIFO)

+
+
실습스택·큐를 직접 구현. 스택=실행취소(Undo), 큐=먼저 온 순서대로 처리.
+ +
+
+
+
+
8일차
+
+

해시 (맵 · 객체) — 열쇠로 한 번에 찾기

+
+
실습JavaScript 객체(Map)로 아이디 → 사용자 정보 캐시 흉내내기. 목록을 뒤지는 것 vs 바로 찾는 것 속도 비교.
+ +
+
+
+
+
9일차
+
+

트리 — 계층으로 담기

+
+
실습폴더 구조·JSON을 트리로 그려보기. 부모-자식 관계 이해.
+ +
+
+
+
+
10일차
+
+

시간복잡도 맛보기 & 미니 퀴즈

+
+
확인"왜 상황마다 다른 구조를 쓰나" = 빠른 것/순서 지키는 것의 차이. Big-O 감각만 가볍게. 퀴즈 10문항.
+
+
+
+
+
+
🎨 디자이너 대체 트랙 (같은 시간)
+

디자인 기본기 ② — 레이아웃·그리드·여백·정렬 원칙. 트리 개념은 함께 들어요(화면이 컴포넌트 트리로 구성된다는 이해 = 개발 협업의 핵심).

+
+
+
+ + +
+
Week 3
+
+
+
WEEK3
+
+

프로그램의 변화 — 지금 우리가 쓰는 것이 어디서 왔나

+
언어와 소프트웨어가 어떻게 진화해 왔는지 큰 지도를 그리고, 우리 스택의 위치를 찾아요.
+
+
+
+
+
11일차
+
+

언어의 진화 — 기계어에서 사람 말까지

+
+
실습같은 동작을 저수준 vs 고수준으로 비교. "왜 점점 사람 말에 가까워졌나" 이야기 나누기.
+ +
+
+
+
+
12일차
+
+

프로그래밍 패러다임 — 절차 → 객체 → 함수

+
+
실습같은 문제를 절차지향 vs 객체지향으로 각각 짜보고 차이 느끼기.
+ +
+
+
+
+
13일차
+
+

웹의 등장 — 클라이언트와 서버로 나뉘다

+
+
실습혼자 도는 프로그램 → 웹으로. 프론트엔드 / 백엔드가 나뉜 이유와 역할 분담 정리.
+ +
+
+
+
+
14일차
+
+

아키텍처의 진화 — 모놀리식 → MSA → 클라우드 → AI

+
+
실습하나로 뭉친 프로그램 vs 잘게 나눈 마이크로서비스의 장단점 토론.
+ +
+
+
+
+
15일차
+
+

우리 서비스 구성도 그려보기 & 미니 퀴즈

+
+
확인배운 흐름으로 우리 시스템 구성도를 직접 그려보기. 퀴즈 10문항.
+
+
+
+
+
+
🎨 디자이너 대체 트랙 (같은 시간)
+

디자인 시스템 & 핸드오프 — 컴포넌트·재사용 개념, 개발자에게 넘기는 스펙(간격·상태·인터랙션) 작성법. 웹의 프론트/백 구분은 함께 들어요.

+
+
+
+ + +
+
Week 4
+
+
+
WEEK4
+
+

통합 · 복습 · 발표 — 배운 걸 내 말로

+
3주간 배운 조각들을 하나로 잇고, "내가 이해한 것"을 발표로 증명해요.
+
+
+
+
+
16–17일차
월·화
+
+

한 요청의 여행 — 클릭에서 응답까지 따라가기

+
+
실습화면에서 버튼 하나 눌렀을 때: React 웹/앱 → REST API → Spring Boot 서버 → DB → 응답까지 전 과정을 추적하고 그림으로 정리.
+ +
+
+
+
+
18일차
+
+

발표 주제 정하고 깊게 파기

+
+
실습각자 가장 흥미로웠던 주제 하나 선택(예: "캐시는 왜 빠른가", "React와 서버는 어떻게 대화하나", "웹과 앱은 뭐가 다른가") — 자료 조사.
+
+
+
+
+
19일차
+
+

발표 자료 만들기

+
+
실습10분 분량 슬라이드 작성. 어려운 말 없이, 내 말로 설명하는 걸 목표로. (멘토가 1:1 코칭)
+
+
+
+
+
20일차
+
+

미니 발표회 🎤 — Layer B의 졸업식

+
+
발표한 사람당 10분 "내가 이해한 컴퓨터 / 우리 시스템". 팀 앞에서 발표하고 질문받기.
+ +
+
+
+
+
+
🎨 디자이너 대체 트랙 (같은 시간)
+

디자이너도 함께 요청 흐름을 따라가고(협업 이해), 발표는 "내가 다시 디자인한다면" 주제로 — 우리가 다루는 서비스 화면 하나를 골라 개선안을 발표해요.

+
+
+
+ + +
+
이해도 확인
+

어떻게 평가하나

+

시험 점수가 목적이 아니에요. "이해했는지, 그리고 내 말로 설명할 수 있는지"를 봅니다.

+
+
매일
3줄 회고 — 오늘 배운 것 / 이해 안 된 것 / 궁금한 것. 이해가 막힌 지점을 멘토가 바로 캐치.
+
매주 금
미니 퀴즈 10문항 — 맞고 틀림보다, 틀린 개념을 다음 주에 보완하는 용도.
+
4주차
미니 발표 (가장 중요) — 어려운 용어 없이 자기 말로 설명하면 진짜 이해한 것. 암기가 아니라 이해를 봐요.
+
상시
"그래서 우리 회사에선?" 대답 — 배운 개념을 우리 실제 개발(React·Spring Boot·앱·클라우드)에 연결해 말할 수 있는지가 최종 목표.
+
+
+

⚠️ 눈높이 주의

+

고교생에게 Big-O 증명이나 CPU 파이프라인 같은 깊은 이론은 목표가 아니에요. "왜 이렇게 만들어졌고, 우리 시스템의 어디에 있는가"라는 큰 그림과 직관을 잡는 게 이 4주의 목적입니다. 못 따라오면 속도를 늦춰도 괜찮아요.

+
+
+ +
+ AWESOMEDEV · 수습 교육 · Layer B 커리큘럼 + 2026 · 수습 가이드와 함께 사용 +
+ +
diff --git a/frontend/public/docs/evaluation-rubric.html b/frontend/public/docs/evaluation-rubric.html new file mode 100644 index 0000000..7acfdad --- /dev/null +++ b/frontend/public/docs/evaluation-rubric.html @@ -0,0 +1,361 @@ +어썸데브 수습 평가 루브릭 + + +
+ +
+
AWESOMEDEV · 수습 평가
+

수습 평가 루브릭 & 기록지

+

멘토가 인쇄해서 바로 채점·기록하는 평가 양식이에요. 5개 축 루브릭 · 주간 1:1 기록 · 자기평가 · 최종 전환 판정까지 한 세트로 담았어요. 점수는 인상이 아니라 매주 쌓인 근거로 매깁니다.

+
+
수습생 이름
+
직무
+
멘토
+
평가 시점
+
+
+ + +
+
평가 원칙
+

이 점수로 채용을 결정해요

+
+
    +
  • 1
    인상이 아닌 근거로. 매주 1:1 기록(8회)이 쌓이면 최종 점수가 데이터가 됩니다. 막판 인상으로 뒤집지 않기.
  • +
  • 2
    완성도보다 성장 속도·태도. 고졸 신입에게 지금의 실력보다 6개월 뒤 그림을 봅니다. 가중치가 그렇게 설계돼 있어요.
  • +
  • 3
    나쁜 신호는 4주차 안에. 전환이 어렵겠다는 판단은 중간평가에서 반드시 공유합니다.
  • +
  • 4
    레벨을 고르고 점수를 적어요. 각 축에서 가장 가까운 레벨에 ○ 표시 후, 제안 점수 범위 안에서 최종 점수를 기입.
  • +
+
+
+ + +
+
루브릭 · 100점
+

5개 축으로 채점

+

각 축마다 4개 레벨 중 하나에 ○, 오른쪽에 최종 점수를 적으세요.

+ + +
+
+
성장 속도 · 학습력
+
배점 25점 · 최우선
+
+
+
Lv1 미흡

같은 피드백을 반복하고, 새 개념 습득이 더디다.

~13점
+
Lv2 보통

알려주면 익히지만 스스로 확장은 약하다.

14–18점
+
Lv3 충족

피드백을 다음에 반영하고, 새 개념을 무리 없이 흡수한다.

19–22점
+
Lv4 우수

한 번 배우면 스스로 응용·확장하고, 남에게 설명할 수 있다.

23–25점
+
+
점수/ 25
+
+ + +
+
+
기본기 · 직무 역량
+
배점 25점
+
+
+
Lv1 미흡

기본 개념 이해가 부족하고, 과제 완성이 어렵다.

~13점
+
Lv2 보통

도움을 받아 과제를 완성한다.

14–18점
+
Lv3 충족

배운 기본기를 이해하고, 주어진 과제를 스스로 완성한다.

19–22점
+
Lv4 우수

기본기를 우리 업무(React·Spring)에 연결해 설명하고, 결과물이 안정적이다.

23–25점
+
+
점수/ 25
+
+ + +
+
+
협업 · 소통
+
배점 20점
+
+
+
Lv1 미흡

보고·질문이 없고, 리뷰 반영이 안 된다.

~10점
+
Lv2 보통

시키면 보고하나, 질문의 맥락이 부족하다.

11–14점
+
Lv3 충족

중간보고를 하고, 30분 룰·3요소로 질문하며 리뷰를 반영한다.

15–17점
+
Lv4 우수

먼저 공유하고 동료를 도우며, 소통이 팀에 도움이 된다.

18–20점
+
+
점수/ 20
+
+ + +
+
+
태도 · 예절 · 책임감
+
배점 20점
+
+
+
Lv1 미흡

시간·약속을 어기고, 기본 예절·보안 인식이 부족하다.

~10점
+
Lv2 보통

대체로 지키나 가끔 놓친다.

11–14점
+
Lv3 충족

시간·예절·보안을 지키고, 맡은 일을 끝까지 한다.

15–17점
+
Lv4 우수

주도적이고 책임감 있으며, 팀 분위기에 긍정적이다.

18–20점
+
+
점수/ 20
+
+ + +
+
+
성장 가능성
+
배점 10점
+
+
+
Lv1 미흡

6개월 뒤 함께 일하는 그림이 그려지지 않는다.

~5점
+
Lv2 보통

가능성은 있으나 확신은 어렵다.

6–7점
+
Lv3 충족

꾸준히 성장 중이며, 함께 일하는 그림이 그려진다.

8–9점
+
Lv4 우수

기대 이상으로, 빠르게 전력이 될 잠재력이 보인다.

10점
+
+
점수/ 10
+
+
+ + +
+
합산
+

총점과 판정 기준

+
+ + + + + + + + + + +
평가 축배점획득 점수
① 성장 속도 · 학습력25
② 기본기 · 직무 역량25
③ 협업 · 소통20
④ 태도 · 예절 · 책임감20
⑤ 성장 가능성10
합계100
+
+
+
75점 이상 · 전환 권장
기대에 부합. 정규직 전환을 추천.
+
60–74점 · 조건부
가능성 있음. 추가 관찰·조건부 전환 검토.
+
60점 미만 · 재검토
전환 어려움. 사유를 명확히 정리.
+
+
+

⚠️ 점수 구간은 가이드일 뿐

+

구간은 참고선이에요. 총점이 낮아도 ④ 태도·⑤ 성장 가능성이 뛰어나면 전환할 수 있고, 총점이 높아도 태도에 큰 문제가 있으면 재검토합니다. 숫자보다 사람을 보되, 숫자로 근거를 남기세요. 회사 상황에 맞게 구간은 조정 가능합니다.

+
+
+ + +
+
주간 기록
+

주간 1:1 기록지 (매주 · 8회)

+

한 주에 하나씩. 잘한 것 1 · 아쉬운 것 1을 매주 기록하면 최종 평가의 근거가 됩니다. 필요한 만큼 복사해 쓰세요.

+
+
+
◻ ___주차 · 날짜 __________
+
+
이번 주 한 일
+
잘한 것 (1)
+
아쉬운 것 · 개선점 (1)
+
전달한 피드백
+
다음 주 목표
+
+
+

※ 인쇄 시 이 블록을 8부 준비하거나, 주차별로 페이지를 나눠 사용하세요. 4주차·8주차 기록엔 중간/최종 평가 결과도 함께 남깁니다.

+
+
+ + +
+
자기평가
+

자기평가서 (수습생 작성 · 4주차 · 8주차)

+

수습생이 직접 작성해요. 자기객관화 능력도 평가의 한 부분입니다.

+
+
+
① 이 기간 내가 잘했다고 생각하는 것
+
② 어려웠던 것 · 아직 부족한 것
+
③ 새로 배운 것 (기술 · 태도 모두)
+
④ 다음 기간에 도전하고 싶은 것
+
⑤ 스스로 매기는 점수와 이유 (100점 만점)
+
+
+
+ + +
+
최종 판정
+

전환 결정 (8주차 · 멘토+대표)

+
+
+
정규직 전환
기대에 부합. 다음 3개월 성장 목표를 함께 설정한다.
+
조건부 전환 · 추가 관찰
가능성 있음. 관찰 기간·보완 목표를 명시한다.
+
미전환
전환 어려움. 사유를 구체적으로 정리해 정중히 전달한다.
+
+
+
종합 의견
+
전환 시 다음 3개월 성장 목표
+
+
+
멘토 서명 / 날짜
+
대표 확인 / 날짜
+
+
+
+ +
+ AWESOMEDEV · 수습 평가 루브릭 & 기록지 (멘토용) + 2026 · 8주 교육 운영안과 함께 사용 +
+ +
diff --git a/frontend/public/docs/intern-guide.html b/frontend/public/docs/intern-guide.html new file mode 100644 index 0000000..93fcda7 --- /dev/null +++ b/frontend/public/docs/intern-guide.html @@ -0,0 +1,580 @@ +AWESOMEDEV 수습 가이드 + + +
+ +
+
AWESOMEDEV
+

반가워요.
사회인이 되는 첫걸음, 함께 갈게요.

+

이 가이드는 첫 출근날 받는 안내서예요. 개발부터 시키지 않아요. 먼저 회사에서 어떻게 지내는지, 그리고 컴퓨터가 어떻게 돌아가는지라는 두 개의 바닥부터 차근차근 배웁니다. 하나하나 다 알려줄 테니 몰라도 괜찮아요.

+
+ 수습 · 9월 1일 – 10월 31일 (8주) + 개발 3 · 디자인 1 +
+
+ + + + +
+
About us
+

우리 회사, 어썸데브

+
+

“고객과 함께 더 나은 세상을 만들어 나가는 기업”혁신과 도전 · Innovation & Challenge

+

㈜어썸데브(AWESOMEDEV)는 2023년 1월에 시작한 소프트웨어 개발 회사예요. 아직 젊은 회사이고, 그래서 여러분처럼 새로 합류하는 사람의 손길이 그대로 회사의 색이 됩니다.

+

우리는 KT엠모바일 · KT알파 · 핀업 같은 기업의 서비스를 함께 만들고, 안정적으로 돌아가도록 유지·발전시켜요. 통신과 금융처럼 수많은 사람이 매일 쓰는 시스템을 다루는 일이라, 작은 코드 한 줄에도 책임이 따르고 그만큼 배우는 것도 많아요. 여러분이 짤 코드와 만들 화면이 실제 사용자에게 가닿습니다.

+
+
설립
2023년 1월
+
하는 일
소프트웨어 개발 · 유지보수
+
함께하는 곳
KT엠모바일 · KT알파 · 핀업
+
+
+
+ + +
+
대표 인사
+

대표가 먼저, 한마디

+
+

반가워요. 어썸데브 대표 장익준입니다.

+

저도 스무 살 무렵부터 개발을 시작해, 20년 넘게 코드를 짜고 시스템을 만들어 왔어요. 그래서 여러분이 지금 느끼는 설렘도, 두려움도 잘 압니다. 처음엔 누구나 서툴러요 — 저도 그랬고요.

+

여러분이 아직 학생이라는 걸 알고 뽑았어요. 지금 실력이 완성돼 있길 기대하지 않아요. 제가 정말 보는 건 얼마나 빨리 배우는지, 그리고 함께 일하기 좋은 사람인지예요.

+

회사 생활은 학교와 달라서, 처음엔 인사 한 번, 이메일 한 줄도 어색할 수 있어요. 그건 부족해서가 아니라 아직 안 배웠기 때문이에요. 이 가이드가, 그리고 여러분의 멘토가 하나하나 알려줄 거예요.

+

실수해도 괜찮아요. 대신 딱 하나만 — 모르면 혼자 끙끙대지 말고 물어보기. 그게 이곳에서 가장 환영받는 태도예요. 두 달 뒤 훌쩍 성장해 있을 여러분을 기대할게요. 잘 부탁해요.

+

— 어썸데브 대표 장익준 드림

+
+
+ + +
+
The plan
+

두 달을, 이 순서로 배워요

+

개발 실무는 맨 마지막이에요. 그 전에 두 개의 바닥을 먼저 깔아요. 바닥이 튼튼해야 그 위에 뭘 쌓아도 무너지지 않으니까요.

+
+
+
A
+
+

회사 생활 · 기본 예절 첫 주 집중 + 8주 내내

+

인사하는 법, 보고하는 법, 이메일·전화·메신저 쓰는 법. 실력보다 먼저 갖춰야 할 '사회인의 태도'예요. 제일 먼저, 제일 오래 봅니다.

+
+
+
+
B
+
+

컴퓨터 기본기 1~4주차 매일 아침

+

컴퓨터 구조 · 자료구조 · 프로그램의 변화. 도구 사용법은 검색하면 나오지만, 이 뼈대는 지금 잡아야 평생 안 흔들려요.

+
+
+
+
C
+
+

실무 3주차부터 조금씩

+

작은 과제 → 실전 티켓 → 팀 프로젝트. 기본기를 배운 위에서 하니까 '왜 이렇게 하는지'를 이해하며 할 수 있어요.

+
+
+
+
+ + +
+
Layer A · 제일 중요
+

회사 생활 · 기본 예절

+

여기 나온 건 '잘하는 법'이 아니라 '기본으로 하는 것'들이에요. 하나씩 몸에 익히면 됩니다.

+ +
+ + +
+
A1

하루의 기본

+

왜? 회사의 신뢰는 대단한 성과가 아니라 매일의 작은 기본에서 쌓여요.

+
    +
  • 출근은 시작 시간 10분 전까지. 자리에 앉아 준비를 마친 상태가 정시예요.늦을 것 같으면 늦기 전에 멘토에게 먼저 연락 — "몇 분 늦습니다, 죄송합니다."
  • +
  • 출근하면 먼저 인사. "안녕하세요!" 밝게 한마디. 퇴근할 땐 "먼저 들어가겠습니다. 수고하셨습니다."
  • +
  • 호칭은 이름 + 직급/님. "김선임님", "박대리님". 잘 모르면 "○○님"으로. 반말·별명 금지.
  • +
  • 복장은 깔끔하고 단정하게. 첫 주엔 무난하게 입고, 팀 분위기를 보고 맞춰가요.
  • +
  • 자리와 공용 공간은 깨끗하게. 회의실·탕비실은 쓴 그대로 두지 않기.
  • +
+
+ +
+ + +
+
A2

보고·연락·상담 — 혼자 판단하지 않기

+

왜? 수습생이 가장 많이 하는 실수는 '혼자 끌어안다가 늦게 터뜨리는 것'이에요. 일이 잘돼도, 안 돼도 중간에 공유하는 게 핵심이에요.

+
    +
  • 보고 — 일을 마치면 바로 결과를 알려요. 끝났는데 말 안 하면 안 한 것과 같아요.
  • +
  • 연락 — 진행 중에도 상황을 공유해요. 특히 문제가 생기면 즉시. 숨기면 더 커져요.
  • +
  • 상담 — 판단이 안 서면 혼자 정하지 말고 먼저 물어봐요. "이렇게 해도 될까요?"
  • +
+
+
중간 보고, 이렇게 한마디면 충분해요
+
"김선임님, 배정해주신 로그인 버그 건 중간 공유드려요.
+원인은 찾았고 지금 수정 중입니다.
+오늘 오후까지 PR 올릴 수 있을 것 같습니다.
+혹시 더 급한 게 있으면 말씀해 주세요."
+
+
+ +
+ + +
+
A3

이메일 쓰는 법

+

왜? 이메일은 회사의 공식 기록이에요. 순서만 지키면 누구나 깔끔하게 쓸 수 있어요.

+
+
기본 형식 — 그대로 따라 쓰세요
+
제목: [수습] 9월 1주차 진행 상황 공유 — 홍길동
+
+안녕하세요, 김선임님.          ← 인사 + 받는 사람
+개발팀 수습 홍길동입니다.        ← 내 소개
+
+배정해주신 로그인 버그 수정 건,   ← 용건(무엇을)
+원인을 찾아 수정 후 PR 올렸습니다.
+확인 부탁드립니다.               ← 상대가 할 일
+
+- PR 링크: (주소)
+- 남은 작업: 테스트 1개 추가 (내일 오전 완료 예정)
+
+감사합니다.                    ← 맺음
+홍길동 드림                       ← 서명
+
+
+
O이렇게
    +
  • 제목만 봐도 내용을 알 수 있게
  • +
  • 용건은 짧게, 결론부터
  • +
  • 보내기 전에 오타·받는 사람 한 번 확인
  • +
+
X이건 피하기
    +
  • 제목 없이 보내기
  • +
  • "저기요", "ㅇㅋ" 같은 말투
  • +
  • 인사·소개 없이 용건만 툭
  • +
+
+
+ +
+ + +
+
A4

메신저(슬랙·사내 채팅) 예절

+

왜? 빠르고 편하지만, 편한 만큼 실수도 쉬워요. 상대의 시간을 존중하는 게 핵심이에요.

+
+
O이렇게
    +
  • 용건을 한 번에 정리해서 보내기
  • +
  • 질문엔 배경도 같이 (아래 A5·30분 룰 참고)
  • +
  • 읽었으면 이모지·"확인했습니다"로 반응
  • +
  • 급하지 않으면 업무시간 안에
  • +
+
X이건 피하기
    +
  • "안녕하세요" 만 보내고 기다리게 하기
  • +
  • 한 문장을 여러 번 끊어 보내 알림 폭탄
  • +
  • 답 없다고 재촉하기 (5분 만에 "?")
  • +
  • 밤·주말에 급하지 않은 연락
  • +
+
+
+ +
+ + +
+
A5

전화 응대

+

왜? 전화는 순간이라 당황하기 쉬워요. 첫 마디와 메모할 것만 정해두면 떨 필요 없어요.

+
+
받을 때 — 첫 마디는 정해져 있어요
+
"네, AWESOMEDEV 개발팀 홍길동입니다."
+
+── 들으면서 메모할 3가지 ──
+ · 누가 (회사·이름)
+ · 무엇을 (용건)
+ · 언제까지 (기한)
+
+잘 모르는 내용이면:
+"확인 후에 다시 연락드리겠습니다.
+ 성함과 연락처 남겨주시겠어요?"
+→ 그리고 담당자/멘토에게 바로 전달
+
+
+ +
+ + +
+
A6

회의 태도 & 피드백 받는 자세

+

왜? 회의와 피드백은 혼나는 자리가 아니라 배우는 자리예요.

+
    +
  • 회의엔 노트를 들고. 정해진 것·내가 할 일을 적어요. 나중에 "뭐였죠?" 안 하려고.
  • +
  • 모르면 그 자리에서 질문. 모르는 채 끄덕이는 게 제일 위험해요.
  • +
  • 피드백은 선물로. 코드리뷰·지적은 여러분을 키우려는 거예요. 변명보다 "왜 그런지"를 먼저 이해해요.
  • +
  • 지적받은 건 메모하고, 같은 걸 두 번 듣지 않기. 이게 성장의 증거예요.
  • +
+
+ +
+ + +
+
A7

정보 · 보안

+

왜? 우리는 통신·금융처럼 수많은 사람의 개인정보가 오가는 서비스를 다뤄요. 여기서만큼은 작은 실수도 크게 번질 수 있어요.

+
    +
  • 회사 코드·자료·고객 정보를 밖으로 가져가지 않기. 개인 계정·개인 PC로 복사 금지.
  • +
  • 비밀번호·접근 토큰을 채팅이나 문서에 붙여넣지 않기.
  • +
  • 헷갈리면 공유하기 전에 먼저 물어보기. "이거 밖에 공유해도 되나요?"
  • +
+
+ +
+
+ + +
+
Layer B
+

컴퓨터 기본기

+

1~4주차 매일 아침 1시간, 배우고 → 손으로 확인해요. 모든 주제 끝엔 "그래서 우리 회사에선?"으로 연결해서, 죽은 지식이 안 되게 합니다. (디자이너는 이 시간에 디자인 기본기 + 개발자와 협업하는 법을 배워요.)

+
+
+

① 컴퓨터 구조 — 컴퓨터는 결국 뭘 하는 기계인가

+
2진수·비트·바이트로 데이터가 0과 1로 저장되는 원리 / CPU·메모리(RAM)·저장장치·입출력의 역할과 차이 / 프로그램이 실행되면 벌어지는 일(메모리에 올라가 CPU가 처리).
+ +
+
+

② 자료구조 — 데이터를 어떻게 담는가

+
배열·리스트·스택·큐·해시(맵)·트리 / "왜 상황마다 다른 구조를 쓰나" = 무엇이 빠르고 무엇이 순서를 지키는가(시간복잡도 감각). 파이썬으로 스택·큐를 직접 만들어봐요.
+ +
+
+

③ 프로그램의 변화 — 지금 우리가 쓰는 것이 어디서 왔나

+
기계어 → 어셈블리 → 고급 언어(왜 점점 사람 말에 가까워졌나) / 절차지향 → 객체지향 → 함수형 / 혼자 도는 프로그램 → 웹 → 마이크로서비스 → 클라우드 → AI 시대.
+ +
+
+
+ + +
+
Layer C
+

실무는 이렇게 이어져요

+

기본기를 배운 위에서, 작은 것부터 진짜 일에 가까워집니다.

+
+ + + + + + + + + + +
주차A · 예절B · 기본기C · 실무
1주차집중 교육컴퓨터 구조환경 세팅
2주차상시 관찰자료구조코드·화면 읽기
3주차상시프로그램의 변화첫 소과제
4주차상시복습 · 미니 발표소과제
5–6주차상시실무에 녹임실전 티켓
7–8주차상시종합 프로젝트 · 발표
+
+
+ + +
+
If you're stuck
+

막혔을 때: 30분 룰

+
+
30
+
+

혼자 30분, 그다음엔 손을 드세요

+

스스로 부딪혀 보는 30분은 성장에 꼭 필요해요. 하지만 그 이상 혼자 헤매는 건 손해예요. 물어보기 전에 이 3가지만 정리해 오면 완벽해요.

+
    +
  • 하려던 것 — 무엇을 하려고 했는지
  • +
  • 시도한 것 — 어떻게 해봤는지
  • +
  • 일어난 일 — 에러 메시지나 이상한 결과 (스크린샷이면 더 좋아요)
  • +
+
+
+
+
+
이렇게 말고
+

"이거 안 돼요. 어떻게 해요?"

+
+
+
이렇게
+

"로그인 붙이는 중인데 토큰을 이렇게 보냈더니 401이 떠요. 헤더 이름이 맞는지 봐주실 수 있을까요? (스크린샷 첨부)"

+
+
+
+ + +
+
How we look at you
+

평가는 이렇게, 놀랄 일은 없어요

+

두 달 뒤 갑자기 결과만 통보하지 않아요. 매주 1:1로 "잘한 것 하나, 더 해볼 것 하나"를 이야기하고, 4주차에 중간 점검을 해요.

+
+
성장 속도 · 학습력같은 피드백을 반복하지 않는지, 새 걸 얼마나 빨리 흡수하는지
25%
+
기본기 · 직무 역량기본기를 이해했는지, 맡은 과제의 완성도
25%
+
협업 · 소통보고·연락·상담, 질문의 질, 리뷰 반영
20%
+
태도 · 예절 · 책임감시간 지키기, 기본 예절, 끝까지 하기, 보안 준수
20%
+
성장 가능성6개월 뒤 함께 일하는 그림이 그려지는지
10%
+
+

가장 무겁게 보는 건 지금의 완성도가 아니라 성장 속도와 태도예요. 지금 못하는 건 당연하니까요.

+
+ + +
+
Week 1 checklist
+

첫 주에 이건 꼭

+
+
    +
  • 출근 첫 인사를 팀원들에게 — "안녕하세요, 오늘부터 수습 시작한 ○○○입니다!"
  • +
  • 내 멘토가 누구인지 알고, 연락 방법과 자리 확인하기
  • +
  • 계정·장비 세팅 완료 (Gitea, 메신저, 사내 위키, 개발 환경)
  • +
  • 이메일 한 통 연습 — 멘토에게 첫 인사 메일을 A3 형식대로 써보기
  • +
  • 우리 제품을 직접 써보기 — 사용자처럼 클릭하며 흐름 익히기
  • +
  • 하루 정리 메모 시작 — 오늘 배운 것 / 막힌 것 3줄
  • +
  • 온보딩 회고 한 장 쓰기 — 뭘 배웠고 뭐가 어려웠는지
  • +
+
+
+ + +
+
Who to ask
+

이럴 땐 이 사람에게

+
+
내 멘토
___________
일·과제 관련 무엇이든 먼저
+
팀 리드
___________
방향·우선순위가 헷갈릴 때
+
인사 · 총무
___________
근태·급여·계약·장비 관련
+
+
+ +
+ AWESOMEDEV · 수습 온보딩 가이드 + 2026 · 개정 시 최신본으로 대체 +
+ +
diff --git a/frontend/public/docs/ticket-pool.html b/frontend/public/docs/ticket-pool.html new file mode 100644 index 0000000..f68e447 --- /dev/null +++ b/frontend/public/docs/ticket-pool.html @@ -0,0 +1,442 @@ +수습용 실전 티켓 풀 20 · AWESOMEDEV + + +
+ +
+
AWESOMEDEV · 수습 실전 티켓 풀
+

실전 티켓 풀 20
템플릿 & 아이디어 카탈로그

+

5~6주차에 수습생에게 줄 실전 티켓을 미리 준비하는 문서예요. 아래 20개는 어떤 프로젝트에나 있는 안전한 티켓 패턴이고, 각 패턴마다 "우리 프로젝트에서 찾는 법"이 붙어 있어요. 멘토가 실제 코드에서 해당 건을 찾아 뒤쪽 양식으로 구체화하면 티켓 풀이 완성됩니다.

+
+ 프론트 7 · 백엔드 6 · 품질 4 · 디자인 3 + 난이도 ★(반나절~1일) · ★★(2~3일) + 멘토용 · D-7까지 구체화 +
+
+ + +
+
티켓 고르는 기준
+

수습용 티켓의 4가지 조건

+
+
    +
  • 1
    망가뜨려도 복구 쉬운 것. 롤백 가능하고, 스테이징에서 검증 가능하고, 결제·인증·개인정보 같은 민감 영역이 아닐 것.
  • +
  • 2
    범위가 한 문장으로 설명되는 것. "이 화면의 이 부분을 이렇게" — 파일 3~4개 이내로 끝나는 크기. 아키텍처를 이해해야만 하는 건 아직 아님.
  • +
  • 3
    완료 조건(DoD)을 명확히 쓸 수 있는 것. "되면 이렇게 보인다/동작한다"를 미리 적을 수 없으면 수습용이 아님.
  • +
  • 4
    실제로 가치 있는 것. 연습용 가짜 일이 아니라, 머지되면 정말 제품이 나아지는 일. (이게 동기부여의 핵심이에요)
  • +
+
+

주지 말 것: 운영 DB 마이그레이션 · 인증/권한 로직 · 결제 · 배포 설정 · 고객사 SLA가 걸린 긴급 건 · "리팩터링 알아서" 같은 범위 무한 티켓.

+
+
+
+ + +
+
🖥️
+

프론트엔드 (React) — 7개

화면에서 보이는 것부터. 결과가 눈에 보여서 첫 실전에 가장 좋아요.
+
+ +
+
FE-01

빈 상태(Empty State) 화면 추가

0.5~1일
+
+
무엇을데이터가 없을 때 휑하게 비는 목록/테이블에 "아직 항목이 없어요 + 안내 문구/버튼"을 보여주기.
+
찾는 법각 화면에서 데이터 0건으로 조회해 보기. .map() 돌리는 목록 중 length가 0일 때 분기가 없는 곳이 후보.
+
완료 조건0건일 때 지정된 문구·아이콘 노출, 1건 이상이면 기존과 동일, 스크린샷 첨부된 PR.
+
멘토 준비대상 화면 지정, 문구 확정(디자이너 수습생과 협업시키면 좋음).
+
+
+ +
+
FE-02

로딩 표시(스피너/스켈레톤) 추가

0.5~1일
+
+
무엇을API 응답을 기다리는 동안 아무것도 안 보이는 화면에 로딩 상태 표시 넣기.
+
찾는 법네트워크를 느리게(개발자도구 throttling) 하고 화면 이동 — 흰 화면이나 깜빡임이 보이는 곳이 후보.
+
완료 조건로딩 중 표시 노출, 로드 완료 시 자연스럽게 교체, 에러 시 로딩이 무한히 남지 않음.
+
멘토 준비기존 공용 로딩 컴포넌트가 있으면 알려주기(새로 만들지 재사용할지).
+
+
+ +
+
FE-03

폼 입력 유효성 검사 보강

1일
+
+
무엇을빈 값·형식 오류를 제출 후에야 알려주는 폼에, 입력 시점 안내 메시지 추가(예: 이메일 형식, 필수값).
+
찾는 법각 폼에 일부러 빈 값/이상한 값 넣고 제출해 보기 — 서버 에러로만 알게 되는 폼이 후보.
+
완료 조건정의된 규칙별 에러 문구 노출, 통과 시 정상 제출, 에러 문구는 한국어로 친절하게.
+
멘토 준비검사 규칙 목록 확정(무엇을 필수로, 어떤 형식으로).
+
+
+ +
+
FE-04

목록 정렬/필터 옵션 추가

★★2일
+
+
무엇을있는 목록 화면에 정렬(최신순/이름순) 또는 필터(상태별) 드롭다운 1개 추가.
+
찾는 법운영/관리 화면 중 항목이 20개 이상 쌓이는 목록에서 사용자가 스크롤로만 찾는 곳.
+
완료 조건옵션 변경 시 목록 갱신, 새로고침해도 기본값 정상, 기존 기능 회귀 없음.
+
멘토 준비정렬/필터 기준 확정, 서버 쿼리 지원 여부 확인(클라이언트 정렬로 충분한지).
+
+
+ +
+
FE-05

에러 화면/토스트 메시지 개선

0.5~1일
+
+
무엇을"Error" 또는 영문 원문이 그대로 뜨는 실패 메시지를, 사용자가 이해할 한국어 안내로 교체.
+
찾는 법서버 꺼두고/네트워크 끊고 주요 동작 실행 — 알 수 없는 문구가 뜨는 곳 수집.
+
완료 조건실패 상황별 지정 문구 노출, 콘솔 에러는 유지(디버깅용), 문구 목록 문서화.
+
멘토 준비상황별 문구 톤 가이드 한 줄(사과·원인·해결 순).
+
+
+ +
+
FE-06

모바일 화면 깨짐 수정

★★1~2일
+
+
무엇을좁은 화면에서 넘치거나 겹치는 요소(테이블·버튼 줄바꿈 등) 1~2군데 반응형 수정.
+
찾는 법개발자도구 모바일 뷰(375px)로 주요 화면 전부 훑기 — 가로 스크롤 생기는 곳이 후보.
+
완료 조건375px·768px·데스크톱 3구간 스크린샷 첨부, 기존 데스크톱 레이아웃 변화 없음.
+
멘토 준비깨지는 화면 목록 미리 수집(학생이 찾는 것부터 시키면 반나절 추가).
+
+
+ +
+
FE-07

공통 컴포넌트로 중복 UI 정리

★★2~3일
+
+
무엇을거의 똑같은 버튼/카드/모달이 여러 파일에 복붙돼 있는 것을 컴포넌트 1개로 추출해 교체.
+
찾는 법비슷한 JSX 덩어리를 검색(클래스명·문구로 grep) — 3곳 이상 반복되는 블록이 후보.
+
완료 조건추출한 컴포넌트 1개 + 기존 사용처 전부 교체, 화면 픽셀 변화 없음(전후 스크린샷).
+
멘토 준비대상 지정 필수(범위 폭주 방지). props 설계는 착수 계획서에서 함께 리뷰.
+
+
+ + +
+
⚙️
+

백엔드 (Spring Boot) — 6개

서버 쪽 안전 지대. 응답·검증·로그처럼 되돌리기 쉬운 것 위주.
+
+ +
+
BE-01

API 응답에 필드 1개 추가

1일
+
+
무엇을화면에서 필요해진 값(예: 등록일, 개수, 상태명)을 기존 조회 API 응답에 추가하고 프론트 반영까지.
+
찾는 법프론트에서 두 번 요청해서 조합하거나, 하드코딩으로 때운 값이 있는 화면의 API가 후보.
+
완료 조건응답 DTO에 필드 추가, 기존 호출부 회귀 없음, 화면에 값 표시, API 문서/주석 갱신.
+
멘토 준비추가할 필드와 출처(어느 테이블/계산) 지정.
+
+
+ +
+
BE-02

요청 검증(Validation) 보강

1일
+
+
무엇을이상한 값(음수, 빈 문자열, 초과 길이)이 들어와도 500으로 터지는 API에 검증 어노테이션과 400 응답 추가.
+
찾는 법등록/수정 API에 일부러 이상한 값 보내 보기 — 500이 뜨는 곳이 후보.
+
완료 조건정의된 규칙 위반 시 400 + 명확한 메시지, 정상 값은 기존대로, 테스트 1개 추가.
+
멘토 준비검증 규칙 표(필드·규칙·에러 메시지) 초안.
+
+
+ +
+
BE-03

에러 로그 메시지 개선

0.5~1일
+
+
무엇을"error occurred" 수준의 로그에 무엇이/어떤 값으로/왜 실패했는지 문맥 추가 (단, 개인정보는 제외).
+
찾는 법최근 장애/문의 때 로그 보고도 원인을 몰랐던 지점을 선임에게 물어 수집.
+
완료 조건지정 지점 로그에 식별자·원인 포함, 개인정보(전화번호 등) 마스킹 확인.
+
멘토 준비개인정보 로깅 금지 규칙 브리핑(이 티켓이 보안 교육 기회).
+
+
+ +
+
BE-04

조회 API 신규 작성 (단순 CRUD의 R)

★★2~3일
+
+
무엇을기존 테이블에서 조건으로 조회하는 GET API 1개를 컨트롤러→서비스→리포지토리 계층대로 신규 작성.
+
찾는 법관리/통계 화면에서 "이 목록도 보고 싶다"고 미뤄둔 요구가 후보 (신규 화면과 세트로 주면 FE 수습생과 협업 티켓이 됨).
+
완료 조건API 동작(페이징 포함 여부 명시), 계층 구조 준수, 테스트 1개, API 명세 공유.
+
멘토 준비대상 테이블·조건·응답 형태 스펙 한 장. 참고할 기존 유사 API 지정.
+
+
+ +
+
BE-05

매직 넘버/하드코딩 값 설정으로 추출

1일
+
+
무엇을코드에 박힌 숫자·문자열(페이지 크기, 제한 횟수, 안내 문구)을 상수/설정 파일로 추출.
+
찾는 법서비스 코드에서 의미를 알 수 없는 숫자 리터럴 grep — 선임이 "이건 바뀔 수 있는 값"이라고 확인해준 것만.
+
완료 조건동작 변화 없음(값 동일), 상수 이름이 의미를 설명, 어디서 왜 쓰는지 주석.
+
멘토 준비추출 대상 목록 지정(전체 수색은 범위 폭주).
+
+
+ +
+
BE-06

느린 조회 1건 개선 (N+1 / 불필요 조회 제거)

★★2~3일
+
+
무엇을목록 조회 시 항목마다 추가 쿼리가 나가는 지점 1개를 한 번에 가져오도록 개선.
+
찾는 법쿼리 로그 켜고 목록 화면 열기 — 같은 모양 쿼리가 수십 번 반복되는 곳이 후보.
+
완료 조건쿼리 수 전/후 비교 기록(예: 51→2), 결과 데이터 동일함 검증, 개선 설명 PR에 작성.
+
멘토 준비대상 지점 확정 필수. JPA 페치 전략을 함께 리뷰(혼자 두면 위험한 티켓).
+
+
+ + +
+
🧪
+

품질 · 테스트 · 문서 — 4개

티가 덜 나지만 팀이 정말 고마워하는 일. 코드 이해 훈련으로도 최고예요.
+
+ +
+
QA-01

버그 재현 → 수정 → 회귀 테스트

★★2~3일
+
+
무엇을알려진 경미한 버그 1개를 재현 절차 문서화 → 원인 찾아 수정 → 재발 방지 테스트 추가.
+
찾는 법이슈 트래커/메모에 쌓인 "급하지 않은 버그" 목록에서 데이터 파손 위험 없는 것.
+
완료 조건재현 절차 문서, 수정 PR, 실패했다가 통과하는 테스트 1개.
+
멘토 준비버그 후보 2~3개 골라두기(1개는 막힐 경우의 교체용).
+
+
+ +
+
QA-02

핵심 함수에 단위 테스트 추가

1~2일
+
+
무엇을테스트가 없는 계산/변환 함수(금액 포맷, 날짜 계산, 상태 판정 등)에 정상·경계·예외 케이스 테스트 작성.
+
찾는 법if 분기가 3개 이상인데 테스트 0개인 함수를 서비스 코드에서 찾기.
+
완료 조건케이스 5개 이상(경계값 포함), 전부 통과, 테스트 이름만 봐도 뭘 검증하는지 알 것.
+
멘토 준비대상 함수 지정 + 기존 테스트 파일 1개를 본보기로 알려주기.
+
+
+ +
+
QA-03

README / 로컬 실행 가이드 현행화

1일
+
+
무엇을본인이 1주차에 환경 세팅하며 막혔던 지점을 살려, 실행 가이드를 "처음 온 사람도 따라 하면 되는" 상태로 갱신.
+
찾는 법본인의 1주차 세팅 메모가 그대로 재료. (이 티켓은 사실상 준비돼 있음)
+
완료 조건문서만 보고 다른 수습생이 클린 세팅 성공(상호 검증), 시크릿 값은 문서에 안 씀.
+
멘토 준비문서 위치 지정, 시크릿 기재 금지 재강조.
+
+
+ +
+
QA-04

미사용 코드/주석 정리 (지정 범위)

0.5~1일
+
+
무엇을멘토가 지정한 폴더 안에서 주석 처리된 죽은 코드, 안 쓰는 import, 미사용 함수 제거.
+
찾는 법에디터/린트의 unused 경고 + 주석 블록 검색. 삭제 전 "정말 안 쓰나" 확인 방법을 배우는 게 포인트.
+
완료 조건빌드·테스트 통과, 삭제 목록을 PR 설명에 정리, 확신 없는 건 삭제 대신 질문.
+
멘토 준비범위 폴더 지정(전체 수색 금지), 리뷰 꼼꼼히.
+
+
+ + +
+
🎨
+

디자이너 티켓 — 3개

디자인 수습생용. 개발 수습생의 FE 티켓과 짝지으면 협업 훈련이 됩니다.
+
+ +
+
DZ-01

빈 상태·에러 상태 디자인 세트

1~2일
+
+
무엇을FE-01·FE-05와 짝: 빈 상태/에러 상태의 일러스트·문구·레이아웃을 디자인하고 개발자에게 핸드오프.
+
완료 조건시안 + 문구 + 간격/색 명세, 개발 구현 결과가 시안과 일치하는지 확인까지.
+
멘토 준비기존 디자인 톤 참고 자료 전달.
+
+
+ +
+
DZ-02

화면 1개 UX 개선 제안 → 반영

★★2~3일
+
+
무엇을2주차 UI 인벤토리에서 찾은 불편 지점 1개를 개선 시안으로 만들고, 팀 리뷰 통과분을 개발 티켓으로 넘기기.
+
완료 조건현행 문제 정의 → 개선 시안(2안) → 선택안 핸드오프 문서 → 구현 확인.
+
멘토 준비개선 후보 화면 2~3개 승인해 두기(고객사 승인 필요 화면 제외).
+
+
+ +
+
DZ-03

UI 컴포넌트 정리표 (미니 디자인 시스템)

★★2~3일
+
+
무엇을서비스에서 쓰이는 버튼·입력·카드·색·글자 크기를 한 장으로 정리한 "컴포넌트 정리표" 제작 — FE-07(공통 컴포넌트화)의 근거 자료가 됨.
+
완료 조건컴포넌트별 변형 수 집계, 통일 제안 포함, 팀 공유 발표 10분.
+
멘토 준비범위(화면 몇 개까지) 지정.
+
+
+ + +
+
구체화 양식
+

티켓 작성 양식 — 이 틀로 실제 티켓을 만드세요

+

위 패턴에서 찾은 실제 건을 이 양식으로 옮기면 수습생에게 바로 줄 수 있는 티켓이 됩니다. 이슈 트래커에 그대로 복붙해도 돼요.

+
+
+
수습 티켓 양식 (복사해서 사용)
+
[제목] (패턴ID) 화면/기능 — 한 줄 요약
+       예: (FE-01) 알림 목록 — 빈 상태 화면 추가
+
+[배경] 왜 필요한가 (1~2문장)
+[할 일] 무엇을 어떻게 (구체적으로)
+[범위] 건드릴 파일/영역 · 건드리지 말 것
+[완료 조건]
+  - [ ] 조건 1 (눈으로 확인 가능하게)
+  - [ ] 조건 2
+  - [ ] PR에 전/후 스크린샷 또는 테스트
+[참고] 비슷한 기존 코드 위치, 참고 문서
+[난이도/예상] ★ or ★★ / 예상 소요
+[멘토] 담당 멘토 이름
+
+
+
+ + +
+
배정 트래커
+

티켓 풀 현황판

+

D-7까지 각 패턴을 실제 건으로 구체화하고, 5~6주차에 배정하면서 채워 나가세요. 학생 1명당 5~6주 동안 2~4건이 적당해요.

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
패턴실제 티켓 제목구체화배정 대상배정일상태
FE-01
FE-02
FE-03
FE-04
FE-05
FE-06
FE-07
BE-01
BE-02
BE-03
BE-04
BE-05
BE-06
QA-01
QA-02
QA-03
QA-04
DZ-01
DZ-02
DZ-03
+
+
+ 배정 팁 — 첫 티켓(5주차)은 ★로 시작해 성공 경험을 만들고, 6주차에 ★★로 올리세요. FE-01+DZ-01, FE-07+DZ-03, BE-04+FE(신규 화면)처럼 짝 티켓으로 주면 협업 평가 재료가 생깁니다. 고객사 코드 반영이 어려운 경우, 사내 도구·내부 프로젝트에서 같은 패턴을 찾아도 좋아요. +
+
+ +
+ AWESOMEDEV · 수습 실전 티켓 풀 템플릿 (멘토용) + 2026 · 5~6주차 강의안·평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week1-assignments.html b/frontend/public/docs/week1-assignments.html new file mode 100644 index 0000000..21c202b --- /dev/null +++ b/frontend/public/docs/week1-assignments.html @@ -0,0 +1,853 @@ +AWESOMEDEV 1주차 일일 과제집 — 온보딩 & 예절 적용 + + +
+ +
+
AWESOMEDEV · 일일 과제집 · 1 / 4
+

1주차 일일 과제집
온보딩 & 예절 적용

+

이 과제집은 오전 강의가 끝난 뒤, 오후 자기주도 시간(10:40–12:00, 13:00–16:30)에 여러분이 스스로 진행하는 과제를 하루 단위로 담고 있어요. 매일 정해진 산출물이 있고, 16:30 멘토 리뷰 때 그대로 제출하면 돼요. 순서대로 읽고, 적힌 대로 하면 하루가 꽉 채워지도록 만들었어요.

+
+ Week 1 · 월–금 + 오후 자기주도 시간용 + 매일 16:30 과제 리뷰 제출 +
+
+ +
+
Daily Rhythm
+

하루의 리듬

+

1주차 내내 매일 같은 리듬으로 움직여요. 시간을 지키는 것 자체가 이번 주의 훈련이에요.

+
+
+
09:30–10:30
아침 강의(멘토) — 그날 배울 예절·온보딩 주제를 멘토와 함께 다뤄요.
+
10:40–12:00
일일 과제 전반 — 이 과제집의 그날 본 과제를 시작해요. 계획 세우기부터.
+
13:00–16:30
일일 과제 후반 — 본 과제 마무리. 막히면 30분 룰: 30분 이상 혼자 붙잡지 말고 지금까지 시도한 것을 정리해 멘토에게 질문해요.
+
16:30–17:00
멘토 과제 리뷰 — 그날의 산출물을 제출하고 피드백을 받아요.
+
17:00–17:30
하루 정리 · 3줄 회고 — 오늘 배운 것 1줄, 어려웠던 것 1줄, 내일 해볼 것 1줄.
+
+

규칙 한 줄: 과제가 일찍 끝나면 놀지 말고 예비 과제로 — 예비 과제도 평가에 반영돼요.

+
+
+ + +
+
MON
+
+

회사 탐색 미션 — 우리 회사 지도 만들기

+

오전 오리엔테이션(회사소개·사규·세팅)에서 들은 내용을, 오후에 내 손으로 직접 확인하고 지도로 그려요.

+
+
+ +
+
+
본 과제

우리 회사 지도 1장 + 팀원 인터뷰 3명

+ 난이도 ★★ +
+
+
+
🎯목표
+

"누구에게 무엇을 물어봐야 하는지"를 아는 것이 신입의 첫 실력이에요 — 회사의 팀·사람·역할을 내 언어로 정리할 수 있게 돼요.

+
+
+
📋진행 순서
+
    +
  1. 사내 위키의 조직도·회사소개 페이지를 정독해요(오전에 안내받은 위치). 모르는 단어(직함·팀명·서비스명)는 만나는 족족 메모장에 적어 두세요.
  2. +
  3. 아래 "우리 회사 지도" 양식을 종이(또는 문서 앱)에 옮겨 그리고, 위키에서 확인한 팀 이름·담당 업무·대표 연락 채널을 채워요. 우리 회사가 다루는 분야(React 웹, Spring Boot 서버, React Native·Flutter 앱, 클라우드)가 각각 어느 팀 소관인지 표시해 보세요.
  4. +
  5. 위키만으로 채워지지 않는 칸을 "물어볼 것" 목록으로 만들어요. 최소 5개.
  6. +
  7. 13:00 이후, 팀원 3명에게 직접 찾아가 인터뷰해요. 순서: (1) 오전에 배운 대로 정중하게 자기소개 → (2) "지금 5분 정도 괜찮으실까요?" 확인 → (3) 아래 인터뷰 메모 양식의 질문 → (4) 감사 인사. 바쁘다고 하시면 "언제쯤 다시 오면 될까요?"라고 여쭤보고 그 시간에 다시 가요.
  8. +
  9. 인터뷰마다 그 자리에서 메모하고, 자리로 돌아와 5분 안에 정리해요(기억은 30분이면 흐려져요).
  10. +
  11. 인터뷰 내용으로 지도의 빈칸을 채우고, 지도 하단에 "내가 막히면 찾아갈 사람 3명"을 상황별로 적어요(예: 장비 문제 → ○○님).
  12. +
  13. 16:00까지 지도와 메모를 최종 정리하고, 스스로 소리 내어 1분 안에 회사 구조를 설명해 보는 리허설을 해요.
  14. +
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • 우리 회사 지도 1장 (손그림 사진 또는 문서 파일)
  • +
  • 팀원 인터뷰 메모 3장 (양식대로)
  • +
  • 리뷰 시간에 지도를 보며 1분 회사 소개를 직접 발표해요.
  • +
+
+
+
+
워크시트 — 우리 회사 지도
+
[ AWESOMEDEV 우리 회사 지도 ]                     작성자: ______  날짜: ______
+
+■ 회사 한 줄 소개: ________________________________________
+■ 주요 고객사(일반적으로): __________________________________
+
+■ 팀 구성
+  팀 이름        하는 일(한 줄)               대표 담당자     연락 채널
+  1. ________   ________________________    __________    __________
+  2. ________   ________________________    __________    __________
+  3. ________   ________________________    __________    __________
+  4. ________   ________________________    __________    __________
+
+■ 기술 분야 → 담당 팀
+  React 웹: ______ / Spring Boot 서버: ______ / RN·Flutter 앱: ______ / 클라우드: ______
+
+■ 내가 막히면 찾아갈 사람
+  장비·계정 문제 → ______ / 과제·일정 질문 → ______ / 사규·행정 질문 → ______
+
+
+
워크시트 — 팀원 인터뷰 메모 (1인 1장)
+
인터뷰 상대: ______님 (팀: ______)     시간: __:__ ~ __:__
+
+Q1. 지금 하고 계신 일을 하나만 소개해 주신다면?
+→ ________________________________________________
+
+Q2. 신입이 첫 달에 꼭 익혔으면 하는 것 한 가지는?
+→ ________________________________________________
+
+Q3. 이 팀에 질문할 때 좋은 방법(채널·시간대)은?
+→ ________________________________________________
+
+내가 느낀 점 한 줄: ______________________________________
+
+
+
+ +
+
+
예비 과제

사규 요약 카드 만들기

+ 난이도 ★ +
+
+
+
🎯목표
+

사규를 "읽었다"에서 "내 것으로 만들었다"로 바꿔요.

+
+
+
📋진행 순서
+
    +
  1. 오전에 정독한 사규·가이드 문서에서 수습 기간의 나에게 직접 해당되는 규칙 10개를 골라요.
  2. +
  3. 각 규칙을 내 말로 한 줄씩 바꿔 적어요(문서 문장을 그대로 베끼면 무효).
  4. +
  5. 그중 "지키기 어려울 것 같은 것" 2개에 별표를 치고, 왜 어려울 것 같은지 한 줄씩 적어요.
  6. +
+
+
+
참고
+

팁: 출퇴근·휴가·보안·장비·커뮤니케이션, 이 다섯 갈래에서 최소 1개씩 고르면 균형이 잡혀요. 16:30 리뷰 때 본 과제와 함께 보여 주세요.

+
+
+
+ +
+
+
디자이너 과제

월요일 — 개발자와 동일

+ 난이도 ★★ +
+
+
+
🎨차이점
+

오늘은 동일해요. 회사 지도와 인터뷰는 직군과 무관하게 똑같이 진행해요. 단, 인터뷰 상대 3명 중 1명은 꼭 디자이너(또는 디자인과 협업이 많은 분)를 포함하고, Q2를 "신입 디자이너가 첫 달에 꼭 익혔으면 하는 것"으로 바꿔 물어보세요.

+
+
+
+ + +
+
TUE
+
+

예절 워크북 — 판단 문제와 중간보고 대본

+

오전 예절 강의①(태도·호렌소·중간보고 롤플레이)에서 배운 판단 기준을, 오후에 글로 써서 내 것으로 만들어요.

+
+
+ +
+
+
본 과제

상황 판단 문제 10제 + 중간보고 대본 3편

+ 난이도 ★★ +
+
+
+
🎯목표
+

"이럴 땐 이렇게"를 몸이 기억하도록 — 보고·연락·상담(호렌소)의 판단 기준을 상황에 적용하는 힘을 길러요.

+
+
+
📋진행 순서
+
    +
  1. 오전 강의 노트를 10분간 다시 읽고, 호렌소 세 글자의 뜻을 각각 한 문장으로 맨 위에 적어요.
  2. +
  3. 아래 판단 문제 10개를 순서대로 풀어요. 각 문제에 O/X를 고르고, 반드시 이유를 2문장 이상 써요. "그냥 그래야 할 것 같아서"는 이유가 아니에요 — 오전에 배운 기준(누가 기다리는가, 언제 알려야 하는가)에 연결해서 쓰세요.
  4. +
  5. 10문제 중 가장 헷갈렸던 2개에 별표를 치고, 무엇이 헷갈렸는지 한 줄씩 남겨요(리뷰 때 이걸 먼저 이야기해요).
  6. +
  7. 13:00 이후, 아래 3개 상황의 중간보고 대본을 각각 6~10문장으로 써요. 말하듯이, 실제로 입 밖에 낼 문장으로요.
  8. +
  9. 대본을 소리 내어 읽으며 시간을 재요. 각 대본이 40초를 넘으면 핵심만 남기고 줄여요.
  10. +
  11. 줄인 최종본을 정서하고, 각 대본 아래에 "이 보고에서 상대가 알게 되는 것"을 한 줄로 적어요.
  12. +
+
+
+
워크시트 — 상황 판단 문제 10제 (O/X + 이유 2문장 이상)
+
1. 아침에 늦잠을 잤다. 지각이 확실해진 순간보다, 회사에 도착해서
+   직접 사과하는 편이 낫다.                                ( O / X )
+2. 맡은 일이 예정보다 일찍 끝났다. 다음 지시가 있을 때까지
+   조용히 기다리는 것이 예의다.                            ( O / X )
+3. 모르는 용어가 나왔지만 회의 흐름을 끊기 싫어서, 회의가 끝나고
+   아무에게도 묻지 않고 검색으로만 해결했다. 잘한 행동이다.  ( O / X )
+4. 3일짜리 일을 받았다. 중간보고는 3일째 완성했을 때 한 번이면
+   충분하다.                                              ( O / X )
+5. 작업 중 실수로 파일을 잘못 건드린 것 같다. 확실해질 때까지
+   말하지 않고 혼자 복구를 시도하는 것이 낫다.              ( O / X )
+6. 멘토가 자리에 없다. 급한 확인 사항이 있으면 메신저로 먼저
+   남겨 두는 것이 예의에 어긋난다.                          ( O / X )
+7. 지시받은 내용이 두 가지로 해석된다. 일단 내 해석대로 진행하고
+   결과물로 보여 주는 것이 더 능동적인 태도다.              ( O / X )
+8. 다른 팀원이 바빠 보인다. 부탁받은 전달 사항은 한가해 보일
+   때까지 며칠 미뤄도 된다.                                ( O / X )
+9. 오후 반차를 쓰고 싶다. 당일 아침에 말해도 규정상 문제없다면
+   그걸로 충분하다.                                        ( O / X )
+10. 일이 막혀서 30분 넘게 진전이 없다. 그래도 "스스로 해결하는
+    모습"을 보이기 위해 하루 종일 붙잡는 것이 성장에 좋다.   ( O / X )
+
+
+
워크시트 — 중간보고 대본 3편
+
공통 뼈대: ① 결론 먼저(지금 어디까지 됐는지) → ② 진행 상황 →
+③ 문제/걱정거리 → ④ 다음에 할 일 → ⑤ 상대에게 바라는 것
+
+[상황 A] 이틀짜리 조사 과제의 1일차 퇴근 30분 전, 멘토에게 진행 보고.
+        (순조롭게 절반쯤 진행된 상태)
+대본: ____________________________________________
+
+[상황 B] 과제 중 예상 못 한 문제 발견. 이대로면 기한을 못 지킬 것 같다.
+        (문제를 알게 된 직후, 멘토에게)
+대본: ____________________________________________
+
+[상황 C] 과제를 예정보다 반나절 일찍 끝냈다. 완료 보고 + 다음 일 요청.
+대본: ____________________________________________
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • 판단 문제 10제 완성본 (O/X + 이유, 별표 2개 포함)
  • +
  • 중간보고 대본 3편 최종본 (각 40초 이내 분량)
  • +
  • 리뷰 시간에 대본 중 1편을 멘토 앞에서 실제로 말로 해 봐요.
  • +
+
+
+
+
+ +
+
+
예비 과제

나의 태도 점검표 만들기

+ 난이도 ★ +
+
+
+
🎯목표
+

배운 태도를 매일 아침 스스로 점검할 수 있는 나만의 체크리스트로 바꿔요.

+
+
+
📋진행 순서
+
    +
  1. 오전 강의에서 배운 태도 항목 중 내가 약할 것 같은 순서대로 7개를 골라요.
  2. +
  3. 각 항목을 "아침에 스스로 물을 수 있는 질문"으로 바꿔요. 예: "인사" → "오늘 사무실에 들어오며 먼저 인사했나?"
  4. +
  5. 체크표(항목 7개 × 월~금 칸)를 만들고, 오늘 것부터 체크해요.
  6. +
+
+
+
참고
+

팁: 이 점검표는 수습 4주 내내 씁니다. 냉정하게 만들수록 나중의 내가 편해져요.

+
+
+
+ +
+
+
디자이너 과제

화요일 — 개발자와 동일

+ 난이도 ★★ +
+
+
+
🎨차이점
+

오늘도 동일해요. 예절과 보고는 직군 공통이에요. 단, 중간보고 대본의 [상황 B]는 "시안 작업 중 참고 자료가 부족해 방향을 잡기 어렵다"는 디자인 상황으로 바꿔 써 보세요 — 여러분이 실제로 하게 될 보고와 더 가까워져요.

+
+
+
+ + +
+
WED
+
+

소통 3종 세트 — 이메일 · 메신저 · 전화

+

오전 예절 강의②(이메일·메신저·전화)의 형식을, 오후에 진짜로 써서 보내며 손에 익혀요.

+
+
+ +
+
+
본 과제

이메일 2통 발송 + 메신저 질문 1건 + 전화 메모 2장

+ 난이도 ★★ +
+
+
+
🎯목표
+

읽는 사람이 30초 안에 용건을 파악하는 이메일·메시지를 실제로 작성해 보내는 힘을 길러요.

+
+
+
📋진행 순서
+
    +
  1. 이메일 ① 인사 메일: 아래 틀을 참고해 멘토에게 인사 메일을 써요. 자기소개 + 수습 기간의 각오 + 궁금한 점 1개. 제목은 "[수습/본인이름] "으로 시작해요. 다 쓰면 3번 소리 내어 읽고 오타를 잡은 뒤 실제로 발송해요.
  2. +
  3. 이메일 ② 진행보고 메일: "어제(화요일) 예절 워크북 과제를 완료했다"는 내용을 가상의 진행보고 형식으로 써서 멘토에게 발송해요. 결론 먼저, 본문은 10줄 이내, 항목은 번호 목록으로.
  4. +
  5. 메신저 질문 1건: 이번 주 과제 중 실제로 궁금했던 것 하나를 골라, 아래 "좋은 질문 틀"(상황 → 시도한 것 → 질문)에 맞춰 사내 메신저로 멘토에게 보내요. "질문 있어요"라고만 보내고 기다리는 건 금지 — 한 번에 읽고 답할 수 있게 완성된 메시지로.
  6. +
  7. 전화 메모 연습: 아래 두 가상 통화 시나리오를 읽고, 각각 전화 메모 양식에 옮겨 적어요. 시나리오는 일부러 말이 섞여 있으니 핵심(누가·용건·기한·회신 필요 여부)만 골라내는 게 연습 포인트예요.
  8. +
  9. 메모 2장을 완성하면, 각 메모를 보고 부재중인 담당자에게 전달하는 메신저 문장을 한 줄씩 만들어 봐요(발송은 안 해요).
  10. +
  11. 보낸 메일 2통과 메신저 질문을 스스로 다시 읽고, "더 줄일 수 있었던 문장"을 각 1개씩 찾아 표시해요.
  12. +
+
+
+
양식 — 업무 이메일 기본 틀
+
제목: [수습/홍길동] 인사드립니다 / 과제 진행 보고 (용건이 제목에 보이게)
+
+○○○ 멘토님, 안녕하세요. 수습 ___기 홍길동입니다.
+
+■ 용건(결론 먼저): ____________________________________
+■ 내용:
+  1. ____________________________________
+  2. ____________________________________
+■ 요청/질문: ____________________________________ (없으면 "회신 불필요합니다")
+
+감사합니다.
+홍길동 드림
+
+
+
양식 — 좋은 메신저 질문 틀
+
[상황] 지금 ___ 과제의 ___ 단계를 하고 있는데요,
+[시도] ___를 해 봤고, ___도 확인해 봤는데 ___까지만 알 수 있었어요.
+[질문] ___가 맞는지(또는 ___는 어떻게 하는지) 여쭤봐도 될까요?
+급하지 않으니 편하실 때 답 주시면 됩니다.
+
+
+
연습 자료 — 가상 통화 시나리오 2건 + 전화 메모 양식
+
[통화 1] 14:05 수신
+"아 네 안녕하세요, 저 협력사 ○○텔레콤 쪽 담당자인데요. 김○○ 팀장님 계신가요?
+아 안 계세요? 음… 다음 주 화요일 미팅 말인데요, 저희가 오전이 안 될 것
+같아서 오후 2시로 옮길 수 있는지 확인 부탁드리려고요. 아 그리고 회의실은
+저희 쪽으로 와 주시는 걸로 알고 있을게요. 오늘 중으로 문자든 뭐든 답
+주시면 감사하겠습니다. 제 번호는 010-1234-5678입니다."
+
+[통화 2] 15:40 수신
+"여보세요, 총무팀 박○○입니다. 이번에 새로 온 수습분들 노트북 자산 등록
+때문에요, 각자 노트북 밑면에 있는 시리얼 번호를 이번 주 금요일까지 저한테
+메일로 보내 주셔야 하거든요. 아 급한 건 아닌데 금요일 넘기면 다음 달
+처리돼요. 메일 주소는 사내 주소록에 있어요. 네네, 부탁드릴게요."
+
+--- 전화 메모 양식 (1통화 1장) ---
+받은 사람: ______   받은 시각: __:__
+건 사람: ______ (소속: ______ / 연락처: ______)
+찾는 사람: ______
+용건(한 줄): ____________________________________
+해야 할 일 / 기한: ____________________________________
+회신 필요? (예/아니오) → 방법·기한: ______
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • 실제 발송한 이메일 2통 (멘토 수신함에서 확인)
  • +
  • 실제 발송한 메신저 질문 1건
  • +
  • 전화 메모 2장 + 각 메모의 전달용 메신저 문장 1줄
  • +
+
+
+
+
+ +
+
+
예비 과제

나쁜 메일 고쳐쓰기

+ 난이도 ★ +
+
+
+
🎯목표
+

나쁜 예를 고쳐 보면 좋은 형식이 왜 좋은지 몸으로 알게 돼요.

+
+
+
📋진행 순서
+
    +
  1. 아래 "나쁜 메일"을 읽고, 문제점을 5개 이상 찾아 번호를 매겨 적어요.
  2. +
  3. 오늘 배운 기본 틀에 맞춰 전체를 다시 써요.
  4. +
  5. 원본과 수정본을 나란히 놓고, 무엇이 어떻게 좋아졌는지 3줄로 정리해요.
  6. +
+
+
+
연습 자료 — 나쁜 메일 원본
+
제목: 안녕하세요!!
+
+넵 안녕하세여 저 수습인데요 ㅎㅎ 다름이 아니라 지난번에 말씀하신 그 자료
+있잖아요 그거 언제까지였죠?? 그리고 제 노트북이 좀 이상한데 이것도 봐주실
+수 있나요 아 그리고 내일 오전에 잠깐 자리 비울 것 같습니다 그럼 이만~
+
+
+
참고
+

힌트: 용건이 몇 개인지 세어 보세요. 용건이 3개면 메일도 구조가 3개여야 하고, 어떤 용건은 메일이 아닌 다른 채널이 맞을 수도 있어요.

+
+
+
+ +
+
+
디자이너 과제

수요일 — 개발자와 동일

+ 난이도 ★★ +
+
+
+
🎨차이점
+

오늘도 동일해요. 단, 이메일 ②(진행보고)는 "시안 2개 방향을 잡았고 내일 오전까지 다듬어 공유하겠다"는 디자인 진행보고로 내용을 바꿔 쓰면, 앞으로 매주 쓰게 될 보고와 같은 형태가 돼요.

+
+
+
+ + +
+
THU
+
+

우리 서비스 탐험 — 화면 15장 리포트

+

오전 회사 업무 이해·환경 세팅(+2진수 기초)에 이어, 오후엔 내가 맡게 될 서비스를 사용자 눈으로 샅샅이 훑어요.

+
+
+ +
+
+
본 과제

서비스 화면 15개 캡처 + 기능 설명 + 궁금한 점 5개

+ 난이도 ★★ +
+
+
+
🎯목표
+

코드를 보기 전에 서비스를 사용자로서 완전히 이해하는 것 — 앞으로 만들 기능이 "어디에 붙는지" 아는 지도를 갖게 돼요.

+
+
+
📋진행 순서
+
    +
  1. 멘토에게 안내받은 담당 예정 서비스에 접속해요(테스트 계정은 오전 세팅 때 받은 것을 사용). 접속이 안 되면 30분 룰을 기다리지 말고 바로 물어보세요 — 환경 문제는 예외예요.
  2. +
  3. 먼저 15분간 목적 없이 자유롭게 서비스를 돌아다녀요. 캡처하지 말고, 첫인상만 메모해요(3줄).
  4. +
  5. 이제 체계적으로: 첫 화면부터 시작해 사용자가 이동하는 순서대로 화면 15개를 캡처해요. 같은 화면의 상태 변화(예: 로그인 전/후, 목록 비었을 때/찼을 때)도 다른 화면으로 쳐요.
  6. +
  7. 캡처마다 아래 리포트 양식의 한 줄 설명을 사용자의 말로 써요. "회원 정보 페이지" 같은 이름표가 아니라 "내 정보를 확인하고 비밀번호를 바꿀 수 있는 화면"처럼 무엇을 할 수 있는지로.
  8. +
  9. 탐험하며 이상하거나 궁금한 점을 만나는 즉시 메모하고, 최종적으로 5개를 골라 리포트에 정리해요. "버튼을 눌렀는데 아무 일도 안 일어남" 같은 관찰도 훌륭한 항목이에요.
  10. +
  11. 15장을 이동 흐름 순서로 정렬하고, 리포트 첫 장에 화면 흐름도(화면 이름을 화살표로 연결한 손그림이면 충분)를 붙여요.
  12. +
  13. 마지막으로 "이 서비스가 없다면 사용자는 무엇이 불편할까?"에 3줄로 답해요 — 서비스의 존재 이유를 내 말로 정리하는 거예요.
  14. +
+
+
+
양식 — 화면 탐험 리포트 (화면 1개당 1블록 × 15)
+
서비스 이름: ______            탐험 날짜: ______   작성: ______
+
+── 화면 #01 ──────────────────────────────
+캡처: (이미지 첨부)
+화면 이름(내가 붙인 것): ______
+한 줄 설명(사용자가 할 수 있는 일): ____________________
+이 화면으로 오는 경로: ______ → ______
+메모(이상한 점·궁금한 점 있으면): ______
+
+… #02 ~ #15 동일 …
+
+── 궁금한 점 TOP 5 ──────────────────────
+1. (화면 #__) ____________________________________
+2. (화면 #__) ____________________________________
+3. (화면 #__) ____________________________________
+4. (화면 #__) ____________________________________
+5. (화면 #__) ____________________________________
+
+── 이 서비스가 없다면? (3줄) ─────────────
+____________________________________
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • 화면 탐험 리포트 1부 (흐름도 + 화면 15블록 + 궁금한 점 5개 + 존재 이유 3줄)
  • +
  • 리뷰 시간에 궁금한 점 5개 중 가장 중요하다고 생각하는 1개를 골라 이유와 함께 설명해요.
  • +
+
+
+
+
+ +
+
+
예비 과제

2진수 변환 연습 12제

+ 난이도 ★ +
+
+
+
🎯목표
+

오전에 배운 2진수를 손으로 굴려서, "컴퓨터는 0과 1로 생각한다"를 감각으로 만들어요.

+
+
+
📋진행 순서
+
    +
  1. 계산기 없이, 오전에 배운 자릿값 표(128·64·32·16·8·4·2·1)를 종이에 그려 두고 시작해요.
  2. +
  3. 아래 12문제를 풀이 과정을 남기며 풀어요. 답만 적으면 무효.
  4. +
  5. 다 풀면 계산기(또는 개발자 도구 콘솔)로 검산하고, 틀린 문제는 어디서 틀렸는지 한 줄씩 적어요.
  6. +
+
+
+
연습 문제 — 2진수 12제 (풀이 과정 필수, 답은 적혀 있지 않아요)
+
[10진수 → 2진수]
+1. 6을 2진수로 나타내세요.
+2. 13을 2진수로 나타내세요.
+3. 25를 2진수로 나타내세요.
+4. 64를 2진수로 나타내세요.
+5. 100을 2진수로 나타내세요.
+6. 255를 2진수로 나타내세요.
+
+[2진수 → 10진수]
+7. 1010(2)를 10진수로 나타내세요.
+8. 1111(2)를 10진수로 나타내세요.
+9. 100000(2)를 10진수로 나타내세요.
+10. 1011011(2)를 10진수로 나타내세요.
+
+[생각해 보기]
+11. 8자리 2진수(1바이트)로 표현할 수 있는 가장 큰 10진수는
+    얼마인지 구하고, 왜 그런지 한 줄로 설명하세요.
+12. 화면의 색을 흔히 R·G·B 각각 0~255로 표현해요. 왜 하필
+    255까지인지, 오늘 배운 내용으로 두 줄 설명해 보세요.
+
+
+
+ +
+
+
디자이너 과제

서비스 화면 15개 — 디자인 관점 탐험

+ 난이도 ★★ +
+
+
+
🎨차이점
+

같은 화면 15개, 다른 렌즈. 개발 과제와 같은 서비스·같은 15장을 캡처하되, 각 화면의 메모를 디자인 관찰로 채워요. 궁금한 점 5개도 디자인 관점으로.

+
+
+
📋진행 순서
+
    +
  1. 본 과제의 1~3단계(접속·자유 탐험·15장 캡처)는 동일하게 진행해요.
  2. +
  3. 화면마다 세 가지를 관찰해 메모해요 — (주로 쓰인 색 2~3개, 강조색이 무엇을 강조하는지), 글자(제목/본문 크기 차이가 충분한지, 줄 길이가 읽기 편한지), 간격(요소 사이 여백이 일정한지, 답답하거나 허전한 곳).
  4. +
  5. 15장 중 가장 잘 됐다고 느낀 화면 1개가장 아쉬운 화면 1개를 골라, 각각 이유를 3줄씩 써요.
  6. +
  7. 아쉬운 화면 1개는 "내가 고친다면"을 글로만 3가지 제안해요(시안 제작은 아직 안 해요).
  8. +
  9. 궁금한 점 5개(예: "이 회색과 저 회색은 왜 다르지?", "버튼 모서리 둥글기가 화면마다 다른 이유는?")를 정리해 리포트를 완성해요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • 디자인 관점 화면 탐험 리포트 1부 (15블록 + 베스트/워스트 화면 + 개선 제안 3가지 + 궁금한 점 5개)
  • +
+
+
+
+
+ + +
+
FRI
+
+

Git 사이클 3회전 + 온보딩 회고

+

오전 Git·코드리뷰·스탠드업 강의(+CPU·메모리 기초)를 그대로 이어서, 오후에 브랜치→커밋→PR 사이클을 몸에 새겨요.

+
+
+ +
+
+
본 과제

연습 저장소에서 브랜치→커밋→PR 3회 반복 + 온보딩 회고 1장

+ 난이도 ★★★ +
+
+
+
🎯목표
+

브랜치→수정→커밋→푸시→PR의 한 바퀴를 생각 없이도 돌 수 있을 만큼 반복해서, 다음 주 실습의 발판을 만들어요.

+
+
+
📋진행 순서
+
    +
  1. 오전에 안내받은 연습 저장소를 클론해요: git clone <저장소 주소> → 폴더로 이동 → git log --oneline -5로 최근 이력을 먼저 구경해요.
  2. +
  3. 1회전 — 내 소개 파일: git switch -c feat/intro-본인이름으로 브랜치를 만들고, members/ 폴더에 본인이름.md를 새로 만들어 자기소개 5줄(이름·목표 스택·이번 주 배운 것 1가지 포함)을 작성해요. git add → 커밋 → 푸시 → PR 생성. 커밋 메시지는 아래 컨벤션 표를 따라요.
  4. +
  5. PR 본문을 아래 PR 양식대로 채우고, 멘토를 리뷰어로 지정해요. PR 링크를 메모해 두세요.
  6. +
  7. 2회전 — 기존 파일 수정: 반드시 git switch maingit pull로 돌아온 뒤 새 브랜치 docs/faq-본인이름을 만들어요. 저장소의 FAQ.md에 "신입이 궁금해할 질문과 답" 1개를 추가하고(이번 주에 진짜 궁금했던 것으로), 같은 사이클로 PR까지 만들어요.
  8. +
  9. 3회전 — 오타·정리: 다시 main으로 → git pull → 브랜치 fix/typo-본인이름. 저장소의 아무 문서에서 오타·어색한 문장·깨진 링크를 1개 이상 찾아 고치고 PR까지. 없어 보이면 멘토에게 "심어 둔 오타" 힌트를 요청해요.
  10. +
  11. 3회전이 끝나면 git log --oneline --all과 PR 목록을 보며, 각 회전에서 헷갈렸던 명령 1개씩을 골라 "왜 헷갈렸는지"를 메모해요.
  12. +
  13. 15:30부터는 온보딩 회고 1장을 아래 양식으로 써요. 솔직하게 — 회고는 잘한 척하는 문서가 아니라 다음 주를 설계하는 문서예요.
  14. +
+
+
+
참고 — 커밋 메시지 컨벤션 (이대로 지켜요)
+
형식: 타입: 한 일 요약 (한국어, 50자 이내, 마침표 없이)
+
+feat: 새 기능·새 파일 추가        예) feat: 홍길동 자기소개 문서 추가
+fix:  잘못된 것 수정              예) fix: FAQ 문서 오타 수정
+docs: 문서 내용 변경              예) docs: FAQ에 브랜치 질문 항목 추가
+chore: 자잘한 정리                예) chore: 불필요한 빈 줄 제거
+
+나쁜 예) "수정", "asdf", "커밋합니다", "금요일 과제"
+
+
+
양식 — PR 본문 (3개 PR 모두 이 형식)
+
## 무엇을 했나요
+- ____________________________________
+
+## 왜 했나요
+- ____________________________________
+
+## 리뷰어가 봐 주셨으면 하는 것
+- ____________________________________
+
+## 스스로 확인한 것
+- [ ] 브랜치 이름이 컨벤션과 맞다
+- [ ] 커밋 메시지가 컨벤션과 맞다
+- [ ] 관련 없는 파일 변경이 섞이지 않았다
+
+
+
양식 — 1주차 온보딩 회고
+
[Keep]  계속하고 싶은 것 (이번 주 잘 됐던 행동) — 3가지
+1. ______  2. ______  3. ______
+
+[Problem] 아쉬웠던 것 (남 탓 말고 내 행동 중심으로) — 3가지
+1. ______  2. ______  3. ______
+
+[Try] 다음 주에 시도할 것 (측정 가능하게: "열심히"는 금지) — 3가지
+1. ______  2. ______  3. ______
+
+[한 줄] 일주일 전의 나에게 해 주고 싶은 말: ______
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • PR 3개 링크 (feat / docs / fix 각 1개, 본문 양식 완성)
  • +
  • 헷갈렸던 명령 메모 3줄
  • +
  • 온보딩 회고 1장
  • +
+
+
+
+
주의
+

절대 규칙: main 브랜치에 직접 커밋·푸시하지 않아요. 회전을 시작할 때마다 반드시 main으로 돌아가 git pull부터 — 이 습관이 다음 주 실전에서 여러분을 지켜 줘요.

+
+
+
+ +
+
+
예비 과제

JavaScript 워밍업 10제 (콘솔에서)

+ 난이도 ★ +
+
+
+
🎯목표
+

다음 주 JavaScript 실습 전에, 브라우저 콘솔에서 값을 다루는 감각을 미리 깨워요.

+
+
+
📋진행 순서
+
    +
  1. 브라우저에서 F12 → Console 탭을 열어요. 여기가 오늘의 실습장이에요.
  2. +
  3. 아래 10문제를 순서대로 콘솔에 직접 입력해 풀어요. 입력한 코드와 결과를 그대로 복사해 문서에 남겨요.
  4. +
  5. 에러가 나면 지우지 말고 에러 메시지도 함께 기록해요 — 에러를 읽는 연습이 절반이에요.
  6. +
+
+
+
연습 문제 — JavaScript 워밍업 10제 (답은 적혀 있지 않아요)
+
1. console.log("안녕하세요, AWESOMEDEV!")를 실행해 인사를 출력하세요.
+2. 변수 name에 본인 이름을, age에 나이를 저장한 뒤 두 변수를
+   각각 출력하세요.
+3. name과 age를 이용해 "저는 ○○이고 ○○살입니다" 형태의
+   한 문장을 만들어 출력하세요.
+4. 7 + 3, 7 - 3, 7 * 3, 7 / 3, 7 % 3 다섯 가지를 각각 실행하고,
+   %가 무엇을 하는 연산인지 주석(//)으로 적으세요.
+5. 변수 price = 12000, count = 3일 때 총액을 계산해 total 변수에
+   저장하고 출력하세요.
+6. "2026" + 1을 실행해 보고, 결과가 왜 그렇게 나오는지 한 줄로
+   설명을 적으세요.
+7. 변수 isDone = false를 만들고, if문을 사용해 isDone이 true면
+   "끝!", false면 "아직!"을 출력하세요.
+8. 배열 days = ["월","화","수","목","금"]을 만들고, days[0]과
+   days[4], 그리고 days.length를 출력하세요.
+9. for문으로 1부터 10까지의 숫자를 차례대로 출력하세요.
+10. for문을 고쳐 1부터 10까지의 "합계"를 구해 출력하세요.
+    (힌트: 합을 담아 둘 변수가 하나 필요해요)
+
+
+
📤산출물 · 제출
+
+

제출: 문제별 입력 코드 + 출력 결과(에러 포함)를 담은 기록 문서 1부. 본 과제와 함께 16:30에 제출해요.

+
+
+
+
+ +
+
+
디자이너 과제

Figma 파일 정리 연습 + 온보딩 회고

+ 난이도 ★★ +
+
+
+
🎨차이점
+

Git 대신 Figma. 개발자의 "브랜치·커밋 규칙"에 해당하는 것이 디자이너에겐 "파일·레이어·네이밍 규칙"이에요. 오늘은 어질러진 파일을 규칙 있게 정리하는 연습을 해요. 회고는 개발 과제와 동일한 양식으로 작성해요.

+
+
+
📋진행 순서
+
    +
  1. 멘토에게 연습용 Figma 파일(일부러 어질러 둔 파일)의 링크를 받아 내 드래프트로 복제해요.
  2. +
  3. 파일 안의 페이지를 "🧭 Cover / 🎨 Design / 🧪 Playground" 3개로 재구성하고, 각 프레임을 알맞은 페이지로 옮겨요.
  4. +
  5. 모든 프레임과 주요 레이어에 일관된 이름 규칙을 붙여요: 프레임은 "화면명/상태"(예: 로그인/기본, 로그인/에러), 레이어는 "Rectangle 41" 같은 자동 이름을 전부 의미 있는 이름으로.
  6. +
  7. 파일 안에서 반복 사용된 색을 모두 찾아 스타일로 등록하고(예: Primary, Gray-Text), 개별 지정된 색을 스타일 참조로 교체해요.
  8. +
  9. 정리 전/후를 비교하는 Before → After 캡처 한 쌍을 Cover 페이지에 배치하고, "내가 적용한 정리 규칙 5가지"를 텍스트로 정리해요.
  10. +
  11. 15:30부터는 개발 과제와 같은 양식으로 온보딩 회고 1장을 써요.
  12. +
+
+
+
📤산출물 · 제출
+
+

16:30에 제출:

+
    +
  • 정리 완료된 Figma 파일 링크 (Before→After 캡처, 정리 규칙 5가지 포함)
  • +
  • 온보딩 회고 1장
  • +
+
+
+
+
+ + +
+
Mentor Guide
+

멘토 리뷰 가이드 (16:30–17:00)

+

요일별로 무엇을 확인하고 어떤 질문을 던질지의 기준이에요. 학생도 미리 읽어 두면 좋아요 — 이 질문에 답할 수 있으면 그날 과제는 성공이에요.

+
+
+
확인: 지도가 위키 베끼기가 아니라 인터뷰로 채워졌는가, 인터뷰 예절(사전 양해·감사 인사)을 지켰는가. 질문: "장비가 고장 나면 지금 누구에게 먼저 가나요? 왜 그분인가요?"
+
확인: O/X보다 이유의 근거가 강의 내용과 연결되는가, 대본이 결론-먼저 구조인가. 질문: "상황 B 대본을 지금 저한테 그대로 말해 보세요 — 40초 안에요."
+
확인: 실제 발송된 메일의 제목·구조·오타, 메신저 질문에 '시도한 것'이 들어 있는가, 전화 메모에서 기한·회신 여부를 놓치지 않았는가. 질문: "통화 1에서 오늘 중으로 해야 할 일이 뭐였죠?"
+
확인: 화면 설명이 '이름표'가 아니라 '할 수 있는 일'로 쓰였는가, 궁금한 점이 구체적 화면에 근거하는가. 질문: "궁금한 점 1위를 골랐던데, 그게 사용자에게 왜 중요한가요?" (디자이너: "워스트 화면의 개선안 3개 중 가장 효과 큰 건?")
+
확인: PR 3개의 브랜치·커밋 컨벤션 준수, main 직접 커밋 여부, 회고 Try가 측정 가능한가. 질문: "2회전 시작 전에 왜 main에서 pull부터 했나요?" (디자이너: "정리 규칙 5가지 중 팀 전체가 따라야 할 1순위는?")
+
+
+
+ +
+
AWESOMEDEV · 1주차 일일 과제집 (학생 배포용) · 4부작 중 1
+
2026 · 1주차 상세 강의안과 함께 사용
+
+ +
diff --git a/frontend/public/docs/week1-detailed.html b/frontend/public/docs/week1-detailed.html new file mode 100644 index 0000000..81469a2 --- /dev/null +++ b/frontend/public/docs/week1-detailed.html @@ -0,0 +1,739 @@ +1주차 상세 강의안 · 온보딩 & 예절 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 1 / 8
+

1주차 상세 강의안
온보딩 & 기본 예절

+

멘토가 강의실에서 그대로 진행하는 분 단위 퍼실리테이터 가이드예요. 각 세션마다 학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수까지 담았습니다. "하나하나 알려줘야 하는" 학생 기준으로 썼어요.

+
+ Week 1 · 월–금 + 대상: 개발 3 · 디자인 1 + 진행: 멘토 +
+
+ + + + +
+
이 강의안 사용법
+

모든 세션은 같은 틀로

+

아래 6개 블록이 세션마다 반복돼요. 이 틀은 2~8주차 강의안에도 똑같이 적용됩니다.

+
+
🎯
학습목표 — 세션이 끝나면 할 수 있는 것
+
🕘
진행표 — 분 단위 흐름(도입·전개·마무리)
+
💬
멘토 스크립트 — 실제로 이렇게 말해요
+
✍️
실습·워크시트 — 학생이 직접 해보는 활동
+
이해 확인 — 넘어가기 전 점검 질문
+
⚠️
흔한 실수 & 대처 — 미리 알고 대응
+
+
+ 진행 팁 — 강의는 짧게(한 번에 15분 이내), 나머지는 실습·대화로. 학생이 말을 많이 하게 하세요. 모르면 물어보는 분위기를 첫날부터 만드는 게 이 주의 최대 목표입니다. +
+
+ + +
+
MON
+

Day 1 · 오리엔테이션

목표: 낯섦 제거. 회사·사람·공간·규칙에 익숙해진다. 오늘은 개발/디자인 실무 없음.
+
+ + +
+
Day1 · 세션 1 · 오전

환영과 회사·팀 소개

약 60분 · 대표 환영사 포함
+
+
🎯학습목표
+
    +
  • 어썸데브가 어떤 회사이고 무슨 일을 하는지 한 문장으로 말할 수 있다.
  • +
  • 같이 일할 팀원과 내 멘토가 누구인지 안다.
  • +
+
+
🕘진행표
+
+
0–10분
대표 환영사 — 장익준 대표. 수습 가이드의 "대표 인사" 톤으로. 실수해도 된다는 메시지.
+
10–25분
회사 소개 — 2023년 설립, KT엠모바일·KT알파·핀업과 함께하는 개발/유지보수. "우리가 만든 게 수많은 사람에게 쓰인다".
+
25–45분
팀·멘토 소개 — 각 팀원 자기소개(1분씩), 멘토 1:1 배정 발표, 학생 자기소개.
+
45–60분
오늘 일정 안내 + 화장실·탕비실·비상구 등 공간 안내.
+
+
+
💬멘토 스크립트 (자기소개 유도)
+
멘토
+

"긴장하지 않아도 돼요. 오늘은 배우는 날이 아니라 익숙해지는 날이에요. 돌아가면서 이름이랑, 요즘 관심 있는 것 하나만 편하게 얘기해 줄래요?"

+

→ 학생이 말문을 열도록, 업무 질문(할 줄 아는 언어 등)은 첫날엔 하지 않는다. 부담 주지 않기.

+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — 첫날부터 기술 수준을 확인하려 든다. → 학생이 위축됨.
+
대처 — 오늘은 사람·환경에만 집중. 실력 파악은 2주차부터 자연스럽게.
+
+
+
+
+ + +
+
Day1 · 세션 2 · 오전

사규 · 보안 · 근무 수칙

약 40분
+
+
🎯학습목표
+
    +
  • 출퇴근·근무시간·휴게·연차 등 기본 근무 규칙을 안다.
  • +
  • 회사 정보·보안에서 하지 말아야 할 것을 안다.
  • +
+
+
🕘진행표
+
+
0–15분
근무 기본 — 근무시간, 출퇴근, 휴게, 연차·병가 절차, 급여일. (연소근로자는 근로시간 규정 별도 안내)
+
15–30분
보안 수칙 — 코드·고객정보 반출 금지, 비밀번호·토큰 취급, 개인 계정/PC 사용 규칙.
+
30–40분
서약 — 보안 서약서·개인정보 동의 등 서류 작성(회사 양식).
+
+
+
이해 확인
+
+

학생에게 물어보고 답하게 한다:

+
    +
  • "회사 코드를 개인 노트북에 복사해도 될까요?" (→ 안 된다)
  • +
  • "지각할 것 같으면 어떻게 해야 하죠?" (→ 늦기 전에 멘토에게 미리 연락)
  • +
+
+
+
+
+ + +
+
Day1 · 세션 3 · 오후

장비 · 계정 세팅 & 시설 안내

약 90분
+
+
🎯학습목표
+
    +
  • 업무에 쓸 계정과 도구에 모두 로그인돼 있다.
  • +
  • 어디에 무엇이 있는지(위키·일정·연락망) 안다.
  • +
+
+
✍️실습 — 세팅 체크리스트
+
+

멘토와 하나씩 확인하며 로그인:

+
    +
  • 사내 메신저 / 이메일 계정
  • +
  • Git(Gitea) 계정 · 프로필 사진 · 이름 설정
  • +
  • 사내 위키 / 일정(캘린더) / 태스크 보드 접근
  • +
  • 노트북 기본 세팅(브라우저·에디터 설치는 목요일 개발환경 세션에서)
  • +
+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — 계정 세팅을 학생 혼자 하게 두고 자리를 뜬다.
+
대처 — 첫날은 옆에서 함께. 막히는 지점을 메모해 두면 온보딩 문서 개선에 쓸 수 있다.
+
+
+
+
+ + +
+
Day1 · 세션 4 · 오후

수습 가이드 함께 읽기

약 40분
+
+
🎯학습목표
+
    +
  • 앞으로 8주가 어떻게 흘러가는지 큰 그림을 안다.
  • +
  • "30분 룰", "모르면 물어보기" 같은 핵심 약속을 이해한다.
  • +
+
+
✍️진행
+
+

수습 가이드(배포본)를 함께 넘기며 핵심만 짚는다:

+
    +
  • 8주 로드맵 (예절·기본기 먼저, 실무는 뒤)
  • +
  • 하루 리듬 · 30분 룰 · 좋은 질문 vs 나쁜 질문
  • +
  • 평가는 "성장 속도·태도" 중심이고 놀랄 일 없다는 점
  • +
+
+
+
오늘의 마무리
+
+

하루 정리 3줄 회고 시작 — 오늘 배운 것 / 어색했던 것 / 궁금한 것. 첫날부터 습관화.

+
+
+
+
+ + +
+
TUE
+

Day 2 · 예절① 태도와 보고

목표: 사회인의 기본 태도와 "혼자 판단하지 않고 보고하는" 습관(호렌소)을 몸에 익힌다.
+
+ + +
+
Day2 · 세션 1 · 오전

왜 예절인가 + 하루의 기본

약 50분 · 수습 가이드 A1
+
+
🎯학습목표
+
    +
  • 회사에서 신뢰가 어떻게 쌓이는지 이해한다.
  • +
  • 출퇴근·인사·호칭·복장의 기본을 실제로 해본다.
  • +
+
+
🕘진행표
+
+
0–10분
도입 질문 — "믿음직한 신입은 어떤 사람일까?" 학생 생각 듣기.
+
10–25분
강의 — 신뢰는 큰 성과가 아니라 매일의 작은 기본에서. A1 항목(시간·인사·호칭·복장·정리) 하나씩.
+
25–45분
실습 — 인사·호칭 연습(아래 활동).
+
45–50분
정리 · 질문받기.
+
+
+
💬멘토 설명 포인트
+
이렇게 설명
+

"출근 시간이 9시라면, 9시에 도착이 아니라 9시에 일 시작이에요. 그래서 10분 전에 와서 자리를 준비하는 거예요. 이건 눈치가 아니라 약속이에요."

+

"호칭은 헷갈리면 무조건 '○○님'. 반말이나 별명은 회사에선 안 써요."

+
+
+
✍️실습 — 인사·호칭 연습
+
+

롤플레이: 학생이 실제로 소리 내어 해본다.

+
    +
  1. 출근 인사: "안녕하세요!" (밝게)
  2. +
  3. 팀원 호칭 부르기: "○○님, 안녕하세요."
  4. +
  5. 퇴근 인사: "먼저 들어가 보겠습니다. 수고하셨습니다."
  6. +
+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — 인사를 작게 하거나 눈을 안 본다.
+
대처 — "인사는 소리와 눈맞춤이 8할"이라고 알려주고 한 번 더 시켜본다. 잘하면 바로 칭찬.
+
+
+
+
+ + +
+
Day2 · 세션 2 · 오전

보고 · 연락 · 상담 (호렌소)

약 50분 · 수습 가이드 A2 · 이 주의 핵심
+
+
🎯학습목표
+
    +
  • 보고·연락·상담의 차이를 안다.
  • +
  • "문제가 생기면 숨기지 않고 즉시 알린다"를 이해한다.
  • +
+
+
🕘진행표
+
+
0–5분
도입 — "일을 다 했는데 아무한테도 말 안 하면?" → 안 한 것과 같다.
+
5–20분
강의 — 보고(결과)·연락(진행·문제)·상담(판단이 안 설 때). 특히 "나쁜 소식일수록 빨리".
+
20–45분
실습 — 중간보고 롤플레이(다음 세션과 연결).
+
45–50분
정리.
+
+
+
💬멘토 스크립트 (핵심 메시지)
+
이렇게 설명
+

"여러분이 가장 많이 하는 실수는 '혼자 해결하려다 늦게 터뜨리는 것'이에요. 일이 잘돼도, 안 돼도 중간에 공유하면 돼요. 특히 문제는 빨리 말할수록 작아지고, 숨길수록 커져요."

+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — 학생이 막혀도 "폐 끼칠까 봐" 말을 못 한다.
+
대처 — "물어보는 게 폐가 아니라, 안 물어보고 헤매는 게 폐"라고 못 박는다. 물어봤을 때 반드시 반갑게 반응.
+
+
+
+
+ + +
+
Day2 · 세션 3 · 오후

롤플레이 — 중간보고 해보기

약 60분 · 실습 중심
+
+
🎯학습목표
+
    +
  • 정해진 틀(무엇을·어디까지·언제까지·도움요청)로 중간보고를 말할 수 있다.
  • +
+
+
✍️롤플레이 대본
+
+
상황: 배정된 일을 진행 중, 멘토에게 중간보고
+
[학생] "○○님, 잠깐 시간 괜찮으세요?
+      배정해주신 __________ 건 중간 공유드려요.
+
+      지금까지: __________ 까지 했고,
+      상황: __________ (되고 있음 / 막힌 부분은 __________),
+      예상: 오늘 __________ 까지 될 것 같습니다.
+
+      혹시 먼저 봐주실 부분 있을까요?"
+
+
+

진행 방법

+
    +
  1. 멘토가 가상의 과제를 하나 준다("A 화면 버튼 색을 바꾸는 일" 등).
  2. +
  3. 학생이 위 틀의 빈칸을 채워 소리 내어 보고한다.
  4. +
  5. 한 명씩 돌아가며, 3번 반복해 익숙해지게.
  6. +
+
+
+
이해 확인
+
+

보고에 4요소가 들어갔는지 체크: 무엇을 · 어디까지 · 언제까지 · 도움요청. 빠진 게 있으면 다시.

+
+
+
+
+ + +
+
WED
+

Day 3 · 예절② 소통 도구

목표: 이메일·메신저·전화를 회사식으로 쓸 수 있다. 템플릿과 롤플레이로 몸에 익힌다.
+
+ + +
+
Day3 · 세션 1 · 오전

이메일 쓰는 법

약 60분 · 수습 가이드 A3
+
+
🎯학습목표
+
    +
  • 제목·인사·소개·용건·맺음 순서로 업무 메일을 쓸 수 있다.
  • +
  • 보내기 전 점검(받는 사람·오타)을 습관화한다.
  • +
+
+
🕘진행표
+
+
0–15분
강의 — 이메일은 공식 기록. 5부분 구조 설명(수습 가이드 A3 템플릿 활용).
+
15–45분
실습 — 멘토에게 보내는 "첫 인사 메일" 작성.
+
45–60분
첨삭 — 멘토가 실제로 열어 함께 고친다.
+
+
+
✍️실습 — 첫 인사 메일
+
+
이 틀로 직접 작성 → 멘토에게 발송
+
제목: [수습] 입사 인사드립니다 — 홍길동
+
+안녕하세요, ○○님.
+9월 1일부터 수습으로 합류한 홍길동입니다.
+
+앞으로 잘 부탁드립니다. 모르는 게 많지만
+빨리 배우겠습니다. 편하게 알려주시면 감사하겠습니다.
+
+감사합니다.
+홍길동 드림
+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — 제목 없음 / "저기요" 말투 / 인사·소개 없이 용건만.
+
대처 — 나쁜 예를 하나 보여주고 학생이 직접 고쳐보게 하면 오래 남는다.
+
+
+
+
+ + +
+
Day3 · 세션 2 · 오후

메신저 예절

약 40분 · 수습 가이드 A4
+
+
🎯학습목표
+
    +
  • 상대의 시간을 존중하는 메신저 사용법을 안다.
  • +
+
+
💬핵심 규칙 (O / X)
+
O 이렇게
+

용건을 한 번에 정리해 보낸다 · 질문엔 배경도 함께 · 읽으면 반응(이모지/"확인했습니다") · 급하지 않으면 업무시간 안에.

+
+
X 이건 피하기
+

"안녕하세요"만 보내고 기다리게 하기 · 한 문장을 여러 번 끊어 알림 폭탄 · 5분 만에 "?" 재촉 · 밤·주말에 급하지 않은 연락.

+
+
+
✍️실습
+
+

학생이 실제 사내 메신저로 멘토에게 "질문 메시지 하나"를 배경까지 담아 보내본다. (예: 어제 세팅 중 막혔던 것)

+
+
+
+
+ + +
+
Day3 · 세션 3 · 오후

전화 응대 (스크립트 + 롤플레이)

약 50분 · 수습 가이드 A5
+
+
🎯학습목표
+
    +
  • 전화 첫 마디를 자신 있게 말하고, 필요한 것을 메모할 수 있다.
  • +
+
+
✍️롤플레이 대본
+
+
멘토가 '외부 전화'가 되어 걸어준다 → 학생이 받는다
+
[받기] "네, 어썸데브 개발팀 홍길동입니다."
+
+── 들으며 메모 3가지 ──
+   · 누가 (회사·이름)
+   · 무엇을 (용건)
+   · 언제까지 (기한)
+
+[모르는 내용이면]
+ "확인 후 다시 연락드리겠습니다.
+  성함과 연락처 남겨주시겠어요?"
+ → 담당자/멘토에게 즉시 전달
+
+
+

진행: 멘토가 시나리오 2개로 전화를 건다. ① 간단한 용건 ② 학생이 모르는 용건(→ 메모+연결 연습). 각 학생 2회씩.

+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — 당황해서 아무 말도 못 하거나, 모르는데 아는 척 답한다.
+
대처 — "모르면 확인 후 연락"이 정답임을 강조. 첫 마디만 외워도 절반은 된다.
+
+
+
오늘의 마무리
+

3줄 회고. "오늘 배운 소통 도구 중 가장 어색했던 것"을 적게 한다.

+
+
+
+ + +
+
THU
+

Day 4 · 업무 이해 · 환경 · 기본기 시작

목표: 우리가 하는 일을 이해하고, 개발/디자인 환경을 세팅한다. 아침엔 기본기(컴퓨터 구조) 시작.
+
+ + +
+
Day4 · 아침 · 09:30–10:30

[기본기 B] 컴퓨터 구조 1일차 — 2진수

60분 · CS 4주 커리큘럼 1일차
+
+
🎯학습목표
+
  • 데이터가 0과 1로 저장되는 원리를 이해한다.
+
+
✍️진행
+

CS 4주 커리큘럼의 1일차를 그대로 진행: 10진↔2진 변환, 글자가 숫자로(ASCII), 색이 숫자로(#2159C5=RGB). 상세 실습은 CS 커리큘럼 문서 참고.

+
+
+
+ + +
+
Day4 · 세션 1 · 오전

우리 회사가 하는 일

약 40분
+
+
🎯학습목표
+
    +
  • 어썸데브가 KT엠모바일·KT알파·핀업에서 무엇을 하는지 이해한다.
  • +
  • 우리가 쓰는 기술(React·Spring Boot·클라우드·앱)의 큰 그림을 안다.
  • +
+
+
💬멘토 설명 포인트
+
이렇게 설명
+

"우리는 큰 회사들의 서비스를 함께 만들고, 잘 돌아가게 유지해요. 통신(KT엠모바일·KT알파), 금융/커머스(핀업)처럼 수많은 사람이 매일 쓰는 시스템이라, 작은 실수도 크게 번질 수 있어요. 그래서 기본기와 책임감을 강조하는 거예요."

+
+
+
⚠️주의
+
고객사 실제 데이터·화면을 다룰 땐 보안 범위를 먼저 안내한다(무엇을 봐도 되고 무엇은 안 되는지).
+
+
+
+ + +
+
Day4 · 세션 2 · 오후

서비스 직접 써보기 + 개발/디자인 환경 세팅

약 120분
+
+
🎯학습목표
+
    +
  • 담당할 서비스를 사용자 입장에서 직접 써보고 흐름을 안다.
  • +
  • 개발/디자인 작업에 필요한 환경 세팅을 완료한다.
  • +
+
+
✍️실습 — 개발자
+
+
    +
  • 에디터·Node·Git 등 개발환경 설치(회사 표준 셋업 문서 따라).
  • +
  • 담당하게 될 프로젝트를 로컬에서 한 번 실행해 보기(멘토 동행).
  • +
  • 화면을 사용자처럼 클릭하며 흐름 익히기.
  • +
+
+
+
✍️실습 — 디자이너
+
+
    +
  • 디자인 도구(Figma 등) 환경 세팅.
  • +
  • 우리 서비스 화면을 캡처하며 UI 첫인상 메모(색·글자·간격).
  • +
+
+
+
이해 확인
+

개발자: 로컬 실행 성공 여부. 디자이너: 화면 5개 이상 캡처·메모. 안 되면 원인을 함께 기록.

+
+
+
+ + +
+
FRI
+

Day 5 · 협업 규칙 & 첫 주 마무리

목표: 우리가 함께 일하는 방식(Git·코드리뷰·스탠드업·30분 룰)을 익히고 첫 주를 돌아본다.
+
+ + +
+
Day5 · 아침 · 09:30–10:30

[기본기 B] 컴퓨터 구조 2일차 — CPU·메모리·저장장치

60분 · CS 4주 커리큘럼 2일차
+
+
✍️진행
+

CS 커리큘럼 2일차 진행: 작업관리자로 CPU·메모리 관찰, "책상=RAM, 책장=디스크" 비유, 우리 클라우드 서버로 연결.

+
+
+
+ + +
+
Day5 · 세션 1 · 오전

Git 기초 & 커밋 컨벤션

약 70분
+
+
🎯학습목표
+
    +
  • 브랜치를 만들고, 커밋하고, PR을 올리는 흐름을 안다.
  • +
  • 우리 커밋 메시지 규칙(feat:·fix: 등)을 안다.
  • +
+
+
🕘진행표
+
+
0–20분
개념 — Git이 왜 필요한가(협업·기록·되돌리기). 브랜치·커밋·PR을 그림으로.
+
20–60분
실습 — 연습용 저장소에서 브랜치 생성 → 파일 수정 → 커밋 → PR 올리기.
+
60–70분
커밋 메시지 — 좋은 예/나쁜 예 비교, 컨벤션 안내.
+
+
+
⚠️흔한 실수 & 대처
+
+
실수 — main에 바로 커밋하거나, "수정", "ㅇㅇ" 같은 커밋 메시지.
+
대처 — "브랜치 → PR → 리뷰"가 기본임을 반복. 커밋 메시지는 "무엇을 왜 바꿨는지" 한 줄.
+
+
+
+
+ + +
+
Day5 · 세션 2 · 오후

코드리뷰 받는 법 · 스탠드업 · 30분 룰

약 50분
+
+
🎯학습목표
+
    +
  • 리뷰 피드백을 방어가 아니라 배움으로 받는 자세를 안다.
  • +
  • 스탠드업에서 무엇을 말하는지, 30분 룰을 안다.
  • +
+
+
💬멘토 설명 포인트
+
이렇게 설명
+

"코드리뷰에서 지적받는 건 여러분이 부족해서가 아니라, 그게 원래 일하는 방식이에요. 저도 매번 리뷰받아요. 변명보다 '왜 그런지'를 먼저 물어보세요."

+

"스탠드업은 딱 세 가지 — 어제 한 것 / 오늘 할 것 / 막힌 것. 30초면 돼요."

+
+
+
✍️실습 — 미니 스탠드업
+

학생들이 실제로 돌아가며 "어제/오늘/막힌 것"을 말해본다. (이번 주 배운 것을 소재로)

+
+
+
+ + +
+
Day5 · 세션 3 · 오후

온보딩 회고 & 1주차 마무리 1:1

약 60분
+
+
🎯학습목표
+
    +
  • 첫 주를 스스로 돌아보고 글로 정리한다.
  • +
+
+
✍️실습 — 온보딩 회고 (1장)
+
+

학생이 작성:

+
    +
  • 이번 주 가장 인상 깊었던 것
  • +
  • 어색했거나 어려웠던 것
  • +
  • 다음 주에 잘해보고 싶은 것
  • +
  • 회사·멘토에게 궁금하거나 바라는 것
  • +
+
+
+
멘토 — 첫 주간 1:1 (평가 기록 시작)
+
+

평가 루브릭의 주간 1:1 기록지에 첫 기록을 남긴다:

+
    +
  • 이번 주 잘한 것 1 / 아쉬운 것 1
  • +
  • 전달한 피드백 · 다음 주 목표
  • +
+

관찰 포인트(1주차): 태도·적응 속도·예절 습득·적극성.

+
+
+
+
+ + +
+ +
+ +
+ AWESOMEDEV · 1주차 상세 강의안 (멘토용) · 8부작 중 1 + 2026 · 수습 가이드·CS 커리큘럼·평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week2-assignments.html b/frontend/public/docs/week2-assignments.html new file mode 100644 index 0000000..c4efd43 --- /dev/null +++ b/frontend/public/docs/week2-assignments.html @@ -0,0 +1,850 @@ +어썸데브 수습 교육 · 2주차 일일 과제집 + + +
+ +
+
AWESOMEDEV · 일일 과제집 · 2 / 4
+

2주차 일일 과제집
코드 읽기 & 자료구조 적용

+

이 과제집은 오후 자기주도 시간(10:40–12:00, 13:00–16:30)에 여러분이 혼자 읽고 그대로 수행할 수 있도록 만든 문서예요. 매일 정해진 산출물이 있고, 16:30 멘토 리뷰 시간에 반드시 제출해요. 오전 강의에서 배운 내용을 오후에 손으로 직접 확인하는 것이 이번 주의 핵심이에요.

+
+ Week 2 · 월–금 + 오후 자기주도 시간용 + 매일 16:30 과제 리뷰 제출 +
+
+ +
+
Daily Rhythm
+

하루의 리듬

+

2주차 내내 매일 같은 리듬으로 움직여요. 시간을 몸에 익히면 과제에만 집중할 수 있어요.

+
+
+
09:30–10:30
아침 강의(멘토) — 이번 주는 자료구조(배열·리스트·스택·큐·해시·트리) + 요일별 주제 강의
+
10:40–12:00
일일 과제 전반 — 과제집을 열고 그날의 본 과제를 시작해요
+
13:00–16:30
일일 과제 후반 — 막히면 30분 룰: 30분 넘게 혼자 못 풀면 질문 메모를 적고 다음 단계로 넘어가요
+
16:30–17:00
멘토 과제 리뷰 — 그날의 산출물을 제출하고 피드백을 받아요
+
17:00–17:30
하루 정리 · 3줄 회고 — 오늘 배운 것 1줄 / 막힌 것 1줄 / 내일 할 것 1줄
+
+

규칙 하나만 기억해요: 과제가 일찍 끝나면 놀지 말고 예비 과제로 — 예비 과제도 평가에 반영돼요.

+
+
+ + +
+
MON
+
+

코드 지도 그리기

+

오전 강의 "코드베이스 구조 훑기"에서 본 폴더들을, 오후에는 직접 열어 보고 내 손으로 지도를 그려요.

+
+
+ +
+
+
본 과제

React · Spring Boot 프로젝트 구조도 그리기

+ 난이도 ★★ +
+
+
+
🎯목표
+

처음 보는 프로젝트를 받아도 "어디에 무엇이 있는지" 10분 안에 파악하는 눈을 만들어요.

+
+
+
📋진행 순서
+
    +
  1. 멘토가 안내한 연습용 React 프로젝트를 에디터(VS Code)로 열어요. 왼쪽 파일 탐색기에서 최상위 폴더부터 하나씩 펼쳐 보세요.
  2. +
  3. 주요 폴더 10개를 골라요. 예: src/, src/components/, src/pages/, src/hooks/, src/api/, public/, node_modules/ 등. 각 폴더 안의 파일 2~3개를 실제로 열어 보고 "이 폴더의 역할"을 한 줄로 적어요. 추측이면 문장 끝에 (추측)이라고 표시해요.
  4. +
  5. 아래 구조도 워크시트 양식에 맞춰 종이나 문서에 트리 형태로 그려요. 그림 도구는 자유(손그림 사진도 OK)지만 폴더 이름과 역할 한 줄은 반드시 들어가야 해요.
  6. +
  7. 같은 방법으로 연습용 Spring Boot 프로젝트를 열어요. src/main/java/ 아래 패키지(controller, service, repository, domain/entity, config 등)와 src/main/resources/를 중심으로 폴더 10개를 골라 역할 한 줄을 적어요.
  8. +
  9. 두 구조도를 나란히 놓고 공통점 2개, 차이점 2개를 문장으로 적어요. (예: "둘 다 화면/요청을 받는 층과 데이터 층이 나뉘어 있다")
  10. +
  11. 마지막으로 "내가 열어 봤지만 역할을 모르겠는 파일" 3개를 골라 파일 경로와 함께 질문으로 적어요. 이건 16:30 리뷰 때 멘토에게 그대로 물어봐요.
  12. +
+
+
+
워크시트 · 구조도 양식 (React / Spring Boot 각 1장)
+
[프로젝트 이름] ______________  (React / Spring Boot 중 하나)
+
+폴더 트리 (10개 이상)
+├─ 폴더명 ..................... 역할 한 줄: ______________________
+│   └─ 대표 파일 1개: ______________
+├─ 폴더명 ..................... 역할 한 줄: ______________________
+│   └─ 대표 파일 1개: ______________
+... (10개까지 반복)
+
+공통점·차이점 (두 장을 다 그린 뒤 작성)
+- 공통점 1: ______________________
+- 공통점 2: ______________________
+- 차이점 1: ______________________
+- 차이점 2: ______________________
+
+모르겠는 파일 질문 3개
+1) 경로: ______________ / 질문: ______________________
+2) 경로: ______________ / 질문: ______________________
+3) 경로: ______________ / 질문: ______________________
+
+
+
📋JS 기초 연습 (13:00 이후 병행)
+

아래 10문제를 새 파일 day1.js에 번호 주석과 함께 풀어요. 브라우저 콘솔이나 Node로 실행해 console.log 결과를 확인하고, 답은 외우지 말고 직접 실행해서 검증해요.

+
+
연습문제 · JS 기초 10제 (변수·조건·반복)
+
1. 변수 name에 자기 이름, age에 나이를 담고 "저는 OO이고 18살입니다" 형태로 출력하세요. (템플릿 문자열 ` ` 사용)
+2. let과 const의 차이를 코드로 증명하세요. const로 선언한 변수에 재할당을 시도하고, 발생한 에러 메시지를 주석으로 남기세요.
+3. 숫자 score가 90 이상이면 "A", 80 이상이면 "B", 그 외에는 "C"를 출력하는 if/else if/else를 작성하세요. score를 95, 82, 60으로 바꿔 3번 실행해 보세요.
+4. 숫자 n이 짝수면 "짝수", 홀수면 "홀수"를 출력하세요. (% 연산자 사용)
+5. for 반복문으로 1부터 10까지 출력하세요.
+6. for 반복문으로 1부터 100까지의 합을 구해 출력하세요. (정답은 5050이 나와야 해요)
+7. while 반복문으로 10부터 1까지 거꾸로 출력하세요.
+8. 1부터 30까지 중 3의 배수만 출력하세요. (if와 % 조합)
+9. 구구단 7단을 "7 x 1 = 7" 형태로 출력하세요.
+10. 이중 for문으로 별 찍기: 1줄에 *, 2줄에 **, ... 5줄에 *****가 나오게 출력하세요.
+
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출할 것 — 팀 공유 폴더의 본인 이름 폴더에 올려요.

+
    +
  • 구조도 2장 (React 1장 + Spring Boot 1장, 워크시트 양식대로)
  • +
  • 공통점·차이점 4문장 + 질문 3개 (구조도에 포함)
  • +
  • day1.js — 10문제 풀이, 문제 번호 주석 포함, 전부 실행돼야 해요
  • +
+
+
+
+
+ +
+
+
예비 과제

package.json 탐험

+ 난이도 ★ +
+
+
+
🎯목표
+

프로젝트의 "명함"인 설정 파일을 읽고 이 프로젝트가 무엇으로 만들어졌는지 설명할 수 있게 돼요.

+
+
+
📋진행 순서
+
    +
  1. React 프로젝트의 package.json을 열어요.
  2. +
  3. scripts 항목의 명령어 각각이 무슨 일을 하는지 추측해서 한 줄씩 적어요. (예: dev, build, lint)
  4. +
  5. dependencies에서 라이브러리 5개를 골라 이름을 검색해 보고, 각각의 용도를 한 줄로 적어요.
  6. +
  7. Spring Boot 쪽 pom.xml(또는 build.gradle)을 열어 같은 방식으로 의존성 5개의 용도를 적어요.
  8. +
+
+
+
제출
+
+

메모 1장(스크립트 설명 + 라이브러리 10개 용도)을 본 과제 산출물과 함께 제출해요. 예비 과제도 평가에 반영돼요.

+
+
+
+
+ +
+
+
디자이너 과제

UI 인벤토리 ① — 색상 팔레트 추출

+ 난이도 ★ +
+
+
+
🎨개발자 과제와의 차이
+
+

디자이너는 코드 대신 화면을 지도화해요. 이번 주 내내 우리 서비스 화면을 뒤져 "UI 인벤토리"를 만들고, 금요일에 개선 리포트로 완성해요. 오늘은 그 첫날, 색상이에요.

+
+
+
+
📋진행 순서
+
    +
  1. 멘토가 안내한 우리 서비스 화면(웹) 5개 이상을 돌아다니며 스크린샷을 찍어요.
  2. +
  3. 스포이드 도구(Figma·브라우저 개발자도구 등)로 화면에 실제 쓰인 색을 전부 추출해요. HEX 값으로 기록해요.
  4. +
  5. Figma에 색상 칩을 나열하고 주색·보조색·회색 계열·경고/성공색으로 그룹핑해요.
  6. +
  7. "거의 같은데 미묘하게 다른 색" 쌍을 찾아 표시해요. (예: #333333 vs #343434) — 목요일 불일치 찾기의 재료가 돼요.
  8. +
  9. 색이 몇 개인지 세어 보고, "이 서비스의 색은 관리되고 있는가?"에 대한 내 생각을 3문장으로 적어요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지:

+
    +
  • Figma 색상 팔레트 페이지 링크 (HEX 값 + 그룹핑 포함)
  • +
  • "미묘하게 다른 색" 쌍 목록 + 내 생각 3문장
  • +
+
+
+
+
+ + +
+
TUE
+
+

요청 추적 리포트

+

오전 강의 "Network 탭 요청 흐름"에서 본 것을, 오후에는 우리 서비스에서 직접 잡아내요.

+
+
+ +
+
+
본 과제

API 요청 5개 추적하기

+ 난이도 ★★ +
+
+
+
🎯목표
+

화면 뒤에서 오가는 요청과 응답을 스스로 관찰하고 "이 요청이 무슨 일을 하는지" 추론하는 힘을 길러요.

+
+
+
📋진행 순서
+
    +
  1. 브라우저에서 우리 서비스를 열고 F12 → Network 탭을 켜요. 필터를 Fetch/XHR로 바꾸면 API 요청만 보여요.
  2. +
  3. 서비스를 자유롭게 사용하면서(로그인, 목록 보기, 검색, 저장 등) Network 탭에 잡히는 요청 중 서로 다른 동작 5개를 골라요. GET만 5개 고르지 말고 POST·PUT·DELETE 중 최소 1개를 포함해요.
  4. +
  5. 각 요청을 클릭해 Headers·Payload·Response를 확인하고 아래 추적표 양식에 기록해요. 응답 JSON이 길면 핵심 필드 5개만 적어도 돼요.
  6. +
  7. 각 요청마다 "이 요청은 ~를 하는 것 같다"라는 추측 문장을 적어요. 근거(URL 단어, 응답 내용 등)도 한 줄 함께요.
  8. +
  9. 5개 중 하나를 골라 상태 코드가 무엇인지 확인하고, 일부러 실패시켜 보세요(예: 로그아웃 상태로 호출, 잘못된 검색어). 성공/실패 시 상태 코드와 응답이 어떻게 다른지 2문장으로 기록해요.
  10. +
  11. 다 적었으면 표를 다시 읽으며 내가 자신 없는 칸에 형광 표시를 해요. 리뷰 때 그 부분부터 질문해요.
  12. +
+
+
+
워크시트 · 요청 추적표 (요청 5개 × 아래 양식)
+
요청 #__  이름(내가 붙인 별명): ______________
+- 언제 발생? (무슨 버튼/화면): ______________________
+- Method: GET / POST / PUT / DELETE 중 ______
+- URL: ______________________
+- 요청 데이터(Payload, 없으면 "없음"): ______________________
+- 응답 상태 코드: ______
+- 응답 데이터(핵심 필드 위주): ______________________
+- 추측: 이 요청은 ______________________ 를 하는 것 같다.
+- 근거: ______________________
+
+(마지막에 한 번만) 실패 실험
+- 실험 방법: ______________________
+- 성공 시: 코드 ____ / 응답 ______________
+- 실패 시: 코드 ____ / 응답 ______________
+
+
+
📋JS 배열 연습 (병행)
+

아침 자료구조 강의(배열·리스트)와 연결되는 문제예요. day2.js에 15문제를 모두 풀고 실행해서 확인해요.

+
+
연습문제 · JS 배열 15제
+
공통 준비: const fruits = ["사과", "바나나", "포도"]; const nums = [3, 1, 4, 1, 5, 9, 2, 6];
+
+1. fruits 배열 끝에 "딸기"를 추가하고(push) 배열 전체를 출력하세요.
+2. fruits 배열의 마지막 요소를 제거하고(pop) 제거된 값과 남은 배열을 각각 출력하세요.
+3. fruits 배열 맨 앞에 "귤"을 추가하세요(unshift). 맨 앞 요소를 제거하는 것(shift)도 해 보세요.
+4. nums 배열의 길이(length)와 3번째 요소(인덱스 주의!)를 출력하세요.
+5. nums에서 5가 몇 번째 인덱스에 있는지 indexOf로 찾아 출력하세요. 없는 값 100을 찾으면 무엇이 나오는지도 확인하세요.
+6. nums 배열에 9가 포함되어 있는지 includes로 확인해 true/false를 출력하세요.
+7. for문으로 nums의 모든 요소를 한 줄씩 출력하세요. 그다음 같은 일을 forEach로 다시 작성하세요.
+8. nums의 모든 요소를 2배로 만든 새 배열을 map으로 만들어 출력하세요. (원본 nums는 그대로여야 해요 — 확인 출력 포함)
+9. nums에서 4 이상인 요소만 남긴 새 배열을 filter로 만들어 출력하세요.
+10. nums의 합계를 구하세요. 먼저 for문으로, 그다음 reduce로 두 번 구현하세요.
+11. nums에서 가장 큰 값을 찾으세요. (반복문으로 직접 — Math.max 금지)
+12. fruits 배열을 join으로 "사과, 바나나, 포도" 형태의 문자열로 만들어 출력하세요.
+13. nums를 오름차순 정렬하세요. sort()만 쓰면 [1,1,2,3,4,5,6,9]가 안 나올 수 있어요 — 왜 그런지 주석으로 설명하고 올바르게 고치세요.
+14. 학생 배열 const students = [{name:"김", score:85}, {name:"이", score:92}, {name:"박", score:78}] 에서 score가 80 이상인 학생의 name만 담긴 배열을 만드세요. (filter + map 조합)
+15. nums에서 중복을 제거한 새 배열을 만드세요. 방법은 자유(반복문, includes, Set 등) — 어떤 방법을 왜 골랐는지 주석 한 줄을 남기세요.
+
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출할 것

+
    +
  • 요청 추적표 5건 + 실패 실험 기록 (문서 1개)
  • +
  • day2.js — 배열 15문제 풀이 (문제 번호 주석 포함)
  • +
+
+
+
+
+ +
+
+
예비 과제

상태 코드 카드 만들기

+ 난이도 ★ +
+
+
+
🎯목표
+

자주 만나는 HTTP 상태 코드를 내 언어로 정리해 두면 앞으로 에러를 볼 때 당황하지 않아요.

+
+
+
📋진행 순서
+
    +
  1. 다음 8개 코드에 대해 조사해요: 200, 201, 301, 400, 401, 403, 404, 500.
  2. +
  3. 각 코드마다 "공식 의미 한 줄 + 내 언어로 다시 쓴 한 줄 + 언제 만날 것 같은지 예시 한 줄"을 적어요.
  4. +
  5. 오늘 Network 탭에서 실제로 본 코드에는 ✔ 표시를 해요.
  6. +
+
+
+
제출
+
+

상태 코드 카드 8장(문서 1개로 정리)을 본 과제와 함께 제출해요.

+
+
+
+
+ +
+
+
디자이너 과제

UI 인벤토리 ② — 타이포그래피

+ 난이도 ★ +
+
+
+
🎨개발자 과제와의 차이
+
+

개발자가 Network 탭을 보는 동안, 디자이너는 개발자도구의 Elements 탭으로 글자를 관찰해요. 같은 도구(F12)를 다른 각도로 쓰는 날이에요.

+
+
+
+
📋진행 순서
+
    +
  1. 어제 본 화면 5개에서 제목·부제목·본문·버튼 라벨·캡션에 해당하는 텍스트를 찾아요.
  2. +
  3. 개발자도구로 각 텍스트를 클릭 검사해 font-family / font-size / font-weight / line-height / 색상을 기록해요.
  4. +
  5. Figma에 "타입 스케일 표"를 만들어요: 역할(제목1, 제목2, 본문…) × 속성 값.
  6. +
  7. 같은 역할인데 값이 다른 경우(예: 본문이 어떤 화면은 14px, 어떤 화면은 15px)를 찾아 표시해요.
  8. +
  9. 글자 크기 종류가 총 몇 개인지 세고, "적절한 개수인가?"에 대한 생각을 3문장으로 적어요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지:

+
    +
  • Figma 타입 스케일 표 링크
  • +
  • 불일치 사례 목록 + 생각 3문장
  • +
+
+
+
+
+ + +
+
WED
+
+

기능 해부 보고서

+

오전 강의 "기능 읽기"에서 배운 추적법으로, 오후에는 기능 하나를 끝까지 해부해요. 월(구조)·화(요청)를 합치는 날이에요.

+
+
+ +
+
+
본 과제

기능 하나를 끝까지 해부하기

+ 난이도 ★★★ +
+
+
+
🎯목표
+

"버튼 클릭부터 응답까지" 한 기능의 전체 여정을 코드와 화면을 오가며 설명할 수 있게 돼요 — 이게 되면 코드 읽기의 절반은 끝난 거예요.

+
+
+
📋진행 순서
+
    +
  1. 해부할 기능을 하나 골라요. 추천: 로그인 또는 목록 조회. 너무 복잡한 기능(결제 등)은 피해요. 고민되면 10:40에 멘토에게 30초만 물어보고 시작해요.
  2. +
  3. 화면에서 관찰: 그 기능을 직접 사용하면서 "무엇을 누르면 → 화면이 어떻게 변하고 → Network 탭에 어떤 요청이 뜨고 → 어떤 응답이 오는지"를 순서대로 메모해요. (어제 배운 추적표 방식 재활용)
  4. +
  5. 프론트 코드에서 찾기: React 프로젝트에서 그 버튼/화면을 담당하는 컴포넌트 파일을 찾아요. 에디터 전체 검색(Ctrl+Shift+F)에 버튼에 적힌 문구나 URL 일부를 넣으면 빨라요. 찾으면 파일 경로와 줄 번호를 기록해요.
  6. +
  7. 그 컴포넌트에서 API를 호출하는 코드(fetch, axios 등)를 찾아 파일·줄을 기록하고, 화면에서 본 요청 URL과 일치하는지 확인해요.
  8. +
  9. 백엔드 코드에서 찾기: Spring Boot 프로젝트에서 그 URL을 받는 컨트롤러를 찾아요(@GetMapping·@PostMapping의 경로 검색). 컨트롤러 → 서비스 → 리포지토리로 이어지는 호출을 따라가며 각 단계의 파일·줄·역할 한 줄을 기록해요. 모든 줄을 이해할 필요는 없어요 — 흐름의 연결만 잡으면 성공이에요.
  10. +
  11. 아래 보고서 양식대로 정리해요. 이해 안 되는 코드는 "❓ 여기서 ~하는 것 같은데 확실하지 않음"으로 솔직하게 적어요. ❓가 있는 보고서가 없는 보고서보다 좋은 보고서예요.
  12. +
  13. 시간이 남으면 흐름을 그림 1장(손그림 OK)으로 그려 보고서에 붙여요.
  14. +
+
+
+
워크시트 · 기능 해부 보고서 양식
+
■ 기능 이름: ______________ (예: 로그인)
+■ 한 줄 요약: 사용자가 ______ 하면 ______ 가 일어난다.
+
+① 화면에서 관찰한 것
+- 트리거(무엇을 눌렀나): ______________
+- 화면 변화: ______________
+- 발생한 요청: METHOD ______ URL ______________
+- 응답(핵심만): ______________
+
+② 프론트 코드
+- 화면/버튼 담당 파일: ______________ (____ 줄 근처)
+- 이 파일이 하는 일 한 줄: ______________
+- API 호출 코드 위치: ______________ (____ 줄 근처)
+
+③ 백엔드 코드
+- 컨트롤러: ______________ (____ 줄) — 역할: ______________
+- 서비스: ______________ (____ 줄) — 역할: ______________
+- 리포지토리/DB 접근: ______________ (____ 줄) — 역할: ______________
+
+④ 전체 흐름 한 문장
+버튼 클릭 → __________ → __________ → __________ → 화면 갱신
+
+⑤ ❓ 이해 못 한 지점 (최소 2개, 솔직하게)
+1) ______________________
+2) ______________________
+
+
+
📋JS 객체 연습 (병행)
+

아침 자료구조 강의(해시)와 연결돼요. 객체와 Map은 "이름표로 값을 찾는 구조"예요. day3.js에 10문제를 풀어요.

+
+
연습문제 · JS 객체·Map 10제
+
1. 자신을 표현하는 객체 me를 만드세요. name, age, school, hobby 4개 속성을 넣고 전체를 출력하세요.
+2. me.hobby를 점 표기법으로, me["school"]을 대괄호 표기법으로 각각 출력하세요. 두 방식의 차이를 주석 한 줄로 적으세요.
+3. me에 새 속성 goal(올해 목표)을 추가하고, age 속성을 delete로 삭제한 뒤 결과를 출력하세요.
+4. Object.keys(me)와 Object.values(me)를 출력하고, 각각 무엇이 나오는지 주석으로 설명하세요.
+5. for...in 반복문으로 me의 모든 "속성이름: 값"을 한 줄씩 출력하세요.
+6. 메뉴판 객체 const menu = { 아메리카노: 4500, 라떼: 5000, 주스: 6000 } 에서 사용자가 고른 메뉴 이름(변수 pick)의 가격을 출력하세요. menu에 없는 메뉴면 "없는 메뉴입니다"를 출력하세요.
+7. 문자열 "hello world"에서 각 글자가 몇 번 나오는지 세는 객체를 만드세요. (예: {h:1, e:1, l:3, ...}) 공백은 제외하세요.
+8. new Map()으로 학번→이름 Map을 만들고 set으로 3명을 넣은 뒤, get으로 1명을 꺼내고 has로 존재 여부를 확인하세요. size도 출력하세요.
+9. 7번을 객체 대신 Map으로 다시 구현하세요. 객체 버전과 Map 버전 중 어느 쪽이 편했는지 주석 한 줄로 적으세요.
+10. 학생 배열 [{name:"김", team:"A"}, {name:"이", team:"B"}, {name:"박", team:"A"}] 를 팀별로 묶은 객체 {A:["김","박"], B:["이"]} 로 변환하세요.
+
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출할 것

+
    +
  • 기능 해부 보고서 1건 (양식의 ①~⑤ 모두 채울 것, ❓ 최소 2개 포함)
  • +
  • day3.js — 객체·Map 10문제 풀이
  • +
+
+
+
+
+ +
+
+
예비 과제

두 번째 기능 미니 해부

+ 난이도 ★ +
+
+
+
🎯목표
+

한 번 해 본 해부를 다른 기능에 반복하면 속도가 2배로 붙는 걸 스스로 느낄 수 있어요.

+
+
+
📋진행 순서
+
    +
  1. 본 과제에서 안 고른 기능 하나를 골라요.
  2. +
  3. 보고서 양식 중 ①(화면 관찰)과 ④(전체 흐름 한 문장)만 채워요. 코드 추적은 여유 있을 때만.
  4. +
  5. 본 과제 때보다 얼마나 빨라졌는지 소요 시간을 기록해요.
  6. +
+
+
+
제출
+
+

미니 보고서 1건(①+④만)과 소요 시간 비교 한 줄을 함께 제출해요.

+
+
+
+
+ +
+
+
디자이너 과제

UI 인벤토리 ③ — 컴포넌트 변형 수집

+ 난이도 ★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

개발자가 기능 하나를 세로로 깊게 파는 동안, 디자이너는 컴포넌트를 가로로 넓게 모아요. 오늘이 인벤토리 중 가장 손이 많이 가는 날이에요.

+
+
+
+
📋진행 순서
+
    +
  1. 서비스 전체에서 버튼을 보이는 대로 전부 스크린샷으로 수집해요. (주 버튼, 보조 버튼, 텍스트 버튼, 비활성 버튼, 아이콘 버튼…)
  2. +
  3. 같은 방법으로 입력 필드(기본, 포커스, 에러, 비활성)와 카드(목록 카드, 상세 카드 등)를 수집해요.
  4. +
  5. Figma에 세 그룹(버튼/입력/카드)으로 나눠 나열하고, 각 변형에 이름을 붙여요. (예: "버튼-주-대", "입력-에러")
  6. +
  7. 변형별로 크기·모서리 반경·색을 메모해요. 정확하지 않아도 눈대중 + 개발자도구 확인이면 충분해요.
  8. +
  9. "이 둘은 사실 하나여야 하지 않나?" 싶은 변형 쌍에 표시를 남겨요 — 내일 불일치 찾기의 핵심 재료예요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지:

+
    +
  • Figma 컴포넌트 인벤토리 페이지 링크 (버튼·입력·카드 3그룹, 변형 이름 포함)
  • +
  • "하나여야 할 것 같은 쌍" 후보 목록
  • +
+
+
+
+
+ + +
+
THU
+
+

PR 실전 연습

+

오전 강의 "Git 실전(브랜치→PR)"의 흐름을, 오후에 연습 저장소에서 3번 반복해 몸에 새겨요.

+
+
+ +
+
+
본 과제

브랜치 → 커밋 → PR 3회전 + 동료 리뷰

+ 난이도 ★★ +
+
+
+
🎯목표
+

실무 협업의 기본 사이클(브랜치→커밋→PR→리뷰)을 혼자서 처음부터 끝까지 3번 돌릴 수 있게 돼요.

+
+
+
📋진행 순서
+
    +
  1. 멘토가 만든 연습 저장소를 clone 해요. 오전에 받은 저장소 주소를 사용하고, git clone 주소cd 폴더명 순서예요.
  2. +
  3. 1회전 — README 오타 수정: git switch -c fix/readme-오타-본인이름 으로 브랜치를 만들고, README에 심어 둔 오타를 찾아 고쳐요. git addgit commit -m "docs: README 오타 수정"git push -u origin 브랜치명 → 저장소 웹 화면에서 PR 생성. PR 설명은 아래 양식대로 써요.
  4. +
  5. 2회전 — 주석 추가: main으로 돌아가(git switch maingit pull) 새 브랜치 docs/주석-본인이름을 만들어요. 저장소의 utils.js에서 주석 없는 함수 2개를 골라 "무엇을 받아 무엇을 돌려주는지" 주석을 달고, 같은 사이클로 PR까지 만들어요.
  6. +
  7. 3회전 — 간단 함수 작성: 브랜치 feat/함수-본인이름에서 utils.js에 아래 세 함수 중 2개를 골라 구현해요: ⓐ sum(arr) 배열의 합 반환 ⓑ maxNum(arr) 배열의 최댓값 반환 ⓒ countChar(str, ch) 문자열에서 특정 글자 개수 반환. console.log로 각각 2가지 입력을 실행해 결과 확인 후 PR까지 만들어요.
  8. +
  9. 동료 리뷰: 옆 사람의 PR 목록에서 아무거나 1개를 열어 코드를 읽고, 리뷰 코멘트 1개 이상을 남겨요. "좋아요"만 쓰지 말고 ⓐ 좋은 점 1가지 + ⓑ 질문 또는 제안 1가지를 꼭 포함해요. (예: "변수 이름이 알아보기 쉬워요. 그런데 빈 배열이 들어오면 어떻게 되나요?")
  10. +
  11. 내 PR에 달린 코멘트가 있으면 답글을 달아요. 수정이 필요하면 같은 브랜치에서 고치고 다시 push하면 PR에 자동 반영되는 것도 확인해요.
  12. +
  13. 3회전 중 헷갈렸던 명령어를 나만의 Git 치트시트로 정리해요(명령어 + 내 언어 설명, 최소 8줄).
  14. +
+
+
+
양식 · PR 설명 템플릿 (PR 3개 모두 이 양식으로)
+
## 무엇을 했나요?
+- (한 줄로: 예. README의 오타 3곳을 수정했습니다)
+
+## 왜 했나요?
+- (예: 새로 온 사람이 문서를 읽을 때 헷갈리지 않도록)
+
+## 확인한 방법
+- (예: 수정한 함수를 console.log로 2가지 입력에 대해 실행해 확인)
+
+## 리뷰어에게
+- 특히 봐 주셨으면 하는 부분: ______________
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출할 것

+
    +
  • PR 3개 링크 (오타 수정 / 주석 추가 / 함수 작성 — 모두 템플릿 양식 사용)
  • +
  • 동료 PR에 남긴 리뷰 코멘트 링크 1개 이상
  • +
  • 나만의 Git 치트시트 (8줄 이상)
  • +
+
+
+
+
+ +
+
+
예비 과제

커밋 메시지 고쳐 쓰기

+ 난이도 ★ +
+
+
+
🎯목표
+

좋은 커밋 메시지와 나쁜 커밋 메시지를 구별하는 눈을 만들어요.

+
+
+
📋진행 순서
+
    +
  1. 아래 나쁜 커밋 메시지 6개를 각각 좋은 메시지로 고쳐 쓰세요. 상황은 자유롭게 상상하되, "무엇을 왜 했는지"가 드러나야 해요.
  2. +
  3. 고친 메시지마다 "원래 메시지의 문제점"을 한 줄씩 적어요.
  4. +
+
+
+
연습문제 · 고쳐 쓸 커밋 메시지 6개
+
1. "수정"
+2. "asdf"
+3. "버그 고침 + 새 기능 + 오타 수정 + 리팩토링"
+4. "금요일 작업분"
+5. "이제 진짜 됨"
+6. "코드 추가"
+
+
+
제출
+
+

고쳐 쓴 메시지 6개 + 문제점 6줄을 문서로 제출해요.

+
+
+
+
+ +
+
+
디자이너 과제

UI 인벤토리 ④ — 불일치 지점 10개 찾기

+ 난이도 ★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

디자이너도 오전 Git 강의는 함께 들어요. 오후에는 개발자의 PR 대신, 월~수 인벤토리(색·글자·컴포넌트)를 증거 삼아 불일치를 잡아내요. 여유가 되면 개발자 연습 저장소에서 README 오타 수정 PR 1개에 도전해 보세요 — 디자이너가 PR을 만들 줄 알면 협업이 훨씬 부드러워져요.

+
+
+
+
📋진행 순서
+
    +
  1. 월~수에 모은 인벤토리를 펼쳐 놓고 불일치 지점 10개를 골라요. (색 2~3개, 타이포 2~3개, 컴포넌트 4~5개 권장)
  2. +
  3. 각 불일치마다 비교 스크린샷 2장(A화면 vs B화면)을 나란히 붙여요.
  4. +
  5. 각 항목에 "무엇이 다른가 / 사용자에게 어떤 혼란을 주는가 / 어느 쪽으로 통일하면 좋을까(내 의견)"를 각 한 줄씩 적어요.
  6. +
  7. 10개에 심각도(상·중·하)를 매기고, 상인 이유를 한 줄로 적어요.
  8. +
+
+
+
📤산출물 · 제출
+
+

16:30까지:

+
    +
  • 불일치 10건 목록 (비교 스크린샷 + 3줄 분석 + 심각도) — Figma 또는 문서
  • +
+
+
+
+
+ + +
+
FRI
+
+

주간 통합 미니 챌린지

+

오전 회고·퀴즈에 이어, 오후에는 이번 주 전부(자료구조 + 코드 읽기)를 하나로 묶는 챌린지예요.

+
+
+ +
+
+
본 과제

스택·큐 직접 구현 + 우리 서비스 속 배열 찾기

+ 난이도 ★★★ +
+
+
+
🎯목표
+

자료구조를 "외운 것"에서 "만들어 본 것"으로 바꾸고, 그것이 실제 서비스 어디에 살아 있는지 연결해요.

+
+
+
📋진행 순서
+
    +
  1. 새 파일 stack.jsStack 클래스를 만들어요. 내부 저장은 배열을 쓰되, 밖에서는 다음 4개 메서드만 쓰게 해요: push(값) 넣기 / pop() 맨 위 꺼내기(비었으면 undefined) / peek() 맨 위 확인만 / isEmpty() 비었는지 true·false.
  2. +
  3. 동작 확인 코드를 작성해요: 1, 2, 3을 push한 뒤 pop을 두 번 하면 3, 2가 나오고 peek이 1인지 console.log로 검증해요. 빈 스택에서 pop하는 경우도 꼭 확인해요.
  4. +
  5. queue.jsQueue 클래스를 만들어요: enqueue(값) 뒤로 넣기 / dequeue() 앞에서 꺼내기 / front() 맨 앞 확인 / isEmpty(). "가", "나", "다"를 넣고 꺼내면 넣은 순서 그대로 나오는지 검증해요.
  6. +
  7. 아래 판단문제 5개를 풀어요. 각 상황이 스택·큐 중 무엇에 어울리는지 고르고 이유를 한 줄씩 적어요.
  8. +
  9. 서비스 탐험: 우리 서비스에서 "목록(배열)이 화면에 그려지는 화면" 3개를 찾아 스크린샷을 찍어요. 각 화면마다 ⓐ 무엇의 목록인지 ⓑ Network 탭 응답에서 배열([ ... ])이 실제로 보이는지 ⓒ 프론트 코드에서 그 목록을 그리는 map 호출을 찾을 수 있으면 파일·줄까지 기록해요. (ⓒ는 3개 중 1개만 성공해도 충분해요)
  10. +
  11. 모든 내용을 주간 챌린지 리포트 1개 문서로 묶어요: 구현 코드 캡처(또는 링크) + 검증 결과 + 판단문제 답 + 화면 3건 기록.
  12. +
  13. 마지막으로 이번 주 3줄 회고의 확장판, 주간 회고 5줄을 리포트 끝에 적어요: 가장 배운 것 / 가장 어려웠던 것 / 30분 룰을 쓴 순간 / 다음 주에 시도할 것 / 멘토에게 바라는 것.
  14. +
+
+
+
연습문제 · 스택/큐 판단문제 5개 (답과 이유를 적으세요)
+
1. 브라우저의 "뒤로 가기" 버튼 — 스택일까 큐일까? 이유는?
+2. 프린터에 먼저 보낸 문서가 먼저 인쇄되는 대기열 — 스택일까 큐일까? 이유는?
+3. 에디터의 Ctrl+Z(실행 취소) — 스택일까 큐일까? 이유는?
+4. 고객센터 상담 대기 순번 — 스택일까 큐일까? 이유는?
+5. 함수가 함수를 호출하고, 안쪽 함수가 끝나야 바깥 함수로 돌아오는 구조 — 스택일까 큐일까? 이유는?
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출할 것

+
    +
  • stack.js + queue.js (검증용 console.log 포함, 전부 실행돼야 해요)
  • +
  • 주간 챌린지 리포트 1건 (판단문제 5개 답 + 배열 화면 3건 + 주간 회고 5줄 포함)
  • +
+
+
+
+
+ +
+
+
예비 과제

스택으로 괄호 검사기 만들기

+ 난이도 ★ +
+
+
+
🎯목표
+

내가 만든 자료구조를 "문제 풀이 도구"로 써 보는 첫 경험이에요.

+
+
+
📋진행 순서
+
    +
  1. 본 과제의 Stack 클래스를 재사용해 함수 isBalanced(str)를 만들어요. 문자열의 소괄호 ( )가 짝이 맞으면 true, 아니면 false를 반환해요.
  2. +
  3. 힌트: (를 만나면 push, )를 만나면 pop. pop할 게 없거나, 끝났는데 스택에 남아 있으면 false예요.
  4. +
  5. 다음 5개 입력으로 검증해요: "(())" → true / "(()" → false / "())(" → false / "안녕(하세요)" → true / "" → true.
  6. +
+
+
+
제출
+
+

balance.js(검증 5건 출력 포함)를 본 과제와 함께 제출해요.

+
+
+
+
+ +
+
+
디자이너 과제

UI 개선점 5개 리포트 완성

+ 난이도 ★★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

디자이너의 주간 통합은 "리포트 완성"이에요. 월~목 인벤토리와 불일치 목록이 재료이고, 오늘은 그것을 남에게 보여줄 수 있는 한 편의 문서로 만들어요.

+
+
+
+
📋진행 순서
+
    +
  1. 어제 찾은 불일치 10개 중 심각도가 높고 고치기 쉬운 것 위주로 개선점 5개를 골라요.
  2. +
  3. 각 개선점을 현재(As-Is) 스크린샷 → 개선안(To-Be) 시안 구성으로 만들어요. To-Be는 Figma로 간단히 그리면 돼요 — 완성도보다 "무엇이 달라지는지"가 보이면 충분해요.
  4. +
  5. 각 개선점에 3줄을 붙여요: 문제 / 개선안 / 기대 효과(사용자 입장에서).
  6. +
  7. 표지(제목·이름·날짜)와 마지막 장(이번 주 인벤토리 요약: 색 몇 개, 글자 크기 몇 종, 버튼 변형 몇 개)을 붙여 리포트를 완성해요.
  8. +
  9. 16:30 리뷰 때 5분 발표로 공유할 수 있게 말할 순서를 머릿속으로 한 번 연습해요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지:

+
    +
  • UI 개선 리포트 1건 (개선점 5개 × As-Is/To-Be + 3줄 분석, 표지·요약 포함)
  • +
  • 리뷰 시간 5분 발표
  • +
+
+
+
+
+ + +
+
Mentor Guide
+

멘토 리뷰 가이드 (16:30–17:00)

+

멘토용 참고 표예요. 학생도 읽어 두면 "리뷰 때 무엇을 물어볼지" 미리 준비할 수 있어요.

+
+
+
확인: 구조도 10개 폴더에 역할이 다 적혔는가, (추측) 표시를 솔직하게 했는가. 질문: "새 화면을 하나 추가한다면 어느 폴더에 파일을 만들 것 같아요?"
+
확인: 5개 요청에 GET 외 메서드가 포함됐는가, 추측에 근거가 붙었는가. 질문: "이 요청이 실패하면 사용자는 화면에서 뭘 보게 될까요?"
+
확인: 프론트→백엔드 흐름이 파일·줄로 연결됐는가, ❓ 항목이 최소 2개 있는가. 질문: "❓ 중 하나를 골라 지금 같이 열어 볼까요?" (즉석 해소 1건)
+
확인: PR 3개가 템플릿을 지켰는가, 리뷰 코멘트에 질문/제안이 들어갔는가. 질문: "리뷰 코멘트를 받았을 때 기분이 어땠어요? 어떤 코멘트가 좋은 코멘트일까요?"
+
확인: 스택·큐가 빈 경우까지 검증됐는가, 배열 화면 3건이 응답 JSON과 연결됐는가. 질문: "이번 주 배운 것 중 다음 주 코드 수정 때 바로 쓸 수 있는 건 뭐예요?" (디자이너는 5분 발표로 대체)
+
+
+
+ +
+
AWESOMEDEV · 2주차 일일 과제집 (학생 배포용) · 4부작 중 2
+
2026 · 2주차 상세 강의안과 함께 사용
+
+ +
diff --git a/frontend/public/docs/week2-detailed.html b/frontend/public/docs/week2-detailed.html new file mode 100644 index 0000000..6a83791 --- /dev/null +++ b/frontend/public/docs/week2-detailed.html @@ -0,0 +1,823 @@ +2주차 상세 강의안 — 기초 다지기 · AWESOMEDEV 수습 교육 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 2 / 8
+

2주차 상세 강의안
기초 다지기 — 읽는 눈 만들기

+

이번 주의 목표는 두 가지예요. 하나, 코드와 화면을 "읽는" 눈을 만드는 것. 우리 React 웹과 Spring Boot 서버가 어떻게 생겼고, 화면이 서버에 어떻게 말을 거는지 눈으로 확인해요. 둘, 지난주에 배운 예절과 질문법을 매일 반복해서 몸에 붙이는 것. 아직 뭔가를 "만들지" 않아도 괜찮아요. 잘 읽는 사람이 결국 잘 만듭니다.

+
+ 대상 · 미림마이스터고 3학년 수습생 4명 + 개발 3 · 디자인 1 + 주 5일 · 아침 기본기 + 실무 트랙 +
+ + +
+ +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 각 세션 카드를 위에서 아래로 그대로 읽어 내려가며 진행하세요.

+
+
+ + +
+
MON
+
+

코드베이스 구조 훑기

+
목표 · 우리 React 폴더와 Spring Boot 프로젝트가 "어떻게 생겼는지" 큰 지도를 함께 그린다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

기본기(B) — 컴퓨터 구조 마무리 → 자료구조 시작

+
09:30 – 10:30 · 60분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 지난주 컴퓨터 구조(CPU·메모리·저장장치)를 한 장으로 마무리한다.
  • +
  • 자료구조의 문을 연다 — 배열·리스트, 스택·큐가 "무엇을 담는 통"인지 감을 잡는다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

「CS 기본기 4주 커리큘럼」 문서의 2주차 1~2일차를 그대로 진행합니다.

+
    +
  • 이 강의안은 시간 슬롯만 잡아두는 포인터예요. 상세 내용·예제·퀴즈는 CS 커리큘럼 문서를 여세요.
  • +
  • 디자이너 수습생도 함께 듣습니다 — 자료구조는 "화면이 데이터를 어떻게 줄 세우는가"의 기초라 UI에도 쓸모가 있어요.
  • +
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오전
+

지도를 펼치자 — 프로젝트 폴더 구조 함께 열기

+
10:40 – 12:00 · 80분
+
+
+
+
🎯학습목표
+
    +
  • React 웹 프로젝트에서 "폴더가 왜 이렇게 나뉘어 있는지" 역할을 말할 수 있다.
  • +
  • Spring Boot 프로젝트의 큰 덩어리(컨트롤러·서비스·설정)가 어디 있는지 손가락으로 짚을 수 있다.
  • +
  • "어디에 무엇이 있나"를 스스로 찾는 습관의 첫 발을 뗀다.
  • +
+
+
+
🕘진행표
+
+
0–10분
워밍업. "지도 없이 처음 간 건물에서 화장실 찾기" 비유로 시작. 코드도 지도가 있으면 안 헤맨다고 연결.
+
10–40분
React 웹 투어. 멘토가 에디터를 띄우고 폴더를 하나씩 열며 설명. 각자 자기 화면으로 똑같이 따라 연다.
+
40–65분
Spring Boot 투어. 서버 프로젝트를 열고 컨트롤러 → 서비스 → 설정 순서로 큰 덩어리만 짚는다.
+
65–80분
실습 안내 + 질문. 오후 구조도 실습을 예고하고 궁금한 폴더를 자유롭게 질문받는다.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자, 오늘은 코드를 고치지 않아요. 그냥 구경만 할 거예요. 새 학교 첫날 건물 한 바퀴 도는 거랑 똑같아요. 어디가 교실이고 어디가 급식실인지만 알면 돼요."

+

"React 웹부터 볼게요. src 폴더 안에 components, pages, api 이런 게 보이죠? components는 버튼·카드처럼 화면 조각이고요, pages는 그 조각을 모아 만든 한 화면이에요. 레고 블록(components)이랑 완성품(pages) 관계라고 생각하면 돼요."

+

"이제 서버 쪽. Spring Boot는 controller가 손님을 맞는 안내데스크예요. service는 실제로 일을 처리하는 주방이고요. 안내데스크가 주문을 받아 주방에 넘기는 거예요. 지금은 이 두 개만 기억해도 충분해요."

+

수습생이 "이게 다 뭔지 모르겠어요"라고 하면 — "당연해요! 오늘은 이름이랑 위치만 익히는 날이에요. 뜻은 이번 주 내내 천천히 붙일 거예요." 라고 안심시킨다.

+
+
+
+
✍️실습 — 구조도 직접 그리기
+
+

손으로 그리는 게 핵심입니다. 종이·화이트보드·아이패드 무엇이든 좋아요. 오후에 각자 자기 지도를 완성해 서로에게 30초씩 설명합니다.

+
    +
  1. React 웹의 최상위 폴더 3~5개를 상자로 그린다.
  2. +
  3. 각 상자 옆에 "무엇을 담는 곳"인지 한 줄로 적는다.
  4. +
  5. Spring Boot도 같은 방식으로 controller · service · config를 그린다.
  6. +
  7. 화면(React)에서 서버(Spring)로 화살표 하나를 긋고 "여기서 요청이 간다"라고 적는다.
  8. +
+
+
+
워크시트 · 빈칸을 채우세요
+
[ 나의 코드베이스 지도 ]
+
+■ React 웹
+  components/  → (               ) 을(를) 담는 곳
+  pages/       → (               ) 을(를) 담는 곳
+  (        )/  → API 호출을 모아둔 곳
+
+■ Spring Boot 서버
+  controller  → (               ) 역할
+  service     → (               ) 역할
+
+■ 화살표: 화면이 서버에게 (         ) 를 보낸다.
+
+
+
+
이해 확인
+
+

다음을 각자 말로 답하면 통과예요.

+
    +
  • "버튼 하나를 새로 만들면 React의 어느 폴더에 파일이 생길까요?"
  • +
  • "서버에서 손님 요청을 가장 먼저 받는 곳 이름은?"
  • +
  • 자기 구조도를 보지 않고 폴더 3개의 역할을 설명한다.
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 모든 폴더를 완벽히 이해하려다 지쳐버린다.
+
대처 · "오늘은 상위 폴더 5개면 충분해요. 하위 폴더는 필요할 때 열어보면 돼요"라고 범위를 좁혀준다.
+
실수 · 파일 이름을 외우려고 한다.
+
대처 · 외우는 게 아니라 "찾는 습관"이 목표임을 강조. 지도는 언제든 다시 열 수 있다.
+
+
+
+
+ + +
+
TUE
+
+

요청 흐름 추적

+
목표 · 화면이 서버에 "말을 거는" 순간을 개발자도구 Network 탭으로 직접 눈으로 본다.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

기본기(B) — 자료구조 이어가기 (배열·리스트)

+
09:30 – 10:30 · 60분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 배열과 리스트가 데이터를 "줄 세우는" 방식의 차이를 한 문장으로 말한다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

「CS 기본기 4주 커리큘럼」 문서의 2주차 3일차를 그대로 진행합니다.

+
    +
  • 예제 코드는 JavaScript(배열)를 우선 사용하고, 필요하면 Java를 보조로 보여주세요.
  • +
+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오후
+

화면이 서버에 말을 건다 — Network 탭 열기

+
13:00 – 14:40 · 100분
+
+
+
+
🎯학습목표
+
    +
  • 브라우저 개발자도구의 Network 탭을 열고 요청 목록을 볼 수 있다.
  • +
  • 요청 하나에서 method(GET/POST) · URL · 응답(Response)을 짚어낼 수 있다.
  • +
  • "React 화면 → REST API → Spring Boot 서버"라는 흐름을 자기 말로 설명한다.
  • +
+
+
+
🕘진행표
+
+
0–15분
도구 열기. 크롬에서 F12 → Network 탭. 다 함께 열고 화면을 새로고침해 요청이 주르륵 뜨는 걸 본다.
+
15–45분
한 줄 뜯어보기. 멘토가 요청 하나를 클릭해 Headers(method·URL)와 Response를 함께 읽는다.
+
45–75분
따라하기. 각자 우리 서비스 화면을 조작하며 자기 Network 탭에 뜨는 요청을 관찰한다.
+
75–100분
워크시트 작성 + 공유. 각자 요청 하나를 골라 표를 채우고 한 명씩 발표한다.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"어제는 코드가 어디 있는지 지도를 그렸죠? 오늘은 그 지도 위에서 편지가 오가는 걸 직접 볼 거예요. 화면이 서버한테 '이 데이터 좀 주세요' 하고 편지를 보내거든요."

+

"키보드에서 F12 눌러보세요. 창이 하나 뜨죠? 위에 Network라고 적힌 탭을 눌러요. 그리고 화면을 새로고침. 자, 줄이 좍 뜨죠? 이게 전부 방금 오간 편지예요. 하나하나가 화면과 서버의 대화예요."

+

"아무거나 하나 클릭해볼게요. Request URL — 이게 편지를 보낸 주소예요. Request MethodGET이면 '주세요', POST면 '이거 저장해주세요'라는 뜻이에요. 밑에 Response는 서버가 보낸 답장이고요."

+

수습생이 요청이 너무 많아 혼란스러워하면 — "필터 칸에 'Fetch/XHR'를 눌러보세요. 진짜 데이터 편지만 남아요. 이미지·폰트 같은 건 잠깐 무시해도 돼요." 라고 안내한다.

+
+
+
+
✍️실습 — 요청 하나 골라 관찰하기
+
+

요청 하나를 골라 아래 표를 채우면 끝입니다. 어려운 요청 말고, 목록 조회처럼 답이 눈에 잘 보이는 GET 요청을 고르라고 안내하세요.

+
    +
  • 우리 서비스 화면에서 목록/조회 화면을 하나 연다.
  • +
  • Network 탭에서 방금 뜬 요청 하나를 클릭한다.
  • +
  • method · URL · 응답의 첫 줄을 워크시트에 옮겨 적는다.
  • +
+
+
+
워크시트 · 요청 관찰 카드
+
[ 내가 관찰한 요청 ]
+
+이 요청은 (어떤 화면 / 버튼) 에서 발생했나요?
+  → ___________________________
+
+Request Method  : GET / POST  (동그라미)
+Request URL     : _______________________
+Status(상태코드): ___  (200이면 성공!)
+
+Response(답장)에서 눈에 띄는 값 하나:
+  → ___________________________
+
+한 줄 소감: 이 요청은 서버에게 무엇을 부탁한 걸까?
+  → ___________________________
+
+
+
+
이해 확인
+
+

발표할 때 다음이 담기면 통과예요.

+
    +
  • "내가 고른 요청은 GET/POST 중 무엇이고 왜 그렇게 판단했는지"
  • +
  • "이 요청의 URL상태코드가 무엇이었는지"
  • +
  • "이 편지가 어느 화면 동작 때문에 발생했는지"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 요청 목록이 수십 개라 어디를 봐야 할지 몰라 얼어붙는다.
+
대처 · 'Fetch/XHR' 필터를 켜서 진짜 데이터 요청만 남기고, 목록을 위에서 하나만 고르게 한다.
+
실수 · 응답(JSON)을 전부 이해하려 한다.
+
대처 · "지금은 값 하나만 짚으면 돼요. 전체 해석은 다음 단계예요"라고 범위를 정해준다.
+
+
+
+
+ + +
+
WED
+
+

기능 읽기 과제

+
목표 · 화면/기능 하나를 골라 "어떻게 동작하는지" 분석해 문서로 정리하고 멘토와 리뷰한다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

기본기(B) — 자료구조 (스택 · 큐)

+
09:30 – 10:30 · 60분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 스택(쌓기)과 큐(줄서기)의 차이를 일상 비유로 설명한다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

「CS 기본기 4주 커리큘럼」 문서의 2주차 4일차를 그대로 진행합니다.

+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오전
+

기능 하나를 통째로 읽기 — 분석 과제 착수

+
10:40 – 12:00 · 80분
+
+
+
+
🎯학습목표
+
    +
  • 화면 기능 하나를 골라 "사용자 동작 → 화면 변화 → 서버 요청"의 순서를 적을 수 있다.
  • +
  • 어제 배운 Network 탭 관찰을 분석 근거로 활용한다.
  • +
  • 모르는 부분을 "질문 리스트"로 남기는 습관을 익힌다.
  • +
+
+
+
🕘진행표
+
+
0–15분
과제 소개. "기능 하나를 탐정처럼 관찰해 리포트를 쓴다." 좋은 예시 리포트 1개를 함께 읽는다.
+
15–30분
기능 고르기. 각자 분석할 기능을 하나 정한다 (예: 로그인, 목록 검색, 좋아요 버튼).
+
30–75분
관찰·기록. 화면을 눌러보고 Network 탭을 확인하며 워크시트를 채운다. 멘토는 순회하며 막힌 곳을 돕는다.
+
75–80분
오후 리뷰 예고. "완벽 말고 솔직하게. 모르는 건 물음표로 남기세요"라고 안내.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘은 여러분이 탐정이에요. 기능 하나를 골라서 '얘가 대체 어떻게 움직이는 거지?'를 캐내는 거예요. 코드를 고치는 게 아니라 설명하는 게 목표예요."

+

"기능은 작은 걸 고르는 게 좋아요. '좋아요 버튼', '검색창', '로그인' 이런 거요. 화려한 화면 말고, 눌렀을 때 뭔가 딱 벌어지는 단순한 걸로요."

+

"순서는 세 칸이에요. ①내가 무엇을 눌렀나 → ②화면이 어떻게 변했나 → ③그때 Network 탭에 어떤 편지가 갔나. 이 세 칸만 채우면 훌륭한 분석이에요."

+

"막히면 물음표로 남기세요"를 반복 강조. 모르는 걸 솔직히 적는 게 감점이 아니라 오히려 좋은 리포트라고 알려준다.

+
+
+
+
✍️실습 — 기능 분석 리포트 (이번 주 산출물 ①)
+
+

이 리포트가 이번 주 공식 산출물입니다. 오전에 초안, 오후 리뷰 후 보완해 완성하세요.

+
    +
  1. 분석할 기능 하나를 정한다.
  2. +
  3. 동작을 ①동작 → ②화면변화 → ③서버요청 세 칸으로 적는다.
  4. +
  5. 모르는 부분은 "질문" 항목에 물음표로 남긴다.
  6. +
+
+
+
템플릿 · 기능 분석 리포트
+
# 기능 분석 리포트
+
+분석한 기능 : _______________________
+작성자       : _______________________
+
+## 1. 내가 한 동작
+  - (예: 검색창에 "KT"를 입력하고 엔터를 눌렀다)
+
+## 2. 화면에서 벌어진 일
+  - (예: 목록이 KT 관련 항목으로 바뀌었다)
+
+## 3. 그때 오간 요청 (Network 탭)
+  - Method / URL : ___________________
+  - 서버가 준 답 : ___________________
+
+## 4. 아직 모르는 것 (물음표 환영!)
+  - ? ___________________________
+
+
+
+
이해 확인 — 오후 멘토 리뷰
+
+

오후 1:1 리뷰에서 다음을 확인합니다.

+
    +
  • ①②③ 세 칸이 순서대로 이어지는지 (동작이 화면 변화로, 화면 변화가 요청으로 연결)
  • +
  • 물음표(모르는 부분)를 솔직하게 남겼는지 — 남겼으면 칭찬
  • +
  • 어제 배운 Network 관찰이 근거로 들어갔는지
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 너무 크고 복잡한 기능을 골라 첫 칸부터 막힌다.
+
대처 · "버튼 하나짜리로 바꿔봐요"라고 스케일을 줄여준다.
+
실수 · 모르는 걸 감추려고 아는 척 문장을 지어낸다.
+
대처 · "물음표가 많은 리포트가 정직한 리포트예요"라고 심리적 안전감을 준다.
+
+
+
+
+ + +
+
THU
+
+

Git 실전 연습

+
목표 · 연습용 저장소에서 브랜치 → 수정 → 커밋 → PR까지 한 바퀴를 직접 돌려본다.
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

기본기(B) — 자료구조 복습 · 미니 문제

+
09:30 – 10:30 · 60분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 이번 주 배운 배열·리스트·스택·큐를 미니 문제로 스스로 점검한다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

「CS 기본기 4주 커리큘럼」 문서의 2주차 5일차(복습·문제)를 그대로 진행합니다.

+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오전
+

Git 한 바퀴 — 브랜치에서 PR까지

+
10:40 – 12:00 · 80분
+
+
+
+
🎯학습목표
+
    +
  • 연습용 저장소에서 브랜치를 만들고 파일을 수정할 수 있다.
  • +
  • 커밋 컨벤션(feat: · fix: · docs:)에 맞춰 커밋 메시지를 쓴다.
  • +
  • PR(Pull Request)을 올려 "리뷰해 주세요" 상태까지 만든다.
  • +
+
+
+
🕘진행표
+
+
0–10분
비유 워밍업. 브랜치 = 원본을 건드리지 않는 "연습용 복사본". 왜 바로 고치지 않는지 설명.
+
10–35분
함께 한 바퀴. 멘토가 화면 공유로 브랜치 생성 → 파일 수정 → 커밋을 시연. 학생은 눈으로 먼저 본다.
+
35–65분
각자 한 바퀴. 연습용 저장소에서 각자 같은 순서를 따라 한다. 멘토 순회 지원.
+
65–80분
PR 올리기 + 컨벤션 복습. PR을 만들고 제목을 커밋 컨벤션에 맞춘다. 서로의 PR을 열어본다.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"Git은 게임의 세이브 파일이랑 비슷해요. 실수해도 이전 세이브로 돌아갈 수 있어요. 그래서 하나도 안 무서워요. 오늘 마음껏 실수해도 돼요 — 연습용 저장소니까요."

+

"먼저 브랜치를 만들어요. 이건 원본을 안 건드리는 '나만의 연습장'이에요. 여기서 파일을 고치고, 다 됐으면 커밋으로 '여기까지 저장!' 도장을 찍어요."

+

"커밋 메시지에는 규칙이 있어요. 새 기능이면 feat:, 고친 거면 fix:, 문서면 docs:를 앞에 붙여요. 지난주에 봤던 그 규칙이에요. 예: docs: 내 소개 파일 추가."

+

"마지막은 PR. '제가 이렇게 고쳤어요, 봐주세요'라고 손드는 거예요. PR을 올리면 리뷰어가 확인하고 합쳐줘요. 오늘은 올리는 것까지가 목표예요."

+
+
+
+
✍️실습 — 연습 PR 올리기 (이번 주 산출물 ②)
+
+

이 PR이 이번 주 공식 산출물입니다. 아래 순서를 그대로 따라오게 하세요. 명령어는 멘토가 화면에 크게 띄워둡니다.

+
    +
  1. 연습용 저장소를 받아 브랜치를 만든다.
  2. +
  3. 자기 소개를 담은 파일 하나를 추가/수정한다.
  4. +
  5. 커밋 컨벤션에 맞춰 커밋한다.
  6. +
  7. PR을 올리고 제목을 컨벤션대로 쓴다.
  8. +
+
+
+
따라 치는 명령 · 빈칸을 채우세요
+
# 1) 내 연습용 브랜치 만들기
+git checkout -b practice/이름-week2
+
+# 2) 파일 수정 후, 바뀐 걸 담기
+git add 파일이름
+
+# 3) 도장 찍기 (컨벤션 지키기!)
+git commit -m "docs: __________ 추가"
+
+# 4) 내 브랜치를 원격으로 밀어올리기
+git push -u origin practice/이름-week2
+
+# 5) 브라우저에서 "Pull Request" 버튼을 눌러 PR 생성
+#    PR 제목도 컨벤션으로!  예)  docs: 수습 자기소개 추가
+
+
+
+
이해 확인
+
+

PR 링크를 멘토에게 공유하며 다음을 확인합니다.

+
    +
  • 브랜치 이름이 main이 아닌 연습용 브랜치인지
  • +
  • 커밋 메시지가 컨벤션 접두어로 시작하는지
  • +
  • PR이 실제로 열려 "리뷰 대기" 상태인지
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 브랜치를 안 만들고 main에서 바로 작업한다.
+
대처 · 첫 단계 git checkout -b를 다 함께 소리 내어 확인하고 시작한다.
+
실수 · 커밋 메시지를 "수정함", "ㅇㅇ"처럼 대충 쓴다.
+
대처 · "미래의 내가 읽는 편지예요"라고 상기시키고 컨벤션 예시를 다시 보여준다.
+
+
+
+
+ + +
+
+
주중 병행 · 디자이너 트랙 · 오후
+

디자이너 — UI 인벤토리 작성 + 개선점 5개 리포트

+
주중 오후 시간 분산 · 총 4~5시간
+
+
+
+
🎯학습목표
+
    +
  • 우리 서비스 화면의 색·타이포·컴포넌트를 목록으로 정리(UI 인벤토리)할 수 있다.
  • +
  • 일관성이 깨진 곳·개선 여지를 5개 찾아 근거와 함께 리포트한다.
  • +
  • 예절과 질문법은 개발자 트랙과 동일하게 상시 관찰 대상이다.
  • +
+
+
+
💬멘토 스크립트
+
+
멘토
+

"개발자 친구들이 코드를 '읽는' 동안, 디자이너는 화면을 '읽어요'. 우리 서비스에 쓰인 색이 몇 개인지, 글자 크기가 몇 종류인지, 버튼 모양이 제각각인지 하나하나 세어보는 거예요."

+

"세다 보면 '어? 이 버튼만 색이 다르네?' 같은 게 보여요. 그게 바로 개선점이에요. 5개만 찾아서 '어디가, 왜 아쉽고, 어떻게 바꾸면 좋을지'를 적어주면 훌륭한 리포트예요."

+
+
+
+
✍️실습 — UI 인벤토리 & 개선 리포트
+
+

화면을 눈으로 훑으며 빈칸을 채웁니다. 스크린샷을 붙여두면 근거가 더 명확해져요.

+
    +
  • 색: 실제 쓰인 색을 모아 목록으로.
  • +
  • 타이포: 글자 크기·굵기 종류를 정리.
  • +
  • 컴포넌트: 버튼·카드·입력창 등 반복되는 조각 정리.
  • +
+
+
+
템플릿 · UI 인벤토리 + 개선점
+
# UI 인벤토리
+
+## 색 (Colors)
+  - 주 색 : #______  / 보조 : #______
+
+## 타이포 (Typography)
+  - 제목 __px / 본문 __px / 캡션 __px
+
+## 컴포넌트 (Components)
+  - 버튼 종류 __개, 카드 __종류 ...
+
+# 개선점 5가지
+  1. 어디가?  → ______
+     왜 아쉽나? → ______
+     어떻게? → ______
+  (2~5 동일 형식)
+
+
+
+
⚠️흔한 실수
+
+
실수 · "그냥 예쁘다/별로다"처럼 취향으로만 평가한다.
+
대처 · "일관성·가독성처럼 근거를 붙여요"라고 방향을 잡아준다.
+
실수 · 개선점을 20개씩 나열해 초점이 흐려진다.
+
대처 · "가장 눈에 띄는 5개만. 깊게 쓰는 게 나아요"라고 범위를 좁힌다.
+
+
+
+
+ + +
+
FRI
+
+

주간 회고 · 1:1 · 미니 퀴즈

+
목표 · 한 주를 스스로 돌아보고(잘한 것 1 / 개선 1), 기본기를 퀴즈로 점검한다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

기본기(B) 미니 퀴즈

+
09:30 – 10:30 · 60분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 이번 주 자료구조 개념을 짧은 퀴즈로 스스로 확인하고, 약한 부분을 안다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

「CS 기본기 4주 커리큘럼」 문서의 2주차 미니 퀴즈를 진행합니다.

+
    +
  • 점수용이 아니라 "어디가 약한지 확인용"임을 분명히 알려주세요. 틀린 문항은 함께 다시 봅니다.
  • +
+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오후
+

주간 회고 & 1:1 — 잘한 것 1 · 개선 1

+
13:00 – 15:00 · 120분 (회고 40분 + 1:1 순차)
+
+
+
+
🎯학습목표
+
    +
  • 한 주를 돌아보며 "잘한 것 1개 / 개선할 것 1개"를 구체적으로 적는다.
  • +
  • 1:1에서 멘토와 다음 주 목표 한 가지를 합의한다.
  • +
  • 이번 주 산출물(기능 분석 문서 · 연습 PR)을 함께 점검한다.
  • +
+
+
+
🕘진행표
+
+
0–20분
개인 회고 작성. 각자 조용히 회고 시트를 채운다. 멘토는 방해하지 않는다.
+
20–40분
돌아가며 공유. 한 명씩 "잘한 것 1 / 개선 1"을 말한다. 서로 박수·격려.
+
40–110분
1:1 (1인당 약 15분). 산출물 점검 + 다음 주 목표 합의. 나머지는 회고 보완·자율 정리.
+
110–120분
마무리. 다음 주 예고와 격려로 한 주를 닫는다.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"한 주 정말 고생했어요. 오늘은 '내가 뭘 했지?'를 정리하는 날이에요. 딱 두 가지만 적어요 — 잘한 것 하나, 다음에 더 잘하고 싶은 것 하나. 많이 안 적어도 돼요."

+

"'잘한 것'은 아주 작아도 좋아요. '처음으로 Network 탭을 열어봤다'도 훌륭한 성취예요. 개선점도 자책이 아니라 '다음 주엔 이걸 해보자' 같은 방향이면 돼요."

+

1:1에서는 이번 주 산출물 두 개(기능 분석 문서, 연습 PR)를 함께 열어보고, 이해력·질문의 질(30분 룰·질문 3요소)·예절 정착도를 부드럽게 피드백한다. 지적보다 성장을 먼저 짚는다.

+
+
+
+
✍️실습 — 주간 회고 시트
+
+

두 칸이면 충분합니다. 다음 주 1:1에서 이 시트를 이어서 봅니다.

+
+
+
템플릿 · 2주차 주간 회고
+
# 2주차 회고 — 이름: ______
+
+■ 이번 주 잘한 것 1가지
+  → ___________________________
+
+■ 다음 주 더 해보고 싶은 것 1가지
+  → ___________________________
+
+■ 멘토와 합의한 다음 주 목표
+  → ___________________________
+
+[ 산출물 체크 ]
+  □ 기능 분석 문서 1개
+  □ 연습 PR 1개
+
+
+
+
이해 확인 — 이번 주 평가 체크포인트
+
+

멘토는 1:1에서 세 가지 축으로 관찰 기록을 남깁니다.

+
    +
  • 이해력 · 구조·요청 흐름·기능 분석을 자기 말로 설명하는가
  • +
  • 질문의 질 · 30분 룰을 지키고, 질문 3요소(무엇을·무엇을 시도·무엇이 막힘)를 담는가
  • +
  • 예절 정착도 · 인사·경청·피드백 수용이 일상으로 자리잡았는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 회고에 "그냥 열심히 했다"처럼 뭉뚱그려 적는다.
+
대처 · "어떤 순간에, 무엇을 했는지 하나만 콕 집어요"라고 구체화를 돕는다.
+
실수 · 개선점을 자기 비난으로 적어 위축된다.
+
대처 · 개선은 "다음 행동"으로 바꿔 쓰게 한다. 우리는 함께 더 나은 세상을 만드는 팀이라는 톤을 유지한다.
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 2주차 상세 강의안 (멘토용) · 8부작 중 2 + 2026 · 수습 가이드·CS 커리큘럼·평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week3-assignments.html b/frontend/public/docs/week3-assignments.html new file mode 100644 index 0000000..f3afb1c --- /dev/null +++ b/frontend/public/docs/week3-assignments.html @@ -0,0 +1,870 @@ +AWESOMEDEV 3주차 일일 과제집 — 첫 소과제 실전 + + +
+ +
+
AWESOMEDEV · 일일 과제집 · 3 / 4
+

3주차 일일 과제집
첫 소과제 실전

+

이 과제집은 오후 자기주도 시간(10:40–12:00, 13:00–16:30)에 쓰는 문서예요. 이번 주는 배정받은 첫 소과제가 메인이고, 이 문서는 그 소과제를 계획→구현→PR→리뷰 반영→회고로 끌고 가는 체크리스트와 양식을 담고 있어요. 매일 16:30 멘토 리뷰 때 제출할 산출물이 정해져 있으니, 하루를 시작할 때 그날의 산출물부터 확인하고 움직여 보세요.

+
+ Week 3 · 월–금 + 오후 자기주도 시간용 + 매일 16:30 과제 리뷰 제출 +
+
+ +
+
Daily Rhythm
+

하루의 리듬

+

3주차 내내 하루의 뼈대는 같아요. 오전 강의에서 배운 것을 오후에 소과제로 바로 이어가는 구조예요.

+
+
+
09:30–10:30
아침 강의(멘토) — 기본기 코너 「프로그램의 변화」 + 그날의 소과제 진행 포인트
+
10:40–12:00
일일 과제 전반 — 머리가 맑은 시간. 그날 과제의 가장 어려운 부분부터 시작해요
+
13:00–16:30
일일 과제 후반 — 막히면 30분 룰: 30분 동안 스스로 시도한 흔적(검색어·시도한 코드·에러 메시지)을 정리한 뒤 멘토에게 질문해요
+
16:30–17:00
멘토 과제 리뷰 — 그날의 산출물을 제출하고 피드백을 받아요
+
17:00–17:30
하루 정리 · 3줄 회고 — 오늘 한 것 / 배운 것 / 내일 할 것을 각 1줄로
+
+

규칙 하나만 기억해요: 과제가 일찍 끝나면 놀지 말고 예비 과제로 — 예비 과제도 평가에 반영돼요.

+
+
+ + +
+
MON
+
+

착수 계획서 — 시작하기 전에 지도를 그린다

+

오전 강의에서 첫 소과제를 배정받고 접근 방법을 공유했어요. 오후엔 그 접근 방법을 문서로 굳혀요.

+
+
+ +
+
+
본 과제

소과제 착수 계획서 1장 쓰기

+ 난이도 ★★ +
+
+
+
🎯목표
+

코드를 치기 전에 "무엇을, 어떻게, 얼마나 걸려서" 할지 스스로 설명하는 힘을 길러요 — 실무에서 가장 먼저 배우는 습관이에요.

+
+
+
📋진행 순서
+
    +
  1. 과제 티켓을 소리 내어 읽어요. 배정받은 소과제의 설명을 3번 읽고, 모르는 단어·기능 이름에 전부 밑줄을 쳐요.
  2. +
  3. 화면에서 확인해요. 실제 서비스(개발 환경)를 열어 과제가 말하는 화면·기능을 직접 눌러 보고, 현재 동작을 스크린샷 1장으로 남겨요.
  4. +
  5. 코드 입구를 찾아요. 화면에 보이는 문구(버튼 이름 등)를 저장소에서 검색해서, 고쳐야 할 파일 후보를 2~3개까지 좁혀요. 확신이 없어도 괜찮아요 — "후보"면 충분해요.
  6. +
  7. 아래 양식을 채워요. 특히 "불확실한 점"을 비워 두지 마세요. 불확실한 점이 하나도 없다면 과제를 아직 덜 이해한 거예요.
  8. +
  9. 작업을 3덩어리로 쪼개요. "화요일 오전 / 화요일 오후 / 수요일 오전"에 각각 무엇을 끝낼지 계획서의 일정 칸에 적어요.
  10. +
  11. 멘토 확인을 받아요. 계획서를 들고 멘토에게 5분만 시간을 요청해서, 방향이 맞는지 확인 도장을 받아요. 16:30 전에 미리 받아도 좋아요.
  12. +
  13. 수정 사항을 반영해요. 멘토 코멘트를 계획서에 빨간 글씨(또는 "멘토 피드백:" 표시)로 추가해서 최종본을 만들어요.
  14. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 착수 계획서 1장 (아래 양식 그대로, 마크다운 파일 plan.md 또는 공유 문서)

+
    +
  • 양식의 6개 항목이 모두 채워져 있을 것
  • +
  • "불확실한 점"이 최소 2개 이상 적혀 있을 것
  • +
  • 멘토 확인 코멘트가 반영돼 있을 것
  • +
+
+
+
+
📤워크시트
+
+
착수 계획서 양식 — 빈칸을 채워요
+
# 소과제 착수 계획서
+
+작성자: ______   작성일: 2026-__-__   과제명: ______________________
+
+## 1. 무엇을 (What)
+- 이 과제가 끝나면 사용자에게 무엇이 달라지나요? (1~2문장)
+  → ________________________________________
+- 현재 동작 스크린샷: (붙여넣기)
+
+## 2. 어떻게 (How)
+- 고칠 것으로 보이는 파일/영역 후보:
+  1) ______________________ — 이유: __________
+  2) ______________________ — 이유: __________
+- 대략의 순서: ____________ → ____________ → ____________
+
+## 3. 예상 소요
+- 화요일 오전: ______________________
+- 화요일 오후: ______________________
+- 수요일 오전: ______________________ (PR 올리기 포함)
+
+## 4. 불확실한 점 (최소 2개)
+- ________________________________________
+- ________________________________________
+
+## 5. 완료 기준 (Definition of Done)
+- [ ] ______________________ 가 화면에서 동작한다
+- [ ] 기존 기능 __________ 이 깨지지 않았다
+- [ ] PR이 올라가고 설명이 채워져 있다
+
+## 6. 멘토 피드백 (확인 후 기록)
+- ________________________________________
+
+
+
+
+ +
+
+
예비 과제

내 과제 주변 코드 탐험 노트

+ 난이도 ★ +
+
+
+
🎯목표
+

내일 구현을 시작할 파일들을 미리 읽어 두면, 화요일 아침의 나에게 큰 선물이 돼요.

+
+
+
📋진행 순서
+
    +
  1. 계획서 2번 항목의 파일 후보를 위에서부터 하나씩 열어요.
  2. +
  3. 파일마다 맨 위(import·컴포넌트/클래스 선언)부터 끝까지 훑고, "이 파일의 역할 1줄"을 노트에 적어요.
  4. +
  5. 모르는 함수·문법이 나오면 일단 이름만 노트에 적고 넘어가요 (전부 이해하려고 멈추지 않기).
  6. +
  7. 파일들 사이의 호출 관계를 화살표로 그려요. 예: 화면 컴포넌트 → API 호출 함수 → 서버 컨트롤러
  8. +
  9. 노트 마지막에 "내일 제일 먼저 열 파일 1개"를 정해서 적어요.
  10. +
+
+
+
제출
+
+

제출: 탐험 노트(파일별 역할 1줄 + 화살표 그림 + 모르는 것 목록). 계획서 뒤에 붙여서 함께 내면 돼요. 일찍 끝났을 때만 하는 과제지만, 하면 화요일이 훨씬 편해져요.

+
+
+
+
+ +
+
+
디자이너 과제

개선 과제 착수 계획 — 대상 화면과 문제 정의

+ 난이도 ★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

구조는 같고 대상만 달라요. 개발자가 "코드 입구"를 찾는 자리에서, 디자이너는 "화면의 문제"를 찾아요. 계획서 양식도 같은 것을 쓰되 2번 항목을 아래처럼 바꿔서 채워요.

+
+
+
+
📋진행 순서
+
    +
  1. 배정받은 개선 대상 화면을 열고, 현재 상태 스크린샷을 전부(기본·비어 있음·로딩·오류 상태) 캡처해요.
  2. +
  3. 화면을 처음 보는 사람처럼 사용해 보며 불편한 점을 5개 이상 메모해요 (사소해도 전부).
  4. +
  5. 5개 중 이번 주에 고칠 핵심 문제 1~2개를 고르고, "누가 / 언제 / 왜 불편한가"를 각 1문장으로 정의해요.
  6. +
  7. 참고할 만한 다른 서비스의 같은 화면을 2개 찾아 스크린샷과 "여기서 배울 점 1줄"을 적어요.
  8. +
  9. 착수 계획서 양식의 2번을 "문제 정의 + 참고 사례"로 채우고, 3번 일정(화: 시안 1차 / 수: 시안 2차 / 목: 핸드오프)을 적어요.
  10. +
  11. 멘토 확인을 받고 피드백을 6번 칸에 기록해요.
  12. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 디자인 착수 계획서 1장 (현재 상태 캡처 + 문제 정의 1~2개 + 참고 사례 2개 + 일정)

+
+
+
+
+ + +
+
TUE
+
+

구현 1일차 — 계획대로 가되, 어긋남을 기록한다

+

오전 강의의 진행 포인트(작게 커밋하기)를 오후 구현에 바로 적용해요. 퇴근 전 중간보고까지가 오늘의 한 세트예요.

+
+
+ +
+
+
본 과제

소과제 구현 1일차 + 퇴근 전 중간보고

+ 난이도 ★★★ +
+
+
+
🎯목표
+

계획을 실제 코드로 옮기면서, 진행 상황을 남이 이해할 수 있게 보고하는 실무 사이클을 처음부터 끝까지 경험해요.

+
+
+
📋진행 순서
+
    +
  1. 작업 브랜치를 만들어요. git switch -c feature/과제이름-이니셜 형태로. 브랜치 이름은 멘토가 봐도 무슨 작업인지 알 수 있게 지어요.
  2. +
  3. 계획서의 "화요일 오전" 덩어리부터 시작해요. 어제 정한 "제일 먼저 열 파일"을 열고, 가장 작은 변경 하나(문구 하나, 조건 하나)로 시작해서 화면에 반영되는지 먼저 확인해요.
  4. +
  5. 동작하는 단위마다 커밋해요. 하루 최소 3커밋. 커밋 메시지는 "무엇을 했는지"가 보이게: feat: 목록 화면에 빈 상태 문구 추가 처럼요.
  6. +
  7. 막히면 30분 룰. 30분 동안 ①에러 메시지 정독 ②검색 ③작은 실험, 이 3가지를 해 보고 그래도 안 되면 시도 내역을 정리해 멘토에게 질문해요. 질문도 실력이에요.
  8. +
  9. 15:50이 되면 손을 멈추고 계획서를 다시 열어요. 계획 대비 어디까지 왔는지, 예상과 달랐던 점을 체크해요.
  10. +
  11. 아래 틀로 중간보고를 작성해요. 1주차에 배운 보고 틀 그대로예요: 사실 → 판단 → 다음 행동.
  12. +
  13. 16:30 리뷰에서 중간보고를 읽고, 오늘 커밋을 화면과 함께 보여줘요.
  14. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: ① 오늘의 진행 커밋(브랜치에 3커밋 이상, 푸시 완료) ② 중간보고 메시지(아래 틀)

+
    +
  • 커밋 메시지만 읽어도 오늘 한 일이 보일 것
  • +
  • 중간보고에 "계획과 달랐던 점"이 솔직하게 적혀 있을 것 (없으면 "없음"이라고 쓰되, 정말인지 한 번 더 생각!)
  • +
+
+
+
+
📤워크시트
+
+
중간보고 메시지 틀 — 메신저에 이 형식으로 보내요
+
[중간보고] 과제명 — 구현 1일차 (화)
+
+■ 오늘 한 것 (사실)
+- ________________________________________
+- ________________________________________
+- 커밋: N개 (브랜치: feature/______)
+
+■ 진행률 (판단)
+- 계획 대비: 예정대로 / 조금 늦음 / 많이 늦음 중 하나 + 이유 1줄
+- 계획과 달랐던 점: ________________________________________
+
+■ 내일 할 것 (다음 행동)
+- 오전: ______________________
+- 오후: ______________________ + PR 올리기
+
+■ 도움이 필요한 것
+- ________________ (없으면 "없음")
+
+
+
+
+ +
+
+
예비 과제

JS 함수 연습 10제 — 손을 멈추지 않기

+ 난이도 ★ +
+
+
+
🎯목표
+

소과제가 일찍 끝났거나 멘토 답변을 기다리는 동안, 함수 작성 근육을 유지해요.

+
+
+
📋진행 순서
+
    +
  1. 새 파일 practice-w3-tue.js를 만들어요.
  2. +
  3. 아래 문제를 1번부터 순서대로 풀어요. 각 문제는 함수 하나로 작성해요.
  4. +
  5. 문제마다 console.log()로 예시 입력 2개 이상을 넣어 결과를 확인해요.
  6. +
  7. 막히는 문제는 건너뛰되, 파일에 // TODO: 왜 막혔는지 1줄을 남겨요.
  8. +
  9. 다 풀면(또는 시간이 되면) 커밋해서 개인 연습 저장소에 푸시해요.
  10. +
+
+
+
📤연습문제
+
+
JS 함수 연습 10제 (답은 스스로!)
+
1. sum(a, b) — 두 수를 받아 합을 반환하는 함수를 작성하세요.
+
+2. isEven(n) — 정수를 받아 짝수면 true, 홀수면 false를 반환하세요.
+
+3. maxOfThree(a, b, c) — 세 수 중 가장 큰 값을 반환하세요.
+   (Math.max 없이 if문으로 먼저 풀고, 그 다음 Math.max로도 풀어 보세요.)
+
+4. repeatText(text, n) — 문자열 text를 n번 이어붙인 문자열을 반환하세요.
+   예: repeatText("하", 3) → "하하하"
+
+5. countVowels(str) — 영어 문자열에서 모음(a,e,i,o,u)의 개수를 반환하세요.
+   대문자도 세어야 합니다.
+
+6. reverseWords(sentence) — 문장을 받아 단어 순서를 뒤집어 반환하세요.
+   예: reverseWords("나는 오늘 출근했다") → "출근했다 오늘 나는"
+
+7. sumArray(numbers) — 숫자 배열의 합을 반환하세요.
+   for문으로 한 번, reduce로 한 번, 두 가지 방법으로 작성하세요.
+
+8. filterLongNames(names, minLength) — 이름 배열에서 글자 수가
+   minLength 이상인 이름만 담은 새 배열을 반환하세요.
+   예: filterLongNames(["김", "이서연", "박준"], 2) → ["이서연", "박준"]
+
+9. toPriceText(price) — 숫자를 받아 천 단위 콤마를 붙인 문자열에
+   "원"을 붙여 반환하세요. 예: toPriceText(1250000) → "1,250,000원"
+
+10. findFirstIndex(arr, target) — 배열에서 target이 처음 나오는
+    인덱스를 반환하고, 없으면 -1을 반환하세요.
+    (indexOf 없이 직접 반복문으로 구현하세요.)
+
+
+
+
제출
+
+

제출: practice-w3-tue.js 파일 링크(푸시된 커밋). 몇 번까지 풀었는지, 어디서 막혔는지를 중간보고 메시지 끝에 한 줄로 덧붙여요.

+
+
+
+
+ +
+
+
디자이너 과제

시안 1차 — 핵심 문제 하나를 화면으로

+ 난이도 ★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

개발자의 "커밋"이 디자이너에겐 "시안 버전"이에요. 하루의 끝에 중간보고를 보내는 것은 동일하고, 커밋 개수 대신 시안 버전 수를 적어요.

+
+
+
+
📋진행 순서
+
    +
  1. 어제 정의한 핵심 문제 1개를 골라, 디자인 툴에 현재 화면을 그대로 복제한 프레임을 먼저 만들어요 (비교 기준).
  2. +
  3. 같은 화면의 개선안을 서로 다른 방향으로 2개 그려요. 하나는 "최소 수정", 하나는 "과감한 수정".
  4. +
  5. 각 시안 옆에 "무엇을 왜 바꿨는지"를 3줄 이내로 메모해요.
  6. +
  7. 기본 상태만 그리지 말고, 비어 있음·로딩·오류 상태 중 최소 1개를 함께 그려요.
  8. +
  9. 15:50에 손을 멈추고 중간보고 틀(위 개발자용과 동일)로 보고를 작성해요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 시안 1차 링크(현재 화면 + 개선안 2방향 + 메모) + 중간보고 메시지

+
+
+
+
+ + +
+
WED
+
+

구현 2일차 + PR — 내 작업을 남에게 건넨다

+

오전 강의에서 좋은 PR의 조건을 배웠어요. 오후엔 구현을 마무리하고 그 조건대로 PR을 올려요.

+
+
+ +
+
+
본 과제

구현 마무리 + 첫 PR 올리기

+ 난이도 ★★★ +
+
+
+
🎯목표
+

"동작하는 코드"를 "리뷰받을 수 있는 PR"로 포장하는 법을 배워요 — PR은 코드가 아니라 커뮤니케이션이에요.

+
+
+
📋진행 순서
+
    +
  1. 오전(10:40–12:00)에 구현을 끝내는 걸 목표로 어제 중간보고의 "내일 할 것"부터 이어가요. 새 기능 욕심은 금지 — 계획서의 완료 기준만 채워요.
  2. +
  3. 완료 기준 체크리스트를 하나씩 검증해요. 화면에서 직접 눌러 보고, 기존 기능이 깨지지 않았는지 주변 기능 3개를 함께 확인해요.
  4. +
  5. 스크린샷/짧은 녹화를 준비해요. 변경 전(월요일 캡처)과 변경 후를 나란히 놓을 수 있게요.
  6. +
  7. 커밋을 정리해요. "wip", "수정" 같은 커밋 메시지가 있으면 의미가 보이게 고민해 보고, 마지막 커밋까지 푸시해요.
  8. +
  9. 아래 틀로 PR을 작성해요. 제목은 50자 이내, "무엇을"이 바로 보이게. 본문은 틀의 모든 항목을 채워요.
  10. +
  11. 스스로 셀프 리뷰를 해요. PR의 Files changed 탭을 처음부터 끝까지 읽고, 스스로 발견한 어색한 부분에 먼저 코멘트를 달아요 (최소 1개).
  12. +
  13. 리뷰어로 멘토를 지정하고, 메신저로 "PR 올렸습니다 + 링크"를 알려요.
  14. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 올라간 PR 링크

+
    +
  • PR 본문의 4개 섹션(무엇/왜/스크린샷/확인 방법)이 모두 채워져 있을 것
  • +
  • 변경 전·후 스크린샷이 붙어 있을 것
  • +
  • 셀프 리뷰 코멘트가 1개 이상 달려 있을 것
  • +
+
+
+
+
📤워크시트
+
+
PR 설명 틀 — 본문에 이 형식 그대로
+
제목: [과제] 목록 화면 빈 상태 안내 추가 ← 예시. 내 과제에 맞게
+
+## 무엇을 바꿨나요
+- ________________________________________
+- ________________________________________
+
+## 왜 바꿨나요
+- 과제 배경 + 이 방법을 고른 이유: __________________
+- 고민했지만 선택하지 않은 방법(있다면): __________
+
+## 스크린샷
+| 변경 전 | 변경 후 |
+|---|---|
+| (이미지) | (이미지) |
+
+## 확인 방법 (리뷰어가 따라 할 수 있게)
+1. ______ 화면으로 이동
+2. ______ 버튼 클릭
+3. ______ 가 보이면 정상
+
+## 스스로 확인한 것
+- [ ] 완료 기준 체크리스트 전부 통과
+- [ ] 주변 기능 3개 정상 동작: __ , __ , __
+- [ ] 콘솔 에러 없음
+
+
+
+
+ +
+
+
예비 과제

"내 코드 설명 문서" 1장

+ 난이도 ★ +
+
+
+
🎯목표
+

내가 바꾼 코드를 말로 설명할 수 있어야 진짜 이해한 거예요 — 내일 리뷰 코멘트에 답할 준비도 돼요.

+
+
+
📋진행 순서
+
    +
  1. 내 PR의 변경 파일 중 가장 핵심인 파일 1개를 골라요.
  2. +
  3. "이 코드를 처음 보는 옆자리 동료"를 독자로 상상하고, 변경한 부분이 줄 단위로 무슨 일을 하는지 설명하는 글을 써요.
  4. +
  5. 설명하다가 "왜 이렇게 썼지?" 싶은 줄이 나오면 솔직하게 "이 부분은 검색해서 가져왔는데 정확히는 모름"이라고 적어요 — 그게 내일 공부할 목록이에요.
  6. +
  7. 마지막에 "이 코드가 고장 난다면 어디부터 볼까?" 1문단을 덧붙여요.
  8. +
  9. A4 1장 분량이 되면 멈추고 저장해요.
  10. +
+
+
+
제출
+
+

제출: 설명 문서 1장(explain.md). PR 링크와 함께 내요. "정확히는 모름" 표시가 있는 문서가 없는 문서보다 좋은 평가를 받아요.

+
+
+
+
+ +
+
+
디자이너 과제

시안 2차 + 개발자 피드백 받기

+ 난이도 ★★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

개발자의 "PR 올리기"가 디자이너에겐 "시안을 개발자에게 보여주기"예요. 리뷰받을 수 있는 형태로 포장한다는 점은 완전히 같아요.

+
+
+
+
📋진행 순서
+
    +
  1. 어제 2개 방향 중 멘토와 함께 고른 방향 1개를 다듬어 2차 시안을 만들어요.
  2. +
  3. 기본·비어 있음·로딩·오류 4개 상태를 전부 그려요.
  4. +
  5. 버튼·입력창의 눌림/비활성 상태도 각각 그려요.
  6. +
  7. 개발 수습 동료 1명에게 시안을 보여주고 "이대로 만들 수 있어? 애매한 부분은 어디야?"를 물어 답을 그대로 받아 적어요 (최소 3개).
  8. +
  9. 받은 피드백 중 반영할 것/안 할 것을 나누고 이유를 1줄씩 적어요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 시안 2차 링크(4개 상태 포함) + 개발자 피드백 3개와 반영 여부 메모

+
+
+
+
+ + +
+
THU
+
+

리뷰 반영 — 코멘트는 공격이 아니라 선물

+

오전 강의에서 리뷰 코멘트를 읽는 법을 배웠어요. 오후엔 실제 코멘트를 하나씩 반영하고 머지까지 가요.

+
+
+ +
+
+
본 과제

리뷰 코멘트 반영 → 머지 → 배포 확인

+ 난이도 ★★★ +
+
+
+
🎯목표
+

리뷰 코멘트를 감정이 아니라 정보로 받아들이고, "무엇을 왜 고쳤는지" 설명하며 반영하는 태도를 익혀요.

+
+
+
📋진행 순서
+
    +
  1. 코멘트를 전부 먼저 읽어요. 하나씩 고치기 전에 끝까지 읽고, 아래 반영 기록표에 코멘트를 번호 붙여 옮겨 적어요.
  2. +
  3. 이해 안 되는 코멘트엔 바로 되물어요. "이 코멘트는 ~라는 뜻이 맞을까요?"라고 PR에서 질문하는 것도 훌륭한 반영이에요.
  4. +
  5. 하나 고칠 때마다 커밋 하나. 코멘트 여러 개를 한 커밋에 뭉치지 않아요. 커밋 메시지에 어떤 코멘트에 대한 수정인지 남겨요.
  6. +
  7. 반영 기록표를 채워요. 코멘트마다 "무엇을 / 왜 그렇게" 고쳤는지 1~2줄. 반영하지 않기로 한 코멘트가 있다면 이유를 적고 리뷰어와 합의해요.
  8. +
  9. 고친 뒤 PR에 답글을 달아요. 각 코멘트에 "반영했습니다 + 커밋 링크" 또는 자기 생각을 답해요. 말없이 고치기만 하면 리뷰어가 다시 다 찾아봐야 해요.
  10. +
  11. 승인(Approve)을 받으면 머지해요. 머지 방식(멘토가 안내한 방식)을 확인하고 실행해요.
  12. +
  13. 배포를 확인해요. 머지 후 개발 서버 화면에서 내 변경이 실제로 보이는지 직접 눌러 확인하고, 확인 스크린샷을 남겨요. "머지 = 끝"이 아니라 "화면에서 보임 = 끝"이에요.
  14. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: ① 머지된 PR 링크 ② 반영 기록표 ③ 배포 확인 스크린샷

+
    +
  • 모든 코멘트에 답글이 달려 있을 것 (반영/미반영 모두)
  • +
  • 반영 기록표의 "왜" 칸이 비어 있지 않을 것
  • +
+
+
+
+
📤워크시트
+
+
리뷰 반영 기록표
+
# 리뷰 반영 기록 — 과제명
+
+| # | 코멘트 요약 | 무엇을 고쳤나 | 왜 그렇게 고쳤나 | 커밋 |
+|---|---|---|---|---|
+| 1 | ______________ | ______________ | ______________ | ____ |
+| 2 | ______________ | ______________ | ______________ | ____ |
+| 3 | ______________ | ______________ | ______________ | ____ |
+
+## 반영하지 않은 코멘트 (있다면)
+- 코멘트: __________ / 이유: __________ / 리뷰어 합의: 예·아니오
+
+## 배포 확인
+- 머지 시각: __:__ / 화면 확인 시각: __:__
+- 확인한 화면·동작: ________________________ (스크린샷 첨부)
+
+## 오늘 리뷰에서 배운 것 1가지
+- ________________________________________
+
+
+
+
+ +
+
+
예비 과제

동료 PR 읽기 — 배운 점 3개

+ 난이도 ★ +
+
+
+
🎯목표
+

남의 PR을 읽는 것은 공짜 과외예요 — 같은 주에 같은 코드베이스에서 일어난 일이라 흡수가 빨라요.

+
+
+
📋진행 순서
+
    +
  1. 다른 수습 동료의 PR 1개를 골라요 (머지됐거나 리뷰 중인 것).
  2. +
  3. 본문을 먼저 읽고, "이 PR이 뭘 하는지" 내 말로 1문장 요약해요.
  4. +
  5. 변경 파일을 처음부터 끝까지 읽어요. 이해 안 되는 줄은 건너뛰되 개수를 세요.
  6. +
  7. 리뷰 코멘트와 답글의 대화를 읽고, 배운 점 3개를 메모해요 (코드 기법 / PR 쓰는 법 / 대화하는 법 무엇이든).
  8. +
  9. 가능하다면 그 PR에 칭찬 코멘트 하나를 남겨요. "이 부분 설명이 이해하기 쉬웠어요" 같은 것도 좋아요.
  10. +
+
+
+
제출
+
+

제출: 메모 1장(PR 링크 + 1문장 요약 + 배운 점 3개 + 이해 못 한 줄 개수). 반영 기록표 뒤에 붙여 함께 내요.

+
+
+
+
+ +
+
+
디자이너 과제

핸드오프 문서 — 시안을 만들 수 있는 명세로

+ 난이도 ★★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

개발자의 "머지"가 디자이너에겐 "핸드오프"예요. 어제 받은 개발자 피드백(애매하다던 부분)이 오늘 명세로 답해야 할 목록이에요.

+
+
+
+
📋진행 순서
+
    +
  1. 최종 시안 프레임을 정리하고, 사용한 색·글자 크기를 이름 붙은 스타일로 정돈해요.
  2. +
  3. 화면 요소 사이의 간격을 숫자로 표기해요 (여백·요소 간 거리, 최소 8곳 이상).
  4. +
  5. 버튼·입력창의 상태별 명세를 표로 써요: 기본 / 눌림 / 비활성 / 오류 — 각각 색·문구가 어떻게 달라지는지.
  6. +
  7. 인터랙션 명세를 문장으로 써요: "이 버튼을 누르면 → 무엇이 → 어떻게 되는가"를 최소 3개.
  8. +
  9. 어제 개발자가 "애매하다"고 한 부분마다 명세에 답이 있는지 체크해요.
  10. +
  11. 같은 개발자에게 문서를 보여주고 "이제 만들 수 있겠어?"라는 답을 받아요.
  12. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 핸드오프 문서 링크(간격 표기 + 상태 명세표 + 인터랙션 3개 + 개발자 확인 코멘트)

+
+
+
+
+ + +
+
FRI
+
+

첫 실무 회고 — 계획과 실제의 간격을 잰다

+

오전엔 한 주 마무리 강의와 1:1 면담이 있어요. 오후엔 이번 주 전체를 숫자와 문장으로 되돌아봐요.

+
+
+ +
+
+
본 과제

"계획 대비 실제" 회고 1장

+ 난이도 ★★ +
+
+
+
🎯목표
+

예측과 실제의 차이를 직접 재 보면, 다음 과제의 예상 소요가 훨씬 정확해져요 — 이게 경력의 핵심 근육이에요.

+
+
+
📋진행 순서
+
    +
  1. 월요일의 착수 계획서와 화·수 중간보고를 나란히 펴 놓아요.
  2. +
  3. 아래 비교표의 "예상" 칸을 계획서에서 그대로 옮겨 적어요 (지금 기억으로 다시 쓰지 않기 — 그대로 옮기는 게 핵심).
  4. +
  5. "실제" 칸을 채워요. 커밋 시각·중간보고·PR 타임라인을 근거로 최대한 정확하게.
  6. +
  7. 예상 못 한 문제를 전부 나열하고, 각각 "미리 알 수 있었나? (예/아니오)"를 표시해요.
  8. +
  9. "다음에 다르게 할 것 3가지"를 써요. "열심히 한다" 같은 다짐 금지 — 행동으로 검증 가능한 문장만: 예) "계획서에 파일 후보를 적기 전에 실제로 파일을 열어 확인한다".
  10. +
  11. 이번 주 가장 뿌듯했던 순간 1개, 가장 힘들었던 순간 1개를 솔직하게 적어요.
  12. +
  13. 오후 1:1 면담(멘토가 시간을 잡아요)에서 회고를 함께 읽으며 이야기해요.
  14. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 회고 1장 (아래 양식, retro-w3.md)

+
    +
  • "예상" 칸이 계획서 원문 그대로일 것
  • +
  • "다음에 다르게 할 것"이 검증 가능한 행동 문장 3개일 것
  • +
+
+
+
+
📤워크시트
+
+
첫 실무 회고 양식
+
# 3주차 회고 — 첫 소과제
+
+## 1. 계획 대비 실제
+| 항목 | 예상 (월요일 계획서 원문) | 실제 | 차이·이유 |
+|---|---|---|---|
+| 구현 완료 시점 | ________ | ________ | ________ |
+| 고친 파일 | ________ | ________ | ________ |
+| PR 올린 시점 | ________ | ________ | ________ |
+| 리뷰 코멘트 개수 | ____개 예상 | ____개 | ________ |
+
+## 2. 예상 못 한 문제 (전부)
+| 문제 | 얼마나 잡아먹었나 | 미리 알 수 있었나 |
+|---|---|---|
+| ____________ | 약 __시간 | 예 / 아니오 |
+| ____________ | 약 __시간 | 예 / 아니오 |
+
+## 3. 다음에 다르게 할 것 3가지 (검증 가능한 행동으로)
+1. ________________________________________
+2. ________________________________________
+3. ________________________________________
+
+## 4. 이번 주의 순간들
+- 가장 뿌듯했던 순간: ________________________
+- 가장 힘들었던 순간: ________________________
+
+## 5. 멘토에게 묻고 싶은 것 1가지
+- ________________________________________
+
+
+
+
+ +
+
+
예비 과제

「프로그램의 변화」 그림 한 장 정리

+ 난이도 ★ +
+
+
+
🎯목표
+

이번 주 아침 기본기 코너에서 배운 "프로그램의 변화(상태가 어떻게 바뀌어 가는가)"를 그림 한 장으로 남기면 오래 기억돼요.

+
+
+
📋진행 순서
+
    +
  1. 이번 주 아침 강의 노트를 훑고, "프로그램의 변화"에서 배운 개념을 키워드로 다 뽑아요 (변수의 값 변화, 상태 전이, 이벤트→상태→화면 등).
  2. +
  3. 종이든 디지털이든 한 장에, 내 소과제 기능을 예로 들어 "사용자 행동 → 상태 변화 → 화면 변화"의 흐름을 그림으로 그려요.
  4. +
  5. 화살표마다 "무엇이 바뀌는지"를 라벨로 붙여요.
  6. +
  7. 그림 아래에 "이번 주 가장 새로웠던 개념 1개"를 2~3문장으로 설명해요.
  8. +
+
+
+
제출
+
+

제출: 그림 1장(사진 또는 파일). 회고와 함께 내요. 잘 그린 그림은 다음 기수 교육 자료로 쓸 수도 있어요.

+
+
+
+
+ +
+
+
디자이너 과제

디자인 회고 — 시안의 계획 대비 실제

+ 난이도 ★★ +
+
+
+
🎨개발자 과제와의 차이
+
+

회고 양식은 개발자용을 그대로 쓰되, 1번 표의 항목을 "시안 1차 완료 / 개발자 피드백 개수 / 핸드오프 완료 시점"으로 바꿔 채워요. 특히 "개발자가 애매하다고 한 것들을 처음부터 명세에 넣었다면?"을 2번 표에서 꼭 다뤄요.

+
+
+
+
📋진행 순서
+
    +
  1. 월요일 디자인 착수 계획서와 화·수 시안 버전들을 나란히 펴요.
  2. +
  3. 회고 양식 1번 표를 디자인 버전 항목으로 바꿔 채워요.
  4. +
  5. 개발자 피드백에서 나온 "애매한 부분"들을 2번 표(예상 못 한 문제)에 넣고, 미리 알 수 있었는지 표시해요.
  6. +
  7. 다음 시안 때 다르게 할 것 3가지를 행동 문장으로 써요. 예) "시안을 넘기기 전에 4개 상태를 그렸는지 셀프 체크한다".
  8. +
  9. 1:1 면담에서 회고를 함께 읽어요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30까지 제출: 디자인 회고 1장 (비교표 + 다르게 할 것 3가지)

+
+
+
+
+ + +
+
For Mentors
+

멘토 리뷰 가이드 (16:30–17:00)

+

매일 리뷰에서 확인할 것과 던질 질문이에요. 산출물의 완성도보다 "스스로 설명할 수 있는가"를 봐 주세요.

+
+
+
월 · 계획서
확인: 불확실한 점이 솔직하게 적혔는가, 일정 3덩어리가 현실적인가. 질문: "이 과제에서 제일 자신 없는 부분이 어디예요? 그게 계획서에 적혀 있나요?"
+
화 · 구현 1일차
확인: 커밋이 의미 단위로 쪼개졌는가, 중간보고에 사실·판단·다음 행동이 구분됐는가. 질문: "오늘 계획과 달랐던 점을 언제 알아챘어요? 더 일찍 알 수 있었을까요?"
+
수 · PR
확인: PR 본문만 읽고 리뷰가 가능한가, 셀프 리뷰 코멘트가 있는가. 질문: "이 PR에서 리뷰어가 제일 걱정할 부분이 어디라고 생각해요?"
+
목 · 리뷰 반영
확인: 모든 코멘트에 답글이 달렸는가, 반영 기록의 "왜"가 채워졌는가, 배포 확인까지 했는가. 질문: "가장 아팠던 코멘트는 뭐였고, 지금은 어떻게 생각해요?"
+
금 · 회고
확인: "예상" 칸이 계획서 원문 그대로인가, 다르게 할 3가지가 행동 문장인가. 질문: "다음 소과제의 예상 소요를 지금 다시 잡는다면 뭘 근거로 잡을 거예요?" — 1:1에서 다음 주 방향까지 합의.
+
+
+
+ +
+
AWESOMEDEV · 3주차 일일 과제집 (학생 배포용) · 4부작 중 3
+
2026 · 3주차 상세 강의안과 함께 사용
+
+ +
diff --git a/frontend/public/docs/week3-detailed.html b/frontend/public/docs/week3-detailed.html new file mode 100644 index 0000000..d06026c --- /dev/null +++ b/frontend/public/docs/week3-detailed.html @@ -0,0 +1,806 @@ +AWESOMEDEV 수습 상세 강의안 · 3주차 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 3 / 8
+

3주차 상세 강의안
첫 실무 — 내 작업이 반영되는 경험

+

이번 주는 수습생이 처음으로 "진짜 코드베이스"를 건드립니다. 아주 작고 안전한 소과제를 배정하고, 접근 방법 공유 → 구현 → 중간보고 → 코드리뷰 반영 → 머지 → 배포 확인까지 실무의 한 사이클을 온전히 밟게 합니다. 목표는 잘 만든 결과물이 아니라, "내가 짠 코드가 실제로 반영되는" 첫 성공 경험을 심는 것입니다.

+
+ 대상 · 수습생 4명 (개발 3 · 디자인 1) + 산출물 · 머지된 첫 PR 1건 + 스택 · React · Spring Boot · JS 우선 +
+
+ + + +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 처음 여는 멘토는 1주차 안내 카드를 먼저 참고하세요.

+
+
+ + +
+
MON
+
+

첫 소과제 배정 — 착수 전에 "어떻게 할지"부터

+
목표 · 안전한 소과제를 하나 받고, 코드를 짜기 전에 접근 방법을 멘토와 말로 맞춘다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

아침 기본기 — 프로그램(언어)의 진화

+
40분 · 포인터 세션
+
+
+
+
🎯학습목표
+
    +
  • 기계어→어셈블리→고급언어, 절차형→객체지향→함수형의 큰 흐름을 한 문장으로 말할 수 있다.
  • +
  • "왜 언어가 계속 바뀌어 왔는지"를 우리 스택(JS·Java) 예로 감 잡는다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

CS 기본기 4주 커리큘럼 문서의 W3 해당 일차를 그대로 진행합니다. 이 세션은 포인터(연결)용이니 30~40분 안에 마치고 본 세션으로 넘어갑니다.

+
    +
  • 커리큘럼 문서 W3 "언어의 진화" 파트를 화면 공유로 함께 읽기.
  • +
  • 마무리 질문: "오늘 우리가 쓰는 JavaScript는 이 흐름의 어디쯤일까?" 한 명씩 짧게 답.
  • +
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오전
+

첫 소과제 배정 — 안전한 것으로

+
70분
+
+
+
+
🎯학습목표
+
    +
  • 자기 이름이 붙은 이슈(과제)를 하나 받고, 무엇을 왜 하는지 자기 말로 설명한다.
  • +
  • 과제 범위가 "아주 작다"는 것을 이해하고, 크게 벌리지 않는다.
  • +
+
+
+
🕘진행표
+
+
0–10분
오늘의 그림 설명. "이번 주는 첫 코드가 실제로 반영되는 주"라고 큰 그림부터.
+
10–35분
과제 배정. 개발 3명에게 안전 과제 1건씩 이슈로 부여(아래 예시 풀에서 선택).
+
35–55분
과제 읽기. 각자 이슈를 소리 내어 다시 설명 → 멘토가 범위 확인.
+
55–70분
디자이너 트랙 분기. 디자인 개선 과제 별도 배정(세션 3 참고).
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘부터 여러분한테 진짜 코드베이스에서 할 일을 하나씩 줄 거예요. 겁먹지 않아도 돼요. 첫 과제는 일부러 아주 작고 안전한 걸로 골랐어요."

+

"예를 들면 버튼 문구에 오타가 있는 걸 고친다든가, 화면에 안 맞는 문장을 다듬는다든가, 로그 메시지를 조금 더 알아보기 쉽게 바꾸는 거예요. 하루 만에 세상을 바꾸는 게 목표가 아니라, 내가 고친 게 실제로 반영되는 걸 한 번 경험하는 게 목표예요."

+

Tip. 과제를 줄 때 "이건 망가뜨려도 되는 안전한 부분"이라고 분명히 말해 심리적 부담을 낮춘다.

+
+
+
+
✍️실습 — 안전 과제 예시 풀
+
+

멘토는 아래에서 각자 수준에 맞게 1건씩 고릅니다. 공통 조건: 반나절 안에 끝나고, 실패해도 서비스에 영향이 없는 것.

+
    +
  • 화면 버튼/안내 문구의 오타·어색한 문장 수정 (예: "저장되엇습니다" → "저장되었습니다").
  • +
  • 비어 있는 화면의 안내 문구 한 줄 추가 (예: 목록이 없을 때 "아직 등록된 항목이 없어요").
  • +
  • 버튼 간격·색·비활성 상태 같은 간단한 UI 다듬기.
  • +
  • 서버 로그 메시지 개선 (무슨 일이 일어났는지 사람이 읽기 쉽게).
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 첫 과제를 "제대로 된 기능"으로 크게 주고 싶어진다.
+
대처 · 이번 주의 목적은 실력 검증이 아니라 사이클 경험이다. 일부러 작게 유지한다.
+
+
+
+
+ +
+
+
Day 1 · 세션 3 · 오후
+

착수 전 접근 방법 공유 (+ 디자이너 트랙)

+
80분
+
+
+
+
🎯학습목표
+
    +
  • 코드를 짜기 전에 "어디를 · 어떻게 고칠지"를 3~4줄로 먼저 말/글로 정리한다.
  • +
  • 멘토의 짧은 확인을 받고 나서 손을 대는 습관을 만든다.
  • +
  • (디자이너) 첫 디자인 개선 과제를 받고 범위를 정한다.
  • +
+
+
+
💬멘토 스크립트
+
+
멘토
+

"바로 코드부터 치고 싶은 마음 알아요. 그런데 실무에서는 손대기 전에 '어떻게 할 건지'를 한 번 말로 맞추는 것이 훨씬 빨라요. 엉뚱한 파일을 오래 헤매는 걸 막아주거든요."

+

"3~4줄이면 충분해요. 어느 파일을 볼 것 같은지, 어떻게 바꿀 생각인지, 확인은 어떻게 할 건지. 완벽하지 않아도 되니까 지금 생각나는 대로 적어봐요."

+
+
+
+
✍️워크시트 — 접근 방법 3줄 정리
+
+
접근 방법 공유 (빈칸을 채워 멘토에게 보내기)
+
과제:  [예: 저장 버튼 문구 오타 수정]
+
+1) 어디를 고칠 것 같은가
+   → [예: web의 저장 버튼 컴포넌트 파일일 것 같다]
+
+2) 어떻게 바꿀 생각인가
+   → [예: 버튼 텍스트 문자열만 올바른 맞춤법으로 교체]
+
+3) 다 되면 어떻게 확인할 것인가
+   → [예: 로컬에서 그 화면 열어 버튼 글자 직접 확인]
+
+막히면?  30분 넘게 혼자 헤매지 않고 멘토에게 물어본다. ✅
+
+
+

디자이너 트랙 (같은 시간, 별도 진행)

+
    +
  • 첫 디자인 개선 과제 배정: 빈 상태(empty state) 화면 또는 버튼의 상태(기본·hover·비활성) 정리.
  • +
  • Figma에서 개선안을 만들고, "개발자가 그대로 만들 수 있게" 핸드오프 메모를 붙이는 것까지가 이번 주 목표라고 안내.
  • +
  • 오늘은 범위만 확정: 무엇을 · 어느 화면을 개선할지 한 줄로 적어 멘토 확인.
  • +
+
+
+
+
이해 확인
+
+

퇴근 전 각자에게 확인:

+
    +
  • 내 과제를 한 문장으로 설명할 수 있는가?
  • +
  • 접근 방법 3줄을 멘토에게 공유했는가?
  • +
  • "막히면 30분 룰"이 뭔지 말할 수 있는가?
  • +
+
+
+
+
+ + +
+
TUE
+
+

구현 시작 — 스스로 해보되, 30분 룰

+
목표 · 접근 방법대로 직접 손을 대고, 막히면 30분 안에 도움을 청한다. PR 초안까지.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

아침 기본기 (포인터)

+
30분 · 포인터 세션
+
+
+
+
🎯학습목표
+
    +
  • 어제 배운 언어의 진화 흐름을 오늘 과제와 한 줄로 연결한다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

CS 기본기 4주 커리큘럼 문서의 W3 해당 일차를 진행합니다. 짧게 복습 + 오늘 분량만.

+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오전~오후
+

직접 구현 — 30분 룰 실습

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 어제 정리한 접근 방법대로 실제 코드를 수정한다.
  • +
  • 혼자 30분 이상 막히면 스스로 손을 들어 도움을 청한다.
  • +
  • 수정한 화면/동작을 로컬에서 직접 확인한다.
  • +
+
+
+
🕘진행표
+
+
0–15분
환경 확인. 브랜치 새로 파기 · 로컬 실행되는지 먼저 확인.
+
15–90분
구현. 각자 진행. 멘토는 순회하며 "막힌 지 몇 분 됐어요?" 물어 30분 룰 상기.
+
90–110분
동작 확인. 고친 부분을 직접 눈으로 확인.
+
110–120분
오늘 상태 메모. 어디까지 했고 뭐가 남았는지 3줄.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"30분 룰 하나만 기억해요. 혼자 30분 넘게 같은 자리에서 막히면, 그때는 물어보는 게 맞아요. 오래 붙잡는 게 성실한 게 아니에요. 실무에선 막힌 걸 빨리 공유하는 사람이 더 신뢰받아요."

+

"물어볼 때는 '안 돼요'만 말고, '이걸 하려는데 여기서 이런 메시지가 떠요, 여기까진 해봤어요'까지 같이 말해주면 서로 훨씬 빨라요."

+
+
+
+
✍️실습 — 브랜치 & 커밋 기본
+
+

오늘의 최소 목표: 새 브랜치에서 수정 → 커밋 → 로컬 확인. 아래 순서를 그대로 따라합니다.

+
    +
  1. 최신 main을 받고, 내 과제용 브랜치를 새로 판다.
  2. +
  3. 접근 방법 3줄대로 파일을 찾아 수정한다.
  4. +
  5. 고친 화면/로그를 로컬에서 직접 확인한다.
  6. +
  7. 작은 단위로 커밋 메시지를 남긴다 (예: fix: 저장 버튼 문구 오타 수정).
  8. +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 막힌 걸 부끄러워 3시간을 혼자 헤맨다.
+
대처 · 30분 룰을 규칙으로 못박는다. 물어본 학생을 칭찬해 분위기를 만든다.
+
실수 · main에서 바로 수정한다.
+
대처 · "무조건 새 브랜치에서"를 첫날부터 몸에 배게 한다.
+
+
+
+
+ +
+
+
Day 2 · 세션 3 · 오후
+

PR 초안 작성

+
60분
+
+
+
+
🎯학습목표
+
    +
  • PR(Pull Request)이 "리뷰해 달라고 내미는 요청서"임을 이해한다.
  • +
  • 무엇을·왜 바꿨는지, 어떻게 확인했는지가 담긴 PR 설명을 쓴다.
  • +
+
+
+
✍️워크시트 — PR 설명 템플릿
+
+
PR 설명 (빈칸 채워 초안 올리기)
+
## 무엇을 바꿨나요
+- [예: 저장 버튼의 "저장되엇습니다" 오타를 "저장되었습니다"로 수정]
+
+## 왜 바꿨나요
+- [예: 사용자에게 보이는 문구라 맞춤법이 틀리면 신뢰가 떨어짐]
+
+## 어떻게 확인했나요
+- [예: 로컬에서 저장 후 토스트 문구를 직접 확인]
+
+## 리뷰어에게
+- [예: 다른 화면에도 같은 오타가 있는지 봐주시면 좋겠어요]
+
+
+
+
이해 확인
+
+

PR 초안을 올리기 전 스스로 점검:

+
    +
  • 제목만 봐도 무엇을 한 PR인지 알 수 있는가?
  • +
  • "어떻게 확인했는지"가 구체적으로 적혀 있는가?
  • +
  • 이 브랜치가 main아닌 내 작업 브랜치인가?
  • +
+
+
+
+
+ + +
+
WED
+
+

중간보고 — 진행 상황을 먼저 말하는 습관

+
목표 · 시키지 않아도 진행 상황을 짧게 보고하고, PR을 리뷰 받을 수 있게 완성한다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

아침 기본기 (포인터)

+
30분 · 포인터 세션
+
+
+
+
🎯학습목표
+
    +
  • W3 커리큘럼의 남은 분량을 마무리하고 한 줄로 요약한다.
  • +
+
+
+
✍️실습 / 진행 안내
+
+

CS 기본기 4주 커리큘럼 문서의 W3 해당 일차를 진행합니다.

+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오전
+

중간보고 실습 — "3줄 보고"

+
70분
+
+
+
+
🎯학습목표
+
    +
  • 물어보기 전에 먼저 "지금 어디까지 됐는지" 보고하는 습관을 만든다.
  • +
  • 보고를 짧고 구조적으로(한 것·할 것·막힌 것) 말한다.
  • +
+
+
+
💬멘토 스크립트
+
+
멘토
+

"실무에서 팀장이 제일 답답한 게 뭔지 알아요? 진행 상황이 안 보이는 거예요. 다 끝낼 때까지 아무 말이 없으면, 잘 되고 있는지 막혀 있는지 알 수가 없거든요."

+

"그래서 오늘은 '3줄 보고'를 연습할 거예요. 어제 뭘 했고, 오늘 뭘 할 거고, 막힌 건 뭔지. 딱 세 줄이면 돼요. 이걸 하루 한 번씩만 해도 신뢰가 확 올라가요."

+
+
+
+
✍️워크시트 — 3줄 중간보고 대본
+
+
중간보고 (팀 채널에 붙여넣기)
+
[중간보고] [내 이름] · [날짜]
+
+✅ 한 것:   [예: 저장 버튼 오타 수정하고 로컬 확인까지]
+▶ 할 것:   [예: PR 설명 다듬고 리뷰 요청하기]
+⚠ 막힌 것: [예: 없음 / 또는: 같은 오타가 다른 화면에도 있는지 확인 중]
+
+
+

진행: 각자 3줄 보고를 작성 → 팀 채널에 올리기 → 멘토가 한 명씩 짧게 피드백. "막힌 것: 없음"도 좋은 보고라고 알려준다.

+
+
+
+
⚠️흔한 실수
+
+
실수 · 보고가 "하고 있어요"처럼 뭉뚱그려진다.
+
대처 · 한 것·할 것·막힌 것 세 칸을 반드시 채우게 한다.
+
+
+
+
+ +
+
+
Day 3 · 세션 3 · 오후
+

PR 완성 & 리뷰 요청 (+ 디자이너 핸드오프)

+
90분
+
+
+
+
🎯학습목표
+
    +
  • 리뷰 받을 수 있는 상태로 PR을 다듬고, 정식으로 리뷰를 요청한다.
  • +
  • (디자이너) 개선안을 개발자가 바로 쓸 수 있게 핸드오프한다.
  • +
+
+
+
🕘진행표
+
+
0–40분
PR 마무리. 설명 채우기 · 불필요한 변경 없나 스스로 diff 훑기.
+
40–60분
리뷰 요청. 멘토를 리뷰어로 지정하고 팀 채널에 한 줄 공지.
+
60–90분
디자이너 핸드오프. Figma 링크 + 핸드오프 메모를 담당 개발자에게 전달.
+
+
+
+
✍️워크시트 — 디자이너 핸드오프 메모
+
+
디자인 핸드오프 (개발자에게 전달)
+
화면:   [예: 목록 비어있을 때(빈 상태) 화면]
+Figma:  [링크]
+
+바뀐 점
+- [예: 빈 상태에 안내 문구 + 일러스트 추가]
+
+개발자가 봐야 할 값
+- 글자색: [예: 보조 텍스트 컬러]
+- 여백:   [예: 위아래 24, 좌우 16]
+- 버튼 상태: [기본 / hover / 비활성 각각 첨부]
+
+궁금하면: [디자이너 이름] 에게 물어보세요 🙂
+
+
+
+
이해 확인
+
+

오늘 끝나기 전:

+
    +
  • 개발 3명 모두 리뷰 요청된 PR이 있는가?
  • +
  • PR에 내 변경만 들어 있는가(엉뚱한 파일 X)?
  • +
  • 디자이너는 핸드오프 메모를 전달했는가?
  • +
+
+
+
+
+ + +
+
THU
+
+

리뷰 반영 → 머지 → 배포 확인

+
목표 · 리뷰 피드백을 반영해 머지하고, 배포된 화면에서 내 변경이 실제로 보이는 걸 확인한다.
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

코드리뷰 함께 읽기

+
70분
+
+
+
+
🎯학습목표
+
    +
  • 리뷰 코멘트가 "혼내는 게 아니라 같이 좋게 만드는 것"임을 안다.
  • +
  • 코멘트를 하나씩 읽고, 무엇을 요청하는지 자기 말로 옮긴다.
  • +
+
+
+
💬멘토 스크립트
+
+
멘토
+

"제가 여러분 PR에 코멘트를 몇 개 달았어요. 미리 말할게요. 이건 잘못했다고 지적하는 게 아니에요. 저도 제 코드에 매일 리뷰를 받아요. 같이 더 좋게 만드는 대화예요."

+

"코멘트를 읽고 이해가 안 되면 '이게 무슨 뜻이에요?'라고 물어봐도 아주 좋아요. 리뷰는 원래 주고받는 거예요."

+
+
+
+
✍️실습 — 코멘트 옮겨 적기
+
+

각 리뷰 코멘트를 아래 형식으로 한 줄씩 정리한 뒤 반영을 시작합니다.

+
    +
  • 코멘트 요약: "무엇을 바꿔달라는가"
  • +
  • 내 판단: "바로 반영 / 질문 필요"
  • +
  • 반영 방법: "어디를 어떻게 고칠지 한 줄"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 리뷰 코멘트를 지적으로 받아들여 위축된다.
+
대처 · 멘토가 먼저 "이건 잘했다"는 코멘트도 섞어 달아 균형을 준다.
+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오후
+

반영 → 머지 → 배포 확인 (하이라이트)

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 리뷰 코멘트를 반영해 커밋을 추가하고 다시 리뷰를 받는다.
  • +
  • 승인 후 머지하고, 배포된 화면에서 내 변경을 직접 확인한다.
  • +
  • "내 코드가 실제로 반영된다"는 성공 경험을 명확히 인지한다.
  • +
+
+
+
🕘진행표
+
+
0–50분
반영. 코멘트대로 수정 → 커밋 추가 → "반영했어요" 코멘트로 알림.
+
50–70분
재확인 & 승인. 멘토가 재리뷰 → 승인(Approve).
+
70–90분
머지. 학생이 직접 머지 버튼을 누른다(멘토 옆에서).
+
90–120분
배포 확인. 배포 후 실제 화면/동작에서 내 변경이 보이는지 눈으로 확인.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자, 머지 버튼은 여러분이 직접 눌러요. 제가 대신 안 눌러요. 방금 여러분이 짠 코드가 우리 진짜 코드에 합쳐지는 순간이에요."

+

"그리고 배포가 끝나면, 실제 화면을 같이 열어봐요. 아까 고친 그 문구, 그 버튼이 실제 서비스에서 그대로 보일 거예요. 이게 바로 개발자가 일하는 이유 중 하나예요 — 내가 만든 게 세상에 나가는 거요."

+
+
+
+
✍️실습 — 배포 확인 체크
+
+
배포 확인 (머지 후 채워서 인증)
+
머지한 PR: [제목/번호]
+
+배포 후 확인
+- 어디서 확인했나: [예: 스테이징 목록 화면]
+- 내 변경이 보이나: [예: "저장되었습니다" 문구로 정상 표시됨]
+- 캡처: [스크린샷 첨부]
+
+지금 기분 한 단어: [__________] 🎉
+
+
+
+
⚠️흔한 실수
+
+
실수 · 머지만 하고 배포 결과를 확인하지 않는다.
+
대처 · "머지 = 끝"이 아니라 배포 확인까지가 한 사이클임을 못박는다.
+
실수 · 배포가 늦어 세션 안에 반영이 안 보인다.
+
대처 · 안전 과제는 미리 배포 파이프라인을 확인해 두고, 안 되면 스테이징에서 확인.
+
+
+
+
+ + +
+
FRI
+
+

회고 · 1:1 · 퀴즈

+
목표 · 첫 실무 사이클을 돌아보고, 잘한 점·아쉬운 점을 기록하며 다음을 준비한다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

첫 실무 회고

+
70분
+
+
+
+
🎯학습목표
+
    +
  • 이번 주 사이클(접근 공유→구현→보고→리뷰→머지→배포)을 자기 말로 되짚는다.
  • +
  • 잘한 점 1개, 아쉬운 점 1개, 다음에 시도할 것 1개를 적는다.
  • +
+
+
+
💬멘토 스크립트
+
+
멘토
+

"이번 주 여러분 전부 진짜 코드를 머지했어요. 첫 실무 주에 이만큼 온 거 정말 대단해요. 결과물 크기는 중요하지 않아요. 사이클을 한 바퀴 돌았다는 게 핵심이에요."

+

"회고는 반성문이 아니에요. 잘한 것도 꼭 적어요. 아쉬운 건 '다음에 이렇게 해볼래'로 바꿔 적으면 그게 성장이에요."

+
+
+
+
✍️워크시트 — 첫 실무 회고
+
+
3주차 회고 (각자 작성 후 공유)
+
이번 주 내가 머지한 것: [한 줄]
+
+😀 잘한 점:        [예: 막혔을 때 30분 룰대로 바로 물어봤다]
+🤔 아쉬운 점:      [예: PR 설명을 너무 짧게 썼다]
+🚀 다음에 해볼 것:  [예: 중간보고를 아침마다 먼저 올리기]
+
+첫 실무 소감 한 줄: [____________________]
+
+
+
+
이해 확인 — 이번 주 퀴즈
+
+

구두 또는 짧은 쪽지로:

+
    +
  • "30분 룰"이 무엇이고 왜 있는가?
  • +
  • PR 설명에 꼭 들어가야 할 것 2가지는?
  • +
  • 머지 다음에 반드시 해야 하는 것은?
  • +
  • (디자이너) 좋은 핸드오프 메모에 들어갈 정보 2가지는?
  • +
+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오후
+

1:1 면담 & 평가 체크포인트

+
인당 15분 · 순차
+
+
+
+
🎯학습목표
+
    +
  • 수습생 각자와 짧게 마주 앉아 이번 주 소감과 다음 주 각오를 나눈다.
  • +
  • 멘토는 평가 루브릭 3항목을 관찰 근거와 함께 기록한다.
  • +
+
+
+
🕘진행표
+
+
인당 0–5분
소감 듣기. "이번 주 가장 기억에 남는 순간?"으로 시작.
+
인당 5–11분
피드백. 잘한 것 먼저, 개선점은 구체적 행동으로.
+
인당 11–15분
다음 주 한 가지 목표 함께 정하기.
+
+
+
+
✍️멘토 기록 — 평가 체크포인트
+
+
평가 루브릭 (멘토용 · 각 수습생별)
+
수습생: [이름]
+
+1) 완주력       — 과제를 머지까지 끝냈는가
+   근거: [관찰한 사실]
+
+2) 피드백 반영 태도 — 리뷰를 방어 없이 받아 반영했는가
+   근거: [관찰한 사실]
+
+3) 보고 습관     — 먼저 진행 상황을 공유했는가
+   근거: [관찰한 사실]
+
+다음 주 개인 목표: [한 줄]
+
+
+
+
⚠️흔한 실수
+
+
실수 · 1:1이 일방적 평가 통보가 된다.
+
대처 · 학생이 말하는 시간을 더 준다. 멘토는 듣고 근거만 기록.
+
실수 · 결과물 완성도로만 평가한다.
+
대처 · 이번 주 평가축은 완주력·반영 태도·보고 습관임을 기억한다.
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 3주차 상세 강의안 (멘토용) · 8부작 중 3 + 2026 · 수습 가이드 · CS 커리큘럼 · 평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week4-assignments.html b/frontend/public/docs/week4-assignments.html new file mode 100644 index 0000000..a7b4df7 --- /dev/null +++ b/frontend/public/docs/week4-assignments.html @@ -0,0 +1,865 @@ +AWESOMEDEV 4주차 일일 과제집 — 통합·발표 준비 + + +
+ +
+
AWESOMEDEV · 일일 과제집 · 4 / 4
+

4주차 일일 과제집
통합·발표 준비

+

이 과제집은 오전 강의가 끝난 뒤, 오후 자기주도 시간(10:40–12:00, 13:00–16:30)에 여러분이 혼자 힘으로 수행하는 과제를 담고 있어요. 매일 16:30 멘토 리뷰 시간에 제출할 산출물이 정해져 있으니, 하루를 시작할 때 그날의 산출물부터 확인하고 거꾸로 시간을 계획해 보세요. 4주차는 지금까지 배운 것을 하나로 묶고, 금요일 미니 발표회로 마무리하는 주예요.

+
+ Week 4 · 월–금 + 오후 자기주도 시간용 + 매일 16:30 과제 리뷰 제출 +
+
+ +
+
Daily Rhythm
+

하루의 리듬

+

4주차도 리듬은 같아요. 다만 이번 주는 "만드는 것"만큼 "정리하고 말로 설명하는 것"이 중요한 주라는 점을 기억하세요.

+
+
+
09:30–10:30
아침 강의(멘토) — 그날 과제의 배경 개념을 배워요. 과제집과 연결되는 부분을 표시하며 들으세요.
+
10:40–12:00
일일 과제 전반 — 과제 카드의 진행 순서 1~3단계를 목표로 시작해요.
+
13:00–16:30
일일 과제 후반 — 산출물을 완성하는 시간. 막히면 30분 룰: 30분 이상 혼자 헤매지 말고, 시도한 것을 정리해서 멘토에게 질문해요.
+
16:30–17:00
멘토 과제 리뷰 — 그날의 산출물을 제출하고 피드백을 받아요.
+
17:00–17:30
하루 정리 · 3줄 회고 — 배운 것 1줄 / 막혔던 것 1줄 / 내일 할 것 1줄.
+
+

규칙: 과제가 일찍 끝나면 놀지 말고 예비 과제로 — 예비 과제도 평가에 반영돼요.

+
+
+ + +
+
MON
+
+

전체 흐름도 완성 — 요청이 지나가는 길을 한 장에

+

오전 강의 "요청 흐름 전체 정리"에서 배운 그림을, 이번엔 내 손으로 처음부터 끝까지 그려요.

+
+
+ +
+
+
본 과제

버튼 클릭에서 화면 갱신까지 — 전체 흐름도 1장

+ 난이도 ★★ +
+
+
+
🎯목표
+

1~3주차에 따로따로 배운 조각들(React, REST API, Spring Boot, DB)을 하나의 요청 흐름으로 꿰어, 시스템 전체를 스스로 설명할 수 있게 돼요.

+
+
+
📋진행 순서
+
    +
  1. 도구를 정해요. 손그림(사진 제출), 파워포인트, 그림판, 화이트보드 앱 — 뭐든 좋아요. 예쁘게 그리는 것보다 단계가 빠짐없이 이어지는 것이 중요해요. 10분 안에 정하고 시작하세요.
  2. +
  3. 큰 상자 7개를 먼저 배치해요. ① 사용자의 버튼 클릭 → ② React 컴포넌트(이벤트 핸들러) → ③ HTTP 요청(REST API 호출) → ④ Spring Boot 서버(Controller → Service) → ⑤ DB(조회/저장) → ⑥ 응답(JSON) → ⑦ React가 상태를 바꿔 화면 갱신. 화살표로 왼쪽에서 오른쪽으로 이어요.
  4. +
  5. 각 상자 아래에 "실제로 일어나는 일"을 1~2줄로 적어요. 예: ③번 상자에는 어떤 HTTP 메서드(GET/POST 등)를 쓰는지, 요청에 무엇이 담기는지를 적어요.
  6. +
  7. 배운 개념을 연결해요. 각 단계마다 1~3주차 강의에서 배운 개념을 최소 1개씩 포스트잇(또는 말풍선)으로 붙여요. 예: ②에 "이벤트 핸들러, 상태(state)", ④에 "Controller, 계층 분리", ⑤에 "테이블, SQL". 단계당 개념 1개 이상, 전체 10개 이상이 목표예요.
  8. +
  9. 실패하는 경우도 1갈래 그려요. 서버가 에러(예: 400 또는 500)를 돌려줄 때 흐름이 어디서 갈라지고, 화면에는 무엇이 보여야 하는지를 점선 화살표로 추가해요.
  10. +
  11. 3주차 소과제와 대조해요. 내가 3주차에 만든 기능에서 실제 코드 파일이 이 그림의 어느 상자에 해당하는지, 파일명을 상자 옆에 적어요(최소 3개 파일).
  12. +
  13. 소리 내어 설명하며 점검해요. 그림을 보며 처음부터 끝까지 2분 안에 말로 설명해 보세요. 말이 막히는 상자가 곧 이해가 빈 곳이에요. 그 부분을 보강해요.
  14. +
+
+
+
완성 점검표
+
+
제출 전 셀프 체크리스트 — 전부 "예"여야 제출
+
[ ] 7단계 상자가 모두 있고 화살표로 이어져 있다
+[ ] 각 단계 아래에 "실제로 일어나는 일"이 1~2줄씩 적혀 있다
+[ ] 1~3주차 개념 연결 표시가 10개 이상 붙어 있다
+[ ] 에러(실패) 흐름이 점선으로 1갈래 이상 그려져 있다
+[ ] 내 3주차 소과제의 실제 파일명이 3개 이상 상자 옆에 적혀 있다
+[ ] 그림만 보고 2분 안에 말로 설명할 수 있다
+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 전체 흐름도 1장.

+
    +
  • 손그림이면 잘 보이게 찍은 사진, 디지털이면 이미지/PDF로 내보내서 제출해요.
  • +
  • 파일명: 주4_월_흐름도_이름.png(또는 pdf)
  • +
  • 리뷰 시간에 멘토 앞에서 2분 설명을 실제로 하게 되니, 설명 연습까지가 과제예요.
  • +
+
+
+
+
+ +
+
+
예비 과제

가장 자신 없는 단계 1개, 파고들기

+ 난이도 ★ +
+
+
+
🎯목표
+

흐름도에서 설명이 가장 얇았던 단계 하나를 골라, 그 단계만큼은 자신 있게 만들어요.

+
+
+
📋진행 순서
+
    +
  1. 흐름도 7단계 중 2분 설명에서 말이 가장 막혔던 단계를 하나 고르세요.
  2. +
  3. 그 단계에 대해 스스로 질문 3개를 적어요. 예: "서버는 JSON을 어떻게 자바 객체로 바꾸지?", "브라우저는 응답을 받은 뒤 무엇을 하지?"
  4. +
  5. 공식 문서·강의 자료·1~3주차 필기에서 답을 찾아 각 질문에 3~5줄로 답을 적어요.
  6. +
  7. 알게 된 내용을 흐름도의 해당 상자에 반영해서 그림을 업데이트해요.
  8. +
+
+
+
제출
+
+

질문 3개 + 답 문서(반 쪽 분량)와 업데이트된 흐름도를 본 과제 제출물에 덧붙여요. 예비 과제까지 한 사람의 흐름도는 리뷰에서 티가 나요.

+
+
+
+
+ +
+
+
디자이너 과제

같은 흐름을 "사용자 여정"으로 그리기

+ 난이도 ★★ +
+
+
+
🎨개발 과제와의 차이
+
+

같은 7단계를 그리되, 관점을 "기계"가 아니라 "사람"에 둬요. 개발자 흐름도가 "요청이 어디를 지나가는가"라면, 디자이너 버전은 "그동안 사용자는 무엇을 보고, 무엇을 느끼고, 무엇을 기다리는가"예요.

+
+
+
+
📋진행 순서
+
    +
  1. 본 과제와 같은 시나리오(버튼 클릭 → 화면 갱신)를 고르고, 가로축을 시간으로 하는 여정 지도를 준비해요.
  2. +
  3. 각 시점마다 3줄을 채워요: 화면에 보이는 것 / 사용자의 생각·감정 / 뒤에서 일어나는 일(한 줄 요약).
  4. +
  5. 특히 기다리는 순간(요청이 서버에 가 있는 동안)을 놓치지 마세요. 로딩 중에 화면이 무엇을 보여줘야 사용자가 불안하지 않을지 스피너·스켈레톤 등 대안을 2가지 스케치해요.
  6. +
  7. 실패했을 때의 화면도 1장 그려요. 에러 메시지는 사용자가 다음에 무엇을 해야 하는지 알려줘야 해요(문구까지 직접 써 보기).
  8. +
  9. 개발 동료 1명에게 여정 지도를 보여주고, "뒤에서 일어나는 일" 요약이 기술적으로 맞는지 확인받아 수정해요.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 사용자 여정 지도 1장 + 로딩·에러 화면 스케치.

+
    +
  • 파일명: 주4_월_여정지도_이름.png(또는 pdf)
  • +
  • 리뷰 때 "사용자가 가장 불안해할 순간이 어디인지" 질문을 받게 돼요.
  • +
+
+
+
+
+ + +
+
TUE
+
+

두 번째 소과제 1일차 — 계획서와 착수

+

오전 강의에서 두 번째 소과제(난이도 상승)를 받았어요. 오늘은 계획을 스스로 완성하고 구현을 시작하는 날이에요.

+
+
+ +
+
+
본 과제

착수 계획서 작성 + 구현 시작 + 중간보고

+ 난이도 ★★★ +
+
+
+
🎯목표
+

3주차에는 멘토가 계획을 다듬어 줬지만, 이번엔 멘토 확인 전에 스스로 계획의 완성도를 끌어올리는 힘을 길러요.

+
+
+
📋진행 순서
+
    +
  1. (10:40–11:20) 계획서 초안을 써요. 아래 양식(3주차와 같은 양식)을 그대로 채우세요. "구현 단계 쪼개기"는 한 단계가 90분을 넘지 않게 쪼개는 것이 기준이에요.
  2. +
  3. (11:20–12:00) 스스로 계획을 공격해요. 초안을 다 쓰고 나서, 아래 3가지 질문으로 자기 계획의 구멍을 찾아 계획서를 고쳐요: ① 이 중 내가 한 번도 안 해 본 것은? ② 가장 늦어질 것 같은 단계는? ③ 수요일 오후까지 안 끝나면 무엇을 뺄 것인가(축소 계획)?
  4. +
  5. (13:00) 브랜치를 만들고 첫 커밋을 해요. 브랜치 이름은 3주차 규칙 그대로(예: feature/기능이름-이름). 첫 커밋은 빈 뼈대(파일·폴더 구조)만이라도 좋아요.
  6. +
  7. (13:00–15:30) 계획서의 1~2단계를 구현해요. 커밋은 단계 하나 끝날 때마다 남겨요. 커밋 메시지에 계획서의 단계 번호를 적으면 리뷰가 쉬워져요(예: "1단계: 목록 조회 API 뼈대").
  8. +
  9. (15:30–16:00) 30분 룰 점검. 오늘 30분 이상 막혔던 지점이 있었다면, 무엇을 시도했고 어디서 막혔는지 3줄로 기록해 두세요. 없었다면 "없음"이라고 적어요.
  10. +
  11. (16:00–16:30) 중간보고를 써요. 계획서 아래에 "1일차 중간보고" 칸을 채워요: 완료한 단계 / 예상과 달랐던 점 / 내일 첫 90분에 할 일.
  12. +
+
+
+
📝양식
+
+
착수 계획서 (3주차 양식 재사용 · 빈칸을 채우세요)
+
■ 소과제 이름: ______________________  ■ 작성자: ________  ■ 날짜: 2026-__-__
+
+1. 한 줄 요약 (이 기능은 사용자가 ______ 할 수 있게 한다)
+   → ________________________________________________
+
+2. 완료 조건 (이게 되면 끝) — 3개
+   ① ______________________________________________
+   ② ______________________________________________
+   ③ ______________________________________________
+
+3. 화면 스케치 / API 설계 (택1 이상)
+   - 화면: 어떤 요소가 어디에? (간단 스케치 첨부)
+   - API: 메서드 ____ / 경로 /api/____________ / 요청 본문 ______ / 응답 ______
+
+4. 구현 단계 쪼개기 (한 단계 = 90분 이내, 순서대로)
+   1단계: ____________________ (예상 __분)
+   2단계: ____________________ (예상 __분)
+   3단계: ____________________ (예상 __분)
+   4단계: ____________________ (예상 __분)
+   5단계: ____________________ (예상 __분)
+
+5. 예상 위험 & 대비
+   - 가장 자신 없는 부분: ______________ → 막히면: ______________
+   - 수요일 오후까지 안 끝나면 뺄 것(축소 계획): ______________
+
+--- 1일차 중간보고 (16:00 작성) ---
+   완료한 단계: ______  /  예상과 달랐던 점: ____________________
+   내일 첫 90분에 할 일: ____________________
+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 완성된 착수 계획서(중간보고 포함) + 오늘의 진행 커밋.

+
    +
  • 계획서 파일명: 주4_화_계획서_이름.md(또는 문서)
  • +
  • 커밋: 브랜치에 2개 이상, 각 커밋이 계획서 단계와 연결되어야 해요.
  • +
  • 멘토는 "계획서만 보고 내일 뭘 할지 알 수 있는가"를 봐요.
  • +
+
+
+
+
+ +
+
+
예비 과제

JavaScript 근육 유지 — 배열·객체 연습 10제

+ 난이도 ★ +
+
+
+
🎯목표
+

소과제에서 매일 쓰게 될 배열·객체 다루기를 손에 완전히 붙여요.

+
+
+
📋진행 순서
+
    +
  1. 새 파일 week4-practice.js를 만들고, 아래 10문제를 위에서부터 순서대로 풀어요.
  2. +
  3. 각 문제의 답 함수 아래에 console.log로 직접 실행해서 결과를 눈으로 확인해요.
  4. +
  5. 검색은 자유지만, 답 코드를 통째로 복사하지 말고 보고 닫은 뒤 직접 타이핑하세요.
  6. +
  7. 다 풀면 가장 어려웠던 문제 번호와 이유를 파일 맨 아래 주석으로 남겨요.
  8. +
+
+
+
🧩연습문제
+
+
배열·객체 연습 10제 (답은 적혀 있지 않아요 — 직접!)
+
1. 숫자 배열 [3, 7, 1, 9, 4]에서 가장 큰 값을 반환하는 함수 max(arr)를 작성하세요.
+   (Math.max 없이 반복문으로 한 번, Math.max로 한 번 — 두 가지 방법 모두)
+
+2. 문자열 배열 ["react", "spring", "cloud"]를 모두 대문자로 바꾼
+   새 배열을 map으로 만들어 반환하는 함수를 작성하세요.
+
+3. 숫자 배열에서 짝수만 골라내는 함수 evens(arr)를 filter로 작성하세요.
+   예: evens([1,2,3,4,5,6]) → [2,4,6]
+
+4. 상품 배열 [{name:"키보드", price:35000}, {name:"마우스", price:18000},
+   {name:"모니터", price:210000}]에서 전체 가격 합계를 reduce로 구하세요.
+
+5. 4번의 상품 배열에서 가격이 30000원 이상인 상품의 이름만 담은 배열을
+   만드세요. (filter와 map을 이어서 사용)
+
+6. 객체 {id: 7, title: "회의", done: false}를 받아 done만 true로 바뀐
+   "새 객체"를 반환하는 함수를 작성하세요. (원본을 수정하면 안 됨,
+   스프레드 문법 사용)
+
+7. 할 일 배열에서 id가 일치하는 항목의 done을 반전(토글)한 새 배열을
+   반환하는 함수 toggle(todos, id)를 작성하세요. 나머지 항목은 그대로.
+
+8. 문자열 "2026-07-14"를 받아 {year: 2026, month: 7, day: 14} 객체로
+   바꾸는 함수를 작성하세요. (split과 Number 사용)
+
+9. 사용자 배열 [{name:"지수", team:"dev"}, {name:"민호", team:"design"},
+   {name:"서연", team:"dev"}]를 받아 {dev: 2, design: 1}처럼 팀별 인원을
+   세는 함수를 작성하세요.
+
+10. fetch로 GET /api/todos를 호출해 응답 JSON을 콘솔에 출력하는
+    async 함수 loadTodos()를 작성하세요. 실패했을 때(네트워크 에러)
+    "불러오기 실패"를 출력하는 try/catch도 포함하세요.
+    (서버가 없어도 좋아요 — catch가 실제로 동작하는지 확인해 보세요)
+
+
+
+
제출
+
+

week4-practice.js 파일을 본 과제 제출물에 덧붙여요. 10문제 중 7문제 이상 실행 확인이 목표예요.

+
+
+
+
+ +
+
+
디자이너 과제

"내가 다시 디자인한다면" 개선안 — 1일차: 문제 정의와 러프 시안

+ 난이도 ★★ +
+
+
+
🎨개발 과제와의 차이
+
+

개발 팀이 두 번째 소과제를 만드는 이틀 동안, 디자이너는 같은 화면을 "다시 디자인"해요. 오늘은 문제 정의와 러프 시안, 내일은 다듬기와 비교 문서예요. 개발 계획서처럼 디자이너도 오늘 "개선 계획서"를 먼저 써요.

+
+
+
+
📋진행 순서
+
    +
  1. (10:40–11:30) 대상 화면을 고르고 문제를 3개 찾아요. 1~3주차에 팀이 만든 화면 중 하나를 골라 스크린샷을 찍고, "사용자 입장에서 불편한 점"을 3개 적어요. 각 문제에 근거를 붙여요(예: "버튼이 어디 있는지 3초 이상 찾게 됨").
  2. +
  3. (11:30–12:00) 개선 계획서를 써요. 문제 3개 / 각각의 개선 방향 1줄 / 이틀 일정(오늘: 러프, 내일: 완성) / "안 바꿀 것"(범위 밖) 목록.
  4. +
  5. (13:00–15:00) 러프 시안을 그려요. 종이 또는 디자인 도구로 개선안 레이아웃을 잡아요. 색·아이콘은 아직 대충, 배치와 흐름에 집중하세요. 대안을 2가지 이상 그려서 비교해요.
  6. +
  7. (15:00–16:00) 개발 동료 1명에게 5분 인터뷰. 두 대안을 보여주고 어느 쪽이 이해하기 쉬운지, 구현 난이도는 어떤지 듣고 기록해요.
  8. +
  9. (16:00–16:30) 내일 할 일을 3줄로 정리해요. 어느 대안으로 갈지, 무엇을 다듬을지.
  10. +
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 개선 계획서 + 러프 시안 2안 + 인터뷰 기록.

+
    +
  • 파일명: 주4_화_개선계획_이름 폴더에 모아서 제출해요.
  • +
  • 멘토는 "문제 정의에 근거가 있는가"를 가장 먼저 봐요.
  • +
+
+
+
+
+ + +
+
WED
+
+

두 번째 소과제 2일차 — 마무리, PR, 셀프 리뷰

+

오전 강의에서 코드 리뷰 관점(읽는 사람 입장)을 배웠어요. 오늘은 그 눈으로 "내 코드"를 스스로 리뷰해요.

+
+
+ +
+
+
본 과제

구현 마무리 + PR 올리기 + 셀프 리뷰 코멘트 3개

+ 난이도 ★★★ +
+
+
+
🎯목표
+

기능을 끝까지 완성해 PR로 정리하고, 남이 지적하기 전에 자기 코드의 약점을 스스로 찾아내는 눈을 길러요.

+
+
+
📋진행 순서
+
    +
  1. (10:40) 어제 중간보고의 "첫 90분 할 일"부터 시작해요. 계획서를 열어 놓고, 끝낸 단계에 체크하며 진행해요.
  2. +
  3. (13:00) 축소 판단 시점. 남은 단계를 보고 16:00까지 다 못 끝낼 것 같으면, 계획서의 "축소 계획"을 지금 실행해요. 늦게 결정할수록 손해예요. 축소했다면 PR 설명에 그 사실과 이유를 적어요.
  4. +
  5. (–15:00) 구현을 마무리하고 완료 조건 3개를 직접 확인해요. 브라우저에서 실제로 눌러 보고, 안 되는 것이 있으면 완료 조건 옆에 솔직하게 "미완"이라고 표시해요.
  6. +
  7. (15:00–15:30) PR을 올려요. 제목은 "기능 요약 (이름)", 본문에는 ① 무엇을 만들었나 ② 어떻게 테스트했나 ③ 아쉬운 점/미완 을 적어요. 스크린샷 1장 이상 첨부.
  8. +
  9. (15:30–16:20) 셀프 리뷰: 내 PR에 코멘트 3개를 직접 달아요. 아래 양식의 세 관점에서 하나씩, 코드 줄을 정확히 지정해서 달아요. "여기 좋음" 같은 칭찬 말고, 리뷰어가 지적할 법한 것을 스스로 먼저 지적하는 거예요.
  10. +
  11. (16:20) 계획서에 2일차 결과를 기록해요. 계획 대비 실제(단계별 예상 시간 vs 실제 시간)를 한 줄씩 적어요. 목요일 발표 자료의 "배운 점" 재료가 돼요.
  12. +
+
+
+
📝양식
+
+
셀프 리뷰 코멘트 3개 — 관점별로 1개씩
+
코멘트 ① [읽기 쉬움] — 이름·구조가 헷갈리는 곳
+   위치: 파일 ________ / 줄 ____
+   내용: "이 변수(함수) 이름은 ______라서 오해할 수 있다.
+          ______로 바꾸면 더 명확하다." 처럼 대안까지 적기
+
+코멘트 ② [깨질 수 있음] — 값이 없거나 이상할 때
+   위치: 파일 ________ / 줄 ____
+   내용: "______가 비어 있으면(또는 서버가 에러를 주면) 여기서
+          ______가 일어난다. ______ 처리가 필요하다."
+
+코멘트 ③ [중복·정리] — 반복되거나 자리가 어색한 코드
+   위치: 파일 ________ / 줄 ____
+   내용: "이 코드는 ______와 거의 같다. 함수로 빼면(또는 ______로
+          옮기면) 한 곳만 고치면 된다."
+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: PR 링크 + 셀프 리뷰 코멘트 3개.

+
    +
  • PR 본문 3요소(무엇을/어떻게 테스트/아쉬운 점) + 스크린샷 필수.
  • +
  • 셀프 리뷰 3개는 각각 다른 관점(읽기 쉬움 / 깨질 수 있음 / 중복·정리)이어야 해요.
  • +
  • 미완이 있어도 감점보다 솔직한 기록이 더 높게 평가돼요.
  • +
+
+
+
+
+ +
+
+
예비 과제

코드 판단 문제 8제 — 어느 쪽이 나을까?

+ 난이도 ★ +
+
+
+
🎯목표
+

셀프 리뷰에서 기른 눈을 판단 문제로 한 번 더 훈련해요 — 정답보다 "이유"가 중요해요.

+
+
+
📋진행 순서
+
    +
  1. 아래 8문제를 읽고, 각 문제에 선택 + 이유 2줄을 문서로 적어요.
  2. +
  3. "상황에 따라 다르다"고 답해도 좋아요. 단, 어떤 상황이면 어느 쪽인지 적어야 해요.
  4. +
  5. 다 쓰면 내 어제·오늘 코드에서 같은 상황이 있었는지 1개 찾아 표시해요.
  6. +
+
+
+
🧩판단 문제
+
+
코드 판단 문제 8제 — 선택과 이유를 적으세요
+
1. 변수 이름: data1, data2, data3  vs  todos, doneTodos, activeTodos
+   — 어느 쪽? 왜?
+
+2. 한 함수가 120줄  vs  30줄짜리 함수 4개로 분리
+   — 어느 쪽? 분리하면 무엇이 좋아지고 무엇이 번거로워질까?
+
+3. React에서 서버 응답을 기다리는 동안: 아무것도 안 보여줌  vs
+   "불러오는 중..." 표시 — 어느 쪽? 사용자는 각각 어떻게 느낄까?
+
+4. 같은 계산 코드가 3군데 복사되어 있음: 그대로 둠  vs  함수로 추출
+   — 언제 추출하는 게 이득이고, 언제 그냥 둬도 될까?
+
+5. API 응답이 실패했을 때: console.log만 찍음  vs  화면에 에러 메시지
+   표시 — 개발 중일 때와 실제 사용자가 쓸 때, 각각 어느 쪽?
+
+6. Spring Boot에서 요청 값 검증: 프론트에서 했으니 서버는 생략  vs
+   서버에서도 다시 검증 — 어느 쪽? 프론트 검증만 있으면 무슨 일이
+   생길 수 있을까?
+
+7. 커밋: 하루 작업을 저녁에 커밋 1개로  vs  단계마다 작은 커밋 여러 개
+   — 어느 쪽? 나중에 버그를 찾을 때 무엇이 달라질까?
+
+8. 주석: 코드 한 줄마다 설명 주석  vs  "왜 이렇게 했는지"만 주석
+   — 어느 쪽? 주석이 코드와 달라져 버리면 어떤 문제가 생길까?
+
+
+
+
제출
+
+

선택+이유 문서를 본 과제 제출물에 덧붙여요. 8문제 전부, 이유 없이 선택만 적은 답은 인정되지 않아요.

+
+
+
+
+ +
+
+
디자이너 과제

"내가 다시 디자인한다면" 개선안 — 2일차: 완성과 전·후 비교

+ 난이도 ★★ +
+
+
+
🎨개발 과제와의 차이
+
+

개발 팀의 "PR + 셀프 리뷰"에 해당하는 것이 디자이너의 "완성 시안 + 전·후 비교 문서"예요. 남에게 보여줄 수 있는 형태로 정리하고, 자기 시안의 약점도 스스로 3개 적어요.

+
+
+
+
📋진행 순서
+
    +
  1. (10:40–14:30) 어제 고른 대안을 완성 시안으로 다듬어요. 색·간격·글자 크기를 정리하고, 실제 문구(버튼 라벨, 에러 메시지)를 진짜처럼 채워요. "여기에 텍스트" 같은 자리표시 문구는 금지.
  2. +
  3. (14:30–15:30) 전·후 비교 문서를 만들어요. 왼쪽에 기존 화면, 오른쪽에 개선안을 나란히 놓고, 화살표로 바뀐 지점 3곳을 짚으며 각각 "무엇이 문제였고 → 어떻게 바꿨고 → 사용자에게 뭐가 좋아지는지"를 한 줄씩 적어요.
  4. +
  5. (15:30–16:00) 셀프 리뷰 3개. 개발 팀과 똑같이, 내 시안의 약점을 스스로 3개 적어요(예: "이 색 대비는 밝은 화면에서 잘 안 보일 수 있다").
  6. +
  7. (16:00–16:30) 개발 동료 1명에게 구현 난이도 확인. 개선안 중 "만들기 어려운 부분"이 있는지 물어보고, 답을 비교 문서에 메모해요.
  8. +
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 완성 시안 + 전·후 비교 문서 + 셀프 리뷰 3개.

+
    +
  • 파일명: 주4_수_개선안_이름 폴더로 제출해요.
  • +
  • 이 결과물이 목요일 발표 자료의 핵심 재료가 돼요.
  • +
+
+
+
+
+ + +
+
THU
+
+

발표 자료 만들기 — "내가 이해한 컴퓨터 / 우리 시스템"

+

오전에 발표 구성법 강의와 1:1 코칭 순서를 배정받았어요. 오후는 온전히 발표 자료를 만드는 시간이에요.

+
+
+ +
+
+
본 과제

10분 발표 슬라이드 초안 + 소리 내어 리허설 1회

+ 난이도 ★★ +
+
+
+
🎯목표
+

4주간 배운 것을 "남이 이해할 수 있는 이야기"로 재구성해요 — 설명할 수 있어야 진짜 아는 거예요.

+
+
+
📋진행 순서
+
    +
  1. (10:40–11:10) 슬라이드를 만들기 전에, 아래 구성 가이드의 빈칸부터 채워요. 각 장에서 할 말이 한 줄로 정해져야 슬라이드가 산으로 안 가요.
  2. +
  3. (11:10–12:00) 도입 1장 + 개념 3장의 초안을 만들어요. 슬라이드 1장에 핵심 문장 1개 + 그림 1개가 기본이에요. 글자를 문단으로 채우지 마세요 — 읽는 자료가 아니라 말하는 자료예요.
  4. +
  5. (13:00–14:30) 우리 시스템 연결 2장 + 배운 점 1장을 만들어요. 연결 2장에는 월요일 흐름도와 화·수 소과제(디자이너는 개선안) 결과물을 꼭 넣어요. 새로 그리지 말고 이번 주 산출물을 재사용하세요.
  6. +
  7. (14:30–15:00) 그림을 점검해요. 슬라이드 7장 중 그림·스크린샷·도식이 있는 장이 5장 이상인지 세어 보세요. 부족하면 글을 그림으로 바꿔요.
  8. +
  9. (15:00–15:40) 소리 내어 리허설 1회. 타이머를 켜고 실제로 서서, 실제 목소리 크기로 처음부터 끝까지 말해요. 속으로 읽는 건 리허설이 아니에요. 걸린 시간을 기록해요(목표 9~11분).
  10. +
  11. (15:40–16:20) 리허설에서 발견한 문제를 고쳐요. 말이 꼬인 장, 시간을 너무 잡아먹은 장을 표시하고 슬라이드나 대본을 수정해요. 1:1 코칭에서 물어볼 질문 2개를 적어 두세요.
  12. +
+
+
+
📝양식
+
+
발표 구성 가이드 — 슬라이드를 만들기 전에 빈칸을 채우세요
+
발표 제목: 내가 이해한 ______________________ (10분)
+
+[도입 1장] 4주 전의 나는 ______를 몰랐다.
+   지금의 나는 ______를 설명할 수 있다. (호기심을 끄는 한 문장)
+
+[개념 3장] 내가 고른 핵심 개념 3개 — 각 장에서 할 말 한 줄
+   개념 ①: ____________ — "____________________________"
+   개념 ②: ____________ — "____________________________"
+   개념 ③: ____________ — "____________________________"
+   (예: 클라이언트와 서버 / API / 상태와 화면 / DB / 배포와 클라우드
+    중에서 자신 있는 3개. 디자이너는 사용자 여정 / 화면 설계 /
+    피드백 반영 등으로 바꿔도 좋아요)
+
+[우리 시스템 연결 2장] 개념이 우리가 만든 것 어디에 있는지
+   연결 ①: 개념 __는 내가 만든 ______에서 이렇게 쓰였다 (스크린샷/흐름도)
+   연결 ②: 개념 __는 ______에서 이렇게 쓰였다 (스크린샷/시안)
+
+[배운 점 1장] 기술 말고 "일하는 방식"에서 배운 것 1가지
+   → ________________________________________________
+   (예: 계획을 쪼개는 법, 막혔을 때 질문하는 법, 리뷰 받는 법)
+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 발표 자료 초안(7장 내외) + 리허설 기록.

+
    +
  • 리허설 기록: 걸린 시간 / 막힌 장 / 내일 고칠 것 — 3줄이면 충분해요.
  • +
  • 1:1 코칭 질문 2개도 함께 제출해요.
  • +
  • 완성도보다 구조(7장 구성이 서 있는가)를 봐요. 꾸미기는 내일 아침에.
  • +
+
+
+
+
+ +
+
+
예비 과제

예상 질문 5개 만들고 답 준비하기

+ 난이도 ★ +
+
+
+
🎯목표
+

발표 뒤 질의응답에서 당황하지 않도록, 질문을 스스로 예측하고 답을 미리 말해 봐요.

+
+
+
📋진행 순서
+
    +
  1. 내 발표를 처음 듣는 사람이 되었다고 상상하고, 나올 법한 질문 5개를 적어요. 최소 1개는 "가장 받기 싫은 질문"이어야 해요.
  2. +
  3. 각 질문에 3~4문장 답을 적어요. 모르는 부분이 섞여 있으면 "여기까지는 알고, 여기부터는 아직 공부 중"이라고 답하는 연습도 해요.
  4. +
  5. 답 5개를 소리 내어 한 번씩 말해 봐요.
  6. +
  7. 질문·답 문서를 발표 자료와 같은 폴더에 저장해요.
  8. +
+
+
+
제출
+
+

예상 질문·답 문서(5문항)를 본 과제 제출물에 덧붙여요. 금요일 발표회에서 멘토가 이 중 하나를 실제로 물어볼 수 있어요.

+
+
+
+
+ +
+
+
디자이너 과제

디자이너의 10분 발표 — 같은 구성, 다른 재료

+ 난이도 ★★ +
+
+
+
🎨개발 과제와의 차이
+
+

진행 순서와 7장 구성, 리허설, 제출물은 본 과제와 완전히 같아요. 다른 것은 재료뿐: 개념 3장은 "사용자 여정·화면 설계·피드백 반영" 같은 디자인 개념으로, 우리 시스템 연결 2장은 월요일 여정 지도와 화·수 개선안 전·후 비교로 채워요. 단, 최소 1장은 "개발 팀과 협업하며 알게 된 기술 이야기"(예: 왜 로딩 화면이 필요한가)를 넣어요 — 그게 이번 4주의 차별점이에요.

+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 발표 자료 초안(7장 내외) + 리허설 기록 + 코칭 질문 2개. (본 과제와 동일)

+
+
+
+
+ + +
+
FRI
+
+

미니 발표회 — 최종 점검과 자기평가

+

오후에 미니 발표회와 중간평가 면담이 있어요. 오전 자기주도 시간은 최종 점검, 발표 후에는 자기평가서를 써요.

+
+
+ +
+
+
본 과제

발표 최종 점검 + 발표 + 자기평가서 작성

+ 난이도 ★★ +
+
+
+
🎯목표
+

준비한 것을 실전에서 전달해 보고, 4주간의 나를 스스로 공정하게 평가하는 경험을 해요.

+
+
+
📋진행 순서
+
    +
  1. (10:40–11:10) 어제 코칭 피드백을 반영해요. 1:1 코칭에서 받은 지적을 슬라이드에 반영하되, 새 장을 추가하지 마세요. 발표 직전의 큰 수술은 사고의 지름길이에요. 고치는 건 문구·그림·순서까지만.
  2. +
  3. (11:10–11:40) 최종 리허설 1회. 어제처럼 타이머를 켜고 서서, 소리 내어 처음부터 끝까지. 9~11분 안에 들어오는지 확인하고, 넘치면 개념 장에서 말을 줄여요(장을 빼는 게 아니라 말을 줄이는 거예요).
  4. +
  5. (11:40–12:00) 발표 환경을 점검해요. 발표용 PC에서 자료가 열리는지, 글자가 뒤에서도 보이는지, 시연 화면이 있다면 미리 띄워지는지 확인해요. 자료는 2곳 이상에 저장(로컬 + 클라우드).
  6. +
  7. (13:00–) 미니 발표회. 내 차례가 아닐 때는 청중 역할이 과제예요: 발표자마다 좋았던 점 1개 + 질문 1개를 메모하고, 질의응답 때 최소 1번은 손을 들어 질문해요.
  8. +
  9. (발표회 종료 후–16:20) 자기평가서를 작성해요. 평가 루브릭 문서의 자기평가 양식(아래에 동일 양식 수록)을 채워요. 근거 없는 점수는 인정되지 않아요 — 모든 항목에 이번 주 산출물을 근거로 대세요.
  10. +
  11. (16:20–16:30) 최종 발표자료와 자기평가서를 제출해요. 중간평가 면담은 이 자기평가서를 바탕으로 진행돼요.
  12. +
+
+
+
📝양식
+
+
자기평가서 (평가 루브릭 문서의 자기평가 양식과 동일)
+
이름: ________  작성일: 2026-__-__
+
+1. 항목별 자기평가 — 각 항목에 1~4점과 근거(이번 주 산출물·사례)를 함께
+   (1: 아직 어려움 / 2: 도움 받으면 가능 / 3: 혼자 가능 / 4: 남에게 설명 가능)
+
+   ① 시스템 이해 (요청 흐름을 설명할 수 있다)        점수: __
+      근거: ____________________________________________
+   ② 구현 능력 (계획한 기능을 완성했다)              점수: __
+      근거: ____________________________________________
+   ③ 일하는 방식 (계획·커밋·30분 룰·리뷰 반영)       점수: __
+      근거: ____________________________________________
+   ④ 소통 (질문·발표·리뷰 코멘트)                    점수: __
+      근거: ____________________________________________
+
+2. 4주 전의 나와 지금의 나 — 가장 크게 달라진 것 1가지 (3~5문장)
+   → ________________________________________________
+
+3. 가장 어려웠던 순간과 그때 내가 한 행동 (3~5문장)
+   → ________________________________________________
+
+4. 남은 기간에 집중하고 싶은 것 2가지 (구체적으로)
+   ① ______________________________________________
+   ② ______________________________________________
+
+5. 멘토에게 하고 싶은 말 / 요청 (자유)
+   → ________________________________________________
+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 최종 발표자료 + 자기평가서.

+
    +
  • 발표자료 파일명: 주4_금_발표_이름.pdf(발표에 쓴 최종본 그대로)
  • +
  • 자기평가서: 4개 항목 전부 점수+근거, 서술 문항 전부 작성.
  • +
  • 청중 메모(좋았던 점+질문)도 함께 제출하면 소통 항목 평가에 반영돼요.
  • +
+
+
+
+
+ +
+
+
예비 과제

4주 산출물 아카이브 — 내 포트폴리오의 씨앗

+ 난이도 ★ +
+
+
+
🎯목표
+

4주간 만든 것을 한 폴더에 정리해, 나중에 포트폴리오로 키울 수 있는 상태로 만들어요.

+
+
+
📋진행 순서
+
    +
  1. 폴더를 하나 만들고(어썸데브_4주_이름), 주차별 하위 폴더 4개를 만들어요.
  2. +
  3. 각 주의 대표 산출물을 모아 넣어요: 계획서, 흐름도, PR 링크 목록, 발표자료, 시안 등.
  4. +
  5. 맨 위에 README 문서를 만들어 "무엇을 만들었고, 뭘 배웠는지"를 주차별 2줄씩, 총 8줄로 적어요.
  6. +
  7. 지금은 부끄러운 결과물도 빼지 마세요 — 성장의 증거는 "전"이 있어야 보여요.
  8. +
+
+
+
제출
+
+

아카이브 폴더(README 포함)를 제출해요. 이 폴더는 중간평가 면담 자료로도 쓰여요.

+
+
+
+
+ +
+
+
디자이너 과제

발표 + 자기평가서 — 개발 팀과 동일 진행

+ 난이도 ★★ +
+
+
+
🎨개발 과제와의 차이
+
+

오늘은 개발·디자인 구분 없이 본 과제와 완전히 동일하게 진행해요. 자기평가서의 ② "구현 능력" 항목만 "시안 완성도(문제 정의 → 시안 → 전·후 비교를 끝까지 완성했다)"로 바꿔 읽고 평가하세요. 근거는 화·수의 개선안 산출물에서 가져와요. 예비 과제(아카이브)도 동일하게 해당돼요.

+
+
+
+
📤산출물 · 제출
+
+

16:30 제출: 최종 발표자료 + 자기평가서(② 항목만 치환). 파일명 규칙은 본 과제와 같아요.

+
+
+
+
+ + +
+
For Mentors
+

멘토 리뷰 가이드 (16:30–17:00)

+

요일별로 무엇을 확인하고 어떤 질문을 던질지 정리했어요. 학생도 이 가이드를 볼 수 있으니, "무엇을 준비해야 하는지"의 기준으로 삼으세요.

+
+
+
흐름도의 연결이 진짜인지 확인. 그림 없이 말로만 2분 설명을 시켜 보고, "④에서 ⑤로 갈 때 정확히 무엇이 전달되나요?"처럼 상자 사이의 화살표를 파고드는 질문을 해요.
+
계획서가 스스로 서는지 확인. "이 계획서만 보고 내일 아침에 뭘 할지 알겠어요?"를 기준으로 보고, "가장 늦어질 것 같은 단계와 그때의 축소 계획"을 학생 입으로 말하게 해요.
+
셀프 리뷰의 깊이 확인. 코멘트 3개 중 하나를 골라 "그럼 어떻게 고칠 건가요?"를 물어요. 미완이 있다면 축소 판단을 13:00에 했는지(늦지 않았는지)를 확인해요.
+
구조와 시간 확인. 7장 구성이 서 있는지, 리허설 시간이 9~11분에 들어오는지 보고, "이 발표에서 딱 한 장만 남긴다면 어느 장인가요?"로 핵심을 묻어요. 꾸미기 지적은 최소로.
+
자기평가의 근거 확인. 점수보다 근거를 봐요. 점수가 근거보다 높으면 "그 점수의 근거가 되는 산출물이 뭐예요?"를, 낮으면 "이 산출물이 있는데 왜 낮게 줬어요?"를 물어 스스로 조정하게 해요. 면담은 4번 항목(남은 기간 집중할 것)에서 출발해요.
+
+
+
+ +
+
AWESOMEDEV · 4주차 일일 과제집 (학생 배포용) · 4부작 중 4
+
2026 · 4주차 상세 강의안과 함께 사용
+
+ +
diff --git a/frontend/public/docs/week4-detailed.html b/frontend/public/docs/week4-detailed.html new file mode 100644 index 0000000..8008612 --- /dev/null +++ b/frontend/public/docs/week4-detailed.html @@ -0,0 +1,792 @@ +4주차 상세 강의안 · 기본기 마무리 · 중간평가 — AWESOMEDEV 수습 교육 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 4 / 8
+

4주차 상세 강의안
기본기 마무리 · 중간평가

+

이번 주는 지금까지 쌓은 기본기를 "내 말로" 발표할 수 있게 마무리하는 주간입니다. 두 번째 소과제로 난이도를 한 단계 올려 손을 조금 놓아 보고, 금요일에는 미니 발표회와 함께 4주차 중간평가 면담을 진행합니다. 잘된 점과 보완점, 그리고 전환에 대한 솔직한 신호를 이번 주에 반드시 전달합니다.

+
+ 대상: 미림마이스터고 3학년 수습생 4명 (개발 3 · 디자인 1) + 산출물: 기본기 발표자료 · 두 번째 머지 PR + ★ 4주차 중간평가 (전환 신호 전달) +
+ + +
+ +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 아침 기본기 세션은 "포인터 세션"으로 짧게 두고, CS 기본기 4주 커리큘럼 문서(W4)의 해당 일차를 그대로 진행하세요.

+
+
+ + +
+
MON
+
+

요청 흐름 전체 정리

+
목표: 클릭 → 서버 → DB → 응답의 전 과정을 한 장의 그림으로 정리하고, 기본기를 총복습한다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

기본기 통합 복습 (포인터 세션)

+
소요 60분 · 09:30–10:30
+
+
+
+
🎯학습목표
+
    +
  • 지난 3주간 배운 기본기(B) 핵심 용어를 다시 한 번 소리 내어 정리한다.
  • +
  • 금요일 발표에서 쓸 "내 말" 표현을 미리 골라 둔다.
  • +
+
+
+
✍️실습 안내
+
+

CS 기본기 4주 커리큘럼 문서(W4)의 1일차를 그대로 진행합니다. 아침 기본기는 짧게 훑고, 오늘은 "발표에 쓸 문장 고르기"에 초점을 둡니다.

+
    +
  • 커리큘럼 W4 1일차 개념을 학생별로 소리 내어 한 문장씩 요약
  • +
  • 어려운 단어가 나오면 즉시 쉬운 말로 바꿔 옆에 적어 두기
  • +
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오후
+

요청 흐름 한 장으로 그리기 — 클릭에서 응답까지

+
소요 90분 · 13:30–15:00
+
+
+
+
🎯학습목표
+
    +
  • 버튼 클릭 한 번이 화면 → 서버 → DB → 다시 화면으로 돌아오는 전 과정을 순서대로 말할 수 있다.
  • +
  • 우리 실제 스택(React 웹 · Spring Boot 서버)에서 각 단계가 어디에 해당하는지 짚을 수 있다.
  • +
  • 자기 그림을 보고 30초 안에 흐름을 설명할 수 있다.
  • +
+
+
+
🕘진행표
+
+
0–10분
도입. "여러분이 앱에서 좋아요 버튼을 누르면 무슨 일이 벌어질까요?"로 질문 시작
+
10–30분
단계 설명. 멘토가 화이트보드에 5칸(브라우저 · React 화면 · 네트워크 요청 · Spring Boot 서버 · DB)을 그려 흐름 시연
+
30–70분
각자 그리기. 학생 4명이 A4에 자기만의 흐름도를 그림. 멘토는 돌아다니며 막힌 칸을 함께 채움
+
70–85분
돌아가며 설명. 한 명씩 자기 그림을 보고 흐름을 말로 설명 (디자이너는 "화면이 언제 바뀌는가" 관점으로)
+
85–90분
정리. 금요일 발표에서 이 그림을 쓸 수 있다고 예고
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 전체
+

"오늘은 새로 배우는 게 아니라, 그동안 조각조각 배운 걸 하나로 이어 보는 시간이에요. 버튼 하나 누르면 사실 엄청 많은 일이 순식간에 일어나거든요."

+

"어렵게 말 안 해도 돼요. '내가 버튼을 누르면, 화면이 서버한테 물어보고, 서버가 데이터베이스를 확인하고, 다시 화면으로 답이 온다' 이 정도만 여러분 입으로 말할 수 있으면 오늘은 대성공이에요."

+

학생이 칸을 못 채우고 머뭇거리면 정답을 바로 주지 말고 "방금 그 앞 칸에서 뭘 보냈죠?"처럼 되짚어 주세요.

+
+
+
+
✍️워크시트
+
+

빈칸 흐름도 채우기. 아래 대본의 빈칸을 학생이 자기 말로 채우게 하세요. 정답 문장이 아니라 "본인 말"이면 충분합니다.

+
+
+
워크시트 · 요청 흐름 한 문장 만들기
+
① 나는 화면에서 [__________] 버튼을 누른다.
+② 그러면 [React 화면] 이(가) 서버에게 "[__________]" 라고 요청을 보낸다.
+③ [Spring Boot 서버] 는 요청을 받아서 [__________] 를 한다.
+④ 서버는 [DB] 에게 "[__________]" 를 물어보거나 저장한다.
+⑤ DB가 답을 주면, 서버는 그 결과를 [__________] 형태로 화면에 돌려준다.
+⑥ 화면은 받은 결과로 [__________] 를 바꿔 보여 준다.
+
+→ 위 6줄을 안 보고 30초 안에 말해 보기 (짝끼리 1번씩)
+
+
+
+
이해 확인
+
+

다음을 학생이 스스로 말로 설명하면 통과입니다.

+
    +
  • "화면이 직접 DB를 건드리지 않고 왜 서버를 거치나요?" 에 한 문장으로 답하기
  • +
  • 자기 흐름도 5칸의 이름을 순서대로 말하기
  • +
  • 요청(request)과 응답(response)이 각각 어느 방향인지 손으로 가리키기
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수: 학생이 완벽한 용어를 못 쓴다고 위축됨. "API 엔드포인트" 같은 말을 못 하면 틀린 줄 안다.
+
대처: "정확한 단어보다 순서가 더 중요해요. 순서만 맞으면 단어는 천천히 붙어요"라고 안심시키기.
+
실수: 화면(React)과 서버(Spring Boot)를 한 덩어리로 뭉뚱그림.
+
대처: "화면은 손님, 서버는 주방"처럼 역할이 다른 두 사람으로 비유해 칸을 분리시키기.
+
+
+
+
+ + +
+
TUE
+
+

두 번째 소과제 시작

+
목표: 난이도를 한 단계 올린 두 번째 소과제를 받고, 멘토 개입을 조금 줄인 채 스스로 출발한다.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

기본기 발표 준비 워밍업 (포인터 세션)

+
소요 45분 · 09:30–10:15
+
+
+
+
🎯학습목표
+
    +
  • CS 커리큘럼 W4를 이어 진행하며 발표에 쓸 개념을 한 번 더 다진다.
  • +
+
+
+
✍️실습 안내
+
+

CS 기본기 4주 커리큘럼 문서(W4)의 2일차를 그대로 진행합니다. 오늘부터는 배운 개념을 "발표 한 문장"으로 바꿔 노트에 모아 두게 하세요.

+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오전~오후
+

두 번째 소과제 브리핑 — 한 단계 위로

+
소요 100분 · 10:30–12:10
+
+
+
+
🎯학습목표
+
    +
  • 첫 과제보다 한 단계 어려운 과제의 요구사항을 스스로 읽고 정리한다.
  • +
  • 막혔을 때 "먼저 15분 혼자 → 그다음 질문" 순서를 몸에 익힌다.
  • +
  • 과제를 작은 작업 단위로 쪼개 오늘 할 것을 고른다.
  • +
+
+
+
🕘진행표
+
+
0–20분
과제 브리핑. 멘토가 두 번째 과제 요구사항을 함께 읽되, 첫 주보다 설명을 짧게 (JavaScript 우선, 필요 시 Java 보조)
+
20–40분
요구사항 내 말로 다시 쓰기. 학생이 과제를 자기 문장으로 옮겨 적고 멘토가 확인
+
40–70분
작업 쪼개기. 큰 과제를 3~5개 작은 단위로 나누고 첫 단위 착수
+
70–95분
자립 코딩. 멘토는 자리를 지키되 먼저 다가가지 않음. 학생이 15분 시도 후 질문하도록
+
95–100분
오늘 목표 확정. 각자 "오늘 끝낼 한 단위"를 말로 선언
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 전체
+

"이번 과제는 지난번보다 살짝 더 어려워요. 일부러 그렇게 냈어요. 여러분이 이제 그만큼 컸다고 믿거든요."

+

"그리고 이번엔 제가 조금 덜 알려 줄 거예요. 막히면 먼저 15분만 혼자 붙잡아 보세요. 그 15분 동안 고민한 게 진짜 실력이 돼요. 그래도 안 되면 그때 편하게 부르면 돼요. 혼자 끙끙대다 하루를 날리는 건 안 돼요."

+

"덜 도와준다"가 "방치"로 느껴지지 않게, 자리를 뜨지 말고 시야 안에 있어 주세요.

+
+
+
+
✍️워크시트
+
+

과제 쪼개기 시트. 학생이 큰 과제를 작은 단위로 나눠 아래 표를 채우게 하세요.

+
+
+
워크시트 · 두 번째 소과제 작업 쪼개기
+
[과제를 내 말로]  [__________________________]
+
+작은 단위로 쪼개기
+ 1) [__________]   (예상: __분)   난이도 ●○○
+ 2) [__________]   (예상: __분)   난이도 ●●○
+ 3) [__________]   (예상: __분)   난이도 ●●●
+
+오늘 반드시 끝낼 것 →  [___번]
+막히면 15분 시도 후 질문 (체크: ☐ 했다)
+
+
+
+
이해 확인
+
+

세션 끝에 학생마다 다음을 확인합니다.

+
    +
  • 과제를 자기 문장으로 한 번에 설명할 수 있는가
  • +
  • 작업을 최소 3개 단위로 쪼갰는가
  • +
  • 오늘의 목표 한 단위를 명확히 골랐는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수: 개입을 줄이자 학생이 "물어봐도 되나" 눈치를 보며 혼자 오래 헤맴.
+
대처: "15분 규칙" 을 칠판에 써 두고, 15분 지나면 손 드는 걸 당연한 절차로 만들기.
+
실수: 과제를 안 쪼개고 통째로 덤벼 방향을 잃음.
+
대처: 첫 단위를 함께 정해 주고 "이거 하나만 먼저"로 시작 장벽을 낮추기.
+
+
+
+
+ + +
+
WED
+
+

두 번째 소과제 진행 · 자립도 올리기

+
목표: 두 번째 과제를 이어 가며 스스로 문제를 해결하는 힘을 키우고, 완성 후 PR을 올린다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

기본기 발표 문장 다듬기 (포인터 세션)

+
소요 45분 · 09:30–10:15
+
+
+
+
🎯학습목표
+
    +
  • CS 커리큘럼 W4를 마무리 방향으로 진행하고, 모은 발표 문장을 순서대로 배열한다.
  • +
+
+
+
✍️실습 안내
+
+

CS 기본기 4주 커리큘럼 문서(W4)의 3일차를 그대로 진행합니다. 그동안 모은 "발표 한 문장"들을 금요일 발표 흐름 순서로 늘어놓게 하세요.

+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오전~오후
+

자립 코딩 · 스스로 막힌 곳 뚫기

+
소요 120분 · 10:30–12:30
+
+
+
+
🎯학습목표
+
    +
  • 에러 메시지를 스스로 읽고 무엇이 문제인지 먼저 추측해 본다.
  • +
  • 질문할 때 "무엇을 하려다 / 무엇을 기대했고 / 실제로 무엇이 났는지" 세 가지를 갖춰 묻는다.
  • +
  • 두 번째 과제를 완성해 PR을 올린다.
  • +
+
+
+
🕘진행표
+
+
0–10분
어제 목표 점검. 각자 어제 정한 단위가 어디까지 됐는지 한 줄 공유
+
10–90분
자립 코딩. 멘토는 "15분 규칙" 을 지키며 대기. 질문은 "좋은 질문 3요소" 로 받기
+
90–110분
마무리 · PR 올리기. 커밋 메시지 함께 다듬고 두 번째 PR 생성
+
110–120분
회고. "오늘 혼자 뚫은 순간" 을 각자 한 개씩 말하기
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 질문하러 온 학생
+

"바로 답 줄 수도 있는데, 먼저 물어볼게요. 뭘 하려다가 막힌 거예요? 어떤 결과가 나올 줄 알았는데, 실제로는 어떤 게 나왔어요?"

+

"좋아요. 그 에러 메시지 마지막 줄 한 번 같이 읽어 볼까요? 사실 답이 거기 반쯤 적혀 있을 때가 많아요."

+

학생이 스스로 원인을 말하면 크게 인정해 주세요. "지금 그거 방금 여러분이 디버깅한 거예요."

+
+
+
+
✍️워크시트
+
+

좋은 질문 3요소 카드. 질문하러 오기 전에 이 카드를 먼저 채우게 하세요. 반은 스스로 풀립니다.

+
+
+
워크시트 · 질문하기 전 3줄
+
① 내가 하려던 것:   [__________________]
+② 기대한 결과:      [__________________]
+③ 실제로 난 것:     [__________________]
+   (에러면 마지막 줄 그대로 적기: [__________])
+
+→ 여기까지 적었는데도 모르겠으면 그때 멘토 호출!
+
+
+
+
이해 확인
+
+

세션 종료 시 확인합니다.

+
    +
  • 두 번째 과제 PR을 올렸는가 (또는 남은 범위를 명확히 말할 수 있는가)
  • +
  • 오늘 스스로 해결한 문제를 한 가지 이상 설명할 수 있는가
  • +
  • 질문 3요소를 갖춰 물어봤는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수: 에러 메시지를 읽지 않고 바로 "안 돼요" 라고 부름.
+
대처: 화면을 같이 보며 메시지 마지막 줄부터 소리 내어 읽히기. 읽기 습관을 반복 강화.
+
실수: PR 욕심에 코드를 한꺼번에 몰아 커밋해 무엇을 바꿨는지 설명 못 함.
+
대처: 작은 단위로 커밋하도록 유도하고, 커밋 메시지를 "무엇을 왜" 한 줄로 쓰게 하기.
+
+
+
+
+ + +
+
THU
+
+

발표 자료 준비 · 1:1 코칭

+
목표: "내가 이해한 컴퓨터 / 우리 시스템" 10분 발표를 준비하고, 멘토 1:1 코칭으로 다듬는다.
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

발표 뼈대 만들기 — 10분을 3덩어리로

+
소요 90분 · 09:30–11:00
+
+
+
+
🎯학습목표
+
    +
  • 발표를 "시작 · 가운데 · 마무리" 3덩어리로 나눠 뼈대를 잡는다.
  • +
  • 어려운 말 없이, 월요일 흐름도와 그동안의 발표 문장을 재료로 슬라이드를 채운다.
  • +
  • 디자이너는 "내가 다시 디자인한다면" 주제로 화면 개선안 뼈대를 잡는다.
  • +
+
+
+
🕘진행표
+
+
0–15분
발표 목표 공유. "심사가 아니라, 내가 이만큼 알게 됐다를 보여 주는 자리" 라고 프레이밍
+
15–30분
3덩어리 나누기. 시작(내 소개·무엇을 배웠나) / 가운데(요청 흐름·과제 이야기) / 마무리(느낀 점)
+
30–80분
슬라이드 채우기. 개발자는 요청 흐름도 + 두 과제 이야기, 디자이너는 개선 전/후 화면 배치
+
80–90분
중간 점검. 슬라이드 장수와 각 덩어리 시간(대략 3·5·2분) 배분 확인
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 전체
+

"발표라고 하면 겁부터 나죠? 근데 이건 시험이 아니에요. '나 4주 동안 이런 걸 알게 됐어요' 를 편하게 보여 주는 자리예요."

+

"멋진 단어 하나도 안 써도 돼요. 오히려 여러분 말로 쉽게 설명할수록 점수가 올라가요. 우리 회사가 '고객과 함께 더 나은 세상을 만들어 나가는' 곳이잖아요. 고객한테 설명하듯, 쉽게요."

+
+
+
+
✍️워크시트
+
+

발표 뼈대 시트. 슬라이드를 만들기 전에 아래 뼈대부터 채우게 하세요. (디자이너 트랙 문구는 괄호로 표기)

+
+
+
워크시트 · "내가 이해한 컴퓨터 / 우리 시스템" 10분 발표
+
[제목]  [____________________]
+        (디자이너: "내가 다시 디자인한다면 — [화면명]")
+
+■ 시작 (약 3분)
+  - 나는 누구, 4주간 무엇을 했나:  [________]
+
+■ 가운데 (약 5분)  ← 핵심
+  - 개발: 클릭→서버→DB→응답 흐름 설명  [________]
+  - 개발: 두 과제에서 막혔다가 뚫은 순간  [________]
+  - (디자이너: 개선 전 화면 문제점 → 개선안 → 왜 더 나은가)
+
+■ 마무리 (약 2분)
+  - 가장 크게 배운 것 한 가지:  [________]
+  - 앞으로 더 해 보고 싶은 것:  [________]
+
+
+
+
⚠️흔한 실수
+
+
실수: 슬라이드 꾸미기(색·애니메이션)에 시간을 다 써서 정작 할 말이 비어 있음.
+
대처: "오늘은 내용 먼저, 꾸미기는 마지막 10분" 규칙을 정해 순서를 강제하기.
+
실수: 남의 발표를 흉내 내려다 자기 경험이 사라짐.
+
대처: "네가 실제로 막혔던 그 순간"을 반드시 한 장 넣게 해서 본인 이야기를 살리기.
+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오후
+

멘토 1:1 발표 코칭

+
소요 120분 · 13:30–15:30 · 학생당 약 25분
+
+
+
+
🎯학습목표
+
    +
  • 준비한 발표를 멘토 앞에서 한 번 소리 내어 리허설한다.
  • +
  • 어려운 말·빠른 말투·비어 있는 부분을 1:1로 구체적으로 고친다.
  • +
+
+
+
🕘진행표
+
+
학생당 0–8분
리허설. 학생이 실제로 발표 (멘토는 끊지 않고 끝까지 듣기)
+
8–18분
피드백. 잘한 점 먼저 2가지 → 고칠 점 1~2가지 (구체적으로)
+
18–25분
다시 한 번. 고친 부분만 짧게 재연습
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 학생 (1:1)
+

"방금 발표 좋았어요. 특히 요청 흐름 설명할 때 '화면이 서버한테 물어본다' 이 표현, 진짜 쉽게 잘했어요."

+

"한 군데만 같이 고쳐 볼까요? 여기 이 부분은 조금 빠르게 지나갔어요. 여러분이 제일 고생한 부분이니까 오히려 천천히, 한 문장 더 붙여도 좋아요."

+

잘한 점 → 고칠 점 순서를 지키세요. 지적으로 시작하면 다음 발표가 위축됩니다.

+
+
+
+
이해 확인
+
+

1:1 종료 시 각 학생이 다음을 손에 쥐고 나가야 합니다.

+
    +
  • 고칠 점 1~2개를 자기 말로 다시 정리했는가
  • +
  • 발표 시간이 대략 10분 안에 들어오는가
  • +
  • 내일 발표에 대한 부담이 (완전히는 아니어도) 조금 줄었는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수: 멘토가 고칠 점을 3개 이상 쏟아 내 학생이 다 못 고치고 혼란.
+
대처: 가장 중요한 것 1~2개만. 나머지는 "지금은 이거 하나만" 으로 남겨 두기.
+
실수: 발표 코칭 자리에서 성급하게 중간평가 결과를 흘림.
+
대처: 평가 신호는 금요일 면담 세션에서 정식으로. 오늘은 발표 다듬기에만 집중.
+
+
+
+
+ + +
+
FRI
+
+

미니 발표회 + 중간평가 면담

+
목표: 어려운 말 없이 내 말로 발표하고, 4주차 중간평가 면담에서 솔직한 신호(전환 포함)를 반드시 전달받는다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

기본기 미니 발표회 — 내 말로 10분

+
소요 90분 · 09:30–11:00 · 학생당 발표 10분 + 격려 5분
+
+
+
+
🎯학습목표
+
    +
  • 4주간 배운 기본기를 어려운 말 없이 자기 말로 발표한다.
  • +
  • 서로의 발표를 듣고 한 가지씩 좋은 점을 말해 준다.
  • +
  • 디자이너는 화면 개선안(전/후)을 발표한다.
  • +
+
+
+
🕘진행표
+
+
0–10분
분위기 만들기. "오늘은 서로 응원하는 자리" 선언. 야유·지적 금지 규칙 공지
+
10–70분
발표. 4명이 순서대로 각 10분 발표 + 동료가 좋았던 점 1개씩 (개발 3 · 디자인 1)
+
70–85분
멘토 총평. 팀 전체에 대한 따뜻한 총평 (개인 평가는 오후 면담으로 미룸)
+
85–90분
기념. 발표자료를 산출물로 제출 확인 (기본기 발표자료 · 두 번째 PR)
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 발표회 시작
+

"4주 전에 처음 왔을 때랑 지금이랑, 여러분 진짜 많이 달라졌어요. 오늘은 그걸 서로 보여 주는 자리예요."

+

"발표하다 막혀도 괜찮아요. 우리 여기서 야유하거나 지적하는 사람 없어요. 발표 끝나면 서로 좋았던 점 하나씩만 말해 줄 거예요. 편하게 시작해요."

+
+
+
+
✍️워크시트
+
+

동료 응원 카드. 듣는 학생이 발표마다 이 카드를 채워 발표자에게 전달합니다.

+
+
+
워크시트 · 동료 응원 카드
+
발표자:  [______]
+
+내가 제일 좋았던 부분:  [________________]
+새로 알게 된 것 하나:   [________________]
+한마디 응원:            [________________]
+
+(지적·비교 금지 — 좋았던 점만!)
+
+
+
+
이해 확인
+
+

발표회로 다음을 확인합니다.

+
    +
  • 어려운 전문 용어에 기대지 않고 자기 말로 설명했는가
  • +
  • 요청 흐름(개발) 또는 개선안 근거(디자인)를 스스로 말했는가
  • +
  • 산출물(발표자료 · 두 번째 PR)을 제출했는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수: 발표 중 학생이 막히자 다른 학생이 웃거나 놀림.
+
대처: 시작 전 "응원만" 규칙을 명확히 공지하고, 즉시 부드럽게 제지하기.
+
실수: 발표회 자리에서 개인 잘잘못을 공개적으로 언급.
+
대처: 개인 피드백은 전부 오후 1:1 면담으로. 공개 자리에선 격려만.
+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오후 · ★ 중간평가
+

4주차 중간평가 1:1 면담 — 솔직한 신호 전달

+
소요 120분 · 13:30–15:30 · 학생당 약 25~30분
+
+
+
+
🎯학습목표
+
    +
  • 평가 루브릭을 기준으로 잘된 점·보완점을 학생별로 정리해 전달한다.
  • +
  • 전환(정규 채용 방향)에 대한 솔직한 신호를 이 자리에서 반드시 전달한다.
  • +
  • 남은 4주 동안 무엇에 집중할지 함께 목표를 정한다.
  • +
+
+
+
🕘진행표
+
+
준비 (사전)
루브릭 채점. 면담 전 학생별 평가 루브릭을 미리 작성. 잘된 점 2 · 보완점 2 · 전환 신호를 문장으로 준비
+
0–5분
편안하게 열기. 학생 스스로 4주 자기 평가를 먼저 말하게 하기
+
5–13분
잘된 점. 구체적 사례로 칭찬 (막연한 칭찬 금지)
+
13–20분
보완점 + 전환 신호. 루브릭 기준으로 솔직하게. 마지막 주에 처음 듣게 하지 않기
+
20–28분
남은 4주 목표. 함께 다음 집중 포인트 1~2개 합의
+
+
+
+
💬멘토 스크립트
+
+
멘토 → 학생 (긍정 흐름)
+

"먼저 잘한 것부터요. 두 번째 과제 때 스스로 에러를 읽고 뚫는 모습이 눈에 확 띄었어요. 처음보다 물어보는 질문의 질이 좋아졌어요. 이건 진짜 실력이에요."

+

"보완할 점도 솔직하게 말할게요. 아직 코드를 작은 단위로 나누는 습관은 조금 더 필요해요. 남은 4주 동안 여기만 같이 잡으면 훨씬 단단해질 거예요."

+
+
+
멘토 → 학생 (전환 신호 · 순조로운 경우)
+

"솔직하게 지금 흐름이면 저는 긍정적으로 보고 있어요. 다만 이건 아직 '확정'은 아니고, 남은 4주에서 방금 말한 부분을 채우는 게 중요해요. 그래서 오늘 미리 말해 주는 거예요. 마지막에 갑자기 듣게 하고 싶지 않아서요."

+
+
+
멘토 → 학생 (전환 신호 · 우려가 있는 경우)
+

"이 얘기는 마지막 주가 아니라 지금 하는 게 맞다고 생각해서 꺼내요. 지금 속도라면 아직 전환을 자신 있게 말하긴 이른 상태예요. 나쁘게 들릴 수 있지만, 아직 4주가 남았고 방향을 바꿀 시간이 충분해요."

+

"그래서 오늘부터 딱 두 가지에 집중해 봤으면 해요: ①작은 단위 커밋 ②막히면 15분 후 질문. 다음 면담 때 이 두 개로 다시 이야기해요. 저도 옆에서 최대한 도울게요."

+

우려 신호는 '가능성을 닫는 통보'가 아니라 '지금이면 바꿀 수 있다는 초대'로 전하세요. 반드시 구체적 행동 2개와 함께.

+
+
+
+
✍️면담 기록 시트
+
+

멘토가 채우는 면담 기록. 학생마다 아래를 남겨 마지막 8주차 최종평가와 연결하세요. (개인정보이므로 학생에게 공개 배포 금지, 멘토 보관용)

+
+
+
면담 기록 · 4주차 중간평가 (멘토 보관용)
+
학생: [____]  트랙: [개발 / 디자인]  일자: 2026-__-__
+
+루브릭 요약 점수:  [____]
+잘된 점 (사례):
+  1) [__________________]
+  2) [__________________]
+보완점 (사례):
+  1) [__________________]
+  2) [__________________]
+
+전환 신호 전달 내용:  [순조 / 조건부 / 우려]
+  → 학생에게 실제로 한 말:  [__________]
+  → 학생 반응:              [__________]
+
+남은 4주 합의 목표 (1~2개):
+  ① [__________]
+  ② [__________]
+
+
+
+
이해 확인
+
+

면담이 제대로 됐는지 멘토 스스로 점검합니다.

+
    +
  • 잘된 점·보완점을 막연하지 않게 구체 사례로 전달했는가
  • +
  • 전환 신호를 (순조든 우려든) 이번 주에 분명히 전달했는가 — 마지막 주에 처음 듣게 하지 않았는가
  • +
  • 학생이 남은 4주에 할 일 1~2개를 자기 말로 다시 말했는가
  • +
  • 면담 기록 시트를 남겼는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수: 부담스러워 전환 관련 신호를 얼버무리고 넘어감. 결국 마지막 주에야 처음 통보하게 됨.
+
대처: "지금 말하는 게 학생을 위하는 것" 임을 기억. 우려도 바꿀 시간이 있을 때 전해야 기회가 됩니다.
+
실수: 보완점만 길게 말해 학생이 "나는 안 되나 보다" 로 받아들임.
+
대처: 잘된 점 → 보완점 → 다음 목표 순서를 지키고, 항상 "함께 하자" 로 닫기.
+
실수: 루브릭 없이 즉흥 인상평으로 평가.
+
대처: 반드시 평가 루브릭 기준으로 사전 채점 후 면담에 들어가기.
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 4주차 상세 강의안 (멘토용) · 8부작 중 4 + 2026 · 수습 가이드 · CS 커리큘럼 · 평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week5-detailed.html b/frontend/public/docs/week5-detailed.html new file mode 100644 index 0000000..eab4a98 --- /dev/null +++ b/frontend/public/docs/week5-detailed.html @@ -0,0 +1,1077 @@ +5주차 상세 강의안 · 실전 티켓 ① · AWESOMEDEV 수습 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 5 / 8
+

5주차 상세 강의안
실전 티켓 ①

+

이번 주부터 수습생은 실제 백로그 티켓 하나를 맡아 "착수 → 구현 → 리뷰 → 배포"의 한 사이클을 스스로 돌립니다. 4주간 쌓은 기본기를 진짜 코드베이스 위에서 처음으로 꺼내 쓰는 주간이에요. 멘토는 정답을 대신 짜 주는 사람이 아니라, 막힐 때 방향을 같이 찾아 주는 페이스메이커로 붙습니다.

+
+ 대상 · 미림마이스터고 3학년 수습생 4명(개발 3 · 디자인 1) + 스택 · React · Spring Boot · 클라우드 · React Native · Flutter + 산출물 · 완료·배포된 티켓 1~2건 +
+ + +
+ +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 각 세션 카드를 위에서 아래로 그대로 읽어 내려가며 진행하면 됩니다.

+
+
+ + +
+
MON
+
+

티켓 배정 · 설계 합의

+
오늘의 목표 · 각자 실제 티켓 1건을 배정받고, 접근 방법과 예상 소요를 멘토와 합의한다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

실무 심화 포인터 ① — React 상태 관리 한눈에

+
소요 40분 (오전 기본기 대체)
+
+
+
+
🎯학습목표
+
    +
  • "상태(state)"가 무엇인지, 왜 컴포넌트마다 따로 관리하는지 한 문장으로 말할 수 있다.
  • +
  • 우리 웹(React)에서 상태가 화면을 어떻게 다시 그리는지 큰 그림을 그린다.
  • +
+
+
+
🕘진행표
+
+
0:00
도입 — "지난 4주는 기본기였고, 오늘부터는 실무 근육이에요"라고 분위기 전환
+
0:05
개념 — 상태 = "화면이 기억해야 하는 값". 카운터 예시로 useState 시연
+
0:20
확장 — props로 값 내려주기 vs 상태 끌어올리기 개념만 그림으로
+
0:32
연결 — 오늘 배정될 티켓에서 상태를 만질 수도 있다고 예고
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자, 오늘부터 아침 기본기 시간은 끝났어요. 대신 실무에서 매일 쓰는 걸 아주 짧게 짚고 갈 거예요. 부담 갖지 말고 '아 이런 게 있구나' 정도만 담아 가면 돼요."

+

"상태라는 건 어렵게 생각할 거 없어요. 버튼 누르면 숫자가 올라가는 화면, 그 숫자를 React가 어딘가 기억하고 있어야 하잖아요? 그 기억하는 값이 상태예요."

+

(카운터를 직접 눌러 숫자가 바뀌는 걸 보여 주며) 지금 이거, 우리가 만든 게 아니라 상태가 바뀌니까 React가 알아서 다시 그린 거예요.

+
+
+
+
✍️실습 안내
+
+

포인터 세션입니다. 깊게 들어가지 말고, 개념 소개 후 곧바로 티켓 배정으로 넘어가세요.

+
    +
  • 더 촘촘한 CS 기초가 필요하면 CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행하되, 5주차는 원칙적으로 티켓 실무에 시간을 몰아 줍니다.
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 상태 관리를 여기서 완벽히 가르치려다 오전을 다 씀.
+
대처 · "티켓 하면서 필요할 때 다시 볼게요"로 끊고 넘어가세요. 오늘 핵심은 티켓 배정입니다.
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오전
+

티켓 배정 — 나에게 맞는 첫 티켓 고르기

+
소요 70분
+
+
+
+
🎯학습목표
+
    +
  • 자기 수준에 맞는 난이도 ★~★★ 티켓 1건을 배정받고 내용을 자기 말로 다시 설명한다.
  • +
  • 티켓의 "완료 조건(Done)"이 무엇인지 명확히 짚는다.
  • +
+
+
+
🕘진행표
+
+
0:00
백로그 열기 — 이슈 트래커에서 수습용으로 골라 둔 티켓 5~6개를 함께 훑기
+
0:15
난이도 설명 — ★=하루 안, ★★=2~3일 예상이라고 눈금 맞추기
+
0:25
1:1 배정 — 개발 3명 각자 1건, 디자이너 1명은 개선 디자인 1건
+
0:45
되말하기 — 각자 자기 티켓을 30초로 요약해 보게 함
+
1:00
완료 조건 합의 — "무엇이 되면 끝인가"를 티켓에 한 줄로 적기
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘부터 여러분은 관람객이 아니라 선수예요. 진짜 우리 제품에 들어갈 코드를 짜는 거예요. 겁먹을 필요 없어요. 여러분이 감당할 수 있는 크기로 티켓을 골라 뒀거든요."

+

"별 하나짜리는 하루 안에 끝낼 수 있는 크기, 별 둘은 2~3일 걸리는 크기예요. 지금은 작게 시작하는 게 훨씬 좋아요. 작은 걸 확실히 끝내는 게 큰 걸 반쯤 하다 마는 것보다 백 배 나아요."

+

(배정 후) 이제 이 티켓, 저한테 다시 설명해 볼래요? 여러분 말로 설명이 되면 반은 이해한 거예요.

+
+
+
+
✍️워크시트 — 티켓 이해 카드
+
+

각자 아래 대본의 빈칸을 채워 멘토에게 제출합니다. 채우지 못하는 칸이 있으면 그게 곧 오늘 물어봐야 할 질문이에요.

+
+
+
티켓 이해 카드 (빈칸 채우기)
+
티켓 번호: #____   제목: ________________   난이도: ★ / ★★
+
+이 티켓을 한 문장으로: "____________을(를) ____________하게 만든다."
+
+손대야 할 화면/파일(추측): ________________
+
+완료 조건(이게 되면 끝): ________________
+
+지금 당장 모르는 것 1가지: ________________
+
+
+
+
이해 확인
+
+

다음 자리로 넘어가기 전, 각자에게 물어보세요.

+
    +
  • "이 티켓은 무엇이 되면 끝인가요?" — 완료 조건을 자기 말로 답할 수 있는가
  • +
  • "어느 화면/파일을 먼저 열어 볼 것 같아요?" — 시작점을 짚는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 욕심내서 별 셋짜리 큰 티켓을 달라고 함.
+
대처 · "이번 주는 사이클 한 바퀴를 도는 게 목표예요. 크기보다 완주가 중요해요"라고 방향을 잡아 주세요.
+
+
+
+
+ +
+
+
Day 1 · 세션 3 · 오후
+

설계 합의 — 어떻게 풀지 먼저 그려 보기

+
소요 90분
+
+
+
+
🎯학습목표
+
    +
  • 코드를 짜기 전에 "접근 방법"을 글로 먼저 적고 멘토와 합의한다.
  • +
  • 예상 소요 시간을 스스로 추정해 본다(맞고 틀림은 나중에 확인).
  • +
  • 디자이너는 개선 디자인 1건의 방향을 개발 반영까지 염두에 두고 잡는다.
  • +
+
+
+
🕘진행표
+
+
0:00
코드 탐색 — 관련 화면/컴포넌트를 열어 "지금 어떻게 돌아가나" 읽기
+
0:25
접근 계획 쓰기 — 아래 대본으로 3~5단계 계획을 글로 작성
+
0:50
1:1 리뷰 — 멘토가 한 명씩 계획을 같이 읽고 구멍 짚기
+
1:10
소요 추정 — 각 단계에 예상 시간 적기, 합계 내기
+
1:20
합의 도장 — "이 방향으로 갑시다" 확정, 내일 착수 준비 완료
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"바로 코드부터 치고 싶은 마음 알아요. 근데 실무에서는 '어떻게 풀지' 먼저 적어 보는 게 습관이에요. 5분 적으면 5시간을 아껴요."

+

"계획이 틀려도 괜찮아요. 틀린 계획이라도 있으면, 제가 어디가 틀렸는지 짚어 줄 수 있거든요. 아무것도 없으면 저도 도와줄 수가 없어요."

+

(디자이너에게) OO는 이번에 화면 개선 하나를 맡을 거예요. 예쁘게만 끝나는 게 아니라, 개발자가 그걸 실제 화면에 반영하는 데까지 같이 따라가 볼 거예요.

+
+
+
+
✍️워크시트 — 접근 계획서
+
+

개발 3명은 아래 계획서를, 디자이너는 그 아래 디자인 계획서를 채웁니다. 이 문서가 곧 내일 아침의 출발점이에요.

+
+
+
접근 계획서 — 개발 트랙
+
티켓 #____ 접근 계획
+
+1단계: ____________________  (예상 __분)
+2단계: ____________________  (예상 __분)
+3단계: ____________________  (예상 __분)
+(필요 시) 4단계: ________________  (예상 __분)
+
+총 예상 소요: 약 __시간
+
+확인이 필요한 것: ________________
+테스트로 확인할 방법: 화면에서 ____를 하면 ____가 보인다
+
+
+
개선 디자인 계획서 — 디자인 트랙
+
개선 대상 화면: ________________
+
+지금의 문제(한 줄): ________________
+개선 방향(한 줄): ________________
+
+개발 반영 시 바뀌는 것: 색 / 간격 / 배치 / 문구 中 ____
+개발자에게 넘길 것: 시안 파일 / 색상값 / 여백 수치
+
+
+
+
이해 확인
+
+

오늘을 닫기 전 각자에게:

+
    +
  • "내일 아침 가장 먼저 열 파일/화면은 뭐예요?"
  • +
  • "이 계획에서 제일 자신 없는 단계는 몇 번이에요?" — 불안한 곳을 미리 드러내게
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 계획을 "코드를 짠다" 한 줄로 뭉뚱그림.
+
대처 · "어디를, 무엇으로, 어떻게 바꾸는지"로 쪼개 달라고 되물어 주세요. 쪼개지는 순간 스스로 길이 보입니다.
+
+
+
+
+ + +
+
TUE
+
+

구현 사이클 시작 · 중간보고

+
오늘의 목표 · 계획대로 코드를 짜기 시작하고, 하루 끝에 중간보고로 진행 상황을 공유한다.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

실무 심화 포인터 ② — Spring Boot 계층 구조

+
소요 35분 (오전 기본기 대체 · 포인터)
+
+
+
+
🎯학습목표
+
    +
  • Controller → Service → Repository의 3층이 각각 무슨 일을 하는지 한 줄로 구분한다.
  • +
+
+
+
🕘진행표
+
+
0:00
비유 — 식당 비유: 홀(Controller)·주방(Service)·창고(Repository)
+
0:12
코드 투어 — 우리 백엔드에서 실제 3개 파일을 열어 층을 짚기
+
0:28
연결 — "네 티켓이 어느 층을 건드리는지" 각자 답하기
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"백엔드는 식당이라고 생각하면 쉬워요. 손님 주문 받는 홀이 Controller, 요리하는 주방이 Service, 재료 꺼내 오는 창고가 Repository예요. 각자 자기 일만 해요."

+

"왜 이렇게 나눌까요? 요리사가 창고 정리까지 하면 정신없잖아요. 나눠 놓으면 고칠 데를 딱 찾기 쉬워요. 여러분 티켓도 '어느 층 문제인지'만 알면 절반은 찾은 거예요."

+
+
+
+
✍️실습 안내
+
+

포인터 세션입니다. Java는 보조 언어이니 문법을 깊게 다루지 말고 "층 구분" 감만 주고 마무리하세요.

+
    +
  • 더 다지고 싶으면 CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행합니다.
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 프론트 티켓을 받은 학생이 "나랑 상관없네" 하고 흘려들음.
+
대처 · "API를 부르는 쪽도 서버가 어떻게 나뉘는지 알면 소통이 빨라져요"라고 연결해 주세요.
+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오전~오후
+

1단계 구현 — 작은 성공을 먼저 만들기

+
소요 180분 (중간 휴식 포함)
+
+
+
+
🎯학습목표
+
    +
  • 어제 계획서의 1단계를 실제로 코드로 옮겨 "화면에서 눈에 보이는 변화" 하나를 만든다.
  • +
  • 막혔을 때 15분 규칙(15분 혼자 → 그래도 안 되면 질문)을 지킨다.
  • +
+
+
+
🕘진행표
+
+
0:00
브랜치 생성 — 티켓 번호로 작업 브랜치 만들기(멘토가 옆에서 확인)
+
0:15
몰입 구현 — 각자 1단계 작업. 멘토는 순회하며 화면 너머로 관찰
+
1:20
중간 점검 — "지금 뭐가 됐고 뭐가 안 되나" 30초씩 돌아가며
+
1:35
이어서 구현 — 걸린 사람은 멘토가 붙어 같이 디버깅
+
2:50
커밋 — 오늘까지 된 만큼 의미 있는 단위로 커밋 남기기
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘 목표는 티켓을 다 끝내는 게 아니에요. '화면에서 뭔가 하나 바뀌는 걸' 만드는 거예요. 버튼 하나라도 원하는 대로 움직이면 오늘은 대성공이에요."

+

"막히면요, 15분만 혼자 붙잡아 봐요. 그래도 안 풀리면 바로 손 들어요. 15분 넘게 혼자 끙끙대는 건 용기가 아니라 시간 낭비예요. 질문하는 게 실력이에요."

+

(에러가 났을 때) 에러 났다고 겁먹지 말고, 빨간 글씨를 소리 내서 같이 읽어 봐요. 대부분 답이 거기 적혀 있어요.

+
+
+
+
✍️실습 — 오늘의 커밋
+
+

실습 결과물: 티켓 브랜치에 최소 1개의 의미 있는 커밋. 커밋 메시지는 아래 형식으로 적게 하세요.

+
+
+
커밋 메시지 · 빈칸 채우기
+
유형 선택: feat / fix / style
+한 줄 요약: ____를 ____하게 바꿈
+
+예) feat: 로그인 버튼 색을 브랜드 색으로 교체
+예) fix: 목록이 빈 화면일 때 안내 문구 표시
+
+
+
+
이해 확인
+
+

커밋 전 각자에게:

+
    +
  • "이 변화를 화면 어디서 확인할 수 있어요?" — 눈으로 검증 가능한가
  • +
  • "방금 커밋, 제목만 봐도 뭘 했는지 알 수 있어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 하루 종일 짠 걸 한 번에 거대 커밋으로 몰아 넣음.
+
대처 · "화면에서 확인 가능한 변화 하나 = 커밋 하나"로 잘라 달라고 안내하세요.
+
+
+
+
+ +
+
+
Day 2 · 세션 3 · 오후
+

첫 중간보고 — 30초로 오늘을 말하기

+
소요 30분
+
+
+
+
🎯학습목표
+
    +
  • "한 것 / 막힌 것 / 내일 할 것"을 30초 안에 말하는 중간보고 형식을 익힌다.
  • +
+
+
+
🕘진행표
+
+
0:00
형식 소개 — 중간보고 3줄 틀 안내
+
0:05
돌아가며 보고 — 4명이 각자 30초~1분 발표
+
0:20
멘토 피드백 — 막힌 지점에 대해 내일 방향 한 줄씩
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"실무에서는 매일 이렇게 짧게 보고해요. 잘한 걸 자랑하는 자리가 아니라, 막힌 걸 나누는 자리예요. 막혔다고 말하는 게 제일 프로다운 거예요."

+

(발표가 위축될 때) 오늘 못 끝냈어도 괜찮아요. 어디까지 왔는지만 정확히 말해 주면 저는 그걸로 충분해요.

+
+
+
+
✍️워크시트 — 중간보고 3줄
+
+

발표 전 각자 아래 3줄을 채워 놓고 보고합니다. 매일 같은 형식을 씁니다.

+
+
+
중간보고 카드 (매일 사용)
+
오늘 한 것:  ________________
+막힌 것:     ________________ (없으면 "없음")
+내일 할 것:  ________________
+
+
+
+
이해 확인
+
+

보고 후 확인:

+
    +
  • 모두 3줄 형식으로 말했는가 (장황하게 늘어놓지 않았는가)
  • +
  • 막힌 사람이 솔직하게 "막혔다"고 말했는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · "그냥 다 잘 됐어요"로 뭉개고 막힌 걸 숨김.
+
대처 · "잘 안 된 것 딱 하나만 말해 볼까요?"로 구체 질문을 던져 안전하게 꺼내게 하세요.
+
+
+
+
+ + +
+
WED
+
+

코드리뷰 받고 반영하기

+
오늘의 목표 · 처음으로 코드리뷰를 받고, 지적을 감정이 아닌 개선으로 받아들여 반영한다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

PR 올리기 — 리뷰받을 수 있는 모양 만들기

+
소요 90분
+
+
+
+
🎯학습목표
+
    +
  • 지금까지 작업을 Pull Request로 올리고, 설명을 남에게 읽히는 글로 적는다.
  • +
  • "무엇을 왜 바꿨는지"를 리뷰어가 이해할 수 있게 정리한다.
  • +
+
+
+
🕘진행표
+
+
0:00
PR 개념 — "내 코드를 봐 주세요"라고 정식으로 요청하는 것
+
0:10
본문 작성 — 아래 PR 템플릿으로 각자 설명글 작성
+
0:40
셀프 점검 — 올리기 전 스스로 코드 한 번 다시 읽기
+
1:00
PR 생성 — 멘토를 리뷰어로 지정해 올리기
+
1:15
디자이너 병행 — 시안을 개발자에게 정식 전달, 반영 항목 합의
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"PR은 '제 코드 좀 봐 주세요' 하고 정식으로 부탁하는 거예요. 그래서 설명을 잘 적어야 해요. 리뷰어는 여러분 머릿속을 못 보니까, 무엇을 왜 바꿨는지 글로 알려 줘야 해요."

+

"올리기 전에 딱 한 번, 내 코드를 남의 코드라 생각하고 읽어 봐요. 오타나 지우다 만 흔적, 거기서 절반은 스스로 잡혀요."

+
+
+
+
✍️워크시트 — PR 설명 템플릿
+
+

아래 빈칸을 채워 PR 본문으로 붙여 넣습니다. 리뷰어가 이 글만 읽고도 맥락을 알 수 있어야 합니다.

+
+
+
Pull Request 본문 · 빈칸 채우기
+
## 무엇을 (What)
+____________________
+
+## 왜 (Why) — 어떤 티켓/문제 때문에
+티켓 #__ · ____________________
+
+## 어떻게 확인하나 (How to test)
+1. ____화면에 들어간다
+2. ____를 누른다
+3. ____가 보이면 정상
+
+## 아직 자신 없는 부분 (리뷰어가 봐 줬으면)
+____________________
+
+
+
+
이해 확인
+
+

PR 올린 뒤:

+
    +
  • "이 PR 제목만 봐도 뭘 했는지 알 수 있어요?"
  • +
  • "리뷰어가 어떻게 테스트하면 되는지 적혀 있어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 설명 없이 코드만 덜렁 올림("보면 알겠죠").
+
대처 · "리뷰어는 여러분이 뭘 고민했는지 몰라요. 3줄이라도 적어 주면 리뷰가 훨씬 빨라져요"라고 이유를 붙여 안내하세요.
+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오후
+

리뷰 반영 — 지적을 성장으로 바꾸기

+
소요 150분
+
+
+
+
🎯학습목표
+
    +
  • 멘토의 리뷰 코멘트를 하나씩 이해하고, 고칠 것과 물어볼 것을 분류한다.
  • +
  • 리뷰를 인신공격이 아니라 코드에 대한 이야기로 받아들인다.
  • +
  • 반영 후 "무엇을 왜 바꿨는지"를 코멘트로 답한다.
  • +
+
+
+
🕘진행표
+
+
0:00
리뷰 함께 읽기 — 멘토가 남긴 코멘트를 학생과 한 줄씩 소리 내 읽기
+
0:25
분류 — 코멘트를 "바로 고침 / 물어보고 고침 / 이번엔 보류" 3칸으로
+
0:45
반영 구현 — 각자 코멘트 반영. 멘토는 순회하며 도움
+
2:00
답글 달기 — 각 코멘트에 "이렇게 고쳤어요" 답 코멘트
+
2:20
재푸시 — 반영 커밋을 PR에 올려 리뷰어에게 재확인 요청
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"리뷰 코멘트 보고 마음 상하는 거, 아주 자연스러운 거예요. 근데 하나만 기억해요. 저는 여러분을 지적한 게 아니라, 코드를 같이 더 좋게 만들려는 거예요. 코드랑 여러분은 다른 사람이에요."

+

"모든 코멘트를 다 고칠 필요도 없어요. 이해가 안 되면 '이건 왜 그런가요?' 하고 물어봐요. 물어보는 거, 반박이 아니라 배움이에요. 오히려 멋진 거예요."

+

(반영 후) 고쳤으면 '이렇게 바꿨어요' 하고 답글 달아 줘요. 그래야 저도 다시 볼 때 빨라요.

+
+
+
+
✍️워크시트 — 리뷰 분류표
+
+

코멘트를 감정 없이 처리하기 위한 표입니다. 코멘트 개수만큼 줄을 늘려 채우세요.

+
+
+
리뷰 코멘트 분류 · 빈칸 채우기
+
코멘트 1: "____________"
+ → 처리: 바로 고침 / 물어보고 / 보류
+ → 내가 한 일: ____________
+
+코멘트 2: "____________"
+ → 처리: 바로 고침 / 물어보고 / 보류
+ → 내가 한 일: ____________
+
+
+
+
이해 확인
+
+

반영 후:

+
    +
  • "고친 것 중에 왜 고쳤는지 모르는 것은 없어요?" — 이해 없이 시키는 대로만 고치지 않았는가
  • +
  • "이해 안 되는 코멘트에 대해 질문을 남겼어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 코멘트를 자기 부정으로 받아들여 표정이 굳음.
+
대처 · "이건 OO가 못한 게 아니라, 원래 다들 처음엔 이렇게 짜요"라고 보편화해 주세요. 리뷰 톤은 항상 코드에 한정합니다.
+
+
+
+
+ + +
+
THU
+
+

마무리 구현 · 배포 준비

+
오늘의 목표 · 남은 구현을 끝내 완료 조건을 채우고, 배포 직전 최종 점검까지 마친다.
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

완료 조건 채우기 — "끝"의 기준에 도달하기

+
소요 160분
+
+
+
+
🎯학습목표
+
    +
  • 월요일에 적은 완료 조건을 하나씩 대조하며 남은 작업을 끝낸다.
  • +
  • "되겠지"가 아니라 화면에서 직접 눌러 보며 확인한다.
  • +
+
+
+
🕘진행표
+
+
0:00
완료 조건 소환 — 티켓 이해 카드의 완료 조건을 다시 꺼내 읽기
+
0:10
남은 구현 — 아직 안 채워진 조건을 목표로 작업
+
1:20
직접 검증 — 각 조건을 화면에서 눌러 보며 O/X 표시
+
2:00
디자이너 반영 확인 — 개발자 화면에서 개선 디자인이 실제로 보이는지 함께 확인
+
2:20
커밋 · 재푸시 — 완료분 커밋하고 PR 갱신
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘은 '끝'에 도달하는 날이에요. 근데 '끝'이 뭐였죠? 월요일에 우리가 적었잖아요. 그 문장을 지금 다시 꺼내서, 한 줄 한 줄 진짜 됐는지 눌러 봐요."

+

"'아마 될 거예요'는 안 돼요. 직접 눌러 보고 '됐어요'가 돼야 해요. 개발자는 눈으로 확인한 것만 믿어요."

+

(디자이너에게) OO 시안이 실제 화면에 들어갔는지 개발자 옆에서 같이 봐요. 여백 하나, 색 하나 다르면 지금 말해 줘야 해요.

+
+
+
+
✍️워크시트 — 완료 조건 체크
+
+

완료 조건을 그대로 옮겨 적고, 하나씩 직접 확인해 O/X를 매깁니다. 전부 O가 되어야 티켓 완료입니다.

+
+
+
완료 조건 체크 · 직접 눌러 확인
+
완료 조건 1: ____________  → 확인: O / X
+완료 조건 2: ____________  → 확인: O / X
+완료 조건 3: ____________  → 확인: O / X
+
+전부 O 인가? 예 / 아니오
+아니오라면, 남은 것: ____________
+
+
+
+
이해 확인
+
+

세션 끝에:

+
    +
  • "완료 조건 모두 O인가요? 하나라도 X면 뭐가 남았어요?"
  • +
  • "방금 그거, 말로만이 아니라 화면에서 직접 눌러 봤어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 코드가 컴파일되니 "됐다"고 판단하고 실제 동작은 안 눌러 봄.
+
대처 · "컴파일은 문법이 맞다는 거지, 원하는 대로 됐다는 게 아니에요. 화면에서 눌러 봐요"라고 반드시 재현시키세요.
+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오후
+

배포 준비 — 세상에 나가기 직전 점검

+
소요 100분
+
+
+
+
🎯학습목표
+
    +
  • 내 변경이 배포되면 실제 사용자(고객사 서비스)에게 나간다는 무게를 이해한다.
  • +
  • 배포 전 최종 체크리스트를 스스로 통과시킨다.
  • +
+
+
+
🕘진행표
+
+
0:00
무게 인식 — "이 코드가 KT엠모바일·KT알파·핀업 화면에 나갈 수도 있다"
+
0:10
최종 리뷰 승인 — 멘토가 PR을 마지막으로 확인하고 승인
+
0:35
체크리스트 — 아래 배포 전 점검표를 각자 통과
+
1:05
머지 준비 — 충돌 없는지 확인, 내일 배포할 상태로 정리
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"이거 배포하면 진짜로 나가요. 우리 회사 슬로건이 '고객과 함께 더 나은 세상을 만들어 나가는 기업'이잖아요. 여러분 코드가 그 '더 나은 세상'의 아주 작은 조각이 되는 거예요. 뿌듯하죠?"

+

"그래서 나가기 전에 딱 한 번 더 봐요. 겁주려는 게 아니라, 프로는 항상 배포 전에 심호흡 한 번 하고 체크리스트를 봐요."

+
+
+
+
✍️워크시트 — 배포 전 체크리스트
+
+

모든 칸에 체크가 들어가야 내일 배포로 넘어갑니다. 하나라도 비면 오늘 채웁니다.

+
+
+
배포 전 최종 점검 · 빈칸 채우기
+
[ _ ] 완료 조건이 전부 O 다
+[ _ ] 리뷰 코멘트에 다 답했고, 멘토가 승인했다
+[ _ ] 화면에서 직접 눌러 확인했다
+[ _ ] 다른 기능을 망가뜨리지 않았다(주변 화면도 눌러 봄)
+[ _ ] (디자인 티켓) 시안과 실제 화면이 같다
+
+한 줄 소감: ____________
+
+
+
+
이해 확인
+
+

퇴근 전:

+
    +
  • "체크리스트 전부 체크됐어요?"
  • +
  • "혹시 주변 화면도 눌러 봤어요? 내 것만 보다 옆을 깨뜨리는 일이 많아요"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 내 티켓 화면만 확인하고, 그 변경이 옆 기능을 깬 걸 못 봄.
+
대처 · "고친 화면의 이웃 화면 하나만 더 눌러 봐요"라고 습관을 심어 주세요.
+
+
+
+
+ + +
+
FRI
+
+

배포 확인 · 회고 · 1:1

+
오늘의 목표 · 티켓을 실제 배포로 마무리하고, 한 주를 돌아보며 1:1로 개인 피드백을 받는다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

배포 & 확인 — 내 첫 티켓이 살아 움직인다

+
소요 90분
+
+
+
+
🎯학습목표
+
    +
  • 승인된 PR을 머지하고, 배포된 화면에서 내 변경이 실제로 반영됐는지 확인한다.
  • +
  • 배포 후 확인(사후 점검)까지가 한 사이클임을 체득한다.
  • +
+
+
+
🕘진행표
+
+
0:00
머지 — 멘토 입회하에 PR 머지(배포 트리거)
+
0:15
배포 대기 — 빌드/배포가 도는 동안 무엇이 일어나는지 설명
+
0:35
사후 확인 — 배포된 환경에서 완료 조건을 다시 O/X로 재검증
+
1:05
티켓 종료 — 이슈 트래커에서 티켓을 Done으로 옮기고 코멘트
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자, 머지 버튼 눌러 볼까요? 이 순간이에요. 여러분이 짠 코드가 진짜 우리 제품 안으로 들어가는 순간. 축하해요, 이거 아무나 하는 거 아니에요."

+

"근데 배포했다고 끝이 아니에요. 배포된 화면 가서 진짜 됐는지 다시 눌러 봐야 해요. '올렸으니 됐겠지'가 사고의 시작이거든요."

+

(확인되면) 봤죠? 이게 여러분 거예요. 이번 주에 여러분은 관람객에서 선수가 됐어요.

+
+
+
+
✍️실습 — 배포 후 사후 점검
+
+

실습 결과물: 배포된 환경에서 완료 조건 재검증 완료 + 티켓 Done 처리. 아래 종료 코멘트를 티켓에 남깁니다.

+
+
+
티켓 종료 코멘트 · 빈칸 채우기
+
배포 완료했습니다. ✅
+
+바뀐 것: ____________
+배포 환경에서 확인함: ____화면에서 ____ 정상
+배운 점 한 줄: ____________
+
+
+
+
이해 확인
+
+

티켓 닫기 전:

+
    +
  • "배포된 환경에서 확인했어요? (내 PC 말고)"
  • +
  • "티켓을 Done으로 옮기고 종료 코멘트를 남겼어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 머지만 하고 배포 환경 확인 없이 티켓을 닫음.
+
대처 · "머지 ≠ 배포 확인. 배포된 화면에서 한 번 더 눌러야 진짜 끝이에요"라고 마지막 절차를 지키게 하세요.
+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오후
+

주간 회고 — 사이클 한 바퀴를 돌아보기

+
소요 70분
+
+
+
+
🎯학습목표
+
    +
  • "착수→구현→리뷰→배포" 한 사이클에서 배운 것과 어려웠던 것을 스스로 정리한다.
  • +
  • 다음 티켓에서 다르게 해 볼 것 1가지를 뽑는다.
  • +
+
+
+
🕘진행표
+
+
0:00
개인 회고 작성 — 아래 회고 카드 각자 채우기
+
0:20
돌아가며 공유 — 4명이 배운 점·어려웠던 점 나누기
+
0:45
공통 주제 정리 — 멘토가 공통으로 나온 어려움을 칠판에 묶기
+
1:00
다음 다짐 — 각자 "다음엔 이걸 다르게" 1줄 발표
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"회고는 반성문이 아니에요. '뭘 배웠나, 다음엔 뭘 다르게 할까'를 챙기는 시간이에요. 잘한 것도 꼭 적어요. 자기가 뭘 잘했는지 알아야 그걸 또 하거든요."

+

(공유가 서로 비슷할 때) 봐요, 다들 비슷한 데서 막혔죠? 그건 여러분이 못해서가 아니라 원래 거기가 어려운 지점이라는 거예요.

+
+
+
+
✍️워크시트 — 주간 회고 카드
+
+

각자 채워서 제출합니다. 멘토는 이 카드를 개인 성장 기록으로 보관합니다.

+
+
+
주간 회고 · 빈칸 채우기
+
이번 주 내가 배포한 티켓: #__
+
+가장 뿌듯했던 것: ____________
+가장 어려웠던 것: ____________
+리뷰에서 얻은 것: ____________
+
+다음 티켓에서 다르게 해 볼 것 1가지:
+____________
+
+
+
+
이해 확인
+
+

회고 마무리에:

+
    +
  • 모두 배운 것 + 다르게 할 것을 각각 1개 이상 적었는가
  • +
  • 회고가 자책이 아니라 다음 행동으로 끝났는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 회고가 "그냥 힘들었어요"로 끝나 배움이 안 남음.
+
대처 · "구체적으로 어느 단계에서, 왜 힘들었어요?"로 파고들어 다음 행동으로 연결하세요.
+
+
+
+
+ +
+
+
Day 5 · 세션 3 · 오후
+

1:1 개인 피드백 — 한 명씩 마주 앉기

+
소요 60분 (1인당 12~15분)
+
+
+
+
🎯학습목표
+
    +
  • 수습생 개개인이 자기주도성·코드 품질 성장·반복 지적 여부에 대한 개인 피드백을 받는다.
  • +
  • 멘토는 평가 체크포인트에 근거해 다음 주 개인 목표를 함께 정한다.
  • +
+
+
+
🕘진행표
+
+
0:00
순번 안내 — 나머지는 다음 주 티켓 후보를 훑어보게 하고 1명씩 호출
+
0:02
먼저 듣기 — "이번 주 스스로 어땠어요?" 학생 말을 먼저
+
0:06
구체 피드백 — 잘한 점 → 성장 지점 → 반복하지 말 것 순서로
+
0:11
다음 목표 합의 — 개인 목표 1개를 함께 적기
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘은 OO 얘기만 들을게요. 이번 주 스스로 어땠어요? 먼저 들어 보고 싶어요."

+

"제가 본 건요, OO는 막혔을 때 15분 규칙 잘 지켰어요. 그게 자기주도성이에요. 대신 같은 리뷰 지적이 두 번 나온 게 하나 있었어요. 다음 주엔 리뷰 받은 걸 메모해 두고 다음 티켓에서 먼저 챙겨 보면 좋겠어요."

+

(마무리) 이번 주 정말 잘했어요. 진짜 티켓 하나를 세상에 내보냈잖아요. 다음 주엔 이 목표 하나만 같이 챙겨 봐요.

+
+
+
+
✍️실습 — 평가 체크포인트 기록
+
+

멘토가 학생별로 아래를 채워 평가 루브릭에 반영합니다. 학생과 화면을 같이 보며 합의한 목표를 남깁니다.

+
+
+
개인 피드백 기록 · 멘토 작성
+
이름: ____   배포 티켓: #__
+
+자기주도성 (막힘 대처·질문 타이밍): 상 / 중 / 하근거 한 줄
+코드 품질 성장 (첫날 대비): 상 / 중 / 하근거 한 줄
+같은 지적 반복 여부: 없음 / 1회 / 반복항목
+
+다음 주 개인 목표(합의): ____________
+
+
+
+
이해 확인
+
+

1:1 종료 시:

+
    +
  • 학생이 다음 주 개인 목표 1개를 자기 말로 확인했는가
  • +
  • 피드백이 잘한 점으로 시작해 다음 행동으로 끝났는가
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 · 개선점만 잔뜩 말해 학생이 위축된 채로 주말을 맞음.
+
대처 · 잘한 점 → 성장 지점 → 다음 목표 순서를 지키고, 마지막 말은 반드시 격려로 닫으세요.
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 5주차 상세 강의안 (멘토용) · 8부작 중 5 + 2026 · 수습 가이드·CS 커리큘럼·평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week6-detailed.html b/frontend/public/docs/week6-detailed.html new file mode 100644 index 0000000..ed93078 --- /dev/null +++ b/frontend/public/docs/week6-detailed.html @@ -0,0 +1,877 @@ +6주차 상세 강의안 — 실전 티켓 ② · AWESOMEDEV 수습 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 6 / 8
+

6주차 상세 강의안
실전 티켓 ② — 더 독립적으로

+

5주차에 실전 티켓을 처음 잡아봤다면, 이번 주는 멘토의 손을 조금 놓습니다. 난이도 ★★ 티켓을 스스로 설계하고, 막혀도 먼저 스스로 해결책을 찾아본 뒤 질문하는 습관을 만듭니다. 금요일에는 다음 2주 종합 프로젝트의 주제와 팀을 정하며 한 단계 도약을 준비합니다.

+
+ 난이도 ★★ + 수습생 4명 · 개발 3 · 디자인 1 + 주 5일 · 미림마이스터고 3학년 +
+
+ + + +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 각 세션 카드를 그대로 따라 읽으면 됩니다.

+
+
+ + +
+
MON
+
+

티켓 배정 · 설계를 스스로 제안하기

+
오늘 목표: 손이 조금 더 가는 ★★ 티켓을 받고, 구현 전에 스스로 설계안을 말로/글로 제안한다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

아침 기본기 — 코드리뷰 딥다이브 (포인터 세션)

+
40분 · 매일 아침 루틴
+
+
+
+
🎯학습목표
+
    +
  • 리뷰에서 자주 나오는 지적 패턴(네이밍·중복·조건문·에러 처리)을 이름으로 알아본다.
  • +
  • 남의 코드를 읽고 "여긴 이렇게 바꾸면 더 좋겠다"를 한 문장으로 말할 수 있다.
  • +
+
+
+
✍️실습
+
+

CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행합니다. 이번 주 아침은 "실무 심화 · 코드리뷰 딥다이브" 트랙으로, 자주 나오는 리뷰 코멘트를 유형별로 정리합니다.

+
    +
  • 커리큘럼 문서 6주차 월요일 분량을 함께 읽고, 예시 코드 1개에 리뷰 코멘트를 각자 2개씩 달기
  • +
  • 디자이너 수습생은 디자인 QA 체크 관점(간격·정렬·상태 표현)으로 같은 화면을 리뷰
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 아침 세션을 "그냥 읽기"로만 흘려보낸다.
+
대처 반드시 각자 코멘트 2개를 소리 내어 발표시켜 참여를 끌어냅니다. 짧아도 좋다고 안심시켜 주세요.
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오전
+

★★ 티켓 배정 — "내 티켓" 이해하기

+
70분
+
+
+
+
🎯학습목표
+
    +
  • 자기 티켓의 목적(누가·왜 필요한지)을 자기 말로 다시 설명할 수 있다.
  • +
  • 티켓을 작은 할 일 3~5개로 스스로 쪼갤 수 있다.
  • +
+
+
+
🕘진행표
+
+
0:00
티켓 배정 — 개발 3명에게 각 1개, 예: React 목록 화면에 필터·정렬 추가, Spring Boot 조회 API에 페이지네이션 붙이기, 폼 유효성 검사 보강
+
0:10
각자 읽기 — 티켓 설명을 조용히 정독하고 모르는 단어에 형광펜
+
0:25
"내 말로 설명" 발표 — 한 명씩 티켓을 자기 문장으로 다시 말하기
+
0:45
할 일 쪼개기 — 워크시트에 작은 단계 3~5개 적기
+
1:05
멘토 점검 — 쪼갠 단계가 말이 되는지 한 명씩 빠르게 확인
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"이번 티켓은 지난주보다 손이 조금 더 가요. 겁먹을 필요는 전혀 없어요. 큰 덩어리 하나를 작은 조각으로 나누면, 결국 우리가 매일 하던 것들이 이어진 거예요."

+

"먼저 티켓을 내 말로 설명해 볼게요. '이 화면을 쓰는 사람이 누구고, 왜 이 기능이 필요할까?' 이거부터 시작해요."

+

(학생이 막히면) "정답을 말하려 하지 말고, 지금 이해한 만큼만 말해줘요. 나머지는 같이 채워요."

+
+
+
+
✍️워크시트
+
+
티켓 쪼개기 워크시트 — 빈칸을 채우세요
+
[티켓 제목] ___________________________
+
+[이 기능을 쓰는 사람] _______________________
+[왜 필요한가 한 줄] _________________________
+
+[작은 할 일로 쪼개기]
+  1. _______________________________________
+  2. _______________________________________
+  3. _______________________________________
+  4. (있다면) ___________________________________
+
+[제일 먼저 손댈 것 하나] ___________________
+[다 됐다고 말하려면 무엇이 보여야 하나] _______
+
+
+
+
이해 확인
+
+

다음을 각자 대답할 수 있으면 통과입니다.

+
    +
  • 내 티켓을 한 문장으로 설명해 보세요.
  • +
  • 가장 먼저 손댈 작은 할 일은 무엇인가요?
  • +
  • "다 됐다"고 말하려면 화면/응답에서 무엇이 보여야 하나요?
  • +
+
+
+
+
+ +
+
+
Day 1 · 세션 3 · 오후
+

설계 제안 — "이렇게 만들게요"를 먼저 말하기

+
90분
+
+
+
+
🎯학습목표
+
    +
  • 코드를 짜기 전에 "어떤 파일을 어떻게 바꿀지"를 짧게 스스로 제안한다.
  • +
  • 멘토가 답을 주기 전에 자기 안(案)을 먼저 내놓는 습관을 만든다.
  • +
+
+
+
🕘진행표
+
+
0:00
설계 제안이란 — "손대기 전 30초 계획"의 뜻을 예시로 설명
+
0:15
각자 설계 초안 작성 — 아래 대본 빈칸 채우기 (25분)
+
0:40
1:1 설계 리뷰 — 멘토가 "왜 그렇게?"만 물으며 스스로 다듬게 함
+
1:10
착수 — 승인된 설계대로 첫 커밋까지 시작
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘부터는 제가 먼저 '이렇게 하세요'라고 말하지 않을 거예요. 대신 여러분이 먼저 '저는 이렇게 만들려고 해요'를 저에게 말해줘요."

+

"틀려도 괜찮아요. 계획이 틀리면 같이 고치면 되고, 그 과정이 진짜 실력이 되는 거예요. 완벽한 설계가 아니라 내 생각을 말로 꺼내는 것이 오늘의 목표예요."

+
+
+
+
✍️설계 제안 대본
+
+
설계 제안 — 멘토에게 말하기 전에 채워보기
+
"제 티켓은 ___________ 입니다.
+
+저는 이렇게 만들려고 해요:
+  - 건드릴 파일: _______________________
+  - 새로 만들 것: _____________________
+  - 데이터는 ___________ 에서 가져와요.
+
+이 방법을 고른 이유는 _____________ 예요.
+
+한 가지 걱정되는 부분은 _____________ 예요."
+
+
+
+
이해 확인
+
+

설계 제안이 통과되면 착수합니다.

+
    +
  • 어떤 파일을 바꿀지 스스로 지목했나요?
  • +
  • "왜 그렇게?"에 한 문장이라도 이유를 댔나요?
  • +
  • 걱정되는 부분(리스크)을 하나 이상 말했나요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 멘토가 답답함을 못 참고 설계를 대신 다 말해준다.
+
대처 이번 주 핵심은 독립성입니다. 답 대신 질문("그럼 그 다음은?")으로 스스로 도달하게 두세요. 침묵 10초는 괜찮습니다.
+
+
+
+
+ + +
+
TUE
+
+

독립 구현 ① · 막힘 해결력

+
오늘 목표: 멘토가 먼저 나서지 않는다. 막혔을 때 스스로 시도한 뒤 질문하는 순서를 익힌다.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

아침 기본기 — 코드리뷰 딥다이브 (포인터 세션)

+
40분 · 매일 아침 루틴
+
+
+
+
🎯학습목표
+
    +
  • 어제 배운 리뷰 패턴을 실제 내 코드에 하나 적용해 본다.
  • +
+
+
+
✍️실습
+
+

CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행합니다. 오늘은 "중복 줄이기 · 함수 나누기" 편으로, 각자 어제 짠 코드에서 중복 한 군데를 찾아 함수로 빼봅니다.

+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오전~오후
+

독립 구현 — 스스로 막힘 뚫기

+
3시간 (점심 제외) · 집중 구현
+
+
+
+
🎯학습목표
+
    +
  • 막혔을 때 바로 손 들지 않고 "스스로 3가지 시도"를 먼저 해본다.
  • +
  • 질문할 때 "무엇을 시도했고 무엇이 안 됐는지"를 함께 말한다.
  • +
+
+
+
🕘진행표
+
+
0:00
오늘의 규칙 공지 — "막히면 먼저 3가지, 그다음 질문" 규칙 안내 (5분)
+
0:05
집중 구현 — 각자 자기 티켓 진행, 멘토는 자리 순회만
+
1:30
중간 체크인 — "지금 어디까지 됐는지" 한 줄씩 공유 (10분)
+
1:40
집중 구현 계속
+
2:50
막힘 로그 정리 — 오늘 막혔던 지점과 해결법 1개 기록
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘은 제가 옆에서 먼저 '이거 이렇게 해요' 하지 않을게요. 여러분이 막히는 건 아주 자연스러운 일이에요. 개발자는 하루 종일 막히고 뚫는 사람이에요."

+

"막히면 이 순서를 지켜봐요. ① 에러 메시지를 끝까지 읽기 ② 검색이나 문서에서 비슷한 예 찾기 ③ 작은 코드로 따로 테스트해 보기. 이 세 가지를 하고도 안 되면, 그때 저를 불러줘요."

+

(질문받을 때) "좋아요. 지금까지 뭘 시도해봤어요? 그걸 먼저 들려줘요."

+
+
+
+
✍️워크시트
+
+
막힘 로그 — 질문 전에 스스로 채우기
+
[막힌 지점] _________________________________
+
+[시도 1] ___________________ → 결과: _______
+[시도 2] ___________________ → 결과: _______
+[시도 3] ___________________ → 결과: _______
+
+[에러 메시지에서 핵심 한 줄] _______________
+
+[멘토에게 물어볼 딱 한 가지 질문]
+  _______________________________________
+
+
+
+
이해 확인
+
+

하루 끝에 각자 답해 봅니다.

+
    +
  • 오늘 스스로 뚫은 막힘이 하나라도 있나요?
  • +
  • 질문할 때 "시도한 것"을 함께 말했나요?
  • +
  • 내일 이어서 할 첫 할 일은 무엇인가요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 "그냥 안 돼요"라고만 말하며 손을 든다.
+
대처 막힘 로그를 먼저 채우게 한 뒤 질문받으세요. 다만 30분 넘게 같은 곳에서 헤매면 개입해 좌절을 막습니다 — 독립성과 방치는 다릅니다.
+
+
+
+
+ +
+
+
Day 2 · 세션 3 · 오후
+

디자이너 트랙 — 프로젝트 화면 방향 구상 시작

+
80분 · 디자인 수습생 중심
+
+
+
+
🎯학습목표
+
    +
  • 다음 2주 프로젝트에서 자신이 맡을 기획·화면 방향을 스스로 그려보기 시작한다.
  • +
  • 개발 수습생이 만드는 화면과 어떻게 맞물릴지 한 장으로 정리한다.
  • +
+
+
+
✍️워크시트
+
+
화면 방향 스케치 — 디자이너 워크시트
+
[이 프로젝트는 누구를 위한 것] _______________
+[핵심 화면 3개]
+  1. _______________  (무엇을 보여주나)
+  2. _______________
+  3. _______________
+[가장 중요한 버튼/행동 하나] _____________
+[개발 파트와 맞춰야 할 것] _______________
+
+
+

진행 방식 종이 스케치나 간단한 와이어프레임으로 충분합니다. 완성도보다 "방향"이 목표예요.

+
    +
  • 거친 손그림 → 멘토와 5분 대화 → 한 번 다시 그리기
  • +
  • 우리 고객사(KT엠모바일 · KT알파 · 핀업) 서비스 화면을 참고 예시로 살펴보기
  • +
+
+
+
+
이해 확인
+
+

세션 끝에 확인합니다.

+
    +
  • 핵심 화면 3개를 말로 설명할 수 있나요?
  • +
  • 개발 파트와 맞춰야 할 지점을 하나 짚었나요?
  • +
+
+
+
+
+ + +
+
WED
+
+

독립 구현 ② · 코드리뷰로 다듬기

+
오늘 목표: 절반쯤 온 코드를 스스로 리뷰하고, 멘토 리뷰를 받아 고쳐 커밋한다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

아침 기본기 — 코드리뷰 딥다이브 (포인터 세션)

+
40분 · 매일 아침 루틴
+
+
+
+
🎯학습목표
+
    +
  • "에러 처리와 예외 상황" 패턴을 알고 내 코드에서 빈틈을 한 곳 찾는다.
  • +
+
+
+
✍️실습
+
+

CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행합니다. 오늘은 "빈 값·에러 상황 다루기" 편입니다. 각자 자기 코드에서 "값이 없으면?" "실패하면?" 두 질문을 던져 봅니다.

+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오전~오후
+

독립 구현 계속 + 셀프 코드리뷰

+
3시간 · 구현 + 자기점검
+
+
+
+
🎯학습목표
+
    +
  • 커밋 전에 스스로 자기 코드를 리뷰하는 셀프체크 습관을 만든다.
  • +
  • 멘토 리뷰 코멘트를 받아 스스로 고쳐서 다시 올린다.
  • +
+
+
+
🕘진행표
+
+
0:00
집중 구현 — 어제 이어서, 절반 지점 통과가 목표
+
1:20
셀프 코드리뷰 — 커밋 전 체크리스트로 자기 코드 점검 (20분)
+
1:40
멘토 리뷰 — 1인당 10분, 코멘트는 2~3개로 짧게
+
2:20
고쳐서 재커밋 — 받은 코멘트를 스스로 반영
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"코드를 다 짰다고 바로 올리지 말고, 올리기 전에 스스로 한 번 읽어봐요. '내가 이 코드를 처음 보는 사람이라면 이해될까?' 이 질문 하나면 충분해요."

+

"제 리뷰 코멘트는 여러분을 혼내는 게 아니에요. 더 좋게 만드는 힌트예요. 반영은 여러분이 직접 해봐요 — 제가 대신 고치지 않을게요."

+
+
+
+
✍️워크시트
+
+
커밋 전 셀프 코드리뷰 체크리스트
+
[ ] 변수·함수 이름만 봐도 뜻이 통하나?
+[ ] 같은 코드가 두 번 반복되지 않나?
+[ ] 값이 없거나 실패할 때도 안 터지나?
+[ ] 안 쓰는 코드·주석·console.log 를 지웠나?
+[ ] 커밋 메시지가 무엇을 바꿨는지 설명하나?
+
+[스스로 발견해 고친 것 한 가지]
+  _______________________________________
+
+
+
+
이해 확인
+
+

재커밋 전에 확인합니다.

+
    +
  • 셀프리뷰에서 스스로 고친 게 하나라도 있나요?
  • +
  • 멘토 코멘트를 내 손으로 반영했나요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 리뷰 코멘트를 "지적당했다"고 받아들여 위축된다.
+
대처 잘한 점을 먼저 한 가지 꼭 짚어준 뒤 개선점을 말하세요. 코멘트는 코드에 대한 것이지 사람에 대한 게 아니라고 분명히 해줍니다.
+
+
+
+
+ + +
+
THU
+
+

독립 구현 ③ · 마무리와 품질

+
오늘 목표: 티켓을 "완료" 상태까지 끌고 간다. 테스트·확인으로 스스로 품질을 보증한다.
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

아침 기본기 — 코드리뷰 딥다이브 (포인터 세션)

+
40분 · 매일 아침 루틴
+
+
+
+
🎯학습목표
+
    +
  • "직접 확인(테스트)" 패턴을 알고, 완료 전 손으로 확인하는 절차를 세운다.
  • +
+
+
+
✍️실습
+
+

CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행합니다. 오늘은 "내가 만든 걸 내가 확인하기" 편으로, 화면/응답을 직접 눌러보며 확인하는 절차를 만듭니다.

+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오전~오후
+

티켓 마무리 — "완료"의 기준 맞추기

+
3시간 · 마감 집중
+
+
+
+
🎯학습목표
+
    +
  • 월요일에 정한 "완료 기준"에 맞춰 스스로 확인하고 마감한다.
  • +
  • 정상 경우뿐 아니라 빈 값·틀린 입력도 직접 눌러 확인한다.
  • +
+
+
+
🕘진행표
+
+
0:00
마감 스프린트 — 남은 할 일을 마무리, 필요한 질문만 짧게
+
1:30
직접 확인(수동 테스트) — 정상/빈값/오류 3경우를 손으로 눌러보기
+
2:00
최종 셀프리뷰 + 커밋 — 완료 기준 대조표 체크
+
2:30
완료 발표 — "무엇을 만들었고 어떻게 확인했는지" 3분 데모
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"오늘 '완료'는 코드를 짰다는 뜻이 아니라, 직접 눌러 확인했다는 뜻이에요. 잘 되는 경우만 보지 말고, 빈칸으로 두거나 이상한 값을 넣으면 어떻게 되는지도 눌러봐요."

+

"마지막 데모는 자랑하는 시간이에요. 한 주 동안 스스로 여기까지 온 거예요. 짧아도 좋으니 당당하게 보여줘요."

+
+
+
+
✍️워크시트
+
+
완료 기준 대조표 — 마감 전 채우기
+
[월요일에 정한 완료 기준] ___________________
+
+직접 확인한 경우
+  [ ] 정상 입력   → 결과: _______________
+  [ ] 빈 값 / 없음 → 결과: _______________
+  [ ] 틀린 입력   → 결과: _______________
+
+[남은 아쉬운 점(다음에 고칠 것)] ___________
+[3분 데모에서 보여줄 핵심 화면] ___________
+
+
+
+
이해 확인
+
+

완료 처리 전에 확인합니다.

+
    +
  • 세 가지 경우(정상·빈값·오류)를 직접 눌러봤나요?
  • +
  • 완료 기준과 실제 결과가 맞나요?
  • +
  • 데모에서 무엇을 보여줄지 정했나요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 "잘 되는 경우"만 한 번 눌러보고 완료라고 선언한다.
+
대처 빈 값·틀린 입력을 반드시 함께 확인시키세요. 실무에서 버그는 대부분 "예상 못한 입력"에서 나온다고 짚어줍니다.
+
+
+
+
+ + +
+
FRI
+
+

종합 프로젝트 예고 · 팀 구성 · 회고

+
오늘 목표: 다음 2주 프로젝트의 주제 후보를 보고, 팀·역할·PM을 정하고, 한 주를 돌아본다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

종합 프로젝트 예고 — 주제 후보 함께 보기

+
80분
+
+
+
+
🎯학습목표
+
    +
  • 다음 2주 종합 프로젝트가 어떤 것인지 큰 그림을 이해한다.
  • +
  • 주제 후보들을 비교하고 "왜 이게 끌리는지"를 말할 수 있다.
  • +
+
+
+
🕘진행표
+
+
0:00
프로젝트 개요 — 2주간 팀으로 하나를 만든다는 흐름 설명
+
0:15
주제 후보 공유 — 예: 사내 미니 도구, 고객 서비스 참고형 화면, 학습 기록 앱 등 3~4개
+
0:40
각자 선호 투표 + 이유 — 끌리는 주제와 이유 한 줄
+
1:00
토론 — 우리 4명이 2주에 만들 수 있는 크기인지 함께 가늠
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"다음 2주는 지금까지 배운 걸 다 모아서, 팀으로 하나의 결과물을 만드는 시간이에요. 혼자가 아니라 넷이 함께요. 우리 회사가 '고객과 함께 더 나은 세상을 만들어 나가는' 것처럼, 여러분도 함께 만들어 볼 거예요."

+

"주제는 제가 정해주는 게 아니라 같이 골라요. 거창하지 않아도 좋아요. '2주에 우리가 진짜 끝낼 수 있는 크기'가 제일 중요해요."

+
+
+
+
✍️워크시트
+
+
프로젝트 주제 고르기 — 각자 채우기
+
[가장 끌리는 후보] ___________________
+[끌리는 이유 한 줄] _________________
+[내가 잘 맡을 수 있을 부분] ___________
+[걱정되는 점 하나] ___________________
+
+
+
+
이해 확인
+
+

세션 끝에 확인합니다.

+
    +
  • 프로젝트가 2주짜리 팀 작업이라는 걸 이해했나요?
  • +
  • 선호 주제와 그 이유를 말했나요?
  • +
+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오후
+

팀 구성 · 역할과 PM 롤 정하기

+
70분
+
+
+
+
🎯학습목표
+
    +
  • 팀 안에서 각자의 역할(개발 파트·디자인 파트)을 정한다.
  • +
  • PM(진행 담당) 역할이 무엇인지 알고 한 명을 정한다.
  • +
+
+
+
🕘진행표
+
+
0:00
역할 소개 — 개발 3 · 디자인 1 구성에서 누가 무엇을 맡을지 예시
+
0:15
PM 롤이란 — 일정·할 일 정리·소통 담당(대장이 아님)임을 설명
+
0:30
역할·PM 정하기 — 서로 상의해 자율 결정, 멘토는 중재만
+
0:55
킥오프 준비물 정리 — 다음 주 첫날 무엇부터 시작할지 메모
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"PM은 '대장'이 아니에요. 팀이 길을 잃지 않게 할 일을 정리하고, 서로 소통을 돕는 사람이에요. 돌아가면서 경험해 보면 좋은 역할이에요."

+

"디자인을 맡은 친구는 화면 방향을, 개발 친구들은 각자 맡을 화면·기능을 나눠요. 정답은 없어요. 서로 하고 싶은 것과 잘하는 것을 얘기하면서 정해봐요."

+
+
+
+
✍️워크시트
+
+
팀 구성표 — 함께 채우기
+
[프로젝트 이름(가칭)] _______________
+[PM(진행 담당)] _______________
+
+역할 나누기
+  - 디자인 파트: _____ → 맡을 것 _______
+  - 개발 A: _____ → 맡을 것 _______
+  - 개발 B: _____ → 맡을 것 _______
+  - 개발 C: _____ → 맡을 것 _______
+
+[다음 주 첫날 가장 먼저 할 일] _________
+
+
+
+
이해 확인
+
+

세션 끝에 확인합니다.

+
    +
  • 각자 맡을 역할이 정해졌나요?
  • +
  • PM이 무슨 일을 하는지 말할 수 있나요?
  • +
  • 다음 주 첫날 시작점을 정했나요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 목소리 큰 한 명이 역할을 다 정해버리고 나머지는 따라간다.
+
대처 4명 모두 "하고 싶은 것"을 한 번씩 말하게 한 뒤 정하도록 멘토가 진행 순서를 잡아줍니다.
+
+
+
+
+ +
+
+
Day 5 · 세션 3 · 오후
+

주간 회고 · 1:1 면담

+
60분
+
+
+
+
🎯학습목표
+
    +
  • 한 주 동안 "더 독립적으로" 해본 경험을 스스로 돌아본다.
  • +
  • 1:1에서 잘한 점·다음 목표를 멘토와 확인한다.
  • +
+
+
+
🕘진행표
+
+
0:00
그룹 회고 — 이번 주 KPT(좋았던 것·아쉬운 것·다음에 시도) 한 바퀴
+
0:25
1:1 면담 — 1인당 7~8분, 나머지는 워크시트 정리
+
0:55
다음 주 예고 — 프로젝트 킥오프 안내로 마무리
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"이번 주 여러분, 제가 옆에서 덜 도와줬는데도 티켓을 끝까지 해냈어요. 그게 얼마나 큰 성장인지 알아줬으면 해요. 막히고 스스로 뚫은 순간들이 진짜 실력이에요."

+

(1:1에서) "이번 주 스스로 제일 잘했다고 느낀 순간은 언제였어요? 다음 주 프로젝트에서 꼭 해보고 싶은 건요?"

+
+
+
+
✍️워크시트
+
+
6주차 회고 — KPT
+
[Keep] 이번 주 좋았던 것 _______________
+[Problem] 아쉬웠던 것 _________________
+[Try] 다음 주 시도할 것 _______________
+
+[스스로 뚫은 막힘 중 가장 뿌듯한 것]
+  _______________________________________
+[프로젝트에서 맡고 싶은 역할] ___________
+
+
+
+
이해 확인 (멘토용 평가 체크포인트)
+
+

이번 주 관찰 항목을 수습생별로 기록합니다.

+
    +
  • 독립성 — 멘토 개입 없이 스스로 판단·진행한 정도
  • +
  • 문제해결력 — 막혔을 때 스스로 시도한 뒤 질문했는가
  • +
  • 협업 태도 — 팀 구성·역할 논의에서 서로를 존중했는가
  • +
  • 산출물: 완료 티켓 · 프로젝트 킥오프 준비(팀·역할·주제)
  • +
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 6주차 상세 강의안 (멘토용) · 8부작 중 6 + 2026 · 수습 가이드 · CS 커리큘럼 · 평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week7-detailed.html b/frontend/public/docs/week7-detailed.html new file mode 100644 index 0000000..f23a69a --- /dev/null +++ b/frontend/public/docs/week7-detailed.html @@ -0,0 +1,961 @@ +7주차 상세 강의안 — 종합 프로젝트 기획·설계 (멘토용) + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 7 / 8
+

7주차 상세 강의안
종합 프로젝트 — 기획 · 설계

+

이번 주는 수습생 4명이 처음으로 한 팀이 되어, 실제 제품에 붙일 수 있는 작지만 끝까지 가는 기능 하나를 함께 기획하고 설계합니다. 개인 강의를 잠시 접고, 주제 브레인스토밍·역할 분담·설계 리뷰로 이어지는 팀 워크숍 형태로 한 주를 굴립니다. 목요일부터는 실제로 손을 대기 시작하고, 금요일에 계획 대비 어디까지 왔는지 함께 점검합니다.

+
+ 미림마이스터고 3학년 · 수습생 4명(개발 3 · 디자인 1) + 핵심 = 팀 협업 · 소통 · 주도성 + 산출물: 기획서 · 화면 설계 · 태스크 보드 · 초기 구현 +
+ + +
+ +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 이번 주는 세션 대부분이 개인 실습이 아니라 팀 워크숍이라는 점만 기억하세요 — 멘토는 강사보다 퍼실리테이터(진행자)에 가깝게 움직입니다.

+
+
+ + +
+
MON
+
+

주제 확정 · 역할 분담

+
목표 — 4명이 만들 "작은 기능 하나"를 정하고, PM 포함 역할을 나눈다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

아침 스탠드업 — 프로젝트 모드로 전환

+
30분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 이번 주부터 아침이 "기본기 강의"가 아니라 "팀 프로젝트 시간"임을 이해한다.
  • +
  • 매일 아침 스탠드업으로 진행 상황을 서로에게 공유하는 습관을 만든다.
  • +
+
+
+
✍️실습 / 안내
+
+

스탠드업 규칙 — 이번 주부터 CS 기본기 4주 커리큘럼은 잠시 멈추고, 아침 시간을 팀 작업으로 씁니다. 매일 아침 각자 30초씩 세 가지만 말해요.

+
    +
  • 어제 한 것
  • +
  • 오늘 할 것
  • +
  • 막힌 것 / 도움이 필요한 것
  • +
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자, 오늘부터 한 주 동안은 여러분 넷이 한 팀이에요. 지금까지는 각자 배웠지만, 이번 주는 같이 하나를 만들어 봅니다. 회사에서 실제로 일하는 방식이 딱 이래요."

+

"아침마다 '스탠드업'이라는 걸 할 거예요. 서서 짧게 하는 회의라 이름이 그래요. 어제 뭐 했고, 오늘 뭐 할 거고, 막힌 게 있으면 말하는 거예요. 잘 못했다고 혼나는 자리 절대 아니고요, 서로 어디쯤 있는지 아는 자리예요."

+
+
+
+
⚠️흔한 실수
+
+
실수 — 스탠드업이 30초를 넘겨 5분짜리 잡담이 된다.
+
대처 — 멘토가 타이머를 잡고, 길어지면 "그건 끝나고 따로 얘기해요"로 끊어 준다.
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오전
+

주제 브레인스토밍 워크숍

+
90분
+
+
+
+
🎯학습목표
+
    +
  • "작지만 실제 제품에 붙일 수 있는 기능"이 어떤 크기인지 감을 잡는다.
  • +
  • 아이디어를 자유롭게 내고, 팀이 함께 후보를 3개로 좁힌다.
  • +
  • 좋은 아이디어와 나쁜 아이디어가 아니라, "1주에 끝나는가"로 판단한다.
  • +
+
+
+
🕘진행표
+
+
0–10분
범위 감 잡기 — 멘토가 "작은 기능" 예시 3~4개를 보여준다.
+
10–35분
개인 아이디어 — 각자 포스트잇/메모에 아이디어 3개씩 조용히 적는다.
+
35–60분
공유 — 한 명씩 벽에 붙이며 30초 설명. 질문·평가 없이 듣기만.
+
60–80분
묶고 좁히기 — 비슷한 것끼리 모으고, 스티커 투표로 상위 3개.
+
80–90분
정리 — 후보 3개를 화이트보드에 크게 적어 남긴다.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"우리가 만들 건 '앱 전체'가 아니에요. 이미 있는 제품에 쏙 붙는 작은 기능 하나예요. 예를 들면 '할 일에 마감일 추가하기', '게시글에 좋아요 버튼', '내가 쓴 글만 모아보기' 정도요. 이 정도면 한 주에 기획하고 설계하고 조금 만들어 볼 수 있어요."

+

"지금은 좋은 아이디어를 뽑는 시간이 아니에요. 많이 내는 시간이에요. 이상해 보여도 일단 적어요. 서로 아이디어에 '에이 그건 안 돼' 하는 말은 잠깐 참아 주세요."

+

디자이너 학생에게: "화면이 그려지는 아이디어가 있으면 그쪽으로 더 밀어봐도 좋아요. 이번 주 화면 설계를 ○○님이 이끌 거니까요."

+
+
+
+
✍️실습 / 워크시트
+
+

아이디어를 이 문장 틀에 맞춰 적으면 크기를 가늠하기 쉬워요. 각자 최소 2개는 이 틀로 채워 봅니다.

+
+
+
아이디어 한 줄 정의 워크시트
+
누가: (예: 앱을 쓰는 일반 사용자 / 관리자)
+무엇을 하고 싶다: (예: 할 일에 마감일을 정하고 싶다)
+그래서 어떤 화면·버튼이 생긴다: (예: 날짜 선택 버튼과 마감일 뱃지)
+한 주에 끝날까? (O/△/X):
+우리 스택으로 되나? (웹=React, 서버=Spring Boot): 
+
+
+
+
⚠️흔한 실수
+
+
실수 — "인스타그램 같은 거요" 처럼 서비스 전체를 만들자고 한다.
+
대처 — "그중에 딱 하나 화면만 고른다면?"이라고 되물어 범위를 잘라 준다.
+
실수 — 목소리 큰 한 명 아이디어로 순식간에 결론난다.
+
대처 — 개인 메모 → 투표 순서를 지켜, 조용한 학생 아이디어도 벽에 올린다.
+
+
+
+
+ +
+
+
Day 1 · 세션 3 · 오후
+

주제 확정 · 역할 분담 회의

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 후보 3개 중 하나를 팀 합의로 최종 결정한다.
  • +
  • PM · 디자인(기획·화면) · 프론트 · 백엔드 역할을 서로 납득하게 나눈다.
  • +
  • 역할이 "칸막이"가 아니라 "책임을 맡는 사람"이라는 걸 이해한다.
  • +
+
+
+
🕘진행표
+
+
0–30분
후보 비교 — 3개를 "재미·유용·난이도" 3칸으로 같이 평가.
+
30–45분
주제 확정 — 손들기로 하나 결정. 멘토는 표결만 돕고 강요하지 않는다.
+
45–90분
역할 분담 — 아래 역할 카드로 누가 무엇을 맡을지 배정.
+
90–110분
팀 헌장 작성 — 팀 이름·규칙·소통 방법을 한 장으로 정리.
+
110–120분
마무리 — PM이 내일 할 일을 한 문장으로 선언.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"이제 셋 중 하나를 골라요. 정답은 없어요. 대신 한번 고르면 이번 주는 그걸로 끝까지 가는 거예요. 마음이 흔들려도요."

+

"역할을 나눌 건데, PM이 제일 궁금하죠? PM은 대장이 아니에요. 팀이 길을 잃지 않게 챙기는 사람이에요. 오늘 뭐 하기로 했는지, 누가 막혔는지 기억하는 사람. 디자인 맡은 ○○님이 이번 주 화면과 기획을 이끌고, 개발 셋은 프론트·백엔드로 나눠서 붙어요. 근데 벽 쌓지 말고, 서로 넘나들어도 돼요."

+
+
+
+
✍️실습 / 워크시트
+
+

역할 카드를 채워 벽에 붙입니다. 이름을 적는 순간 "내 일"이 생겨요.

+
+
+
팀 헌장 · 역할 배정 워크시트
+
팀 이름: ____________________
+우리가 만들 기능(한 줄): ____________________
+
+PM          : ______  (역할: 일정·할 일 챙기기, 스탠드업 진행)
+기획·디자인 : ______  (역할: 기획서·화면 설계 주도, 스펙 전달)
+프론트엔드  : ______  (역할: React 화면 구현)
+백엔드     : ______  (역할: Spring Boot API 구현)
+
+우리 팀 규칙 3가지:
+ 1. ____________________
+ 2. ____________________
+ 3. ____________________
+소통 방법(예: 채팅방 / 보드): ____________________
+
+
+
+
이해 확인
+
+

퇴근 전 각자 한 문장으로 답하게 하세요.

+
    +
  • 우리가 이번 주에 만들 기능을 한 줄로 말해 볼까요?
  • +
  • 내 역할은 무엇이고, 내일 아침 내가 첫 번째로 할 일은?
  • +
  • PM이 하는 일은 "명령"인가요, "챙김"인가요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 — 개발 3명이 다 백엔드를 하겠다고 몰린다.
+
대처 — "화면 없으면 사용자가 못 써요"로 프론트의 가치를 세워 균형을 맞춘다.
+
실수 — 디자이너가 "저는 코드 몰라서요" 하고 뒤로 빠진다.
+
대처 — 이번 주 화면·기획의 주인이 디자이너임을 분명히 하고, 개발자에게 스펙을 넘기는 역할을 강조한다.
+
+
+
+
+ + +
+
TUE
+
+

기획서 · 화면 설계

+
목표 — "무엇을 왜 만드는지"를 기획서로, "어떻게 보이는지"를 화면 설계로 남긴다.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

아침 스탠드업

+
15분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 어제 확정한 주제·역할을 다시 확인하고 오늘 목표를 공유한다.
  • +
+
+
+
✍️실습 / 안내
+
+

스탠드업 (PM 진행) — 각자 어제 한 것 / 오늘 할 것 / 막힌 것을 30초씩. 오늘은 기획서·화면 설계가 목표라는 걸 PM이 선언합니다. (아침 기본기 강의는 이번 주 없음 — 프로젝트 작업으로 대체)

+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오전
+

기획서 함께 쓰기

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 기획서가 "왜 · 누가 · 무엇을 · 성공 기준"을 담는 문서임을 안다.
  • +
  • 디자이너가 초안을 이끌고, 팀 전원이 한 문단씩 채운다.
  • +
  • "다 안 만들어도 되는 것(이번 주 범위 밖)"을 분명히 적는다.
  • +
+
+
+
🕘진행표
+
+
0–15분
좋은 기획서 예시 — 멘토가 한 장짜리 예시 하나를 같이 읽는다.
+
15–40분
배경·목표 — "왜 이걸 만드나"를 팀이 대화하며 채운다(디자이너 주도).
+
40–75분
사용자 시나리오 — 사용자가 이 기능을 쓰는 흐름을 문장으로.
+
75–100분
범위 · 비범위 — 이번 주에 할 것 / 안 할 것을 나눈다.
+
100–120분
성공 기준 — "이렇게 되면 성공"을 2~3개 적는다.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"기획서는 어렵게 쓰는 게 아니에요. 나중에 우리끼리 딴소리 안 하려고 미리 정해 두는 메모예요. '어? 이거 만들기로 했었나?' 이걸 막아줘요."

+

"제일 중요한 칸은 안 만들 것 칸이에요. 욕심내면 한 주에 절대 못 끝내요. '이번 주엔 여기까지만' 하고 선을 긋는 것도 실력이에요. 우리 회사 슬로건이 '고객과 함께 더 나은 세상을 만들어 나가는 기업'인데, 그 시작이 이렇게 작은 기능 하나를 제대로 끝내는 거예요."

+

디자이너에게: "○○님이 이 문서 주인이에요. 개발자들이 헷갈리면 ○○님한테 물어보게 될 거예요."

+
+
+
+
✍️실습 / 워크시트
+
+

기획서 한 장을 이 틀로 완성합니다. 문서 도구(노션·구글 문서 등) 하나에 함께 씁니다.

+
+
+
기획서 원페이저 템플릿
+
기능 이름: ____________________
+1. 배경 / 왜 만드나
+   ____________________
+2. 누가 쓰나 (대상 사용자)
+   ____________________
+3. 사용자 시나리오 (쓰는 흐름)
+   ① 사용자가 ____ 한다
+   ② 그러면 화면에 ____ 이 보인다
+   ③ 사용자가 ____ 하면 ____ 된다
+4. 이번 주에 만들 것 (범위)
+   - ____________________
+5. 이번 주에 안 만들 것 (비범위)
+   - ____________________
+6. 성공 기준
+   - ____________________
+
+
+
+
⚠️흔한 실수
+
+
실수 — 기획서를 화면 얘기(버튼 색·위치)로 가득 채운다.
+
대처 — "그건 다음 화면 설계 시간에요. 여기선 왜·무엇만"이라고 나눠 준다.
+
실수 — 성공 기준이 "잘 되면 성공"처럼 뭉뚱그려진다.
+
대처 — "무엇을 보면 알 수 있죠?"로 구체적 문장(예: 마감일이 화면에 뜬다)으로 바꾼다.
+
+
+
+
+ +
+
+
Day 2 · 세션 3 · 오후
+

화면 설계 (와이어프레임) — 디자이너 주도

+
150분
+
+
+
+
🎯학습목표
+
    +
  • 기획서의 시나리오를 실제 화면 그림(와이어프레임)으로 옮긴다.
  • +
  • 화면에 어떤 요소·버튼이 있고, 무엇을 누르면 어디로 가는지 정한다.
  • +
  • 디자이너가 화면을 이끌고, 개발자가 "이건 어떻게 동작해요?"를 질문한다.
  • +
+
+
+
🕘진행표
+
+
0–20분
화면 목록 뽑기 — 이 기능에 필요한 화면이 몇 개인지 센다(보통 1~3개).
+
20–90분
손그림 와이어프레임 — 종이/피그마로 화면마다 요소 배치.
+
90–120분
흐름 연결 — 화살표로 "이 버튼 → 이 화면" 연결.
+
120–150분
개발자 질문 타임 — 각 요소가 데이터로 뭘 필요로 하는지 메모.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"화면 설계는 처음부터 예쁘게 그릴 필요 없어요. 네모랑 글씨면 충분해요. '여기 목록이 있고, 위에 추가 버튼, 각 줄에 마감일 뱃지' 이렇게요."

+

"개발자 셋은 그림 보면서 계속 질문해요. '이 버튼 누르면 저장돼요? 어디에 저장돼요?' 이 질문이 내일 API 설계로 바로 이어져요. 디자이너랑 개발자가 지금 대화를 많이 할수록 나중에 덜 헤매요."

+
+
+
+
✍️실습 / 워크시트
+
+

화면 카드를 화면 수만큼 만듭니다. 개발자가 붙는 "데이터 메모" 칸이 핵심이에요.

+
+
+
화면 설계 카드 (화면 1개당 1장)
+
화면 이름: ____________________  (예: 할 일 목록 화면)
+이 화면에 보이는 것:
+   - ____________________  (예: 할 일 목록)
+   - ____________________  (예: [+ 추가] 버튼)
+   - ____________________  (예: 각 항목의 마감일 뱃지)
+누르면 일어나는 일:
+   - [____] 버튼 → ____________________
+개발자 데이터 메모 (이 화면이 서버에서 받아야 할 것):
+   - ____________________  (예: 할 일 제목, 마감일)
+
+
+
+
이해 확인
+
+

오후 마무리로 팀에게 물어보세요.

+
    +
  • 우리 기능은 화면이 몇 개예요? 각 화면에서 사용자가 뭘 하죠?
  • +
  • 가장 중요한 버튼 하나를 누르면 무슨 일이 나요?
  • +
  • 이 화면들을 그리면서 새로 생긴 질문이 있나요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 — 색·폰트·간격을 다듬느라 시간을 다 쓴다.
+
대처 — "지금은 '무엇이 어디 있나'만. 예쁘게는 나중에"라고 방향을 잡아 준다.
+
실수 — 개발자들이 그림만 구경하고 질문을 안 한다.
+
대처 — 멘토가 "이 버튼 누르면 데이터 어디서 와요?"를 먼저 시범 질문한다.
+
+
+
+
+ + +
+
WED
+
+

API 설계 · 태스크 보드

+
목표 — 화면과 서버가 주고받을 데이터를 API로 정하고, 할 일을 보드에 쪼갠다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

아침 스탠드업

+
15분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 화면 설계까지 온 상태를 확인하고, 오늘 API·보드 목표를 공유한다.
  • +
+
+
+
✍️실습 / 안내
+
+

스탠드업 (PM 진행) — 30초씩 3가지. 어제 화면 설계에서 남은 질문이 있으면 오늘 API 설계로 넘겨 해결한다고 정리합니다.

+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오전
+

API 설계 워크숍 — 화면과 서버 잇기

+
150분
+
+
+
+
🎯학습목표
+
    +
  • API가 "화면(React)과 서버(Spring Boot)가 데이터를 주고받는 약속"임을 이해한다.
  • +
  • 화면 카드의 "데이터 메모"를 실제 요청·응답 모양으로 옮긴다.
  • +
  • 어떤 주소(URL)로, 어떤 방식(GET/POST)으로 주고받을지 팀이 합의한다.
  • +
+
+
+
🕘진행표
+
+
0–20분
API 개념 — 멘토가 "식당 주문서" 비유로 요청/응답을 설명.
+
20–50분
필요한 동작 나열 — 화면에서 필요한 동작(목록 보기·추가·삭제)을 적는다.
+
50–110분
API 표 채우기 — 동작마다 주소·방식·주고받는 데이터를 정한다.
+
110–140분
응답 예시(JSON) — 서버가 돌려줄 데이터 모양을 손으로 적어 본다.
+
140–150분
합의 — 프론트·백엔드가 이 표대로 만들기로 악수.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"API가 어려운 말 같죠? 쉽게 생각해요. 식당에서 주문서나온 음식이에요. 화면이 '할 일 목록 주세요' 하고 주문서(요청)를 보내면, 서버가 목록(응답)을 내주는 거예요."

+

"이 표를 지금 같이 정해 두면, 프론트 맡은 사람이랑 백엔드 맡은 사람이 따로 앉아서도 같은 걸 만들 수 있어요. '나는 이 주소로 보낼게' '나는 이 주소로 받을 준비할게' 이렇게 약속이 되니까요."

+
+
+
+
✍️실습 / 워크시트
+
+

API 명세 표를 동작마다 한 줄씩 채웁니다. 우리 스택은 웹=React, 서버=Spring Boot(Java)라는 걸 기억하세요.

+
+
+
API 명세 워크시트
+
동작        방식     주소(URL)             보내는 것        받는 것
+--------    ----     ----------------      -----------     -----------------
+목록 보기   GET      /api/todos           (없음)          할 일 배열
+할 일 추가  POST     /api/todos           제목, 마감일     추가된 할 일
+할 일 삭제  DELETE   /api/todos/{id}      (없음)          성공 여부
+
+응답 예시(JSON) — 목록 보기:
+[
+  { "id": 1, "title": "청소하기", "dueDate": "2026-07-20" },
+  { "id": 2, "title": "숙제하기", "dueDate": "2026-07-18" }
+]
+
+
+
+
이해 확인
+
+

표를 다 채운 뒤 확인하세요.

+
    +
  • "목록 보기"는 왜 GET이고 "추가"는 왜 POST일까요?
  • +
  • 프론트 담당자: 이 주소로 무엇을 보내고 무엇을 받죠?
  • +
  • 백엔드 담당자: 이 응답 JSON을 만들려면 서버가 뭘 저장해야 하죠?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 — 프론트와 백엔드가 서로 다른 주소·데이터 이름을 상상한 채 넘어간다.
+
대처 — 표를 한 장에 같이 적어 "이 이름 그대로 쓴다"를 못 박는다.
+
실수 — API를 실제 제품 수준으로 완벽히 설계하려 든다.
+
대처 — "이번 기능에 필요한 동작만"으로 3~4줄이면 충분하다고 안심시킨다.
+
+
+
+
+ +
+
+
Day 3 · 세션 3 · 오후
+

태스크 보드 만들기 + 멘토 설계 리뷰

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 기획·화면·API를 "작은 할 일 카드"로 쪼개 보드에 올린다.
  • +
  • 각 카드에 담당자와 순서를 붙여, 목요일에 바로 손댈 수 있게 한다.
  • +
  • 멘토 리뷰를 통해 설계에 큰 구멍이 없는지 점검받는다.
  • +
+
+
+
🕘진행표
+
+
0–15분
보드 소개 — 할 일 / 하는 중 / 끝 3칸 칸반을 만든다.
+
15–55분
카드 쪼개기 — 기능을 하루 안에 끝날 크기의 카드로 나눈다.
+
55–75분
담당·순서 — 카드마다 이름 붙이고, 먼저 할 것을 위로.
+
75–115분
멘토 설계 리뷰 — 팀이 기획~보드를 5분 발표, 멘토가 질문·보완.
+
115–120분
정리 — PM이 "목요일 아침 첫 카드"를 지목.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"이제 큰 덩어리를 손에 잡히는 작은 카드로 쪼갤 거예요. '할 일 기능 만들기'는 너무 커요. '목록 화면 뼈대 만들기', '추가 버튼 붙이기', '서버에 저장 API 만들기'처럼 하루 안에 끝날 크기로 잘라요."

+

설계 리뷰에서 (격려 톤): "지금부터 제가 몇 가지 물어볼 텐데, 틀렸다고 지적하는 게 아니에요. 여러분이 놓친 게 있으면 지금 찾는 게 훨씬 이득이라서 그래요. 잘 만들었어요, 여기서 한 걸음만 더 봐요."

+
+
+
+
✍️실습 / 워크시트
+
+

칸반 보드를 벽이나 노션/트렐로 같은 도구에 만들고, 카드를 채웁니다.

+
+
+
태스크 보드 · 카드 템플릿
+
[ 할 일 ]        [ 하는 중 ]      [ 끝 ]
+----------       ----------       ----------
+
+카드 한 장 형식:
+  제목  : ____________________ (예: 목록 화면 뼈대 만들기)
+  담당  : ______
+  크기  : 반나절 / 하루
+  메모  : ____________________ (예: 화면 카드 1번 참고)
+
+
+
+
이해 확인 · 멘토 리뷰 체크
+
+

멘토가 리뷰 때 이 질문들로 설계 구멍을 확인합니다.

+
    +
  • 기획서의 "성공 기준"을 보드 카드들이 다 커버하나요?
  • +
  • 화면에 있는 버튼마다, 그걸 처리할 API 카드가 있나요?
  • +
  • 목요일 아침에 누가 어떤 카드부터 잡을지 정해졌나요?
  • +
  • 혹시 한 사람에게만 카드가 몰려 있지 않나요?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 — 카드가 "기능 완성"처럼 너무 커서 며칠이 걸린다.
+
대처 — "이걸 반나절짜리 두세 개로 자르면?"으로 함께 쪼갠다.
+
실수 — 리뷰에서 지적받자 학생이 위축된다.
+
대처 — "설계 단계에서 찾은 문제는 100% 이득"이라고 프레임을 바꿔 준다.
+
+
+
+
+ + +
+
THU
+
+

개발 착수

+
목표 — 보드의 첫 카드들을 실제 코드로 옮기기 시작한다(React · Spring Boot).
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

아침 스탠드업 — 개발 첫날

+
15분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 각자 오늘 잡을 첫 카드를 말로 선언하고 시작한다.
  • +
+
+
+
✍️실습 / 안내
+
+

스탠드업 (PM 진행) — 오늘부터는 "오늘 할 것"을 보드 카드 이름으로 말합니다. 예: "저는 '목록 화면 뼈대' 카드 잡을게요." 카드를 '하는 중'으로 옮기며 시작해요.

+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오전~오후
+

구현 착수 — 페어로 손대기

+
240분 (중간 휴식 포함)
+
+
+
+
🎯학습목표
+
    +
  • 설계 문서(기획·화면·API)를 보며 첫 코드를 작성한다.
  • +
  • 프론트는 React로 화면 뼈대, 백엔드는 Spring Boot로 API 뼈대를 만든다.
  • +
  • 막히면 혼자 오래 붙들지 않고 팀·멘토에게 빨리 묻는다.
  • +
+
+
+
🕘진행표
+
+
0–20분
환경 확인 — 각자 프로젝트가 실행되는지(React npm run dev, 서버 기동) 먼저 확인.
+
20–120분
첫 카드 구현 — 프론트=화면 뼈대, 백엔드=API 뼈대(빈 응답이라도).
+
120–130분
중간 싱크 — 5분 서서 "어디까지 됐나" 짧게 공유.
+
130–220분
이어서 구현 — 프론트-백엔드가 API 주소로 실제 연결 시도.
+
220–240분
카드 정리 — 끝난 카드는 '끝'으로, 못 끝낸 건 메모 남기기.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"드디어 코드예요! 근데 오늘 목표는 '완성'이 아니라 '시작해서 굴러가게'예요. 화면에 목록이 딱 하나라도 뜨면 대성공이에요."

+

"막히는 건 당연해요. 규칙 하나만 지켜요 — 15분 넘게 혼자 못 풀면 손 드세요. 오래 붙들고 있는 게 멋진 게 아니라, 빨리 물어보고 앞으로 가는 게 프로예요. 옆 사람이랑 화면 같이 보면서 해도 좋아요."

+

디자이너에게: "○○님은 화면이 설계대로 나오는지 봐 주고, 개발자가 스펙을 헷갈려 하면 짚어 주세요. 여유가 되면 간단한 스타일도 손대 보고요."

+
+
+
+
✍️실습 / 안내
+
+

오늘의 최소 목표(각 역할) — 완벽하지 않아도 됩니다. "한 조각이라도 화면/서버에서 돌아간다"가 기준입니다.

+
    +
  • 프론트: React로 첫 화면이 열리고, 목록 자리(빈 목록이라도)가 보인다.
  • +
  • 백엔드: Spring Boot에서 GET /api/todos가 200으로 응답한다(내용은 가짜여도 OK).
  • +
  • 디자이너: 화면 설계와 실제 화면을 나란히 비교해 다른 점 3개를 메모한다.
  • +
  • PM: 오늘 끝난 카드와 남은 카드를 세어 내일 아침에 공유할 준비.
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 — 처음부터 예쁜 UI·완벽한 코드에 매달려 아무것도 안 굴러간다.
+
대처 — "일단 못생겨도 돌아가게, 다듬기는 나중에"를 반복해 준다.
+
실수 — 프론트·백엔드가 서로 안 맞춰서 연결이 안 된다.
+
대처 — 수요일 API 표를 다시 펴서 주소·데이터 이름이 같은지 대조하게 한다.
+
+
+
+
+ + +
+
FRI
+
+

중간 점검 · 진행률 리뷰

+
목표 — 계획 대비 어디까지 왔는지 함께 확인하고, 다음 주 할 일을 정한다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

아침 스탠드업 + 오늘 마무리 계획

+
20분 (포인터 세션)
+
+
+
+
🎯학습목표
+
    +
  • 어제까지 진행을 확인하고, 오늘 "점검·발표"가 목표임을 공유한다.
  • +
+
+
+
✍️실습 / 안내
+
+

스탠드업 (PM 진행) — 30초씩 3가지에 더해, PM이 보드를 보며 "지금 끝난 카드 / 남은 카드 개수"를 숫자로 알려 줍니다. 오전은 남은 카드를 조금 더 밀고, 오후는 점검·발표라고 안내합니다.

+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오전
+

마무리 스프린트 + 진행률 집계

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 오늘 오전에 끝낼 수 있는 카드를 골라 마무리한다.
  • +
  • 계획(월~수 보드) 대비 실제 진행률을 숫자로 정리한다.
  • +
  • 못 끝낸 것을 "실패"가 아니라 "다음 주 할 일"로 옮긴다.
  • +
+
+
+
🕘진행표
+
+
0–70분
마무리 구현 — 거의 다 된 카드를 우선 끝낸다(새 카드 시작 금지).
+
70–95분
진행률 집계 — 전체 카드 중 몇 개 끝났는지 세어 % 계산.
+
95–120분
데모 준비 — 오후 발표용으로 "지금 되는 것"을 정리.
+
+
+
+
✍️실습 / 워크시트
+
+

진행률 카드를 채워 오후 발표에 씁니다. "계획 대비 실행"이 이번 주 평가 포인트 중 하나예요.

+
+
+
주간 진행률 워크시트
+
전체 카드 수      : ______ 개
+끝낸 카드 수      : ______ 개
+진행률           : ______ %  (끝낸 ÷ 전체 × 100)
+
+지금 실제로 되는 것 (데모 가능):
+  - ____________________
+아직 안 되는 것 (다음 주로):
+  - ____________________
+계획과 달라진 점 / 배운 것:
+  - ____________________
+
+
+
+
⚠️흔한 실수
+
+
실수 — 발표 직전에 새 기능을 욕심내다 되던 것까지 망가뜨린다.
+
대처 — "지금부터 새 카드 금지, 되는 걸 지키자"로 잠금한다.
+
+
+
+
+ +
+
+
Day 5 · 세션 3 · 오후
+

진행률 리뷰 · 팀 회고

+
120분
+
+
+
+
🎯학습목표
+
    +
  • 팀이 만든 것을 서로에게 데모하고, 한 주를 회고한다.
  • +
  • 협업·소통·주도성·계획 대비 실행을 스스로/서로 돌아본다.
  • +
  • 다음 주에 이어서 할 일을 보드에 명확히 남긴다.
  • +
+
+
+
🕘진행표
+
+
0–25분
데모 — "지금 되는 것"을 화면으로 시연(작아도 OK).
+
25–70분
회고 (KPT) — 좋았던 것 / 아쉬운 것 / 다음에 해볼 것.
+
70–95분
서로 칭찬 — 팀원별로 "이 사람 덕분에 좋았던 점" 한마디씩.
+
95–120분
다음 주 준비 — 남은 카드 정리, 멘토 마무리 코멘트.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"일주일 동안 정말 많은 걸 했어요. 아이디어에서 시작해서 기획서, 화면, API, 보드, 그리고 실제로 돌아가는 코드까지요. 아직 다 안 됐어도 괜찮아요. 진짜 개발은 원래 다음 주로 이어져요."

+

"회고는 잘잘못 따지는 자리가 아니에요. '이건 좋았으니 계속하자, 이건 아쉬웠으니 바꿔보자'를 찾는 자리예요. 특히 서로한테 고마웠던 점을 꼭 말해 주세요. 같이 일하는 힘이 거기서 나와요."

+
+
+
+
✍️실습 / 워크시트
+
+

KPT 회고를 각자 적고 벽에 붙인 뒤, 팀이 함께 읽습니다.

+
+
+
KPT 회고 워크시트
+
Keep (좋았던 것, 계속할 것)
+  - ____________________
+Problem (아쉬웠던 것, 힘들었던 것)
+  - ____________________
+Try (다음 주에 새로 해볼 것)
+  - ____________________
+
+이번 주 나의 한마디:
+  - 내가 팀에 기여한 것: ____________________
+  - 팀원 ○○ 덕분에 좋았던 것: ____________________
+
+
+
+
이해 확인 · 평가 체크포인트
+
+

멘토는 회고를 들으며 이번 주 평가 포인트를 관찰·기록합니다.

+
    +
  • 협업·소통 — 스탠드업·리뷰에서 서로 묻고 도왔는가?
  • +
  • 주도성 — 시키기 전에 카드를 잡고 스스로 움직였는가?
  • +
  • 계획 대비 실행 — 보드 계획과 실제 결과의 차이를 스스로 설명하는가?
  • +
  • 디자이너는 화면·기획을 주도하고 개발자에게 스펙을 넘겼는가?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 — 회고가 "누구 때문에 안 됐다"는 탓하기로 흐른다.
+
대처 — "사람이 아니라 방법을 말해요"로 방향을 돌리고, 칭찬 라운드로 마무리한다.
+
실수 — 다 못 끝냈다고 팀 전체가 풀이 죽는다.
+
대처 — "한 주에 여기까지 온 게 대단한 것"이라고 성취를 구체적으로 짚어 준다.
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 7주차 상세 강의안 (멘토용) · 8부작 중 7 + 2026 · 수습 가이드·CS 커리큘럼·평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/week8-detailed.html b/frontend/public/docs/week8-detailed.html new file mode 100644 index 0000000..620e59d --- /dev/null +++ b/frontend/public/docs/week8-detailed.html @@ -0,0 +1,939 @@ +8주차 상세 강의안 · 구현 · 발표 · 최종평가 — AWESOMEDEV 수습 교육 + + +
+ +
+
AWESOMEDEV · 수습 상세 강의안 · 8 / 8
+

8주차 상세 강의안
구현 · 발표 · 최종평가

+

드디어 마지막 주입니다. 이번 주는 만들던 것을 실제로 동작하는 상태까지 완성하고, 경영진과 팀 앞에서 데모로 발표하며, 8주간의 성장을 함께 돌아보고 마무리합니다. 금요일에는 최종 평가와 전환 면담으로 다음 여정을 이야기합니다.

+
+ 수습생 4명 · 개발 3 · 디자인 1 + 미림마이스터고 3학년 + 최종 주차 · 전환 결정 +
+
+ + + +
+
+

6블록 틀(학습목표 · 진행표 · 멘토 스크립트 · 실습 · 이해 확인 · 흔한 실수)은 1주차 강의안과 동일합니다. 처음 여시는 멘토는 1주차 안내 카드를 먼저 참고하세요.

+
+
+ + +
+
MON
+
+

구현 스프린트 · 기능 완성

+
오늘의 목표: 남은 기능을 끝까지 밀어붙여 "실제로 동작하는" 화면을 만든다.
+
+
+ +
+
+
Day 1 · 세션 1 · 오전
+

아침 스탠드업 + 프로젝트 마무리 착수

+
약 40분 (스탠드업 15분 + 착수 25분)
+
+
+
+
🎯학습목표
+
    +
  • 이번 주에 "완성"이 무엇인지, 각자 남은 일이 무엇인지 말로 정리한다.
  • +
  • 마무리 주간의 스탠드업 리듬(어제·오늘·막힌 곳)을 몸에 익힌다.
  • +
+
+
+
🕘진행표
+
+
0–5분
멘토 오프닝. "이번 주가 마지막 주"라는 사실을 담담하고 따뜻하게 알림.
+
5–20분
스탠드업 한 바퀴. 4명이 순서대로 어제 한 일 · 오늘 할 일 · 막힌 곳을 한 문장씩.
+
20–35분
남은 작업 쪼개기. 각자 오늘 끝낼 기능 1~2개를 화이트보드에 붙임.
+
35–40분
착수. 자리로 돌아가 바로 코딩 시작.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자, 드디어 마지막 주예요. 지난 7주 동안 정말 많이 컸어요. 이번 주는 새로 배우는 주가 아니라, 여러분이 만들던 걸 끝까지 완성해서 자랑스럽게 보여주는 주예요."

+

"스탠드업 규칙 기억하죠? 딱 세 가지만 말해요. 어제 뭐 했는지, 오늘 뭐 할 건지, 막힌 게 있는지. 막힌 걸 말하는 건 부끄러운 게 아니라 제일 잘하는 거예요. 그래야 제가 도와줄 수 있어요."

+

디자이너에게: "OO은 오늘부터 발표에서 보여줄 화면이랑 기획 흐름을 다듬어 주세요. 개발 팀이 완성하는 화면이랑 계속 맞춰보면서요."

+
+
+
+
✍️실습 · 스탠드업 워크시트
+
+

오늘의 마무리 보드를 각자 채웁니다. 포스트잇 한 장에 아래 대본을 그대로 옮겨 화이트보드에 붙이세요.

+
+
오늘의 마무리 보드 (각자 작성)
+
이름: ________
+
+[어제 한 일]  ________________________
+[오늘 끝낼 기능 ①]  ____________________
+[오늘 끝낼 기능 ②]  ____________________
+[지금 막힌 곳]  ______________________
+[도움이 필요한가?]  ☐ 혼자 가능   ☐ 멘토 도움
+
+
+
+
+
이해 확인
+
+

착수 전에 한 명씩 물어봅니다.

+
    +
  • "오늘 끝낼 기능을 딱 한 문장으로 말해볼래요?"
  • +
  • "그게 끝났는지 어떻게 알 수 있어요? (완료 기준)"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 마지막 주라고 갑자기 새 기능을 욕심내서 벌린다.
+
대처 "완성이 목표지 확장이 목표가 아니에요. 지금 있는 걸 동작하게 만드는 게 먼저예요"라고 범위를 좁혀준다.
+
+
+
+
+ +
+
+
Day 1 · 세션 2 · 오전~오후
+

기능 완성 스프린트 — 동작하는 상태까지

+
약 3시간 (중간 체크 포함)
+
+
+
+
🎯학습목표
+
    +
  • 맡은 기능을 화면에서 실제로 눌러서 동작하는 상태까지 만든다.
  • +
  • "됐다고 생각한 것"과 "정말 되는 것"의 차이를 직접 확인한다.
  • +
+
+
+
🕘진행표
+
+
0–70분
집중 코딩 1. 각자 기능 ①에 몰입. 멘토는 조용히 순회하며 어깨너머로 관찰.
+
70–80분
중간 데모. 한 명씩 지금까지 만든 화면을 30초씩 옆 사람에게 보여줌.
+
80–150분
집중 코딩 2. 기능 ②로 넘어가거나 ①의 마무리.
+
150–180분
오늘 만든 것 커밋. 동작하는 지점에서 커밋 메시지 남기고 push.
+
+
+
+
💬멘토 스크립트
+
+
멘토 (순회 중, 막힌 학생에게)
+

"화면은 나왔는데 버튼이 안 먹는 거죠? 좋아요, 같이 봐요. 우선 버튼 눌렀을 때 함수가 진짜 불리는지부터 확인해요. console.log 한 줄 찍어볼까요?"

+

"거봐요, 로그가 안 찍히죠? 그럼 문제는 함수 안이 아니라 연결이에요. onClick이 제대로 걸렸는지 봐요. 이렇게 하나씩 좁혀가는 거예요. 한 번에 다 보려고 하면 오히려 안 보여요."

+
+
+
+
✍️실습 · 완료 확인 체크
+
+

기능 하나를 "끝났다"고 말하기 전에, 아래 3가지를 실제로 클릭해서 확인합니다.

+
+
"진짜 되는가" 셀프 체크 (기능마다)
+
기능 이름: __________
+
+☐ 정상 경우: 제대로 입력하면 기대한 화면이 나온다
+☐ 빈 경우: 아무것도 입력 안 하고 눌러도 앱이 안 터진다
+☐ 이상한 경우: 이상한 값을 넣어도 에러 화면 대신
+   안내 메시지가 나온다
+
+세 개 다 ☑ 되면 → git commit -m "___기능 완성___"
+
+
+
+
+
이해 확인
+
+

중간 데모 때 각자에게:

+
    +
  • "방금 보여준 게 정상 경우죠? 빈 값 넣으면 어떻게 돼요?" (즉석에서 눌러보게 한다)
  • +
  • "이 커밋 메시지만 보고 나중에 무슨 작업인지 알 수 있어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 화면에 보이기만 하면 "됐다"고 넘어가고, 예외 경우를 한 번도 안 눌러본다.
+
대처 멘토가 직접 그 자리에서 빈 값·이상한 값을 넣어 앱을 한 번 "터뜨려" 보이고, "이게 발표날 나면 진땀 나요"라고 웃으며 예방의 필요를 느끼게 한다.
+
+
+
+
+ + +
+
TUE
+
+

통합 · 연결하기

+
오늘의 목표: 따로 만든 조각들을 하나의 앱으로 연결해 처음부터 끝까지 흘러가게 한다.
+
+
+ +
+
+
Day 2 · 세션 1 · 오전
+

아침 스탠드업 (포인터 세션)

+
약 15분
+
+
+
+
🎯학습목표
+
    +
  • 어제 완성분을 공유하고, 오늘 "연결"의 목표를 한 문장으로 세운다.
  • +
+
+
+
✍️진행 안내
+
+

아침 스탠드업은 어제와 같은 마무리 보드 형식으로 짧게 진행합니다. 오늘은 "내 기능이 옆 사람 기능과 어디서 만나는가"를 한 마디씩 덧붙이게 하세요. (CS 기본기가 필요한 팀원이 있으면 CS 기본기 4주 커리큘럼 문서의 해당 일차를 진행하되, 마지막 주인 만큼 프로젝트 완성이 우선입니다.)

+
+
+
+
+ +
+
+
Day 2 · 세션 2 · 오전~오후
+

통합 작업 — 프론트와 데이터를 잇다

+
약 3시간 30분
+
+
+
+
🎯학습목표
+
    +
  • React 화면(프론트)과 데이터(백엔드/API)를 연결해 실제 데이터가 화면에 뜨게 한다.
  • +
  • 처음 화면부터 마지막 화면까지 "한 흐름(사용자 시나리오)"으로 이어본다.
  • +
+
+
+
🕘진행표
+
+
0–15분
연결 지도 그리기. 화면 → 어떤 데이터를 부르는지 화살표로 칠판에 그림.
+
15–90분
연결 1. 각자 자기 화면에서 실제 데이터를 fetch해 뿌리기.
+
90–100분
합류 점검. 두 명씩 짝지어 서로 화면을 이어 눌러봄.
+
100–190분
전체 시나리오 연결. 로그인 → 목록 → 상세처럼 처음부터 끝까지 이어봄.
+
190–210분
통합본 커밋 & 공유. 하나로 합친 버전을 push하고 화면 녹화(30초).
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"어제까지는 각자 자기 방을 꾸민 거예요. 오늘은 그 방들을 복도로 연결해서 한 집으로 만드는 날이에요. 손님(사용자)이 현관에서 들어와서 방까지 쭉 걸어갈 수 있어야 해요."

+

"연결할 때 제일 자주 나는 문제가 뭔지 알아요? 데이터가 안 오는 게 아니라 모양이 다른 거예요. 백엔드는 userName으로 줬는데 화면은 name을 찾고 있으면 빈칸이 떠요. 그럴 땐 브라우저 개발자도구 Network 탭에서 진짜 온 데이터를 눈으로 확인해요."

+
+
+
+
✍️실습 · 연결 지도
+
+

코딩 전에 연결 지도를 먼저 채웁니다. 내 화면이 어떤 데이터를 필요로 하는지 말로 정리하면 코드가 쉬워집니다.

+
+
내 화면 연결 지도
+
내 화면 이름: ____________
+
+이 화면이 열릴 때 불러야 할 데이터:
+  주소(API): /api/________
+  받는 것:   ____________________
+
+화면에 보여줄 항목:
+  ① ________  ← 데이터의 ______ 필드
+  ② ________  ← 데이터의 ______ 필드
+
+데이터가 안 왔을 때 화면: ☐ 로딩중 표시  ☐ 빈 목록 안내
+
+
+
+
+
이해 확인
+
+

합류 점검 때 짝끼리 서로 물어보게 합니다.

+
    +
  • "이 화면은 데이터 오기 전에 뭐가 보여? 빈 화면이면 안 돼."
  • +
  • "Network 탭에서 진짜 온 데이터랑 화면에 뜬 게 같아?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 각자 자기 브랜치에서만 동작하고, 합쳤더니 충돌(conflict)로 앱이 안 켜진다.
+
대처 오전 중 한 번은 반드시 main으로 합쳐보게 하고, 충돌 해결을 멘토가 옆에서 한 번 같이 해준다. "합치는 건 자주 할수록 안 아파요"를 반복.
+
+
+
+
+ + +
+
WED
+
+

버그 정리 · 안정화

+
오늘의 목표: 남은 버그를 잡고, 발표 때 실제로 보여줄 흐름이 끊김 없이 돌게 만든다.
+
+
+ +
+
+
Day 3 · 세션 1 · 오전
+

아침 스탠드업 + 버그 목록 만들기

+
약 30분
+
+
+
+
🎯학습목표
+
    +
  • 지금 남은 문제들을 한 곳에 모아 "고칠 순서"를 정한다.
  • +
  • 모든 버그가 똑같이 급하지 않다는 것(우선순위)을 이해한다.
  • +
+
+
+
🕘진행표
+
+
0–10분
스탠드업. 어제 통합에서 깨진 곳을 각자 한 가지씩 말함.
+
10–25분
버그 보드 채우기. 발견된 문제를 포스트잇으로 다 붙이고 급함/안급함으로 나눔.
+
25–30분
분배. 급한 것부터 각자 이름표를 붙임.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"버그가 많아 보여도 걱정 마요. 프로 개발자도 발표 전엔 다 이래요. 중요한 건 다 고치는 게 아니라 발표에 보여줄 길을 매끄럽게 만드는 것이에요."

+

"그래서 오늘은 버그를 두 줄로 나눠요. 왼쪽은 '발표 흐름에서 사용자가 만나는 것', 오른쪽은 '깊이 들어가야 보이는 것'. 왼쪽부터 잡아요. 오른쪽은 회고에 '다음에 개선'으로 적으면 그것도 훌륭한 결과예요."

+
+
+
+
✍️실습 · 버그 보드
+
+

팀 공용 버그 보드를 채웁니다. 각 버그를 아래 형식으로 적어야 나중에 다시 재현할 수 있습니다.

+
+
버그 카드 (한 장에 하나)
+
[무엇을 했더니]  ______________________
+[무엇이 나왔나]  ______________________
+[원래 나와야 할 것]  __________________
+
+발표 흐름에 포함?  ☐ 예(급함)  ☐ 아니오(나중)
+담당: ______
+
+
+
+
+
이해 확인
+
+

분배 직전에:

+
    +
  • "이 버그, 발표 시연에서 심사위원이 볼까요 안 볼까요?"
  • +
  • "제일 먼저 잡아야 할 버그 하나를 고른다면?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 눈에 잘 안 띄지만 재밌어 보이는 버그부터 붙잡고 반나절을 쓴다.
+
대처 "그건 오른쪽 줄이에요. 발표 길부터 뚫고, 시간 남으면 돌아와요"라고 우선순위로 되돌린다.
+
+
+
+
+ +
+
+
Day 3 · 세션 2 · 오후
+

안정화 스프린트 — 발표 흐름 끝까지 돌리기

+
약 3시간
+
+
+
+
🎯학습목표
+
    +
  • 발표에서 보여줄 시나리오를 처음부터 끝까지 한 번도 안 끊고 돌린다.
  • +
  • 버그를 고친 뒤 "다시 눌러 확인"하는 습관을 익힌다.
  • +
+
+
+
🕘진행표
+
+
0–90분
급한 버그 잡기. 각자 왼쪽 줄(발표 흐름) 버그부터 처리.
+
90–120분
전체 통과 시험 1회. 팀 전원이 모여 시작→끝 시나리오를 한 번 쭉 실행.
+
120–170분
남은 흐름 버그 처리. 통과 시험에서 끊긴 지점 고침.
+
170–180분
오늘 결과 커밋 + 회고 메모. 못 고친 건 "다음에 개선"으로 기록.
+
+
+
+
💬멘토 스크립트
+
+
멘토 (버그 하나 고친 학생에게)
+

"고쳤어요? 좋아요! 그런데 여기서 제일 중요한 한 가지. 고쳤다고 바로 다음으로 넘어가지 말고, 방금 그 버그가 났던 그 동작을 다시 한 번 그대로 눌러봐요. 진짜 사라졌는지 눈으로 확인해야 해요."

+

"그리고 하나 더. 고치다가 옆 기능이 같이 깨지는 경우가 많아요. 그래서 고친 뒤에는 짧게라도 전체를 한 번 훑는 거예요. 이걸 잘하면 발표날 사고가 확 줄어요."

+
+
+
+
✍️실습 · 발표 흐름 통과표
+
+

팀이 함께 발표 흐름 통과표를 만들고, 오후에 한 번은 전 구간 ☑를 목표로 합니다.

+
+
발표 시나리오 통과표 (팀 공용)
+
1단계  첫 화면 진입 ................ ☐ 통과
+2단계  ___________________ .......... ☐ 통과
+3단계  ___________________ .......... ☐ 통과
+4단계  ___________________ .......... ☐ 통과
+5단계  마무리 화면 ................ ☐ 통과
+
+전 구간 한 번에 통과한 시각: __:__
+
+
+
+
+
이해 확인
+
+

전체 통과 시험 후:

+
    +
  • "방금 어디서 끊겼죠? 그게 급한 버그로 보드에 있나요?"
  • +
  • "버그 고친 뒤에 다시 눌러 확인했어요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 데모 데이터가 없어서 발표 때 화면이 텅 비어 보인다.
+
대처 오늘 안에 "보기 좋은 예시 데이터"를 미리 넣어두게 한다. 빈 화면보다 채워진 화면이 훨씬 잘 전달된다는 걸 실제로 비교해 보인다.
+
+
+
+
+ + +
+
THU
+
+

리허설 · 마무리

+
오늘의 목표: 데모 시나리오를 확정하고, 실제처럼 발표 리허설을 하며, 남은 버그를 정리한다.
+
+
+ +
+
+
Day 4 · 세션 1 · 오전
+

아침 스탠드업 + 데모 시나리오 확정

+
약 50분
+
+
+
+
🎯학습목표
+
    +
  • 내일 발표에서 "누가, 어느 화면을, 어떤 순서로" 보여줄지 확정한다.
  • +
  • 15분 발표의 뼈대(문제 → 우리가 만든 것 → 시연 → 배운 점)를 잡는다.
  • +
+
+
+
🕘진행표
+
+
0–10분
스탠드업. 어제 남은 버그와 오늘 리허설 목표 공유.
+
10–30분
발표 뼈대 짜기. 4개 파트(문제/솔루션/시연/회고)에 담당자 배정.
+
30–45분
시연 대본 초안. 시연 담당이 클릭 순서를 한 줄씩 적음.
+
45–50분
디자이너 파트 확인. 화면·기획 발표 흐름을 발표 뼈대에 끼워 맞춤.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"발표는 코딩이랑 달라요. 잘 만든 걸 잘 전하는 것까지가 실력이에요. 15분이면 생각보다 짧아요. 그래서 순서를 정해둬야 안 헤매요."

+

"순서는 간단해요. ①우리가 풀려던 문제가 뭐였는지, ②그래서 뭘 만들었는지, ③실제로 이렇게 됩니다(시연), ④8주 동안 뭘 배웠는지. 이 네 칸만 채우면 발표가 돼요."

+

디자이너에게: "OO은 ②와 함께 화면을 어떻게 설계했는지, 왜 이렇게 배치했는지 기획 이야기를 맡아주세요. 개발자들이 만든 걸 '왜'로 이어주는 다리예요."

+
+
+
+
✍️실습 · 발표 대본 뼈대
+
+

팀이 함께 발표 대본 뼈대를 채웁니다. 담당자와 대략의 시간까지 적어야 리허설이 가능합니다.

+
+
15분 발표 대본 뼈대 (팀 공용)
+
① 문제 (약 2분)   담당 ____
+   "우리는 ________ 문제를 풀고 싶었습니다."
+② 솔루션 (약 3분) 담당 ____ + 디자이너 ____
+   "그래서 ________ 을 만들었습니다."
+③ 시연 (약 7분)   담당 ____
+   클릭 순서: _______________________
+④ 배운 점 (약 3분) 담당 ____
+   "8주 동안 가장 크게 배운 것: ________"
+
+
+
+
+
이해 확인
+
+

뼈대를 다 채운 뒤:

+
    +
  • "시연에서 첫 클릭이 뭐예요? 마지막 클릭은요?"
  • +
  • "내 파트를 한 문장으로 시작한다면 무슨 말로 열래요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 기능을 전부 다 보여주려다 15분을 훌쩍 넘기고 핵심이 묻힌다.
+
대처 "제일 자랑스러운 흐름 하나에 집중해요. 다 보여주는 것보다 하나를 확실히 보여주는 게 강해요"라고 덜어내게 돕는다.
+
+
+
+
+ +
+
+
Day 4 · 세션 2 · 오후
+

발표 리허설 — 실전처럼 두 번

+
약 2시간 30분
+
+
+
+
🎯학습목표
+
    +
  • 실제 장비·실제 화면으로 처음부터 끝까지 리허설을 완주한다.
  • +
  • 발표 중 사고(끊김·질문)에 대응하는 법을 미리 연습한다.
  • +
+
+
+
🕘진행표
+
+
0–30분
개별 파트 연습. 각자 자기 파트를 소리 내어 2~3번.
+
30–75분
리허설 1회차. 실제 화면 띄우고 처음부터 끝까지. 멘토는 시간만 잰다.
+
75–95분
피드백. 좋았던 점 먼저, 고칠 점은 구체적으로 하나씩.
+
95–135분
리허설 2회차 + 예상 질문 연습. 멘토가 심사위원 역할로 질문.
+
135–150분
남은 버그 최종 정리. 리허설에서 걸린 곳만 마지막으로 손봄.
+
+
+
+
💬멘토 스크립트
+
+
멘토 (리허설 후 피드백)
+

"방금 정말 좋았어요. 특히 시연에서 화면 넘어가는 게 매끄러웠어요. 딱 하나만 다듬어요. 시작할 때 조금 빨랐어요. 첫 문장은 심호흡하고 천천히. '안녕하세요, 저희 팀이 만든 건…' 이렇게 여유 있게 열면 나머지가 편해져요."

+

"그리고 만약 발표 중에 화면이 안 뜨면요? 당황하지 말고 이렇게 말하면 돼요. '잠시 화면을 다시 불러오겠습니다.' 이 한마디면 프로처럼 보여요. 사고는 누구나 나요. 대응이 실력이에요."

+
+
+
+
✍️실습 · 리허설 피드백 카드
+
+

리허설 1회차 뒤, 서로에게 피드백 카드를 한 장씩 써 줍니다. 좋은 점을 반드시 먼저 적는 규칙입니다.

+
+
동료 피드백 카드
+
받는 사람: ______
+
+[좋았던 점 — 꼭 하나 이상]
+  ________________________________
+
+[더 좋아질 한 가지 — 구체적으로]
+  ________________________________
+
+[내일 이거 하나만 기억하면 돼요]
+  ________________________________
+
+
+
+
+
이해 확인
+
+

2회차 후 각자에게:

+
    +
  • "발표 도중 화면이 멈추면 첫 마디를 뭐라고 할래요?"
  • +
  • "예상 질문 하나를 미리 준비했어요? 뭐예요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 리허설 직전까지 코드를 고치다가 잘 되던 발표 버전을 깨뜨린다.
+
대처 목요일 오후 이후로는 발표용 버전을 별도 브랜치/태그로 얼려두기. "여기서부터는 발표본을 지킵니다"를 팀 규칙으로 선언한다.
+
+
+
+
+ + +
+
FRI
+
+

데모 발표 · 최종 면담

+
오늘의 목표: 경영진·팀 앞에서 데모를 발표하고, 자기평가서를 제출하고, 최종 평가와 전환 면담으로 8주를 마무리한다.
+
+
+ +
+
+
Day 5 · 세션 1 · 오전
+

발표 준비 + 자기평가서 제출

+
약 60분
+
+
+
+
🎯학습목표
+
    +
  • 발표 직전 장비·화면·데이터를 최종 점검한다.
  • +
  • 8주를 돌아보는 자기평가서를 스스로 작성해 제출한다.
  • +
+
+
+
🕘진행표
+
+
0–15분
장비 리허설. 발표장 화면 연결, 데모 앱 켜서 첫 화면 확인.
+
15–45분
자기평가서 작성. 조용히 각자 8주 회고 + 자기평가.
+
45–55분
제출 & 발표 순서 최종 확인. 누가 언제 나설지 한 번 더 맞춤.
+
55–60분
파이팅. 짧게 서로 응원하고 발표장으로 이동.
+
+
+
+
💬멘토 스크립트
+
+
멘토
+

"자기평가서는 잘 보이려고 쓰는 게 아니에요. 8주 전의 나랑 지금의 나를 비교하는 거예요. 못한 걸 솔직히 적어도 괜찮아요. 오히려 '이건 아직 부족하고, 다음엔 이렇게 하고 싶다'가 있으면 저는 그게 제일 좋아요."

+

"발표 전 긴장되는 거 알아요. 긴장은 잘하고 싶은 마음이에요. 나쁜 게 아니에요. 리허설한 대로만 하면 돼요. 우리 이미 어제 두 번 다 해봤잖아요."

+
+
+
+
✍️실습 · 자기평가서
+
+

각자 자기평가서를 아래 항목대로 작성해 제출합니다. (평가 루브릭과 함께 사용)

+
+
수습 자기평가서 (8주 회고)
+
이름: ______   트랙: ☐ 개발  ☐ 디자인
+
+1. 8주 중 가장 크게 성장했다고 느낀 것
+   ________________________________
+2. 이번 프로젝트에서 내가 맡아 끝낸 것
+   ________________________________
+3. 아직 부족하다고 느끼는 것 (솔직하게)
+   ________________________________
+4. 앞으로 3개월 안에 더 잘하고 싶은 것
+   ________________________________
+5. 스스로에게 주는 한마디
+   ________________________________
+
+
+
+
+
이해 확인
+
+

발표장 이동 전 최종 확인:

+
    +
  • "데모 앱, 지금 첫 화면 떠 있죠? 인터넷·로그인 다 됐죠?"
  • +
  • "자기평가서 5칸 다 채웠어요? 빈칸 없죠?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 발표 직전에 코드를 또 만지거나 브랜치를 바꿔 발표본이 안 켜진다.
+
대처 "오늘은 코드 그만. 우리는 어제 얼려둔 발표본으로만 갑니다"를 다시 못 박는다. 노트북은 발표본 상태 그대로 유지.
+
+
+
+
+ +
+
+
Day 5 · 세션 2 · 오후
+

데모 발표 — 경영진·팀 대상 15분

+
약 90분 (발표 + 질의응답 + 강평)
+
+
+
+
🎯학습목표
+
    +
  • 준비한 15분 발표를 경영진과 팀 앞에서 완주한다.
  • +
  • 질문에 당황하지 않고 자기 언어로 답하는 경험을 한다.
  • +
+
+
+
🕘진행표
+
+
0–5분
오프닝. 멘토가 8주 과정과 발표 순서를 소개하며 분위기를 데운다.
+
5–20분
팀 발표 15분. 문제 → 솔루션 → 시연 → 배운 점 순.
+
20–35분
질의응답. 경영진·팀의 질문. 멘토가 필요하면 다리를 놓아줌.
+
35–50분
강평. 참석자들이 좋았던 점 위주로 격려 코멘트.
+
50–90분
기념 촬영 & 정리. 발표 마무리, 발표자료·회고 취합.
+
+
+
+
💬멘토 스크립트
+
+
멘토 (발표 오프닝, 경영진·팀 앞에서)
+

"안녕하세요. 지난 8주 동안 미림마이스터고에서 온 수습생 네 명과 함께 달려왔습니다. 오늘은 그 결과를 직접 보여드리는 자리예요. 첫 주에 console.log 하나에 감탄하던 친구들이 이제 동작하는 앱을 만들어 왔습니다. 따뜻하게 봐주세요."

+
멘토 (질문이 어려워 학생이 멈칫할 때)
+

"좋은 질문이에요. 잠깐 제가 거들면요, 지금 질문은 '이 기능을 어떻게 더 빠르게 만들 수 있냐'는 거예요. OO이 시도해본 방향으로 답해볼래요?"

+
+
+
+
✍️실습 · 발표 진행 대본
+
+

발표 진행자(멘토 또는 팀장 역할 수습생)가 아래 진행 대본을 손에 들고 흐름을 이끕니다.

+
+
데모 발표 진행 대본
+
[열기]  "지금부터 ___팀명___ 의 데모를 시작하겠습니다."
+[전환] 파트 넘길 때: "다음은 __이름__ 이(가)
+        __파트__ 를 이어가겠습니다."
+[시연 안내] "실제 화면으로 보여드리겠습니다. 화면 봐주세요."
+[질문 받기] "질문 있으실까요? 편하게 말씀해 주세요."
+[닫기]  "저희 발표를 들어주셔서 감사합니다.
+        8주간 정말 즐거웠습니다."
+
+
+
+
+
이해 확인
+
+

발표 직전 진행자에게:

+
    +
  • "파트가 넘어갈 때 이름을 불러서 넘겨줄 거죠?"
  • +
  • "질문이 아무도 없으면 어떻게 자연스럽게 닫을래요?"
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 시연 중 앱이 멈추자 학생이 굳어서 말을 잃는다.
+
대처 멘토가 즉시 개입해 "이런 경우엔 준비한 녹화 영상으로 보여드릴게요"라며 백업 화면 녹화로 전환. (그래서 화·수에 30초 녹화를 남겨둔 것.) 학생을 탓하지 않는다.
+
+
+
+
+ +
+
+
Day 5 · 세션 3 · 오후
+

최종 평가 · 전환 면담 (1:1)

+
약 90분 (1인당 약 20분 + 마무리)
+
+
+
+
🎯학습목표
+
    +
  • 멘토 평가 + 데모 + 자기평가를 루브릭으로 종합해 전환 결과를 전달한다.
  • +
  • 결과가 무엇이든 성장을 인정하고, 다음 3개월 성장목표를 함께 세운다.
  • +
+
+
+
🕘진행표 (1인 기준)
+
+
0–3분
따뜻하게 열기. 8주 고생을 먼저 인정하며 긴장을 풀어줌.
+
3–8분
함께 돌아보기. 자기평가서를 같이 보며 성장한 지점을 짚음.
+
8–13분
결과 전달. 루브릭 종합 결과(전환/조건부/미전환)를 분명하고 다정하게.
+
13–18분
다음 3개월 목표. 결과와 무관하게 구체적 성장목표 2~3개를 함께 적음.
+
18–20분
닫기. 감사와 응원. 언제든 물어보라는 말로 문을 열어둠.
+
+
+
+
💬멘토 스크립트
+
+
멘토 — 전환 결정 시
+

"결론부터 말할게요. OO, 정식으로 함께하게 됐어요. 축하해요! 데모에서 보여준 완성도랑, 막혔을 때 끝까지 파고드는 태도가 특히 좋았어요. 다음 3개월은 이 세 가지를 같이 키워봐요."

+
멘토 — 조건부 전환 시
+

"OO은 '조건부 전환'이에요. 무슨 뜻이냐면, 가능성은 충분히 봤고 딱 한두 가지만 더 채우면 된다는 거예요. 부족하다는 게 아니라 '거의 다 왔다'는 신호예요. 앞으로 3개월 동안 이 부분을 이렇게 채워가면 돼요. 제가 계속 봐줄게요."

+
멘토 — 미전환 시
+

"OO, 이번엔 정식 전환까지는 이르지 못했어요. 그런데 이건 OO이 못했다는 뜻이 절대 아니에요. 8주 전과 지금을 비교하면 정말 많이 자랐어요. 지금 이 성장 방향을 이렇게 이어가면 분명히 좋은 개발자가 돼요. 오늘 결과보다 앞으로가 훨씬 길어요."

+
+
+
+
✍️실습 · 최종 면담 기록지
+
+

멘토는 면담마다 기록지를 채우고, 다음 3개월 목표는 수습생과 함께 소리 내어 적습니다.

+
+
최종 평가 · 전환 면담 기록지 (멘토 작성)
+
수습생: ______   트랙: ☐ 개발  ☐ 디자인
+
+[루브릭 종합]
+  멘토 평가 __ / 데모 __ / 자기평가 __
+[전환 결정]  ☐ 전환   ☐ 조건부 전환   ☐ 미전환
+[결정의 근거 — 잘한 점 먼저]
+  ________________________________
+
+[다음 3개월 성장목표 — 함께 작성]
+  ① ____________________________
+  ② ____________________________
+  ③ ____________________________
+
+멘토 서명 ______   면담일 2026-__-__
+
+

진행 팁: 결과가 무엇이든 "잘한 점 → 결과 → 앞으로"의 순서를 지키세요. 결과를 먼저 던지면 뒤 이야기가 들리지 않습니다.

+
+
+
+
이해 확인 (멘토 셀프)
+
+

각 면담을 닫기 전에 멘토 스스로 점검합니다.

+
    +
  • 결과를 분명한 단어로 전달했는가? (에둘러 말해 오해를 남기지 않았는가)
  • +
  • 미전환·조건부라도 다음 3개월 목표를 함께 손에 쥐여 보냈는가?
  • +
  • 이 학생이 방을 나설 때 주눅이 아니라 방향을 안고 나가는가?
  • +
+
+
+
+
⚠️흔한 실수
+
+
실수 미전환을 전하기 미안해 말을 흐려서, 학생이 결과를 정확히 이해하지 못한 채 나간다.
+
대처 다정함과 명확함은 반대말이 아니다. "결과는 미전환이에요"를 분명히 말한 뒤, 곧바로 성장과 다음 목표로 시간을 충분히 쓴다.
+
실수 전환된 학생에게 칭찬만 하고 다음 목표를 안 준다.
+
대처 전환도 끝이 아니라 시작이다. 잘한 학생일수록 다음 3개월 성장목표를 더 구체적으로 함께 세운다.
+
+
+
+
+ +
+ +
+ +
+ AWESOMEDEV · 8주차 상세 강의안 (멘토용) · 8부작 중 8 + 2026 · 수습 가이드·CS 커리큘럼·평가 루브릭과 함께 사용 +
+ +
diff --git a/frontend/public/docs/weekly-plan.html b/frontend/public/docs/weekly-plan.html new file mode 100644 index 0000000..88619a5 --- /dev/null +++ b/frontend/public/docs/weekly-plan.html @@ -0,0 +1,456 @@ +어썸데브 수습 8주 상세 교육안 + + +
+ +
+
AWESOMEDEV · 수습 교육 운영안
+

수습 8주
주차별 상세 교육안

+

멘토가 이 문서 하나로 8주를 운영할 수 있게 만들었어요. 매주 목표 · 요일별 활동 · 산출물 · 평가 체크포인트를 담았고, 예절(A)·기본기(B)·실무(C) 세 트랙이 어떻게 맞물리는지 한눈에 볼 수 있어요.

+
+ 9월 1일 – 10월 31일 · 8주 + 개발 3 · 디자인 1 + 멘토용 (대외 배포 X) +
+
+ + + + +
+
운영 원칙
+

세 트랙을 매일 병행해요

+

강의만 몰아치지 않아요. 아침엔 기본기를 배우고, 낮엔 손으로 일하고, 예절은 8주 내내 관찰합니다.

+
+
A

회사 생활 · 기본 예절

W1 집중 교육 후 8주 내내 관찰. 인사·보고·이메일·전화. 수습 가이드 참고.

+
B

컴퓨터 기본기

W1~W4 매일 아침 1시간. 컴퓨터 구조·자료구조·프로그램의 변화. CS 4주 커리큘럼 참고.

+
C

실무

W3부터 점점 비중↑. 소과제 → 실전 티켓 → 종합 프로젝트.

+
+ +
+
09:00–09:30
출근 · 자리 정리 · 하루 준비
+
09:30–10:30
기본기 학습(B) — W1~4. W5부터는 실무 심화로 대체
+
10:30–10:40
데일리 스탠드업 — 어제/오늘/막힌 것 한 사람당 1분
+
10:40–12:00
오전 활동 (그 주 핵심 과제)
+
13:00–17:30
오후 활동 — 실습 · 과제 · 코드리뷰 · 멘토링
+
17:30–18:00
하루 정리 · 3줄 회고 (오늘 배운 것 / 막힌 것 / 궁금한 것)
+
+
+ + +
+
시작 전
+

멘토 준비 체크리스트 (D-7)

+

막상 시작했는데 줄 일이 없어 방치되는 게 최악이에요. 미리 준비합니다.

+
+
    +
  • 멘토 1:1 배정 — 개발 3명(백/프론트 담당), 디자인 1명(디자인 or 프론트 리드)
  • +
  • 수습용 태스크 풀 20개+ 미리 확보 — 난이도 ★~★★ 소과제·티켓 목록
  • +
  • 계정·장비·좌석·접근권한 사전 세팅 (Git·메신저·위키·개발환경)
  • +
  • 교육 자료 준비 — 수습 가이드 인쇄본, CS 4주 커리큘럼, 사규·보안 문서
  • +
  • 일정 예약 — 데일리 스탠드업, 주간 1:1, 4주차 중간평가, 8주차 최종평가·데모
  • +
  • 연소근로자 노무 점검 — 만 18세 미만 시 근로계약·친권자 동의·근로시간(HR 확인)
  • +
+
+
+ + +
+
+ Phase 1 · 적응 +
+
WEEK1
+

온보딩 & 회사 적응

낯섦을 없애고, 사회인의 기본 예절을 몸에 익힌다. 아직 개발은 시키지 않는다.
+
+
+
아침 기본기 (B)
목·금 컴퓨터 구조 도입
+
이번 주 핵심
예절 집중 교육 (A) + 환경 세팅
+
산출물
첫 인사 이메일 · 온보딩 회고
+
+
+
오리엔테이션

대표 환영사 · 회사·팀 소개 · 멘토 배정 · 사규/보안 교육 · 장비·계정 세팅 · 수습 가이드 배포·정독.

+
예절 집중 ① — 태도와 보고

A1 하루의 기본 / A2 보고·연락·상담(호렌소). 롤플레이: 멘토에게 중간보고 말해보기.

+
예절 집중 ② — 소통 도구

A3 이메일 / A4 메신저 / A5 전화. 실습: 첫 인사 이메일 작성, 전화 응대 롤플레이.

+
제품·업무 이해 + 개발환경

우리가 KT엠모바일·KT알파·핀업에서 하는 일 소개. 개발환경 세팅 시작. (아침: 기본기 1일차)

+
협업 규칙 워크숍

Git 브랜치·커밋 / 코드리뷰 받는 법 / 스탠드업 / 30분 룰. 온보딩 회고 작성·공유.

+
+
+
산출물첫 인사 이메일 1통, 온보딩 회고 1장(뭘 배웠고 뭐가 어려웠는지)
+
평가 체크출근·시간 준수, 예절 습득 속도, 적극성·태도. (주간 1:1 기록 시작)
+
디자이너예절 동일. 목·금 디자인 환경 세팅 + 우리 서비스 UI 둘러보기.
+
+
+
+ + +
+
+ Phase 1 · 적응 +
+
WEEK2
+

기초 다지기 — 읽는 눈 만들기

코드와 화면을 '읽는' 눈을 만들고, 예절을 일상으로 정착시킨다.
+
+
+
아침 기본기 (B)
컴퓨터 구조 마무리 → 자료구조
+
이번 주 핵심
코드베이스·화면 읽기 + Git 연습
+
산출물
기능 분석 문서 · 연습 PR
+
+
+
코드베이스 구조 훑기

우리 React 폴더 구조Spring Boot 프로젝트 구조가 어떻게 생겼는지 큰 지도 그리기.

+
요청 흐름 추적

개발자도구 Network 탭으로 React → REST API → Spring Boot 흐름 눈으로 확인.

+
기능 읽기 과제

화면/기능 하나를 골라 어떻게 동작하는지 분석해 문서화. 멘토와 함께 검토.

+
Git 실전 연습

연습용 브랜치 생성 → 커밋 → PR 올려보기. 커밋 메시지 컨벤션 익히기.

+
주간 회고 · 미니 퀴즈

1:1 면담(잘한 것 1 / 더 할 것 1). 기본기 미니 퀴즈.

+
+
+
산출물기능 분석 문서 1개, 연습 PR 1개
+
평가 체크이해력, 질문의 질(30분 룰·3요소), 예절 정착도
+
디자이너UI 인벤토리 작성(색·타이포·컴포넌트) + 개선점 5개 리포트
+
+
+
+ + +
+
+ Phase 2 · 실무 입문 +
+
WEEK3
+

첫 실무 — 내 작업이 반영되는 경험

아주 작은 진짜 과제를, 리뷰를 거쳐 실제로 반영해본다.
+
+
+
아침 기본기 (B)
프로그램의 변화
+
이번 주 핵심
첫 소과제 착수→PR→머지
+
산출물
머지된 첫 PR 🎉
+
+
+
첫 소과제 배정

안전한 것(오타·문구·간단 UI·로그 개선 등). 착수 전 접근 방법을 먼저 공유하게 한다.

+
화·수
구현 + 중간보고

스스로 구현. 막히면 30분 룰. 진행 상황 중간보고 습관 잡기. PR 작성.

+
리뷰 반영 → 머지 → 배포 확인

코드리뷰 피드백 반영. "내 코드가 반영되는" 첫 경험. 배포 후 실제 동작 확인.

+
회고 · 1:1 · 퀴즈

첫 실무 소감 나누기. 잘한 점·아쉬운 점 기록.

+
+
+
산출물머지된 첫 PR 1건
+
평가 체크완주력, 피드백 반영 태도, 보고 습관
+
디자이너첫 디자인 개선 과제(빈 상태·버튼 상태 등) + 개발자에게 핸드오프 연습
+
+
+
+ + +
+
+ Phase 2 · 실무 입문 +
+
WEEK4
+

기본기 마무리 · 중간평가 중요

기본기를 '내 말로' 발표하고, 두 번째 과제로 난이도를 한 단계 올린다.
+
+
+
아침 기본기 (B)
통합·복습 → 금 미니 발표회
+
이번 주 핵심
두 번째 소과제 + 중간평가 면담
+
산출물
발표자료 · 두 번째 PR
+
+
+
요청 흐름 전체 정리

클릭 → 서버 → DB → 응답 전 과정을 그림으로 정리(기본기 총복습).

+
화·수
두 번째 소과제 (난이도 ↑)

첫 과제보다 한 단계 위. 멘토 개입은 조금 줄인다.

+
발표 자료 준비

"내가 이해한 컴퓨터/우리 시스템" 10분 발표 준비. 멘토 1:1 코칭.

+
기본기 미니 발표회 🎤 + 중간평가

발표(어려운 말 없이 내 말로). 이어 4주차 중간평가 면담.

+
+
+
산출물기본기 발표자료, 두 번째 머지 PR
+
평가 체크중간평가 — 전환 신호 반드시 전달. "지금 이대로면 애매하다"는 피드백은 이때 줘야 한다(마지막에 처음 듣게 하지 않기)
+
디자이너발표 주제 "내가 다시 디자인한다면" — 화면 개선안 발표 + 중간평가
+
+
+
+ + +
+
+ Phase 3 · 실전 +
+
WEEK5
+

실전 티켓 ①

실제 백로그 티켓을 '착수→구현→리뷰→배포' 사이클대로 처리한다.
+
+
+
아침 (B 종료)
실무 심화 — React 상태·Spring 계층 짧게
+
이번 주 핵심
실전 티켓 ★~★★ 처리
+
산출물
완료 티켓 1~2건
+
+
+
티켓 배정 · 설계

실제 티켓 배정. 접근 방법·예상 소요를 멘토와 합의.

+
화–목
구현 · 리뷰 · 반영 반복

사이클을 스스로 돌린다. 매일 중간보고. 코드리뷰 반영.

+
회고 · 1:1

티켓 마무리·배포 확인. 주간 피드백 기록.

+
+
+
산출물완료·배포된 티켓 1~2건
+
평가 체크자기주도성, 코드 품질 성장, 같은 지적 반복 여부
+
디자이너실제 개선 디자인 1건 → 개발 반영까지 따라가기
+
+
+
+ + +
+
+ Phase 3 · 실전 +
+
WEEK6
+

실전 티켓 ② — 더 독립적으로

난이도 ★★, 멘토 개입을 줄이고 스스로 더 많이 판단한다.
+
+
+
아침
실무 심화 · 코드리뷰 딥다이브
+
이번 주 핵심
독립적 티켓 처리 + 프로젝트 예고
+
산출물
완료 티켓 · 프로젝트 팀 구성
+
+
+
티켓 배정 (난이도 ★★)

조금 더 손이 가는 티켓. 설계를 스스로 제안하게 한다.

+
화–목
독립 구현

멘토는 질문에 답하되 먼저 나서지 않는다. 막힘 해결력 관찰.

+
종합 프로젝트 예고 · 팀 구성

다음 2주 프로젝트 주제 후보 공유, 역할·PM 롤 논의. 회고·1:1.

+
+
+
산출물완료 티켓, 프로젝트 킥오프 준비
+
평가 체크독립성, 문제해결력, 협업 태도
+
디자이너프로젝트에서 맡을 기획·화면 방향 구상 시작
+
+
+
+ + +
+
+ Phase 4 · 종합 프로젝트 +
+
WEEK7
+

종합 프로젝트 — 기획 · 설계

4명이 함께 작지만 끝까지 가는 기능 하나를 기획하고 설계한다.
+
+
+
협업 구조
디자인 1(기획·화면) + 개발 3(백/프론트), PM 롤 지정
+
이번 주 핵심
주제 확정 → 설계 → 개발 착수
+
산출물
기획서 · 화면 설계 · 태스크 보드
+
+
+
주제 확정 · 역할 분담

실제 제품에 붙일 수 있는 작은 기능 하나. 역할·PM 롤 지정.

+
화·수
기획 · 설계

기획서, 화면 설계, API 설계, 태스크 보드 작성. 멘토 리뷰.

+
목·금
개발 착수 · 중간 점검

구현 시작. 매일 스탠드업으로 진행 공유. 금요일 진행률 점검.

+
+
+
산출물기획서, 화면 설계, 태스크 보드, 초기 구현
+
평가 체크협업·소통, 주도성, 계획 대비 실행
+
디자이너기획·화면 주도 + 개발자에게 스펙 핸드오프
+
+
+
+ + +
+
+ Phase 4 · 종합 프로젝트 +
+
WEEK8
+

구현 · 발표 · 최종평가 피날레

완성하고, 데모로 발표하고, 8주를 마무리한다.
+
+
+
하이라이트
데모 발표 (경영진·팀 대상)
+
이번 주 핵심
완성 → 발표 → 최종평가·전환 면담
+
산출물
동작 데모 · 발표자료 · 회고
+
+
+
월–수
구현 · 통합 · 테스트

기능 완성, 통합, 버그 정리. 실제로 동작하는 상태까지.

+
리허설 · 마무리

데모 시나리오 점검, 발표 리허설, 남은 버그 정리.

+
데모 발표 🎤 + 최종평가

15분 데모 발표. 자기평가서 제출. 이어 최종 평가 · 전환 면담.

+
+
+
산출물동작 데모, 발표자료, 프로젝트 회고, 자기평가서
+
평가 체크최종평가 — 루브릭 종합(멘토 평가 + 데모 + 자기평가) → 전환 결정. 결과 통보 + 전환 시 다음 3개월 성장 목표 함께 제시
+
디자이너프로젝트 화면·기획 발표 파트 담당
+
+
+
+ + +
+
+

📌 8주 내내 지킬 것

+
    +
  • 주간 1:1은 거르지 않기 — 8번의 기록이 쌓이면 최종 평가가 인상이 아닌 근거가 됩니다.
  • +
  • 나쁜 신호는 빨리, 4주차 안에 — 전환이 어렵겠다는 판단은 반드시 중간에 공유합니다.
  • +
  • 완성도보다 성장 속도·태도 — 고졸 신입에게 지금의 실력보다 6개월 뒤 그림을 봅니다.
  • +
+
+

함께 쓰는 문서: 수습 가이드(학생 배포용) · CS 기본기 4주 커리큘럼(Layer B) · 평가 루브릭(작성 예정).

+
+ +
+ AWESOMEDEV · 수습 8주 상세 교육 운영안 (멘토용) + 2026 · 개정 시 최신본으로 대체 +
+ +
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..706b113 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,121 @@ +// 이 파일이 하는 일: 앱의 라우팅(주소 → 페이지 연결)과 공통 레이아웃(상단 네비게이션)을 정의한다. +// /login 이외의 모든 페이지는 로그인해야 볼 수 있고, /mentor는 MENTOR 역할만 접근 가능하다. +import { Routes, Route, NavLink, Navigate, Outlet } from 'react-router-dom'; +import { useAuth } from './AuthContext'; +import LoginPage from './pages/LoginPage'; +import SignupPage from './pages/SignupPage'; +import FindAccountPage from './pages/FindAccountPage'; +import DashboardPage from './pages/DashboardPage'; +import CodingBasicsPage from './pages/CodingBasicsPage'; +import DocsPage from './pages/DocsPage'; +import DocViewerPage from './pages/DocViewerPage'; +import AssignmentsPage from './pages/AssignmentsPage'; +import SetupGuidePage from './pages/SetupGuidePage'; +import MentorPage from './pages/MentorPage'; + +// 학습 포인트: "로그인 필요" 가드 컴포넌트. +// Outlet 자리에 자식 라우트가 렌더링되며, 비로그인이면 /login으로 돌려보낸다. +function RequireAuth() { + const { user, loading } = useAuth(); + if (loading) { + // /auth/me 확인이 끝나기 전 — 아무것도 판단하지 말고 잠깐 기다린다. + return
불러오는 중...
; + } + if (!user) { + return ; + } + return ; +} + +// 학습 포인트: 역할(role) 가드. 학생이 /mentor 주소를 직접 쳐도 대시보드로 돌려보낸다. +// 단, 진짜 보안은 서버(API)가 담당한다 — 프론트 가드는 UX용 안내일 뿐이다. +function RequireMentor() { + const { user } = useAuth(); + if (user?.role !== 'MENTOR') { + return ; + } + return ; +} + +// 공통 레이아웃: 상단 네비게이션 + 페이지 본문 +function Layout() { + const { user, logout } = useAuth(); + + return ( + <> +
+
+
+ AWESOMEDEV 수습 플랫폼 +
+ +
+ + {user?.name} + {user?.role === 'MENTOR' ? ' 멘토' : ''} + + +
+
+
+
+ +
+ + ); +} + +export default function App() { + return ( + + {/* 비로그인 접근 가능 영역: 로그인·회원가입·계정 찾기. + 학습 포인트: 이 라우트들은 RequireAuth 바깥에 있어서 세션 없이도 열린다. + 당연한 얘기 같지만, 회원가입 페이지가 "로그인해야 접근 가능"하면 아무도 가입할 수 없다. */} + } /> + } /> + } /> + {/* 로그인해야 접근 가능한 영역 */} + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + {/* 멘토 전용 영역 */} + }> + } /> + + + + {/* 그 외 주소는 대시보드로 */} + } /> + + ); +} diff --git a/frontend/src/AuthContext.jsx b/frontend/src/AuthContext.jsx new file mode 100644 index 0000000..2624bea --- /dev/null +++ b/frontend/src/AuthContext.jsx @@ -0,0 +1,54 @@ +// 이 파일이 하는 일: "지금 누가 로그인해 있는가"를 앱 전체에서 공유하는 Context. +// 앱이 처음 뜰 때 GET /api/auth/me로 세션을 확인하고, +// login()/logout() 함수를 하위 컴포넌트에 내려준다. +import { createContext, useContext, useEffect, useState } from 'react'; +import client from './api/client'; + +// 학습 포인트: Context는 "props를 여러 단계 내려보내는 번거로움"을 해결한다. +// 사용자 정보처럼 앱 전역에서 필요한 값에 딱 맞는 도구다. +// Context가 없다면? App → Layout → 각 페이지 → 버튼까지 user를 props로 +// 일일이 전달해야 하고, 중간 컴포넌트들은 쓰지도 않는 값을 나르기만 하게 된다. +// (이걸 "prop drilling"이라고 부른다.) +const AuthContext = createContext(null); + +export function AuthProvider({ children }) { + // user: 로그인한 사용자 객체 {id, username, name, role, track} 또는 null + const [user, setUser] = useState(null); + // loading: 새로고침 직후 /auth/me 확인이 끝나기 전인지 여부. + // 학습 포인트: 이 플래그가 없으면 새로고침할 때마다 + // "아직 확인 중인데 user가 null이니까" 로그인 페이지로 튕겨버린다. + const [loading, setLoading] = useState(true); + + useEffect(() => { + // 앱 최초 로딩 시 세션 쿠키가 살아 있는지 서버에 물어본다. + client + .get('/auth/me') + .then((res) => setUser(res.data)) + .catch(() => setUser(null)) // 401이면 비로그인 상태 + .finally(() => setLoading(false)); + }, []); + + async function login(username, password) { + // 실패하면 axios가 예외를 던지므로 호출한 쪽(LoginPage)에서 try/catch로 잡는다. + const res = await client.post('/auth/login', { username, password }); + setUser(res.data); + return res.data; + } + + async function logout() { + await client.post('/auth/logout'); + setUser(null); + } + + return ( + + {children} + + ); +} + +// 학습 포인트: useContext를 감싼 커스텀 훅. +// 각 페이지는 `const { user } = useAuth()` 한 줄이면 된다. +export function useAuth() { + return useContext(AuthContext); +} diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js new file mode 100644 index 0000000..97322cf --- /dev/null +++ b/frontend/src/api/client.js @@ -0,0 +1,35 @@ +// 이 파일이 하는 일: 백엔드 API를 호출하는 axios 인스턴스를 하나만 만들어 공유한다. +// 모든 페이지가 이 client를 import해서 쓰므로 baseURL·인증 설정이 한 곳에 모인다. +import axios from 'axios'; + +const client = axios.create({ + // vite dev 서버의 proxy 설정(vite.config.js)이 '/api'를 백엔드로 넘겨준다. + baseURL: '/api', + // 학습 포인트: 세션 쿠키 인증은 브라우저가 쿠키를 자동으로 실어 보내야 동작한다. + // withCredentials: true가 없으면 로그인해도 다음 요청부터 401이 난다. + // 원리: 로그인 성공 시 서버가 Set-Cookie 헤더로 세션 ID를 심어 주고, + // 이후 요청마다 브라우저가 그 쿠키를 도로 보내면 서버가 "아, 아까 그 사람"을 알아본다. + // withCredentials는 "이 요청에 쿠키를 포함해도 된다"는 axios 쪽 허락 스위치다. + withCredentials: true, +}); + +// 학습 포인트: 응답 인터셉터 — 모든 API 응답이 여기를 한 번 거친다. +// 세션이 만료되어 401이 오면 페이지마다 처리 코드를 두는 대신 +// 여기서 한 번에 로그인 화면으로 보낸다. +client.interceptors.response.use( + (response) => response, + (error) => { + const status = error.response?.status; + const url = error.config?.url || ''; + // 예외: 로그인 시도 자체의 401(비밀번호 틀림)과 + // 로그인 여부 확인용 /auth/me의 401은 리다이렉트하면 안 된다. + // (로그인 페이지에서 무한 새로고침이 되어버린다.) + const isAuthCheck = url.includes('/auth/login') || url.includes('/auth/me'); + if (status === 401 && !isAuthCheck && window.location.pathname !== '/login') { + window.location.href = '/login'; + } + return Promise.reject(error); + } +); + +export default client; diff --git a/frontend/src/components/Badge.jsx b/frontend/src/components/Badge.jsx new file mode 100644 index 0000000..445c549 --- /dev/null +++ b/frontend/src/components/Badge.jsx @@ -0,0 +1,28 @@ +// 이 파일이 하는 일: 종류(kind)·상태(status)·카테고리를 색깔 알약으로 보여주는 배지 컴포넌트. +// 학습 포인트: "코드값 → 한국어 라벨 + 색" 변환표를 한 곳에 모아두면 +// 화면마다 라벨이 어긋나는 사고를 막을 수 있다. + +const STYLES = { + // 과제 종류 + MAIN: { label: '본 과제', className: 'badge-primary' }, + EXTRA: { label: '예비 과제', className: 'badge-amber' }, + DESIGN: { label: '디자이너 과제', className: 'badge-teal' }, + // 제출 상태 + SUBMITTED: { label: '제출됨', className: 'badge-amber' }, + REVIEWED: { label: '리뷰 완료', className: 'badge-teal' }, + // 문서 카테고리 + GUIDE: { label: '가이드', className: 'badge-primary' }, + CURRICULUM: { label: '커리큘럼', className: 'badge-teal' }, + PLAN: { label: '운영 계획', className: 'badge-amber' }, + RUBRIC: { label: '평가 기준', className: 'badge-rose' }, + LESSON: { label: '수업 자료', className: 'badge-primary' }, + ASSIGNMENT: { label: '과제 안내', className: 'badge-amber' }, + TICKET: { label: '실무 티켓', className: 'badge-teal' }, +}; + +export default function Badge({ value }) { + // 학습 포인트: 변환표에 없는 값이 와도 앱이 죽지 않도록 기본값(원래 코드값 그대로)을 둔다. + // 서버에 새 상태가 추가돼도 화면은 일단 깨지지 않고, 표에 한 줄만 더하면 된다. + const style = STYLES[value] || { label: value, className: 'badge-primary' }; + return {style.label}; +} diff --git a/frontend/src/components/Card.jsx b/frontend/src/components/Card.jsx new file mode 100644 index 0000000..7db8ba9 --- /dev/null +++ b/frontend/src/components/Card.jsx @@ -0,0 +1,13 @@ +// 이 파일이 하는 일: 흰 배경 + 둥근 모서리 + 은은한 그림자의 공용 카드 컴포넌트. +// 학습 포인트: onClick이 있으면 클릭 가능한 스타일(호버 시 살짝 떠오름)을 자동으로 붙인다. +export default function Card({ children, onClick, style }) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/src/components/ProgressBar.jsx b/frontend/src/components/ProgressBar.jsx new file mode 100644 index 0000000..5945435 --- /dev/null +++ b/frontend/src/components/ProgressBar.jsx @@ -0,0 +1,19 @@ +// 이 파일이 하는 일: "몇 개 중 몇 개 완료" 진행률을 막대로 보여주는 작은 컴포넌트. +export default function ProgressBar({ value, total }) { + // 학습 포인트: total이 0일 때 0으로 나누면 NaN이 된다. 항상 방어할 것. + const percent = total > 0 ? Math.round((value / total) * 100) : 0; + + return ( +
+
+ 이번 주 진행률 + + {value} / {total} ({percent}%) + +
+
+
+
+
+ ); +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..26d2eac --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,24 @@ +// 이 파일이 하는 일: React 앱의 진입점. index.html의 #root에 앱 전체를 붙인다. +// 학습 포인트: BrowserRouter를 여기(최상단)에서 한 번만 감싸면 +// 하위 어디서든 useNavigate/Link 같은 라우터 훅을 쓸 수 있다. +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App.jsx'; +import { AuthProvider } from './AuthContext.jsx'; +import './styles/global.css'; + +ReactDOM.createRoot(document.getElementById('root')).render( + // 학습 포인트: StrictMode는 개발 중에만 동작하는 안전 점검 모드다. + // useEffect를 일부러 두 번 실행해 "정리(cleanup)를 빠뜨린 코드"를 드러낸다. + // 개발 서버에서 API가 두 번 호출되는 것처럼 보이는 건 버그가 아니라 이것 때문. + + + {/* 학습 포인트: AuthProvider가 라우터 안쪽에 있어야 + 로그인/로그아웃 시 페이지 이동(useNavigate)이 가능하다. */} + + + + + +); diff --git a/frontend/src/pages/AssignmentsPage.jsx b/frontend/src/pages/AssignmentsPage.jsx new file mode 100644 index 0000000..0ce0c1e --- /dev/null +++ b/frontend/src/pages/AssignmentsPage.jsx @@ -0,0 +1,182 @@ +// 이 파일이 하는 일: 주차별 과제 목록을 보여주고, 각 과제에 대해 +// 제출 폼(내용 + 링크)과 내 제출 상태·멘토 피드백을 표시한다. +import { useEffect, useState } from 'react'; +import client from '../api/client'; +import Card from '../components/Card'; +import Badge from '../components/Badge'; + +const WEEKS = [1, 2, 3, 4, 5, 6, 7, 8]; + +// 과제 하나 = 카드 하나. 제출 폼의 입력 상태는 이 컴포넌트가 각자 가진다. +// 학습 포인트: 폼 상태를 부모(페이지)에 모으면 과제 수만큼 상태 관리가 꼬인다. +// "각 카드가 자기 폼을 책임진다"가 훨씬 단순하다. +function AssignmentCard({ assignment, submission, onSubmitted }) { + const [content, setContent] = useState(submission?.content || ''); + const [link, setLink] = useState(submission?.link || ''); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(''); + const [savedMessage, setSavedMessage] = useState(''); + + async function handleSubmit(e) { + e.preventDefault(); + setError(''); + setSavedMessage(''); + if (!content.trim()) { + setError('제출 내용을 적어 주세요.'); + return; + } + setSaving(true); + try { + // 같은 과제에 다시 제출하면 서버가 기존 제출을 갱신해 준다. + await client.post('/submissions', { + assignmentId: assignment.id, + content, + link: link.trim() || null, + }); + setSavedMessage(submission ? '다시 제출했어요.' : '제출 완료!'); + onSubmitted(); // 부모에게 "내 제출 목록 다시 불러와" 신호 + } catch { + setError('제출 중 문제가 생겼어요. 잠시 후 다시 시도해 주세요.'); + } finally { + setSaving(false); + } + } + + return ( + +
+ + {assignment.day}일차 + {submission && } +
+
{assignment.title}
+

+ {assignment.summary} +

+ + {/* 멘토 피드백이 있으면 눈에 띄게 보여준다 */} + {submission?.feedback && ( +
+
+ 멘토 피드백 +
+
{submission.feedback}
+
+ )} + +
+
+ +