Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions planner/templates/planner/includes/recovery_card.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{% comment %}
복구안 카드.
{% include "planner/includes/recovery_card.html" with plan=p selected=True %}

plan 에 필요한 값
id RecoveryPlan PK (라디오 value, 적용 대상 특정용)
recovery_type maintain_volume / core_focus
type_label 분량 유지형 / 핵심 집중형
summary 한 줄 설명. RecoveryPlan.summary는 생성 시 저장되지 않으므로
모델 필드를 그대로 쓰지 말고 View에서 구성해서 넘길 것
feasibility_status ok / warn / bad (적용 후 판정)
feasibility_status_label 지금 가능 / 추가 시간 필요 / ...
min_pct band_pct mark_pct need_label_pct Fit Bar 좌표
min_minutes max_minutes available_minutes axis_max
excluded_count excluded_minutes
total_minutes daily_average_minutes daily_diff
extra_minutes_needed 0이면 '없음'
excluded_tasks[] subject_name / title / depth / importance / minutes

주의: RecoveryPlan 모델에도 status 필드가 있지만 값이 다릅니다
(pending/applied/discarded). 여기 feasibility_status는 그것과 별개로
"적용 후 가능성 판정" 값이라 이름을 분리했습니다. 모델 인스턴스를
그대로 넘기지 말고 위 이름들로 매핑해서 넘겨주세요.
{% endcomment %}

<label class="rec-card {% if selected %}on{% endif %}">
<input type="radio" name="recovery" value="{{ plan.id }}"
class="sr-only js-rec" {% if selected %}checked{% endif %}>

<div class="rec-head">
<div class="kind num">{{ plan.recovery_type|upper }}</div>
<div class="row" style="justify-content:space-between">
<h3>{{ plan.type_label }}</h3>
<span class="badge badge-{{ plan.feasibility_status }}">
<i class="dot dot-{{ plan.feasibility_status }}"></i>{{ plan.feasibility_status_label }}
</span>
</div>
<p>{{ plan.summary }}</p>
</div>

<div class="rec-body">
{% include "planner/includes/fit_bar.html" with status=plan.feasibility_status min_pct=plan.min_pct band_pct=plan.band_pct mark_pct=plan.mark_pct need_label_pct=plan.need_label_pct min_minutes=plan.min_minutes max_minutes=plan.max_minutes available_minutes=plan.available_minutes axis_max=plan.axis_max %}

<dl class="rec-stat">
<div>
<dt>제외되는 작업</dt>
<dd class="num {% if plan.excluded_count %}bad{% endif %}">
{% if plan.excluded_count %}{{ plan.excluded_count }}개 · {{ plan.excluded_minutes }}분{% else %}없음{% endif %}
</dd>
</div>
<div>
<dt>남은 총 공부량</dt>
<dd class="num">{{ plan.total_minutes }}분</dd>
</div>
<div>
<dt>하루 평균 공부량</dt>
<dd class="num">{{ plan.daily_average_minutes }}분
{% if plan.daily_diff %}<small>({{ plan.daily_diff }})</small>{% endif %}
</dd>
</div>
<div>
<dt>추가로 필요한 시간</dt>
<dd class="num {% if plan.extra_minutes_needed %}warn{% else %}ok{% endif %}">
{% if plan.extra_minutes_needed %}+{{ plan.extra_minutes_needed }}분{% else %}없음{% endif %}
</dd>
</div>
</dl>
</div>

