From fd26ffaaeecfa4cb11d1ba269269043121a5a667 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 13:48:15 +0900 Subject: [PATCH 01/20] =?UTF-8?q?style:=20#54=20FE1=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20CSS=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/components.css | 116 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 static/css/components.css diff --git a/static/css/components.css b/static/css/components.css new file mode 100644 index 0000000..262a1ed --- /dev/null +++ b/static/css/components.css @@ -0,0 +1,116 @@ +/* PLAN B - 공통 컴포넌트 + forms.py 의 widget class(form-control, form-select 등)와 이름을 맞춰뒀음. + tokens.css 의 변수만 사용 — 새 색상 임의로 추가하지 말 것. */ + +.btn{ + display:inline-flex; align-items:center; justify-content:center; gap:6px; + height:44px; padding:0 20px; border-radius:10px; + background:var(--neutral11); color:#fff; font-size:14px; font-weight:600; + border:none; cursor:pointer; +} +.btn:hover{ background:var(--neutral10); } +.btn:disabled{ background:var(--neutral6); cursor:not-allowed; } +.btn.btn-brand{ background:var(--brand4); } +.btn.btn-brand:hover{ background:var(--brand5); } +.btn.btn-ghost{ background:#fff; color:var(--neutral11); border:1px solid var(--neutral5); } +.btn.btn-block{ width:100%; } + +.card{ + background:var(--neutral1); border:1px solid var(--neutral4); border-radius:16px; + padding:24px 26px; +} + +.form-group{ margin-bottom:16px; } +.form-label{ display:block; font-size:13px; font-weight:600; color:var(--neutral9); margin-bottom:6px; } + +.form-control, .form-select, .textarea-input{ + width:100%; height:44px; padding:0 14px; border-radius:10px; + border:1px solid var(--neutral5); background:var(--neutral2); + font-family:var(--ui); font-size:14px; color:var(--neutral10); +} +.textarea-input{ height:auto; padding:12px 14px; resize:vertical; } +.form-control:focus, .form-select:focus, .textarea-input:focus{ + outline:none; border-color:var(--brand4); background:#fff; +} +.file-input{ padding:10px 14px; height:auto; background:var(--neutral2); } +.number-input{ text-align:center; } + +.field-error{ color:var(--judge3); font-size:12px; margin-top:5px; } +.form-error-box{ + background:var(--judge3-bg); border:1px solid var(--judge3-line); color:var(--judge3); + border-radius:10px; padding:10px 14px; font-size:13px; margin-bottom:16px; +} + +.page-title{ font-size:24px; font-weight:700; color:var(--neutral11); margin-bottom:6px; } +.page-sub{ font-size:13px; color:var(--neutral7); margin-bottom:20px; } + +/* 판정 뱃지 (가능/위험/불가능) — planner/feasibility 등에서 재사용 */ +.badge{ display:inline-flex; align-items:center; gap:5px; font-size:12px; font-weight:700; padding:4px 10px; border-radius:999px; } +.badge::before{ content:""; width:6px; height:6px; border-radius:50%; } +.badge.badge-possible{ background:var(--judge1-bg); color:var(--judge1); border:1px solid var(--judge1-line); } +.badge.badge-possible::before{ background:var(--judge1); } +.badge.badge-risky{ background:var(--judge2-bg); color:var(--judge2); border:1px solid var(--judge2-line); } +.badge.badge-risky::before{ background:var(--judge2); } +.badge.badge-impossible{ background:var(--judge3-bg); color:var(--judge3); border:1px solid var(--judge3-line); } +.badge.badge-impossible::before{ background:var(--judge3); } + +/* 과목 우선순위 원형 배지 (H/M/L) — Figma 브랜드 블루 계층 그대로 사용 */ +.priority-dot{ + width:26px; height:26px; border-radius:50%; flex-shrink:0; + display:flex; align-items:center; justify-content:center; + font-size:10.5px; font-weight:800; +} +.priority-dot.priority-high{ background:var(--brand4); color:#fff; } +.priority-dot.priority-medium{ background:var(--brand3); color:var(--neutral10); } +.priority-dot.priority-low{ background:var(--brand2); color:var(--neutral9); } + +/* 탭바 — 실제로는 페이지 이동 링크()지만 시각적으로는 탭처럼 보이게 */ +.tabbar{ display:flex; gap:6px; margin-bottom:14px; flex-wrap:wrap; } +.tabbar a.tab{ + padding:8px 16px; border-radius:9px; background:var(--neutral2); color:var(--neutral8); + font-size:12px; font-weight:600; text-decoration:none; display:inline-block; +} +.tabbar a.tab.active{ background:var(--neutral11); color:#fff; } +.auth-wrap{ min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; } +.auth-card{ width:380px; } +.auth-link{ text-align:center; font-size:13px; color:var(--neutral8); margin-top:16px; } +.auth-link a{ color:var(--brand4); font-weight:700; text-decoration:none; } + +/* 2열 레이아웃 (subject_form 등) */ +.two-col{ display:flex; gap:20px; align-items:flex-start; } +.two-col > *{ flex:1; min-width:0; } +.grow{ flex:1; min-width:0; } + +/* 리스트 카드 한 줄 (등록된 과목 등) */ +.list-card{ + display:flex; align-items:center; gap:10px; + padding:11px 14px; border-radius:11px; background:var(--neutral2); + margin-bottom:8px; font-size:12.5px; +} + +/* 범용 필/탭 배지 — .tabbar 밖에서 단독으로도 씀 (task_review의 과목/깊이 태그 등) */ +.tab{ + padding:8px 16px; border-radius:9px; background:var(--neutral2); color:var(--neutral8); + font-size:12px; font-weight:600; display:inline-block; +} +.tab.active{ background:var(--neutral11); color:#fff; } + +/* 세그먼트 선택형 (중요도/깊이/난이도) — 작업 수정 모달에서 사용 */ +.segment{ display:flex; gap:6px; } +.segment .opt{ + flex:1; text-align:center; padding:9px 0; border-radius:9px; + font-size:11.5px; font-weight:700; background:var(--neutral2); color:var(--neutral8); + border:none; cursor:pointer; font-family:var(--ui); +} +.segment .opt.sel{ background:var(--neutral11); color:#fff; } + +/* 모달 — 작업 수정 화면에서 사용 */ +.modal-backdrop{ + position:fixed; inset:0; background:rgba(20,25,35,.35); + display:none; align-items:center; justify-content:center; z-index:50; +} +.modal-backdrop.open{ display:flex; } +.modal{ + width:480px; max-width:92vw; background:#fff; border-radius:18px; + padding:30px 30px 26px; box-shadow:0 10px 40px rgba(0,0,0,.2); +} \ No newline at end of file From a6d049bb3dad19427fb6268a4704c3b50af1d086 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 13:48:26 +0900 Subject: [PATCH 02/20] =?UTF-8?q?feat:=20#54=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=C2=B7=EB=A1=9C=EA=B7=B8=EC=9D=B8=C2=B7=EC=8B=9C?= =?UTF-8?q?=ED=97=98=EA=B8=B0=EA=B0=84=20=EB=93=B1=EB=A1=9D=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/templates/accounts/login.html | 40 ++++++++++++++++ accounts/templates/accounts/signup.html | 56 +++++++++++++++++++++++ exams/templates/exams/_topbar.html | 11 +++++ exams/templates/exams/period_form.html | 60 ++++++++++++++++++++++++ exams/templates/exams/period_list.html | 61 +++++++++++++++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 exams/templates/exams/_topbar.html diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html index e69de29..58429c2 100644 --- a/accounts/templates/accounts/login.html +++ b/accounts/templates/accounts/login.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}로그인 - PLAN B{% endblock %} +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/accounts/templates/accounts/signup.html b/accounts/templates/accounts/signup.html index e69de29..74921e3 100644 --- a/accounts/templates/accounts/signup.html +++ b/accounts/templates/accounts/signup.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}회원가입 - PLAN B{% endblock %} +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+
PLAN B 시작하기
+
시험 계획을 함께 관리해요
+ + {% if form.non_field_errors %} +
{{ form.non_field_errors|striptags }}
+ {% endif %} + +
+ {% csrf_token %} + +
+ + {{ form.email }} + {% if form.email.errors %}
{{ form.email.errors|striptags }}
{% endif %} +
+ +
+ + {{ form.nickname }} + {% if form.nickname.errors %}
{{ form.nickname.errors|striptags }}
{% endif + %} +
+ +
+ + {{ form.password1 }} + {% if form.password1.errors %}
{{ form.password1.errors|striptags }}
{% + endif %} +
+ +
+ + {{ form.password2 }} + {% if form.password2.errors %}
{{ form.password2.errors|striptags }}
{% + endif %} +
+ + +
+ + +
+
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/_topbar.html b/exams/templates/exams/_topbar.html new file mode 100644 index 0000000..9b0b03e --- /dev/null +++ b/exams/templates/exams/_topbar.html @@ -0,0 +1,11 @@ +{# 사용법: {% include "exams/_topbar.html" %} + accounts:logout 은 POST 전용 뷰라서 폼으로 감싸야 함 (accounts/urls.py 참고) #} +
+ PLAN B +
+ {% csrf_token %} + +
+
\ No newline at end of file diff --git a/exams/templates/exams/period_form.html b/exams/templates/exams/period_form.html index e69de29..a8bdff7 100644 --- a/exams/templates/exams/period_form.html +++ b/exams/templates/exams/period_form.html @@ -0,0 +1,60 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}시험기간 등록 - PLAN B{% endblock %} +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+ {% if period %}시험기간 수정하기{% else %}시험기간 등록하기{% endif %} +
+
이 기간 동안의 계획을 세울게요
+ + {% if form.non_field_errors %} +
{{ form.non_field_errors|striptags }}
+ {% endif %} + +
+
+ {% csrf_token %} + +
+ + {{ form.title }} + {% if form.title.errors %}
{{ form.title.errors|striptags }}
{% endif %} +
+ +
+
+ + {{ form.start_date }} +
+
+ + {{ form.end_date }} +
+
+ {% if form.start_date.errors or form.end_date.errors %} +
{{ form.start_date.errors|striptags }} {{ form.end_date.errors|striptags }}
+ {% endif %} + + +
+
+ + {% if period %} +
+ {% csrf_token %} + +
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/period_list.html b/exams/templates/exams/period_list.html index e69de29..52c6b66 100644 --- a/exams/templates/exams/period_list.html +++ b/exams/templates/exams/period_list.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}시험기간 목록 - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
내 시험기간
+
진행 중인 시험기간을 이어가거나 새로 시작해보세요
+ + {% if not has_periods %} +
+
아직 등록된 시험기간이 없어요
+ 첫 시험기간 만들기 +
+ {% else %} +
+ + 새 시험기간 +
+ + {% if ongoing_periods %} +
진행 중
+ {% for period in ongoing_periods %} + +
+
+
{{ period.title }}
+
{{ period.start_date|date:"n/j" }} ~ + {{ period.end_date|date:"n/j" }}
+
+ + {{ period.get_status_display }} + +
+
+ {% endfor %} + {% endif %} + + {% if completed_periods %} +
지난 시험기간
+ {% for period in completed_periods %} + +
+
+
{{ period.title }}
+
{{ period.start_date|date:"n/j" }} ~ + {{ period.end_date|date:"n/j" }}
+
+ {{ period.get_status_display + }} +
+
+ {% endfor %} + {% endif %} + {% endif %} +
+{% endblock %} \ No newline at end of file From 445c1d557bca0affeb450358e125ec01bbfdfaae Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 13:53:21 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat:=20#54=20=EA=B3=BC=EB=AA=A9=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=C2=B7=EA=B0=80=EC=9A=A9=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=C2=B7=EC=8B=9C=ED=97=98=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/exams/available_time_form.html | 195 ++++++++++++++++++ exams/templates/exams/material_detail.html | 85 ++++++++ exams/templates/exams/material_form.html | 113 ++++++++++ exams/templates/exams/period_detail.html | 79 +++++++ exams/templates/exams/subject_form.html | 67 ++++++ exams/templatetags/__init__.py | 0 exams/templatetags/exam_extras.py | 60 ++++++ 7 files changed, 599 insertions(+) create mode 100644 exams/templatetags/__init__.py create mode 100644 exams/templatetags/exam_extras.py diff --git a/exams/templates/exams/available_time_form.html b/exams/templates/exams/available_time_form.html index e69de29..7c4546f 100644 --- a/exams/templates/exams/available_time_form.html +++ b/exams/templates/exams/available_time_form.html @@ -0,0 +1,195 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %}가용시간 입력 - PLAN B{% endblock %} +{% block extra_css %} + + +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_calendar_grid period.id as calendar_days %} + +
+
+ PLAN B +  ›  가용시간 입력 +
+
가용시간 입력하기
+
하루 전체에서 공부 가능한 시간을 날짜별로 입력해주세요
+ + {% if formset.non_form_errors %}
{{ formset.non_form_errors|striptags }}
{% endif %} + +
+ {% csrf_token %} + {{ formset.management_form }} + + +
+ {% for form in formset %} +
+ {{ form.id }}{{ form.date }}{{ form.hours }}{{ form.minutes }} +
+ {% endfor %} +
+ +
+
+
{{ period.start_date|date:"n월 j일" }} - {{ + period.end_date|date:"n월 j일" }}
+
+
+
+
+
+
+
+
+ + {% for day in calendar_days %} + {% if forloop.first %} + + {% else %} + + {% endif %} + {% endfor %} +
+
+ +
+
날짜를 선택하세요
+
선택한 날짜
+ +
전체 가용 시간
+ +
분 단위로 입력
+ + +
+
+ + +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/material_detail.html b/exams/templates/exams/material_detail.html index e69de29..cc39eee 100644 --- a/exams/templates/exams/material_detail.html +++ b/exams/templates/exams/material_detail.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}{{ material.title }} - PLAN B{% endblock %} +{% block extra_css %}{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
+ {{ material.exam.subject_name }}  ›  {{ material.title }} +
+
{{ material.title }}
+
{{ material.get_material_type_display }}
+ + + {% if material.material_type == 'pdf' %} +
+
텍스트 추출
+ + {% if material.status == 'completed' %} + 추출 완료 +
{{ material.extracted_text|truncatechars:600 }}
+ + {% elif material.status == 'failed' %} + 추출 실패 +
{{ material.error_message }}
+
+ {% csrf_token %} + +
+ + {% elif material.status == 'processing' %} + 추출 중... + + + {% else %} + 대기 중 +
+ {% csrf_token %} + +
+ {% endif %} +
+ {% endif %} + + + {% if material.status == 'completed' %} +
+
AI 학습 작업 분석
+ + {% if material.analysis_status == 'completed' %} + 분석 완료 + 학습 작업 확인하러 가기 + + {% elif material.analysis_status == 'processing' %} + 분석 중... +
잠시만 기다려주세요
+ + + {% elif material.analysis_status == 'failed' %} + 분석 실패 +
{{ material.analysis_error_message }}
+
재시도 {{ material.analysis_retry_count }}회 진행됨
+
+ {% csrf_token %} + +
+ + {% else %} +
+ {% csrf_token %} + +
+ {% endif %} +
+ {% endif %} + +
+ {% csrf_token %} + +
+
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/material_form.html b/exams/templates/exams/material_form.html index e69de29..4f22502 100644 --- a/exams/templates/exams/material_form.html +++ b/exams/templates/exams/material_form.html @@ -0,0 +1,113 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %}시험범위 등록 - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_period_exams exam.exam_period_id as period_exams %} + +
+
+ PLAN B  ›  시험범위 등록 +
+
시험범위 등록하기
+
과목별 시험범위를 알려주세요
+ + +
+ {% for e in period_exams %} + {{ + e.subject_name }} + {% endfor %} +
+ + {% if form.non_field_errors %}
{{ form.non_field_errors|striptags }}
{% endif %} + +
+ +
+ + +
+ +
+ {% csrf_token %} + + + +
+ {{ form.file }} +
파일을 끌어다 놓거나 클릭해서 + 업로드 · 텍스트 추출 가능한 PDF만 지원
+ {% if form.file.errors %}
{{ form.file.errors|striptags }}
{% endif %} +
+ + + + {{ form.material_type }} + +
+
+
+ + + + + + +{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/period_detail.html b/exams/templates/exams/period_detail.html index e69de29..b3d701c 100644 --- a/exams/templates/exams/period_detail.html +++ b/exams/templates/exams/period_detail.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}{{ period.title }} - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
+ PLAN B  ›  {{ + period.title }} +
+ +
+
+
{{ period.title }}
+
{{ period.start_date|date:"n월 j일" }} ~ {{ + period.end_date|date:"n월 j일" }}
+
+ 기간 수정 +
+ + +
+
+
날짜별 공부 가능시간
+
+ 총 {{ available_times|length }}일 등록됨 +
+
+ 가능시간 입력하기 +
+ + +
+
과목 ({{ exams|length }})
+ + 과목 추가 +
+ + {% if not exams %} +
+ 아직 등록된 과목이 없어요. 먼저 과목을 추가해주세요. +
+ {% endif %} + + {% for exam in exams %} +
+
+
+
{{ exam.subject_name }}
+
+ 시험일 {{ exam.exam_date|date:"n월 j일" }} · 우선순위 {{ exam.get_priority_display }} +
+
+
+ 수정 +
+ {% csrf_token %} + +
+
+
+ +
+ 시험범위 등록 + 학습 작업 확인 +
+
+ {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/subject_form.html b/exams/templates/exams/subject_form.html index e69de29..16eeaad 100644 --- a/exams/templates/exams/subject_form.html +++ b/exams/templates/exams/subject_form.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %}과목 등록 - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_period_exams period.id as period_exams %} + +
+
+ PLAN B +  ›  과목 등록 +
+
과목 등록하기
+
시험일과 우선순위를 입력하세요
+ + {% if form.non_field_errors %}
{{ form.non_field_errors|striptags }}
{% endif %} + +
+
+
+ {% csrf_token %} + +
+ + {{ form.subject_name }} + {% if form.subject_name.errors %}
{{ form.subject_name.errors|striptags }} +
{% endif %} +
+ +
+ + {{ form.exam_date }} + {% if form.exam_date.errors %}
{{ form.exam_date.errors|striptags }}
{% + endif %} +
+ +
+ + {{ form.priority }} +
+ + +
+
+ +
+
등록된 과목 {{ period_exams|length }}개
+ {% for exam in period_exams %} +
+ {{ exam.priority|slice:":1"|upper }} + {{ exam.subject_name }} + {{ exam.exam_date|date:"n/j" }} +
+ {% empty %} +
아직 등록된 과목이 없어요 +
+ {% endfor %} +
+
+ + 다음 +
+{% endblock %} \ No newline at end of file diff --git a/exams/templatetags/__init__.py b/exams/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/exams/templatetags/exam_extras.py b/exams/templatetags/exam_extras.py new file mode 100644 index 0000000..99fad69 --- /dev/null +++ b/exams/templatetags/exam_extras.py @@ -0,0 +1,60 @@ +""" +FE1(신예원) 전용 템플릿 태그. + +여기 있는 이유: +- subject_create, material_create, task_review 뷰가 '같은 시험기간의 다른 과목 목록'을 + context로 안 내려주고 있어서, Figma처럼 "등록된 과목 리스트"나 "과목 탭 전환"을 + 보여주려면 어딘가에서 그 데이터를 가져와야 함. +- views.py를 직접 고치는 대신(BE2 담당 파일), 프론트 표시 목적으로만 쓰는 조회 로직을 + 템플릿 태그로 분리해둠. 나중에 백엔드 뷰가 정식으로 context를 내려주게 되면 + 이 템플릿 태그 호출부만 지우면 되니까 되돌리기 쉬움. +- 여기서는 절대 데이터를 만들거나 바꾸지 않음(읽기 전용 조회만). +""" +from datetime import timedelta + +from django import template +from exams.models import Exam, AvailableTime, ExamPeriod + +register = template.Library() + + +@register.simple_tag +def get_period_exams(period_id): + """해당 시험기간에 등록된 과목 목록 (시험일 순).""" + return Exam.objects.filter(exam_period_id=period_id).order_by('exam_date') + + +@register.simple_tag +def get_calendar_grid(period_id): + """ + 가용시간 입력 화면의 캘린더 그리드용 데이터. + + 시험기간 시작일~종료일의 모든 날짜를 순서대로 돌면서, + - 시험일이면 is_exam_day=True (AvailableTime에 없는 날) + - 아니면 AvailableTime formset 안에서 몇 번째(index)에 해당하는지 계산 + + formset은 AvailableTime.objects.filter(exam_period=period).order_by('date') + 순서와 동일하다고 가정함 (모델 Meta.ordering = ['date']). + """ + period = ExamPeriod.objects.get(pk=period_id) + exam_dates = set( + Exam.objects.filter(exam_period_id=period_id).values_list('exam_date', flat=True) + ) + available_dates = list( + AvailableTime.objects.filter(exam_period_id=period_id) + .order_by('date') + .values_list('date', flat=True) + ) + date_to_index = {d: i for i, d in enumerate(available_dates)} + + days = [] + current = period.start_date + while current <= period.end_date: + days.append({ + 'date': current, + 'weekday': current.isoweekday(), # 1=월 ... 7=일 + 'is_exam_day': current in exam_dates, + 'formset_index': date_to_index.get(current), + }) + current += timedelta(days=1) + return days \ No newline at end of file From fd21ed7d607bfc319092c65c96b26e706ac6e783 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 14:04:32 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=20#54=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=ED=99=95=EC=9D=B8=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B6=94=EA=B0=80=C2=B7=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/exams/partials/_task_modal.html | 194 ++++++++++++++++++ exams/templates/exams/task_form.html | 59 ++++++ exams/templates/exams/task_review.html | 106 ++++++++++ static/js/task_api.js | 48 +++++ 4 files changed, 407 insertions(+) create mode 100644 exams/templates/exams/partials/_task_modal.html create mode 100644 static/js/task_api.js diff --git a/exams/templates/exams/partials/_task_modal.html b/exams/templates/exams/partials/_task_modal.html new file mode 100644 index 0000000..0602ec7 --- /dev/null +++ b/exams/templates/exams/partials/_task_modal.html @@ -0,0 +1,194 @@ +{# 사용법: task_review.html 에서 {% include "exams/partials/_task_modal.html" %} +API 명세서.pdf 계약대로 fetch 로 처리 (페이지 이동 없음). +주의: /exams/tasks/create/, /update/, /delete/ 는 문서상 "구현 상태: 연결 전" — +BE3가 실제 뷰를 만들기 전까진 호출 시 404 남. #} + + + + \ No newline at end of file diff --git a/exams/templates/exams/task_form.html b/exams/templates/exams/task_form.html index e69de29..c19811e 100644 --- a/exams/templates/exams/task_form.html +++ b/exams/templates/exams/task_form.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} +{% load static %} +{% block title %}작업 추가 - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} + +
+
+ {{ + exam.subject_name }} 학습 작업  ›  추가 +
+
학습 작업 추가하기
+
직접 작업을 추가할 수 있어요
+ + {% if form.non_field_errors %}
{{ form.non_field_errors|striptags }}
{% endif %} + +
+
+ {% csrf_token %} + +
+ + {{ form.unit_name }} +
+ +
+ + {{ form.title }} + {% if form.title.errors %}
{{ form.title.errors|striptags }}
{% endif %} +
+ +
+ + {{ form.task_type }} +
+ +
+
+ + {{ form.importance }} +
+
+ + {{ form.depth }} +
+
+ + {{ form.difficulty }} +
+
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/task_review.html b/exams/templates/exams/task_review.html index e69de29..5a08b33 100644 --- a/exams/templates/exams/task_review.html +++ b/exams/templates/exams/task_review.html @@ -0,0 +1,106 @@ +{% extends "base.html" %} +{% load static exam_extras %} +{% block title %}학습 작업 - PLAN B{% endblock %} +{% block extra_css %} +{% endblock %} + +{% block content %} +{% include "exams/_topbar.html" %} +{% get_period_exams exam.exam_period_id as period_exams %} + +
+
PLAN B  ›  과목 등록
+ +
+
학습 작업
+ +
+ +
+ +
+
+
과목
+ {% for e in period_exams %} + + {{ e.subject_name }} + {{ e.study_tasks.count }}개 + + {% endfor %} +
+
+ + +
+ {% if formset.non_form_errors %}
{{ formset.non_form_errors|striptags }}
{% + endif %} + +
+ {% csrf_token %} + {{ formset.management_form }} + + {% if not formset.forms %} +
+ 아직 학습 작업이 없어요. 시험범위 등록 화면에서 AI 분석을 먼저 실행해주세요. +
+ {% endif %} + + {% for form in formset %} +
+ + {{ form.id }} + +
+
+ {{ exam.subject_name + }} + {{ + form.instance.get_depth_display|default:form.instance.depth|upper }} +
+
+ {{ form.instance.unit_name + }} + {{ form.title }} +
+
{{ form.task_type }}{{ form.importance }}{{ form.depth }}{{ + form.difficulty }}{{ form.unit_name }}
+
+ +
+ {{ form.instance.estimated_min_minutes }}~{{ form.instance.estimated_max_minutes }}분 +
+ {% if form.DELETE %} + + {% endif %} +
+ {% endfor %} + + {% if formset.forms %} + + +
+ {% endif %} +
+ + {% if formset.forms %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/static/js/task_api.js b/static/js/task_api.js new file mode 100644 index 0000000..2359eca --- /dev/null +++ b/static/js/task_api.js @@ -0,0 +1,48 @@ +/** + * PLAN B - 학습작업 fetch API 클라이언트 + * API 명세서.pdf (생성일 2026-08-03) 계약 그대로 구현함. + * BE3가 /exams/tasks/create/, /exams/tasks//update/, /exams/tasks//delete/ + * 를 아직 연결 전이라고 문서에 적어놨어서, 이 파일은 그 API가 붙기 전까지는 + * 실제로 호출하면 404가 날 수 있음. URL/요청 형식은 문서와 100% 맞춰뒀음. + */ + +function getCsrfToken() { + const match = document.cookie.match(/csrftoken=([^;]+)/); + return match ? match[1] : ''; +} + +async function requestTaskApi(url, data = null) { + const options = { + method: 'POST', + headers: { + 'X-CSRFToken': getCsrfToken(), + 'Content-Type': 'application/json', + }, + }; + if (data !== null) { + options.body = JSON.stringify(data); + } + + const response = await fetch(url, options); + const body = await response.json(); + + if (!response.ok || !body.isSuccess) { + throw body; + } + return body.result; +} + +/** 학습작업 추가. exam_id 필수. */ +function createTask(payload) { + return requestTaskApi('/exams/tasks/create/', payload); +} + +/** 학습작업 수정. exam_id는 보내지 않음(수정 모달에서 과목은 읽기 전용). */ +function updateTask(taskId, payload) { + return requestTaskApi(`/exams/tasks/${taskId}/update/`, payload); +} + +/** 학습작업 삭제. */ +function deleteTask(taskId) { + return requestTaskApi(`/exams/tasks/${taskId}/delete/`); +} \ No newline at end of file From f1ae9a2ae05ba616989fcb1f59a0be950a8f7e98 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 14:20:33 +0900 Subject: [PATCH 05/20] =?UTF-8?q?fix:=20#54=20=ED=95=99=EC=8A=B5=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=88=98=EC=A0=95=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20?= =?UTF-8?q?=EC=8B=A4=EC=A0=9C=20=EB=B7=B0(formset=20=EC=9D=BC=EA=B4=84?= =?UTF-8?q?=EC=A0=80=EC=9E=A5)=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/exams/partials/_task_modal.html | 194 ------------------ exams/templates/exams/task_review.html | 86 ++++---- static/js/task_api.js | 48 ----- 3 files changed, 49 insertions(+), 279 deletions(-) delete mode 100644 exams/templates/exams/partials/_task_modal.html delete mode 100644 static/js/task_api.js diff --git a/exams/templates/exams/partials/_task_modal.html b/exams/templates/exams/partials/_task_modal.html deleted file mode 100644 index 0602ec7..0000000 --- a/exams/templates/exams/partials/_task_modal.html +++ /dev/null @@ -1,194 +0,0 @@ -{# 사용법: task_review.html 에서 {% include "exams/partials/_task_modal.html" %} -API 명세서.pdf 계약대로 fetch 로 처리 (페이지 이동 없음). -주의: /exams/tasks/create/, /update/, /delete/ 는 문서상 "구현 상태: 연결 전" — -BE3가 실제 뷰를 만들기 전까진 호출 시 404 남. #} - - - - \ No newline at end of file diff --git a/exams/templates/exams/task_review.html b/exams/templates/exams/task_review.html index 5a08b33..d71995d 100644 --- a/exams/templates/exams/task_review.html +++ b/exams/templates/exams/task_review.html @@ -9,17 +9,18 @@ {% get_period_exams exam.exam_period_id as period_exams %}
-
PLAN B  ›  과목 등록
+
PLAN B  ›  학습 작업
-
-
학습 작업
- +
+
+
학습 작업
+
수정 후 꼭 "변경사항 저장"을 눌러야 반영돼요
+
+ + 작업 추가하기
- +
과목
@@ -33,7 +34,7 @@
- +
{% if formset.non_form_errors %}
{{ formset.non_form_errors|striptags }}
{% endif %} @@ -44,47 +45,64 @@ {% if not formset.forms %}
- 아직 학습 작업이 없어요. 시험범위 등록 화면에서 AI 분석을 먼저 실행해주세요. + 아직 학습 작업이 없어요. 시험범위 등록 화면에서 AI 분석을 먼저 실행하거나, 직접 추가해주세요.
{% endif %} {% for form in formset %} -
- +
{{ form.id }} - -
-
- {{ exam.subject_name - }} - {{ - form.instance.get_depth_display|default:form.instance.depth|upper }} +
+
+ + {{ form.unit_name }}
-
- {{ form.instance.unit_name - }} +
+ {{ form.title }} + {% if form.title.errors %}
{{ form.title.errors|striptags }}
{% + endif %}
-
{{ form.task_type }}{{ form.importance }}{{ form.depth }}{{ - form.difficulty }}{{ form.unit_name }}
-
- {{ form.instance.estimated_min_minutes }}~{{ form.instance.estimated_max_minutes }}분 +
+
+ + {{ form.task_type }} +
+
+ + {{ form.importance }} +
+
+ + {{ form.depth }} +
+
+ + {{ form.difficulty }} +
+
+ {{ form.instance.estimated_min_minutes }}~{{ form.instance.estimated_max_minutes }}분 +
+ + {% if form.instance.is_confirmed %} + 확정됨 + {% endif %} + {% if form.DELETE %} -
{% endfor %} {% if formset.forms %} - - -
+ {% endif %} @@ -97,10 +115,4 @@
- - {% endblock %} \ No newline at end of file diff --git a/static/js/task_api.js b/static/js/task_api.js deleted file mode 100644 index 2359eca..0000000 --- a/static/js/task_api.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * PLAN B - 학습작업 fetch API 클라이언트 - * API 명세서.pdf (생성일 2026-08-03) 계약 그대로 구현함. - * BE3가 /exams/tasks/create/, /exams/tasks//update/, /exams/tasks//delete/ - * 를 아직 연결 전이라고 문서에 적어놨어서, 이 파일은 그 API가 붙기 전까지는 - * 실제로 호출하면 404가 날 수 있음. URL/요청 형식은 문서와 100% 맞춰뒀음. - */ - -function getCsrfToken() { - const match = document.cookie.match(/csrftoken=([^;]+)/); - return match ? match[1] : ''; -} - -async function requestTaskApi(url, data = null) { - const options = { - method: 'POST', - headers: { - 'X-CSRFToken': getCsrfToken(), - 'Content-Type': 'application/json', - }, - }; - if (data !== null) { - options.body = JSON.stringify(data); - } - - const response = await fetch(url, options); - const body = await response.json(); - - if (!response.ok || !body.isSuccess) { - throw body; - } - return body.result; -} - -/** 학습작업 추가. exam_id 필수. */ -function createTask(payload) { - return requestTaskApi('/exams/tasks/create/', payload); -} - -/** 학습작업 수정. exam_id는 보내지 않음(수정 모달에서 과목은 읽기 전용). */ -function updateTask(taskId, payload) { - return requestTaskApi(`/exams/tasks/${taskId}/update/`, payload); -} - -/** 학습작업 삭제. */ -function deleteTask(taskId) { - return requestTaskApi(`/exams/tasks/${taskId}/delete/`); -} \ No newline at end of file From 9495610947f62117a6cc5147bc681541f54116d6 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 19:08:12 +0900 Subject: [PATCH 06/20] =?UTF-8?q?style:=20#54=20=EA=B3=B5=ED=86=B5=20CSS?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC=20-=20fff=20=ED=95=98=EB=93=9C=EC=BD=94?= =?UTF-8?q?=EB=94=A9=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EB=B3=84=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/css/components.css | 816 ++++++++++++++++++++++++++++++++++---- 1 file changed, 736 insertions(+), 80 deletions(-) diff --git a/static/css/components.css b/static/css/components.css index 262a1ed..b498b39 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -2,115 +2,771 @@ forms.py 의 widget class(form-control, form-select 등)와 이름을 맞춰뒀음. tokens.css 의 변수만 사용 — 새 색상 임의로 추가하지 말 것. */ -.btn{ - display:inline-flex; align-items:center; justify-content:center; gap:6px; - height:44px; padding:0 20px; border-radius:10px; - background:var(--neutral11); color:#fff; font-size:14px; font-weight:600; - border:none; cursor:pointer; +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + height: 44px; + padding: 0 20px; + border-radius: 10px; + background: var(--neutral11); + color: var(--neutral1); + font-size: 14px; + font-weight: 600; + border: none; + cursor: pointer; } -.btn:hover{ background:var(--neutral10); } -.btn:disabled{ background:var(--neutral6); cursor:not-allowed; } -.btn.btn-brand{ background:var(--brand4); } -.btn.btn-brand:hover{ background:var(--brand5); } -.btn.btn-ghost{ background:#fff; color:var(--neutral11); border:1px solid var(--neutral5); } -.btn.btn-block{ width:100%; } -.card{ - background:var(--neutral1); border:1px solid var(--neutral4); border-radius:16px; - padding:24px 26px; +.btn:hover { + background: var(--neutral10); } -.form-group{ margin-bottom:16px; } -.form-label{ display:block; font-size:13px; font-weight:600; color:var(--neutral9); margin-bottom:6px; } +.btn:disabled { + background: var(--neutral6); + cursor: not-allowed; +} + +.btn.btn-brand { + background: var(--brand4); +} + +.btn.btn-brand:hover { + background: var(--brand5); +} + +.btn.btn-ghost { + background: var(--neutral1); + color: var(--neutral11); + border: 1px solid var(--neutral5); +} -.form-control, .form-select, .textarea-input{ - width:100%; height:44px; padding:0 14px; border-radius:10px; - border:1px solid var(--neutral5); background:var(--neutral2); - font-family:var(--ui); font-size:14px; color:var(--neutral10); +.btn.btn-block { + width: 100%; } -.textarea-input{ height:auto; padding:12px 14px; resize:vertical; } -.form-control:focus, .form-select:focus, .textarea-input:focus{ - outline:none; border-color:var(--brand4); background:#fff; + +.card { + background: var(--neutral1); + border: 1px solid var(--neutral4); + border-radius: 16px; + padding: 24px 26px; +} + +.form-group { + margin-bottom: 16px; +} + +.form-label { + display: block; + font-size: 13px; + font-weight: 600; + color: var(--neutral9); + margin-bottom: 6px; +} + +.form-control, +.form-select, +.textarea-input { + width: 100%; + height: 44px; + padding: 0 14px; + border-radius: 10px; + border: 1px solid var(--neutral5); + background: var(--neutral2); + font-family: var(--ui); + font-size: 14px; + color: var(--neutral10); } -.file-input{ padding:10px 14px; height:auto; background:var(--neutral2); } -.number-input{ text-align:center; } -.field-error{ color:var(--judge3); font-size:12px; margin-top:5px; } -.form-error-box{ - background:var(--judge3-bg); border:1px solid var(--judge3-line); color:var(--judge3); - border-radius:10px; padding:10px 14px; font-size:13px; margin-bottom:16px; +.textarea-input { + height: auto; + padding: 12px 14px; + resize: vertical; } -.page-title{ font-size:24px; font-weight:700; color:var(--neutral11); margin-bottom:6px; } -.page-sub{ font-size:13px; color:var(--neutral7); margin-bottom:20px; } +.form-control:focus, +.form-select:focus, +.textarea-input:focus { + outline: none; + border-color: var(--brand4); + background: var(--neutral1); +} + +.file-input { + padding: 10px 14px; + height: auto; + background: var(--neutral2); +} + +.number-input { + text-align: center; +} + +.field-error { + color: var(--judge3); + font-size: 12px; + margin-top: 5px; +} + +.form-error-box { + background: var(--judge3-bg); + border: 1px solid var(--judge3-line); + color: var(--judge3); + border-radius: 10px; + padding: 10px 14px; + font-size: 13px; + margin-bottom: 16px; +} + +.page-title { + font-size: 24px; + font-weight: 700; + color: var(--neutral11); + margin-bottom: 6px; +} + +.page-sub { + font-size: 13px; + color: var(--neutral7); + margin-bottom: 20px; +} /* 판정 뱃지 (가능/위험/불가능) — planner/feasibility 등에서 재사용 */ -.badge{ display:inline-flex; align-items:center; gap:5px; font-size:12px; font-weight:700; padding:4px 10px; border-radius:999px; } -.badge::before{ content:""; width:6px; height:6px; border-radius:50%; } -.badge.badge-possible{ background:var(--judge1-bg); color:var(--judge1); border:1px solid var(--judge1-line); } -.badge.badge-possible::before{ background:var(--judge1); } -.badge.badge-risky{ background:var(--judge2-bg); color:var(--judge2); border:1px solid var(--judge2-line); } -.badge.badge-risky::before{ background:var(--judge2); } -.badge.badge-impossible{ background:var(--judge3-bg); color:var(--judge3); border:1px solid var(--judge3-line); } -.badge.badge-impossible::before{ background:var(--judge3); } +.badge { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 12px; + font-weight: 700; + padding: 4px 10px; + border-radius: 999px; +} + +.badge::before { + content: ""; + width: 6px; + height: 6px; + border-radius: 50%; +} + +.badge.badge-possible { + background: var(--judge1-bg); + color: var(--judge1); + border: 1px solid var(--judge1-line); +} + +.badge.badge-possible::before { + background: var(--judge1); +} + +.badge.badge-risky { + background: var(--judge2-bg); + color: var(--judge2); + border: 1px solid var(--judge2-line); +} + +.badge.badge-risky::before { + background: var(--judge2); +} + +.badge.badge-impossible { + background: var(--judge3-bg); + color: var(--judge3); + border: 1px solid var(--judge3-line); +} + +.badge.badge-impossible::before { + background: var(--judge3); +} /* 과목 우선순위 원형 배지 (H/M/L) — Figma 브랜드 블루 계층 그대로 사용 */ -.priority-dot{ - width:26px; height:26px; border-radius:50%; flex-shrink:0; - display:flex; align-items:center; justify-content:center; - font-size:10.5px; font-weight:800; +.priority-dot { + width: 26px; + height: 26px; + border-radius: 50%; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 10.5px; + font-weight: 800; +} + +.priority-dot.priority-high { + background: var(--brand4); + color: var(--neutral1); +} + +.priority-dot.priority-medium { + background: var(--brand3); + color: var(--neutral10); +} + +.priority-dot.priority-low { + background: var(--brand2); + color: var(--neutral9); } -.priority-dot.priority-high{ background:var(--brand4); color:#fff; } -.priority-dot.priority-medium{ background:var(--brand3); color:var(--neutral10); } -.priority-dot.priority-low{ background:var(--brand2); color:var(--neutral9); } /* 탭바 — 실제로는 페이지 이동 링크()지만 시각적으로는 탭처럼 보이게 */ -.tabbar{ display:flex; gap:6px; margin-bottom:14px; flex-wrap:wrap; } -.tabbar a.tab{ - padding:8px 16px; border-radius:9px; background:var(--neutral2); color:var(--neutral8); - font-size:12px; font-weight:600; text-decoration:none; display:inline-block; +.tabbar { + display: flex; + gap: 6px; + margin-bottom: 14px; + flex-wrap: wrap; +} + +.tabbar a.tab { + padding: 8px 16px; + border-radius: 9px; + background: var(--neutral2); + color: var(--neutral8); + font-size: 12px; + font-weight: 600; + text-decoration: none; + display: inline-block; +} + +.tabbar a.tab.active { + background: var(--neutral11); + color: var(--neutral1); +} + +.auth-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.auth-card { + width: 380px; +} + +.auth-link { + text-align: center; + font-size: 13px; + color: var(--neutral8); + margin-top: 16px; +} + +.auth-link a { + color: var(--brand4); + font-weight: 700; + text-decoration: none; } -.tabbar a.tab.active{ background:var(--neutral11); color:#fff; } -.auth-wrap{ min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; } -.auth-card{ width:380px; } -.auth-link{ text-align:center; font-size:13px; color:var(--neutral8); margin-top:16px; } -.auth-link a{ color:var(--brand4); font-weight:700; text-decoration:none; } /* 2열 레이아웃 (subject_form 등) */ -.two-col{ display:flex; gap:20px; align-items:flex-start; } -.two-col > *{ flex:1; min-width:0; } -.grow{ flex:1; min-width:0; } +.two-col { + display: flex; + gap: 20px; + align-items: flex-start; +} + +.two-col>* { + flex: 1; + min-width: 0; +} + +.grow { + flex: 1; + min-width: 0; +} /* 리스트 카드 한 줄 (등록된 과목 등) */ -.list-card{ - display:flex; align-items:center; gap:10px; - padding:11px 14px; border-radius:11px; background:var(--neutral2); - margin-bottom:8px; font-size:12.5px; +.list-card { + display: flex; + align-items: center; + gap: 10px; + padding: 11px 14px; + border-radius: 11px; + background: var(--neutral2); + margin-bottom: 8px; + font-size: 12.5px; } /* 범용 필/탭 배지 — .tabbar 밖에서 단독으로도 씀 (task_review의 과목/깊이 태그 등) */ -.tab{ - padding:8px 16px; border-radius:9px; background:var(--neutral2); color:var(--neutral8); - font-size:12px; font-weight:600; display:inline-block; +.tab { + padding: 8px 16px; + border-radius: 9px; + background: var(--neutral2); + color: var(--neutral8); + font-size: 12px; + font-weight: 600; + display: inline-block; +} + +.tab.active { + background: var(--neutral11); + color: var(--neutral1); } -.tab.active{ background:var(--neutral11); color:#fff; } /* 세그먼트 선택형 (중요도/깊이/난이도) — 작업 수정 모달에서 사용 */ -.segment{ display:flex; gap:6px; } -.segment .opt{ - flex:1; text-align:center; padding:9px 0; border-radius:9px; - font-size:11.5px; font-weight:700; background:var(--neutral2); color:var(--neutral8); - border:none; cursor:pointer; font-family:var(--ui); +.segment { + display: flex; + gap: 6px; +} + +.segment .opt { + flex: 1; + text-align: center; + padding: 9px 0; + border-radius: 9px; + font-size: 11.5px; + font-weight: 700; + background: var(--neutral2); + color: var(--neutral8); + border: none; + cursor: pointer; + font-family: var(--ui); +} + +.segment .opt.sel { + background: var(--neutral11); + color: var(--neutral1); } -.segment .opt.sel{ background:var(--neutral11); color:#fff; } /* 모달 — 작업 수정 화면에서 사용 */ -.modal-backdrop{ - position:fixed; inset:0; background:rgba(20,25,35,.35); - display:none; align-items:center; justify-content:center; z-index:50; -} -.modal-backdrop.open{ display:flex; } -.modal{ - width:480px; max-width:92vw; background:#fff; border-radius:18px; - padding:30px 30px 26px; box-shadow:0 10px 40px rgba(0,0,0,.2); +.modal-backdrop { + position: fixed; + inset: 0; + background: rgba(20, 25, 35, .35); + display: none; + align-items: center; + justify-content: center; + z-index: 50; +} + +.modal-backdrop.open { + display: flex; +} + +.modal { + width: 480px; + max-width: 92vw; + background: var(--neutral1); + border-radius: 18px; + padding: 30px 30px 26px; + box-shadow: 0 10px 40px rgba(0, 0, 0, .2); +} + +/* 학습 작업 확인 화면 전용 */ +.task-review-wrap { + max-width: 1080px; + margin: 0 auto; + padding-bottom: 60px; +} + +.crumb-text { + font-size: 11.5px; + color: var(--neutral7); + margin-bottom: 10px; +} + +.task-review-header { + display: flex; + justify-content: space-between; + align-items: flex-end; + margin-bottom: 24px; +} + +.task-review-body { + display: flex; + gap: 24px; + align-items: flex-start; +} + +.task-nav-col { + flex: 0 0 200px; +} + +.task-nav-card { + padding: 18px; +} + +.task-nav-title { + font-size: 11px; + font-weight: 700; + color: var(--neutral7); + letter-spacing: 0.03em; + margin-bottom: 10px; +} + +.task-list-col { + flex: 1; + min-width: 0; +} + +.task-empty-card { + text-align: center; + padding: 48px 20px; + color: var(--neutral7); + font-size: 13px; +} + +.task-item-card { + margin-bottom: 14px; + padding: 22px 24px; + border-left: 3px solid var(--neutral5); +} + +.task-item-card.priority-high { + border-left-color: var(--brand4); +} + +.task-item-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.task-unit-label { + font-size: 11px; + font-weight: 700; + color: var(--neutral7); + letter-spacing: 0.02em; +} + +.task-readonly-input { + background: var(--neutral3) !important; + color: var(--neutral8) !important; + cursor: not-allowed; + margin-bottom: 14px; +} + +.task-field-group { + margin-bottom: 14px; +} + +.task-select-row { + display: flex; + gap: 10px; + margin-bottom: 16px; +} + +.task-select-item { + flex: 1; + min-width: 0; +} + +.task-item-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 14px; + border-top: 1px solid var(--neutral4); +} + +.task-time-pill { + background: var(--neutral2); + padding: 5px 12px; + border-radius: 999px; + font-size: 12.5px; + font-weight: 700; + color: var(--neutral10); +} + +.task-action-row { + display: flex; + gap: 10px; + margin-top: 4px; +} + +.task-action-row .btn { + flex: 1; +} + +.task-confirm-btn { + background: var(--neutral11); + color: var(--neutral1); +} + +.task-delete-hidden { + display: none; +} + +.delete-confirm-box { + width: 360px; + text-align: center; +} + +.delete-confirm-title { + font-size: 15px; + font-weight: 700; + margin-bottom: 8px; +} + +.delete-confirm-body { + font-size: 13px; + color: var(--neutral7); + margin-bottom: 20px; +} + +.delete-confirm-actions { + display: flex; + gap: 8px; +} + +.delete-confirm-actions .btn { + flex: 1; +} + +/* 시험범위 등록 화면 - Figma 6페이지 레이아웃 맞춤 */ +.material-form-wrap { + max-width: 900px; + margin: 0 auto; + padding-bottom: 60px; +} + +.material-form-card { + padding: 32px; +} + +.material-upload-panel input[type="file"] { + display: none; +} + +.material-upload-box { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + min-height: 280px; + border: 1.5px dashed var(--neutral5); + border-radius: 14px; + background: var(--neutral2); + cursor: pointer; +} + +.material-upload-icon { + font-size: 30px; + color: var(--neutral6); +} + +.material-upload-text { + font-size: 13px; + color: var(--neutral8); +} + +.material-upload-hint { + font-size: 11.5px; + color: var(--neutral7); + text-align: center; + margin-top: 10px; +} + +#text-panel textarea { + width: 100%; + min-height: 280px; + border: 1.5px dashed var(--neutral5); + border-radius: 14px; + background: var(--neutral2); + padding: 16px; + font-family: var(--ui); + font-size: 13px; + color: var(--neutral10); + resize: vertical; +} + +.material-submit-btn { + margin-top: 20px; + height: 48px; +} + +/* 학습자료 상세(추출/분석 자동 진행) 화면 */ +.material-detail-wrap { + max-width: 560px; + margin: 0 auto; + padding-bottom: 60px; +} + +.section-label { + font-size: 13px; + font-weight: 700; + margin-bottom: 10px; +} + +.extracted-text-box { + margin-top: 10px; + max-height: 140px; + overflow-y: auto; + font-size: 12px; + color: var(--neutral8); + background: var(--neutral2); + border-radius: 8px; + padding: 10px 12px; + white-space: pre-wrap; +} + +.delete-material-btn { + color: var(--judge3); + border-color: var(--judge3-line); +} + +.hidden-auto-form { + display: none; +} + +.auto-loading-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(20, 25, 35, 0.4); + align-items: center; + justify-content: center; + z-index: 60; +} + +.auto-loading-overlay.open { + display: flex; +} + +.auto-loading-box { + width: 320px; + text-align: center; +} + +.auto-loading-spinner { + width: 44px; + height: 44px; + border-radius: 50%; + border: 4px solid var(--neutral4); + border-top-color: var(--brand4); + margin: 0 auto 16px; + animation: spin 0.9s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.auto-loading-text { + font-size: 13px; + font-weight: 700; +} + +.auto-loading-sub { + font-size: 11.5px; + color: var(--neutral7); + margin-top: 4px; +} + +/* 가용시간 입력 화면 (Figma 5페이지 - 캘린더 + 사이드패널) */ +.avail-wrap { + max-width: 900px; + margin: 0 auto; + padding-bottom: 60px; +} + +.avail-body { + display: flex; + gap: 20px; + align-items: flex-start; +} + +.avail-cal-col { + flex: 1.6; +} + +.avail-cal-title { + font-size: 14px; + font-weight: 700; + margin-bottom: 4px; +} + +.avail-cal-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 8px; + margin-top: 14px; +} + +.avail-weekday { + font-size: 11px; + color: var(--neutral7); + text-align: center; + padding-bottom: 2px; +} + +.avail-day { + height: 46px; + border-radius: 9px; + background: var(--neutral2); + border: none; + font-size: 12px; + color: var(--neutral9); + font-family: var(--mono); + display: flex; + align-items: flex-start; + justify-content: flex-start; + padding: 6px; + cursor: pointer; + position: relative; +} + +.avail-day.exam-day { + background: var(--neutral3); + color: var(--neutral6); + cursor: not-allowed; +} + +.avail-day.has-value::after { + content: ""; + position: absolute; + bottom: 5px; + right: 5px; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--judge1); +} + +.avail-day.sel { + background: var(--brand4); + color: var(--neutral1); +} + +.avail-side-col { + flex: 1; + position: sticky; + top: 16px; +} + +.avail-side-col.disabled { + opacity: 0.45; + pointer-events: none; +} + +.avail-side-date { + font-size: 15px; + font-weight: 700; + margin-bottom: 2px; +} + +.avail-side-caption { + font-size: 11.5px; + color: var(--neutral7); + margin-bottom: 16px; +} + +.avail-side-hint { + font-size: 11px; + color: var(--neutral7); + margin-top: 4px; +} + +.avail-save-btn { + margin-top: 14px; +} + +.avail-next-btn { + margin-top: 18px; +} + +.formset-hidden { + display: none; } \ No newline at end of file From 3c3b55d636ceed99595ac10adbb6a41bbe24c020 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 19:08:19 +0900 Subject: [PATCH 07/20] =?UTF-8?q?fix:=20#54=20=EA=B0=80=EC=9A=A9=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=9E=85=EB=A0=A5=20=ED=99=94=EB=A9=B4=20=EC=9D=B8?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/exams/available_time_form.html | 226 ++++++++---------- 1 file changed, 97 insertions(+), 129 deletions(-) diff --git a/exams/templates/exams/available_time_form.html b/exams/templates/exams/available_time_form.html index 7c4546f..53f7681 100644 --- a/exams/templates/exams/available_time_form.html +++ b/exams/templates/exams/available_time_form.html @@ -1,191 +1,159 @@ {% extends "base.html" %} {% load static exam_extras %} -{% block title %}가용시간 입력 - PLAN B{% endblock %} +{% block title %} +가용시간 입력 - PLAN B +{% endblock %} + {% block extra_css %} - {% endblock %} {% block content %} {% include "exams/_topbar.html" %} {% get_calendar_grid period.id as calendar_days %} -
-
- PLAN B +
+
+ PLAN B  ›  가용시간 입력
-
가용시간 입력하기
-
하루 전체에서 공부 가능한 시간을 날짜별로 입력해주세요
+
+ 가용시간 입력하기 +
+
+ 하루 전체에서 공부 가능한 시간을 날짜별로 입력해주세요 +
- {% if formset.non_form_errors %}
{{ formset.non_form_errors|striptags }}
{% endif %} + {% if formset.non_form_errors %} +
+ {{ formset.non_form_errors|striptags }} +
+ {% endif %}
{% csrf_token %} {{ formset.management_form }} -
{% for form in formset %}
- {{ form.id }}{{ form.date }}{{ form.hours }}{{ form.minutes }} + {{ form.id }} + {{ form.date }} + {{ form.hours }} + {{ form.minutes }}
{% endfor %}
-
-
-
{{ period.start_date|date:"n월 j일" }} - {{ - period.end_date|date:"n월 j일" }}
-
-
-
-
-
-
-
-
+
+
+
+ {{ period.start_date|date:"n월 j일" }} - + {{ period.end_date|date:"n월 j일" }} +
+ +
+
+
+
+
+
+
+
{% for day in calendar_days %} {% if forloop.first %} - + {% if day.is_exam_day %} + {% else %} - + + {% endif %} + {% else %} + {% if day.is_exam_day %} + + {% else %} + + {% endif %} {% endif %} {% endfor %}
-
-
날짜를 선택하세요
-
선택한 날짜
+
+
+ 날짜를 선택하세요 +
+
선택한 날짜
전체 가용 시간
-
분 단위로 입력
+
분 단위로 입력
- +
- +
{% endblock %} \ No newline at end of file From 17905bf7b60c1821ff9535e60ea05578c81d1470 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 19:08:35 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20#54=20=ED=95=99=EC=8A=B5=EC=9E=90?= =?UTF-8?q?=EB=A3=8C=20=EC=83=81=EC=84=B8=20=ED=99=94=EB=A9=B4=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=C2=B7=EB=B6=84=EC=84=9D=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=ED=9D=90=EB=A6=84=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exams/templates/exams/material_detail.html | 232 +++++++++++++++------ 1 file changed, 169 insertions(+), 63 deletions(-) diff --git a/exams/templates/exams/material_detail.html b/exams/templates/exams/material_detail.html index cc39eee..1c10843 100644 --- a/exams/templates/exams/material_detail.html +++ b/exams/templates/exams/material_detail.html @@ -1,85 +1,191 @@ {% extends "base.html" %} {% load static %} -{% block title %}{{ material.title }} - PLAN B{% endblock %} -{% block extra_css %}{% endblock %} +{% block title %} +{{ material.title }} - PLAN B +{% endblock %} + +{% block extra_css %} + +{% endblock %} {% block content %} {% include "exams/_topbar.html" %} -
-
+
+
{{ material.exam.subject_name }}  ›  {{ material.title }}
-
{{ material.title }}
-
{{ material.get_material_type_display }}
+
+ {{ material.title }} +
+
+ {{ material.get_material_type_display }} +
- {% if material.material_type == 'pdf' %} -
-
텍스트 추출
- - {% if material.status == 'completed' %} - 추출 완료 -
{{ material.extracted_text|truncatechars:600 }}
- - {% elif material.status == 'failed' %} - 추출 실패 -
{{ material.error_message }}
-
- {% csrf_token %} - -
- - {% elif material.status == 'processing' %} - 추출 중... - - - {% else %} - 대기 중 -
- {% csrf_token %} - -
- {% endif %} +
+ + + {% if material.status == 'completed' %} + 추출 완료 +
+ {{ material.extracted_text|truncatechars:600 }}
+ + {% elif material.status == 'failed' %} + 추출 실패 +
+ {{ material.error_message }} +
+
+ {% csrf_token %} + +
+ + {% elif material.status == 'processing' %} + 추출 중... + + {% else %} + + 대기 중 + + {% endif %} +
{% endif %} - {% if material.status == 'completed' %} -
-
AI 학습 작업 분석
- - {% if material.analysis_status == 'completed' %} - 분석 완료 - 학습 작업 확인하러 가기 - - {% elif material.analysis_status == 'processing' %} - 분석 중... -
잠시만 기다려주세요
- - - {% elif material.analysis_status == 'failed' %} - 분석 실패 -
{{ material.analysis_error_message }}
-
재시도 {{ material.analysis_retry_count }}회 진행됨
-
- {% csrf_token %} - -
- - {% else %} -
- {% csrf_token %} - -
- {% endif %} +
+ + + {% if material.analysis_status == 'completed' %} + 분석 완료 + + 학습 작업 확인하러 가기 + + + {% elif material.analysis_status == 'processing' %} + 분석 중... + + {% elif material.analysis_status == 'failed' %} + 분석 실패 +
+ {{ material.analysis_error_message }} +
+
+ 재시도 {{ material.analysis_retry_count }}회 진행됨
+
+ {% csrf_token %} + +
+ + {% else %} + + 대기 중 + + {% endif %} +
{% endif %}
+ onsubmit="return confirm('이 학습자료를 삭제할까요?');" style="margin-top:14px;"> {% csrf_token %} - +
+ +
+
+
+
+ 처리 중이에요 +
+
잠시만 기다려주세요
+
+
+ +
+ {% csrf_token %} +
+ +
+ {% csrf_token %} +
+ + {% endblock %} \ No newline at end of file From 071841e7578501684f436692750b5cf4983439bd Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 19:08:42 +0900 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20#54=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=ED=99=94=EB=A9=B4=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=9D=EC=97=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exams/templates/exams/task_review.html | 193 ++++++++++++++++++------- 1 file changed, 137 insertions(+), 56 deletions(-) diff --git a/exams/templates/exams/task_review.html b/exams/templates/exams/task_review.html index d71995d..d4023f3 100644 --- a/exams/templates/exams/task_review.html +++ b/exams/templates/exams/task_review.html @@ -1,118 +1,199 @@ {% extends "base.html" %} {% load static exam_extras %} -{% block title %}학습 작업 - PLAN B{% endblock %} +{% block title %} +학습 작업 - PLAN B +{% endblock %} + {% block extra_css %} -{% endblock %} + +{% endblock %} {% block content %} {% include "exams/_topbar.html" %} {% get_period_exams exam.exam_period_id as period_exams %} -
-
PLAN B  ›  학습 작업
+
+
+ PLAN B  ›  학습 작업 +
-
+
-
학습 작업
-
수정 후 꼭 "변경사항 저장"을 눌러야 반영돼요
+
+ 학습 작업 +
+
+ {{ exam.subject_name }} · 총 {{ formset.forms|length }}개 +
- + 작업 추가하기 + + + 작업 추가하기 +
-
- -
-
-
과목
+
+ - -
- {% if formset.non_form_errors %}
{{ formset.non_form_errors|striptags }}
{% - endif %} +
+ {% if formset.non_form_errors %} +
+ {{ formset.non_form_errors|striptags }} +
+ {% endif %} -
+ {% csrf_token %} {{ formset.management_form }} {% if not formset.forms %} -
- 아직 학습 작업이 없어요. 시험범위 등록 화면에서 AI 분석을 먼저 실행하거나, 직접 추가해주세요. +
+ 아직 학습 작업이 없어요. 시험범위 등록 화면에서 + AI 분석을 먼저 실행하거나, 직접 추가해주세요.
{% endif %} {% for form in formset %} -
+
{{ form.id }} -
-
- - {{ form.unit_name }} + +
+
+ {{ form.instance.unit_name|default:"단원 미지정" }}
-
- - {{ form.title }} - {% if form.title.errors %}
{{ form.title.errors|striptags }}
{% - endif %} + {% if form.instance.is_confirmed %} + 확정됨 + {% endif %} +
+ + {# 단원명: 읽기 전용 — 값은 그대로 formset에 전송되지만 수정은 못 하게 처리 #} + + +
+ + {{ form.title }} + {% if form.title.errors %} +
+ {{ form.title.errors|striptags }}
+ {% endif %}
-
-
+
+
{{ form.task_type }}
-
+
{{ form.importance }}
-
+
{{ form.depth }}
-
+
{{ form.difficulty }}
-
- {{ form.instance.estimated_min_minutes }}~{{ form.instance.estimated_max_minutes }}분 -
- {% if form.instance.is_confirmed %} - 확정됨 - {% endif %} + {% if form.DELETE %} - +
+ {{ form.DELETE }} +
{% endif %}
{% endfor %} {% if formset.forms %} - +
+ + +
{% endif %} +
+
+
- {% if formset.forms %} -
- {% csrf_token %} - -
- {% endif %} + + + {% endblock %} \ No newline at end of file From ae5ba8dfb9d82b2e4595b0f86d5848a51eeeff4b Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 19:08:51 +0900 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20#54=20=EA=B3=BC=EB=AA=A9=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=EB=B2=84=ED=8A=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exams/templates/exams/subject_form.html | 128 +++++++++++++++--------- 1 file changed, 81 insertions(+), 47 deletions(-) diff --git a/exams/templates/exams/subject_form.html b/exams/templates/exams/subject_form.html index 16eeaad..dec461e 100644 --- a/exams/templates/exams/subject_form.html +++ b/exams/templates/exams/subject_form.html @@ -1,67 +1,101 @@ {% extends "base.html" %} {% load static exam_extras %} -{% block title %}과목 등록 - PLAN B{% endblock %} +{% block title %} +과목 등록 - PLAN B +{% endblock %} + {% block extra_css %} -{% endblock %} + +{% endblock %} {% block content %} {% include "exams/_topbar.html" %} {% get_period_exams period.id as period_exams %}
-
- PLAN B -  ›  과목 등록 -
-
과목 등록하기
-
시험일과 우선순위를 입력하세요
- - {% if form.non_field_errors %}
{{ form.non_field_errors|striptags }}
{% endif %} - -
-
-
- {% csrf_token %} +
+ + PLAN B + +  ›  과목 등록 +
+
+ 과목 등록하기 +
+
+ 시험일과 우선순위를 입력하세요 +
-
- - {{ form.subject_name }} - {% if form.subject_name.errors %}
{{ form.subject_name.errors|striptags }} -
{% endif %} -
+ {% if form.non_field_errors %} +
+ {{ form.non_field_errors|striptags }} +
+ {% endif %} -
- - {{ form.exam_date }} - {% if form.exam_date.errors %}
{{ form.exam_date.errors|striptags }}
{% - endif %} -
+
+
+ + {% csrf_token %} -
- - {{ form.priority }} -
+
+ + {{ form.subject_name }} + {% if form.subject_name.errors %} +
+ {{ form.subject_name.errors|striptags }} +
+ {% endif %} +
- - +
+ + {{ form.exam_date }} + {% if form.exam_date.errors %} +
+ {{ form.exam_date.errors|striptags }} +
+ {% endif %}
-
-
등록된 과목 {{ period_exams|length }}개
- {% for exam in period_exams %} -
- {{ exam.priority|slice:":1"|upper }} - {{ exam.subject_name }} - {{ exam.exam_date|date:"n/j" }} -
- {% empty %} -
아직 등록된 과목이 없어요 -
- {% endfor %} +
+ + {{ form.priority }}
+ + +
- 다음 +
+
+ 등록된 과목 {{ period_exams|length }}개 +
+ {% for exam in period_exams %} +
+ + {{ exam.priority|slice:":1"|upper }} + + + {{ exam.subject_name }} + + + {{ exam.exam_date|date:"n/j" }} + +
+ {% empty %} +
+ 아직 등록된 과목이 없어요 +
+ {% endfor %} +
+
{% endblock %} \ No newline at end of file From 0543c34de6279534123681e0405a0d9f51792d05 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 19:09:05 +0900 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20#54=20=ED=85=9C=ED=94=8C=EB=A6=BF?= =?UTF-8?q?=20=EB=AC=B8=EB=B2=95=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20(=EC=A4=84=EB=B0=94=EA=BF=88=20=EA=B9=A8=EC=A7=90=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/templates/accounts/signup.html | 67 ++++++++++++++++++------- exams/templates/exams/_topbar.html | 8 +-- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/accounts/templates/accounts/signup.html b/accounts/templates/accounts/signup.html index 74921e3..9b163fa 100644 --- a/accounts/templates/accounts/signup.html +++ b/accounts/templates/accounts/signup.html @@ -1,6 +1,9 @@ {% extends "base.html" %} {% load static %} -{% block title %}회원가입 - PLAN B{% endblock %} +{% block title %} +회원가입 - PLAN B +{% endblock %} + {% block extra_css %} {% endblock %} @@ -8,48 +11,78 @@ {% block content %}
-
PLAN B 시작하기
-
시험 계획을 함께 관리해요
+
+ PLAN B 시작하기 +
+
+ 시험 계획을 함께 관리해요 +
{% if form.non_field_errors %} -
{{ form.non_field_errors|striptags }}
+
+ {{ form.non_field_errors|striptags }} +
{% endif %}
{% csrf_token %}
- + {{ form.email }} - {% if form.email.errors %}
{{ form.email.errors|striptags }}
{% endif %} + {% if form.email.errors %} +
+ {{ form.email.errors|striptags }} +
+ {% endif %}
- + {{ form.nickname }} - {% if form.nickname.errors %}
{{ form.nickname.errors|striptags }}
{% endif - %} + {% if form.nickname.errors %} +
+ {{ form.nickname.errors|striptags }} +
+ {% endif %}
- + {{ form.password1 }} - {% if form.password1.errors %}
{{ form.password1.errors|striptags }}
{% - endif %} + {% if form.password1.errors %} +
+ {{ form.password1.errors|striptags }} +
+ {% endif %}
- + {{ form.password2 }} - {% if form.password2.errors %}
{{ form.password2.errors|striptags }}
{% - endif %} + {% if form.password2.errors %} +
+ {{ form.password2.errors|striptags }} +
+ {% endif %}
- +
diff --git a/exams/templates/exams/_topbar.html b/exams/templates/exams/_topbar.html index 9b0b03e..6912b03 100644 --- a/exams/templates/exams/_topbar.html +++ b/exams/templates/exams/_topbar.html @@ -1,11 +1,11 @@ -{# 사용법: {% include "exams/_topbar.html" %} - accounts:logout 은 POST 전용 뷰라서 폼으로 감싸야 함 (accounts/urls.py 참고) #}
- PLAN B + + PLAN B +
{% csrf_token %}
-
\ No newline at end of file +
From b077121606497bcb2f569cf2ea02661c491f3c62 Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 22:51:33 +0900 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20#54=20=ED=95=99=EC=8A=B5=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=B6=94=EA=B0=80=20=ED=99=94=EB=A9=B4=20=EC=A0=9C?= =?UTF-8?q?=EB=AA=A9=20=EC=83=81=EB=8B=A8=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B9=A8=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exams/templates/exams/task_form.html | 132 +++++++++++++++++---------- 1 file changed, 82 insertions(+), 50 deletions(-) diff --git a/exams/templates/exams/task_form.html b/exams/templates/exams/task_form.html index c19811e..680c96a 100644 --- a/exams/templates/exams/task_form.html +++ b/exams/templates/exams/task_form.html @@ -1,59 +1,91 @@ {% extends "base.html" %} {% load static %} -{% block title %}작업 추가 - PLAN B{% endblock %} +{% block title %} +작업 추가 - PLAN B +{% endblock %} + {% block extra_css %} -{% endblock %} + +{% endblock %} {% block content %} {% include "exams/_topbar.html" %} -
- -
학습 작업 추가하기
-
직접 작업을 추가할 수 있어요
- - {% if form.non_field_errors %}
{{ form.non_field_errors|striptags }}
{% endif %} - -
-
- {% csrf_token %} - -
- - {{ form.unit_name }} -
- -
- - {{ form.title }} - {% if form.title.errors %}
{{ form.title.errors|striptags }}
{% endif %} -
- -
- - {{ form.task_type }} -
- -
-
- - {{ form.importance }} -
-
- - {{ form.depth }} -
-
- - {{ form.difficulty }} -
-
- - -
-
+
+ +
+ 학습 작업 추가하기 +
+
+ 직접 작업을 추가할 수 있어요 +
+ + {% if form.non_field_errors %} +
+ {{ form.non_field_errors|striptags }} +
+ {% endif %} + +
+
+ {% csrf_token %} + +
+ + {{ form.unit_name }} +
+ +
+ + {{ form.title }} + {% if form.title.errors %} +
+ {{ form.title.errors|striptags }} +
+ {% endif %} +
+ +
+ + {{ form.task_type }} +
+ +
+
+ + {{ form.importance }} +
+
+ + {{ form.depth }} +
+
+ + {{ form.difficulty }} +
+
+ + +
+
{% endblock %} \ No newline at end of file From 82d6de9582811b245ae85b914caac927e4aa643d Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 22:51:40 +0900 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20#54=20=EA=B0=80=EC=9A=A9=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=9E=85=EB=A0=A5=20=ED=99=94=EB=A9=B4=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20=EC=9E=85=EB=A0=A5=EA=B0=92=20=ED=91=9C=EC=8B=9C(?= =?UTF-8?q?=EC=B4=88=EB=A1=9D=EC=A0=90)=20=EC=9C=A0=EC=A7=80=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/exams/available_time_form.html | 281 +++++++++--------- exams/templatetags/exam_extras.py | 8 +- 2 files changed, 151 insertions(+), 138 deletions(-) diff --git a/exams/templates/exams/available_time_form.html b/exams/templates/exams/available_time_form.html index 53f7681..570bb2e 100644 --- a/exams/templates/exams/available_time_form.html +++ b/exams/templates/exams/available_time_form.html @@ -13,151 +13,162 @@ {% get_calendar_grid period.id as calendar_days %}
-
- PLAN B -  ›  가용시간 입력 -
-
- 가용시간 입력하기 -
-
- 하루 전체에서 공부 가능한 시간을 날짜별로 입력해주세요 +
+ PLAN B +  ›  가용시간 입력 +
+
+ 가용시간 입력하기 +
+
+ 하루 전체에서 공부 가능한 시간을 날짜별로 입력해주세요 +
+ + {% if formset.non_form_errors %} +
+ {{ formset.non_form_errors|striptags }} +
+ {% endif %} + +
+ {% csrf_token %} + {{ formset.management_form }} + +
+ {% for form in formset %} +
+ {{ form.id }} + {{ form.date }} + {{ form.hours }} + {{ form.minutes }} +
+ {% endfor %}
- {% if formset.non_form_errors %} -
- {{ formset.non_form_errors|striptags }} -
- {% endif %} - - - {% csrf_token %} - {{ formset.management_form }} - -
- {% for form in formset %} -
- {{ form.id }} - {{ form.date }} - {{ form.hours }} - {{ form.minutes }} -
- {% endfor %} +
+
+
+ {{ period.start_date|date:"n월 j일" }} - + {{ period.end_date|date:"n월 j일" }}
-
-
-
- {{ period.start_date|date:"n월 j일" }} - - {{ period.end_date|date:"n월 j일" }} -
- -
-
-
-
-
-
-
-
- - {% for day in calendar_days %} - {% if forloop.first %} - {% if day.is_exam_day %} - - {% else %} - - {% endif %} - {% else %} - {% if day.is_exam_day %} - - {% else %} - - {% endif %} - {% endif %} - {% endfor %} -
-
- -
-
- 날짜를 선택하세요 -
-
선택한 날짜
- -
전체 가용 시간
- -
분 단위로 입력
- - -
+
+
+
+
+
+
+
+
+ + {% for day in calendar_days %} + {% if forloop.first %} + {% if day.is_exam_day %} + + {% else %} + + {% endif %} + {% else %} + {% if day.is_exam_day %} + + {% else %} + + {% endif %} + {% endif %} + {% endfor %}
+
- - +
+
+ + +
{% endblock %} \ No newline at end of file diff --git a/exams/templatetags/exam_extras.py b/exams/templatetags/exam_extras.py index 99fad69..fc64c73 100644 --- a/exams/templatetags/exam_extras.py +++ b/exams/templatetags/exam_extras.py @@ -40,12 +40,13 @@ def get_calendar_grid(period_id): exam_dates = set( Exam.objects.filter(exam_period_id=period_id).values_list('exam_date', flat=True) ) - available_dates = list( + available_rows = list( AvailableTime.objects.filter(exam_period_id=period_id) .order_by('date') - .values_list('date', flat=True) + .values_list('date', 'available_minutes') ) - date_to_index = {d: i for i, d in enumerate(available_dates)} + date_to_index = {d: i for i, (d, _) in enumerate(available_rows)} + date_to_minutes = {d: m for d, m in available_rows} days = [] current = period.start_date @@ -55,6 +56,7 @@ def get_calendar_grid(period_id): 'weekday': current.isoweekday(), # 1=월 ... 7=일 'is_exam_day': current in exam_dates, 'formset_index': date_to_index.get(current), + 'has_value': date_to_minutes.get(current, 0) > 0, }) current += timedelta(days=1) return days \ No newline at end of file From 5fde07c93b2e32303aabcac9ad6f426ccd1986ea Mon Sep 17 00:00:00 2001 From: daynight0219-ux Date: Wed, 5 Aug 2026 22:52:17 +0900 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20#54=20=EC=8B=9C=ED=97=98=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EB=93=B1=EB=A1=9D=20=EC=97=90=EB=9F=AC=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EC=B6=94=EC=B6=9C?= =?UTF-8?q?=C2=B7=EB=B6=84=EC=84=9D=20=EC=9E=90=EB=8F=99=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EB=A1=9C=EC=A7=81=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exams/templates/exams/material_detail.html | 155 ++++++++++++--------- exams/templates/exams/material_form.html | 16 ++- 2 files changed, 96 insertions(+), 75 deletions(-) diff --git a/exams/templates/exams/material_detail.html b/exams/templates/exams/material_detail.html index 1c10843..599e8c4 100644 --- a/exams/templates/exams/material_detail.html +++ b/exams/templates/exams/material_detail.html @@ -15,7 +15,7 @@
{{ material.exam.subject_name }}  ›  {{ material.title }}
-
+
{{ material.title }}
@@ -23,7 +23,7 @@
{% if material.material_type == 'pdf' %} -
+
{% if material.status == 'completed' %} @@ -56,7 +56,7 @@ {% endif %} {% if material.status == 'completed' %} -
+
{% if material.analysis_status == 'completed' %} @@ -92,7 +92,7 @@ {% endif %}
+ onsubmit="return confirm('이 학습자료를 삭제할까요?');" style="margin-top:14px;"> {% csrf_token %} +
- + {% csrf_token %} -
+ {% csrf_token %}
{% endblock %} \ No newline at end of file diff --git a/exams/templates/exams/material_form.html b/exams/templates/exams/material_form.html index 30cbaf3..d76f248 100644 --- a/exams/templates/exams/material_form.html +++ b/exams/templates/exams/material_form.html @@ -58,8 +58,15 @@ {{ form.material_type }}
+ {% if form.file.errors %} +
+ {{ form.file.errors|striptags }} +
+ {% endif %} +
-