Skip to content

Commit 38ac002

Browse files
authored
Merge pull request #65 from pirogramming/fix/data-linking
Fix/data linking
2 parents 903ce69 + fb7e2e4 commit 38ac002

10 files changed

Lines changed: 670 additions & 230 deletions

File tree

‎allnight/views.py‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
from calcs.night_schedule import calc_night_schedule, next_dose
2020
from calcs.pharmacokinetics import DAILY_LIMIT_MG, concentration_at
2121
from diary.calc_bridge import doses_for_user, service_day_bounds
22+
from diary.models import Drink
2223

2324
from .models import AllNightSession
2425
from .serializers import NightSessionEndSerializer, NightSessionStartSerializer
2526

27+
# 메인피드(feed.js)의 즐겨찾는 음료 컵 슬롯과 동일하게 최대 3개까지만 보여준다.
28+
MAX_FAVORITE_DRINKS = 3
29+
2630

2731
def _iso(dt):
2832
"""타임존 인지 datetime을 서버 로컬시각(Asia/Seoul) ISO-8601 문자열로."""
@@ -124,7 +128,12 @@ def post(self, request):
124128

125129

126130
class NightSessionCurrentView(APIView):
127-
"""GET/PATCH /night-sessions/current/ — 밤샘모드 진행 조회·종료(NIGHT-002~003)."""
131+
"""GET/PATCH /night-sessions/current/ — 밤샘모드 진행 조회·종료(NIGHT-002~003).
132+
133+
GET 응답의 favorite_drinks는 사용자의 즐겨찾는 음료(최대 3개, 메인피드 컵
134+
슬롯과 동일 기준)마다 이번 회차 섭취량(dose_mg)이 그 음료 몇 잔에 해당하는지
135+
(dose_cups)를 같이 내려준다 — "몇 잔 마시면 되는지"를 음료별로 보여주기 위함.
136+
"""
128137

129138
permission_classes = [permissions.IsAuthenticated]
130139

@@ -175,6 +184,22 @@ def get(self, request):
175184
upcoming = next_dose(schedule, now)
176185
marginal = calc_marginal_utility(schedule.total_mg, schedule.dose_mg, body_weight_kg)
177186