{# 제외되는 작업은 접지 않습니다. 이 화면의 '가격표'라 항상 보여야 합니다. #}
<div class="rec-foot">
{% if plan.excluded_tasks %}
<div class="rec-foot-label">빠지는 작업</div>
<ul class="rec-excluded">
{% for t in plan.excluded_tasks %}
<li>
{% if t.depth == 'optional' %}<span class="tag tag-optional">OPTIONAL</span>
{% else %}<span class="tag">{{ t.depth|upper }}</span>{% endif %}
<span class="t">{{ t.title }}</span>
<span class="m num">{{ t.minutes }}분</span>
</li>
{% endfor %}
</ul>
{% else %}
<div class="rec-keep">
<svg class="ico"><use href="#i-done"/></svg>
모든 학습 작업을 그대로 유지합니다
</div>
{% endif %}
</div>
</label>
5 changes: 4 additions & 1 deletion planner/templates/planner/includes/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{# {% include "planner/includes/sidebar.html" with nav="dashboard" %} #}

<aside class="side">
{# 로고를 누르면 사이드바가 접힙니다 (planner.js) #}
<button class="brand" id="toggleSide" type="button">PLAN <em>B</em></button>

<nav>
<a class="nav-i {% if nav == 'dashboard' %}on{% endif %}" href="#">
<a class="nav-i {% if nav == 'dashboard' %}on{% endif %}" href="{% url 'planner:dashboard' %}">
<svg class="ico"><use href="#i-dashboard"/></svg>대시보드
</a>
<a class="nav-i {% if nav == 'today' %}on{% endif %} {% if not exam_period %}off{% endif %}" href="#">
Expand Down
109 changes: 109 additions & 0 deletions planner/templates/planner/recovery_compare.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{% extends "base.html" %}
{% load static %}

{% block title %}복구안 비교 · PLAN B{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/planner.css' %}">
{% endblock %}

{% block body %}
<div class="app">

{% include "planner/includes/sidebar.html" with nav="dashboard" %}

<div class="main">
<header class="topbar">
<button class="icon-btn" id="openSide" type="button" aria-label="메뉴">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
<h2>계획 복구</h2>
<span class="badge badge-warn ml-auto">
<i class="dot dot-warn"></i>아직 적용되지 않음
</span>
</header>

<div class="content">

{% for message in messages %}
<div class="banner" style="margin-bottom:20px">{{ message }}</div>
{% endfor %}

{# ---- 왜 복구가 필요한지 ---- #}
<div class="banner" style="margin-bottom:24px;padding:22px 24px">
<svg class="ico ico-lg"><use href="#i-alert"/></svg>
<div class="body">
<h4 style="font-size:17px">{{ reason.headline }}</h4>
<p style="margin-top:8px">{{ reason.detail }}</p>
</div>
</div>

<div class="grid-3" style="margin-bottom:32px">
<div class="card card-pad">
<div class="kpi-label">오늘 남은 분량</div>
<div class="kpi-value">{{ reason.remaining_minutes }}<small>분</small></div>
<div class="kpi-sub">미완료 작업 {{ reason.remaining_count }}개의 남은 분량 (일부완료 포함)</div>
</div>
<div class="card card-pad">
<div class="kpi-label">학습 속도 보정 반영</div>
<div class="kpi-value warn">+{{ reason.speed_added_minutes }}<small>분</small></div>
<div class="kpi-sub">재추정된 공부량</div>
</div>
<div class="card card-pad">
<div class="kpi-label">시험까지 남은 가능시간</div>
<div class="kpi-value">{{ reason.available_minutes }}<small>분</small></div>
<div class="kpi-sub">{{ reason.available_days }}일 (시험 당일 제외)</div>
</div>
</div>

{# ---- 복구안 비교 ---- #}
<div class="section-head">
<div>
<h3 style="font-size:17px">복구안 비교</h3>
<p style="font-size:13px;color:var(--neutral8);margin-top:4px">
같은 눈금 위에 그렸습니다. 검은 선이 띠보다 오른쪽이면 시험일까지 끝낼 수 있습니다.
</p>
</div>
</div>

{% if plans %}
<div class="rec-grid">
{% for p in plans %}
{% include "planner/includes/recovery_card.html" with plan=p selected=forloop.last %}
{% endfor %}
</div>
{% else %}
{% url 'planner:dashboard' as dashboard_url %}
<div class="card">
{% include "planner/includes/empty_state.html" with icon="i-alert" title="현재 남은 가용시간으로 적용 가능한 복구안을 만들 수 없습니다." btn_label="대시보드로 돌아가기" btn_url=dashboard_url %}
</div>
{% endif %}
</div>

{% if plans %}
{% with selected_plan=plans|last %}
<div class="bottom-bar">
<div>
<div class="info">선택한 복구안 · <span id="recLabel">{{ selected_plan.type_label }}</span></div>
<div class="desc">적용하면 내일부터의 계획만 바뀝니다. 오늘까지의 기록은 그대로입니다.</div>
</div>
<div class="row ml-auto" style="gap:8px">
<a class="btn btn-ghost" href="{% url 'planner:dashboard' %}">나중에 결정</a>
{# TODO: 미리보기 URL 로 교체. data-plan-id는 선택된 RecoveryPlan PK — BE에서 이 값 기준으로 미리보기 View 연결 #}
<a class="btn btn-accent btn-lg" href="#" id="previewBtn" data-plan-id="{{ selected_plan.id }}">
미리보기<svg class="ico"><use href="#i-arrow-right"/></svg>
</a>
</div>
</div>
{% endwith %}
{% endif %}
</div>
</div>

{% include "includes/icons.html" %}
{% endblock %}

{% block extra_js %}
<script src="{% static 'js/planner.js' %}"></script>
{% endblock %}

146 changes: 146 additions & 0 deletions planner/templates/planner/recovery_result.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{% extends "base.html" %}
{% load static %}

{% block title %}복구안 미리보기 · PLAN B{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/planner.css' %}">
{% endblock %}

{% block body %}
<div class="app">

{% include "planner/includes/sidebar.html" with nav="dashboard" %}

<div class="main">
<header class="topbar">
<button class="icon-btn" id="openSide" type="button" aria-label="메뉴">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
<h2>복구안 미리보기</h2>
<span class="badge badge-warn ml-auto">
<i class="dot dot-warn"></i>아직 적용되지 않음
</span>
</header>

<div class="content">

{% for message in messages %}
<div class="banner" style="margin-bottom:20px">{{ message }}</div>
{% endfor %}

<div class="page-head">
<div>
<h1>{{ plan.type_label }}</h1>
<div class="sub">{{ preview.start_date|date:"n월 j일" }}부터 각 과목 시험일까지의 계획만 바뀝니다</div>
</div>
</div>

{# ---- 날짜별 공부량 변화 ---- #}
<div class="section-head">
<h3>날짜별 공부량</h3>
<div class="row" style="gap:16px;font-size:11.5px;color:var(--neutral7)">
<span class="row" style="gap:6px">
<i style="width:12px;height:8px;border-radius:2px;background:var(--judge2);display:block"></i>가능시간 초과
</span>
<span class="row" style="gap:6px">
<i style="width:2px;height:12px;background:var(--neutral11);opacity:.35;display:block"></i>입력한 가능시간
</span>
</div>
</div>

<div class="diff">
<div class="diff-col">
<div class="diff-head">
<svg class="ico"><use href="#i-clock"/></svg>지금 계획
</div>
{% for d in preview.days %}
<div class="diff-row">
<span class="date num">{{ d.date|date:"n/j" }}</span>
{% if d.is_exam_day %}
<div class="bar exam"></div>
<span class="min num">시험</span>
{% else %}
<div class="bar">
<i class="{% if d.before_over %}over{% endif %}" style="width:{{ d.before_pct }}%"></i>
<em style="left:{{ d.limit_pct }}%"></em>
</div>
<span class="min num">{{ d.before_minutes }}분</span>
{% endif %}
</div>
{% endfor %}
</div>

<div class="diff-arrow"><svg class="ico"><use href="#i-arrow-right"/></svg></div>

<div class="diff-col after">
<div class="diff-head">
<svg class="ico"><use href="#i-done"/></svg>복구 후
</div>
{% for d in preview.days %}
<div class="diff-row">
<span class="date num">{{ d.date|date:"n/j" }}</span>
{% if d.is_exam_day %}
<div class="bar exam"></div>
<span class="min num">시험</span>
{% else %}
<div class="bar">
<i class="acc" style="width:{{ d.after_pct }}%"></i>
<em style="left:{{ d.limit_pct }}%"></em>
</div>
<span class="min num">{{ d.after_minutes }}분</span>
{% endif %}
</div>
{% endfor %}
</div>
</div>

{# ---- 요약 ---- #}
<div class="grid-3" style="margin-top:28px">
<div class="card card-pad">
<div class="kpi-label">유지되는 작업</div>
<div class="kpi-value">{{ preview.keep_count }}<small>개</small></div>
<div class="kpi-sub">CORE {{ preview.keep_core_count }}개 포함 · 전부 유지</div>
</div>
<div class="card card-pad">
<div class="kpi-label">날짜가 바뀌는 작업</div>
<div class="kpi-value">{{ preview.move_count }}<small>개</small></div>
<div class="kpi-sub">남은 날짜로 분산</div>
</div>
<div class="card card-pad excluded">
<div class="kpi-label">계획에서 빠지는 작업</div>
<div class="kpi-value bad">{{ preview.exclude_count }}<small>개 · {{ preview.exclude_minutes }}분</small></div>
<div class="kpi-sub">{{ preview.exclude_summary }}</div>
</div>
</div>

<div class="banner info" style="margin-top:24px">
<svg class="ico"><use href="#i-recalc"/></svg>
<div class="body">
<h4>공부가 다시 밀리면 새 복구안을 만들 수 있습니다</h4>
<p>공부가 또 밀리면 그때의 상태로 새 복구안을 만들어 드립니다.
빠진 작업은 삭제되지 않고 &lsquo;제외됨&rsquo;으로 남습니다.</p>
</div>
</div>
</div>

<div class="bottom-bar">
{# TODO: 비교 화면 URL 로 교체 #}
<a class="btn btn-secondary" href="#">비교로 돌아가기</a>
<div class="row ml-auto" style="gap:8px">
<a class="btn btn-ghost" href="{% url 'planner:dashboard' %}">나중에 결정</a>
{# TODO: 복구안 적용 POST 연결 #}
<button class="btn btn-accent btn-lg" type="button" data-plan-id="{{ plan.id }}">
{{ plan.type_label }} 적용<svg class="ico"><use href="#i-check"/></svg>
</button>
</div>
</div>
</div>
</div>

{% include "includes/icons.html" %}
{% endblock %}

{% block extra_js %}
<script src="{% static 'js/planner.js' %}"></script>
{% endblock %}
Loading