187+
# 회차별 섭취량(dose_mg, 모든 회차 공통값)을 즐겨찾는 음료마다 "몇 잔"으로
188+
# 환산한다. 즐겨찾는 음료가 하나도 없으면 빈 목록 — 화면에서 자연히 생략된다.
189+
favorite_drinks = list(
190+
Drink.objects.filter(user=request.user, is_favorite=True, is_active=True)
191+
.order_by("-created_at")[:MAX_FAVORITE_DRINKS]
192+
)
193+
favorite_drinks_data = [
194+
{
195+
"drink_id": d.id,
196+
"name": d.name,
197+
"caffeine_mg": d.caffeine_mg,
198+
"dose_cups": round(schedule.dose_mg / d.caffeine_mg, 1) if d.caffeine_mg > 0 else None,
199+
}
200+
for d in favorite_drinks
201+
]
202+
178203
warnings = []
179204
if schedule.exceeds_daily_limit:
180205
warnings.append(
@@ -194,6 +219,7 @@ def get(self, request):
194219
"in_target_range": schedule.c_min <= current_mg <= schedule.c_max,
195220
"next_dose_at": _iso(upcoming.at) if upcoming else None,
196221
"next_dose_mg": upcoming.dose_mg if upcoming else None,
222+
"favorite_drinks": favorite_drinks_data,
197223
"schedule": [
198224
{"at": _iso(d.at), "dose_mg": d.dose_mg, "done": d.at <= now}
199225
for d in schedule.doses

‎diary/urls.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,5 @@
9292
path("mypage/", TemplateView.as_view(template_name="diary/mypage.html"), name="mypage"),
9393
path("allnight-setup/", views.AllNightSetupView.as_view(), name="allnight-setup"),
9494
path("allnight-mode/", views.AllNightModeView.as_view(), name="allnight-mode"),
95-
path("mydiary/", TemplateView.as_view(template_name="diary/mydiary.html"), name="mydiary"),
95+
path("mydiary/", views.MyDiaryView.as_view(), name="mydiary"),
9696
]

‎diary/views.py‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ class FeedStatusView(APIView):
295295
CALC-003(남은허용량/잔수), CALC-005(개인화 θ),
296296
CALC-006(한계효용)을 이 안에서 호출해 한 번에 내려준다.
297297
?compact=true면 drinks/graph를 생략한다.
298+
?drink_id=<id>를 주면 CALC-002/003/006의 "1잔" 기준량을
299+
그 음료의 caffeine_mg로 바꿔 다시 계산한다("한잔 마시기"
300+
버튼에서 고른 음료가 바뀔 때 프론트가 미리보기용으로 호출).
298301
299302
명세(Notion API 명세서) URI는 /feed이지만, 그 경로는 diary/urls.py의
300303
"feed/"에서 프론트가 작업 중인 HTML 화면(FeedView)이 이미 쓰고 있다.
@@ -336,7 +339,7 @@ def get(self, request):
336339
current_mg = round(concentration_at(doses, now), 1)
337340

338341
bedtime = resolve_next_occurrence(profile.target_sleeptime, local_now)
339-
ref_dose = reference_dose_mg(user)
342+
ref_dose = self._resolve_ref_dose(request, user)
340343

341344
cutoff_result = calc_cutoff(now, bedtime, ref_dose, doses, theta_mg)
342345
allowance_result = calc_allowance(bedtime, doses, theta_mg, ref_dose)
@@ -366,6 +369,7 @@ def get(self, request):
366369
"target_bedtime": profile.target_sleeptime.strftime("%H:%M"),
367370
"threshold_mg": theta_mg,
368371
"current_mg": current_mg,
372+
"ref_dose_mg": ref_dose,
369373
"cutoff_at": cutoff_at_local.isoformat() if cutoff_at_local else None,
370374
"remaining_mg": allowance_result.remaining_mg,
371375
"remaining_cups": allowance_result.cups,
@@ -396,6 +400,24 @@ def get(self, request):
396400

397401
return Response(data, status=status.HTTP_200_OK)
398402

403+
@staticmethod
404+
def _resolve_ref_dose(request, user):
405+
"""CALC-002/003/006에 쓸 '1잔' 기준량(mg).
406+
407+
?drink_id=<id>가 오면 그 음료의 caffeine_mg로 미리보기 계산을 해준다
408+
("한잔 마시기" 버튼에서 고른 음료가 바뀔 때마다 프론트가 재조회).
409+
caffeine_mg는 CaffeineLogSerializer의 스냅샷 원칙과 동일하게 클라이언트가
410+
직접 보낸 값을 쓰지 않고, 반드시 본인 소유 활성 음료의 id로만 조회해 채운다.
411+
id가 없거나, 숫자가 아니거나, 본인 소유가 아니면 기존 기본값
412+
(reference_dose_mg: 가장 최근 즐겨찾기, 없으면 180mg)으로 조용히 폴백한다.
413+
"""
414+
drink_id = request.query_params.get("drink_id", "")
415+
if drink_id.isdigit():
416+
drink = Drink.objects.filter(user=user, id=int(drink_id), is_active=True).first()
417+
if drink:
418+
return drink.caffeine_mg
419+
return reference_dose_mg(user)
420+
399421
@staticmethod
400422
def _sleep_survey_required(user, local_now, today, day_start):
401423
"""SLEEP-001: 수면설문 노출 판정.
@@ -447,6 +469,13 @@ class AllNightModeView(LoginRequiredMixin, TemplateView):
447469
template_name = "diary/allnight_mode.html"
448470
extra_context = {"page_title": "밤샘모드"}
449471

472+
473+
# 나의 다이어리 — /feed-status/, /sleep-logs/, /caffeine-logs/를 호출하므로 로그인 필수.
474+
class MyDiaryView(LoginRequiredMixin, TemplateView):
475+
login_url = "/auth/login"
476+
template_name = "diary/mydiary.html"
477+
extra_context = {"page_title": "나의 다이어리"}
478+
450479
# 수면 설문
451480
class SleepLogTimeView(LoginRequiredMixin, TemplateView):
452481
login_url = "/auth/login"

‎static/css/allnight_mode.css‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,12 @@ main {
231231
display: grid;
232232
grid-template-columns: auto auto;
233233
justify-content: center;
234+
align-items: start; /* 회차 블록이 (음료 3종 세로 나열로) 길어져도 시각/본문이 각 행 위쪽에서 나란히 시작하도록 */
234235
column-gap: 13em;
235236
font-size: 20px;
236-
row-gap: 13px;
237+
row-gap: 28px; /* 회차 블록이 여러 줄이 되면서 13px로는 회차끼리 붙어 보여 넓혔다 */
237238
overflow: hidden;
238-
max-height: 500px;
239+
max-height: 2000px; /* 회차 블록이 길어진 만큼(기존 500px로는 회차가 많으면 잘림) 여유를 크게 잡음 */
239240
transition: max-height 0.3s ease;
240241
}
241242

@@ -250,10 +251,25 @@ main {
250251

251252
.schedule_list .time {
252253
text-align: right;
254+
/* 아래 .work가 "N번째 섭취" + 음료 3종 세로 나열로 여러 줄이 된 만큼,
255+
늘어난 세로 공간에 맞춰 시각 글자도 함께 키운다. */
256+
font-size: 1.4em;
253257
}
254258

255259
.schedule_list .work {
256260
text-align: left;
261+
display: flex;
262+
flex-direction: column;
263+
gap: 4px;
264+
}
265+
266+
.schedule_list .work_title {
267+
/* "N번째 섭취" — .time과 같은 비율로 키워 한 행의 헤더 역할을 하게 한다. */
268+
font-size: 1.4em;
269+
}
270+
271+
.schedule_list .work_cup {
272+
opacity: 0.85;
257273
}
258274

259275
/* .schedule_list li는 display:contents라 li 자체엔 박스가 없다 —
@@ -267,6 +283,13 @@ main {
267283
text-decoration: line-through;
268284
}
269285

286+
/* .work가 flex 컨테이너라 자식(work_title/work_cup)이 flex item이 되면서 부모의
287+
line-through가 자동으로 이어지지 않을 수 있어 명시적으로 상속시킨다. */
288+
.schedule_list .work_title,
289+
.schedule_list .work_cup {
290+
text-decoration: inherit;
291+
}
292+
270293

271294
@media (max-width: 768px) {
272295
.allnight_header {

‎static/css/feed.css‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@
3434

3535
/* ---- 상태 표시: "오늘의 카페인 마감 시간은 8:00" ---- */
3636

37+
/* CALC-002(마감시간 초과)/CALC-006(한계효용) 등 /feed-status/의 warnings를 보여주는 줄.
38+
본문 폰트를 써서 위 라벨(display 폰트, 손글씨 톤)과 구분되는 "알림"으로 읽히게 한다. */
39+
.cd-feed-hero__warning {
40+
text-align: center;
41+
font-family: var(--cd-font-body);
42+
font-size: 14px;
43+
font-weight: 500;
44+
color: #C0392B;
45+
margin-bottom: 10px;
46+
}
47+
3748
.cd-feed-hero__label {
3849
text-align: center;
3950
font-size: 25px;
@@ -308,6 +319,7 @@
308319

309320

310321
/* 오늘의 마감 시간은 부분 */
322+
.cd-feed-hero__warning {font-size: 12px;transform: translateX(20px);}
311323
.cd-feed-hero__label {font-size: 18px;transform: translateX(20px);}
312324
/* 마감 시간 숫자 */
313325
.cd-feed-hero__time {font-size: 40px; transform: translateX(20px);}

0 commit comments

Comments
 (0)