Files
habit-tracker/app/templates/journal.html
T
shinalokandClaude Sonnet 5 8a49757882
CI/CD / test (pull_request) Successful in 51s
CI/CD / deploy (pull_request) Skipped
enlarge journal calendar cells and pair day/title on one line
- calendar-cell-journal: bump min-height 44px -> 76px, scale up date/dot/title font sizes
- journal calendar titles were centered; left-align them
- rework JournalCalendarDay: replace separate category_colors/titles lists
  (mismatched lengths, no way to pair a dot with its title) with a single
  entries list of (color, title) pairs so the dot and its title render on
  the same non-wrapping row per entry

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 17:13:38 +09:00

280 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}일기 · 해빗랩{% endblock %}
{% block nav_journal %}active{% endblock %}
{% block shell_class %} wide{% endblock %}
{% block content %}
<h1>일기</h1>
<div style="display:flex; align-items:center; gap: var(--space-1);">
<div class="tabs" style="flex:1; min-width:0;">
<a href="/journal?year={{ year }}&month={{ month }}" class="{{ 'active' if tab != 'manage' and not category_id }}">전체</a>
{% for category in categories %}
<a
href="/journal?year={{ year }}&month={{ month }}&category_id={{ category.id }}"
class="{{ 'active' if tab != 'manage' and category_id == category.id }}"
>{{ category.name }}</a>
{% endfor %}
</div>
<a href="/journal?tab=manage" class="btn btn-secondary{{ ' active' if tab == 'manage' }}" style="flex-shrink:0; margin-bottom: var(--space-2); text-decoration:none;">⚙ 관리</a>
</div>
{% if tab == "manage" %}
{% if categories %}
<div class="card">
<div class="day-detail-list" id="journal-category-list">
{% for category in categories %}
<div class="journal-entry-item" data-category-id="{{ category.id }}" x-data="{ editing: false }">
<div class="day-detail-item" x-show="!editing">
<span style="display:flex; align-items:center; gap: 8px;">
<span class="drag-handle" aria-hidden="true"></span>
<span class="badge" style="border-color: {{ category.color or 'var(--color-accent)' }};">{{ category.name }}</span>
<span class="badge">기록 {{ entry_counts.get(category.id, 0) }}개</span>
</span>
<span style="display:flex; gap:8px;">
<button type="button" class="btn btn-secondary" @click="editing = true">수정</button>
<button
type="button"
class="btn btn-danger-ghost"
hx-post="/journal/categories/{{ category.id }}/delete"
hx-target="body"
hx-swap="none"
hx-confirm="'{{ category.name }}' 카테고리를 삭제할까요? 이 카테고리의 모든 기록({{ entry_counts.get(category.id, 0) }}개)과 첨부파일도 함께 삭제됩니다."
>삭제</button>
</span>
</div>
<form
x-show="editing"
x-cloak
style="padding: 8px 0;"
hx-post="/journal/categories/{{ category.id }}/edit"
hx-target="#journal-category-edit-error-{{ category.id }}"
hx-swap="innerHTML"
>
<div class="field">
<label for="edit-category-name-{{ category.id }}">카테고리 이름</label>
<input type="text" id="edit-category-name-{{ category.id }}" name="name" required value="{{ category.name }}" />
</div>
<div class="field">
<label for="edit-category-color-{{ category.id }}">색상</label>
<input type="color" id="edit-category-color-{{ category.id }}" name="color" value="{{ category.color or '#d97757' }}" />
</div>
<div class="field">
<label for="edit-category-template-{{ category.id }}">기본 작성 틀 (선택)</label>
<textarea id="edit-category-template-{{ category.id }}" name="content_template" rows="4" placeholder="이 카테고리로 새 기록을 쓸 때 내용칸에 미리 채워줄 틀을 입력하세요">{{ category.content_template or '' }}</textarea>
</div>
<div id="journal-category-edit-error-{{ category.id }}" class="form-error-text"></div>
<div style="display:flex; gap:8px;">
<button type="submit" class="btn btn-primary" style="flex:1;">저장</button>
<button type="button" class="btn btn-secondary" @click="editing = false">취소</button>
</div>
</form>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="card" x-data="{ open: false }">
<button type="button" class="btn btn-secondary btn-block" @click="open = !open">
<span x-show="!open">+ 카테고리 추가</span>
<span x-show="open" x-cloak>닫기</span>
</button>
<form x-show="open" x-cloak style="margin-top: var(--space-2);" hx-post="/journal/categories/new" hx-target="#journal-category-error" hx-swap="innerHTML">
<div class="field">
<label for="new-category-name">카테고리 이름</label>
<input type="text" id="new-category-name" name="name" required placeholder="예: 투자" />
</div>
<div class="field">
<label for="new-category-color">색상 (선택)</label>
<input type="color" id="new-category-color" name="color" value="#d97757" />
</div>
<div class="field">
<label for="new-category-template">기본 작성 틀 (선택)</label>
<textarea id="new-category-template" name="content_template" rows="4" placeholder="이 카테고리로 새 기록을 쓸 때 내용칸에 미리 채워줄 틀을 입력하세요"></textarea>
</div>
<div id="journal-category-error" class="form-error-text"></div>
<button type="submit" class="btn btn-primary btn-block">추가하기</button>
</form>
</div>
{% else %}
{% if on_this_day %}
<div class="card on-this-day-card">
<h3 style="margin-top:0;">📅 {{ on_this_day|length }}개의 지난 오늘</h3>
{% for item in on_this_day %}
<div class="day-detail-item">
<span>
<span class="badge" style="border-color: {{ item.category_color or 'var(--color-accent)' }};">{{ item.category_name }}</span>
{{ item.entry_date.year }}년 · {{ item.years_ago }}년 전
</span>
<span>{{ item.title or (item.content[:30] ~ ('…' if item.content|length > 30 else '')) }}</span>
</div>
{% endfor %}
</div>
{% endif %}
<div
class="card"
x-data="{
open: false,
moods: [],
toggleMood(v) { this.moods.includes(v) ? this.moods = this.moods.filter(m => m !== v) : this.moods.push(v) },
categoryTemplates: {{ category_templates|tojson|forceescape }},
content: '',
lastAutoFilled: '',
previewMode: false,
togglePreview() {
this.previewMode = !this.previewMode;
if (this.previewMode) { this.$refs.contentField.dispatchEvent(new Event('input')); }
},
onCategoryChange(categoryId) {
const tmpl = this.categoryTemplates[categoryId] || '';
if (this.content === this.lastAutoFilled) {
this.content = tmpl;
this.lastAutoFilled = tmpl;
this.$nextTick(() => {
if (this.$refs.contentField._easymde) { this.$refs.contentField._easymde.value(tmpl); }
this.$refs.contentField.dispatchEvent(new Event('input'));
});
}
},
init() { this.onCategoryChange(this.$refs.categorySelect.value); },
}"
>
<button type="button" class="btn btn-primary btn-block" @click="open = !open">
<span x-show="!open">+ 새 기록 쓰기</span>
<span x-show="open" x-cloak>닫기</span>
</button>
{% if prompt %}
<div class="journal-prompt-card">💭 {{ prompt.question_text }}</div>
{% endif %}
<form
x-show="open"
x-cloak
style="margin-top: var(--space-2);"
hx-post="/journal/new"
hx-target="#journal-form-error"
hx-swap="innerHTML"
hx-encoding="multipart/form-data"
>
<div class="field">
<label for="new-entry-category">카테고리</label>
<select id="new-entry-category" name="category_id" x-ref="categorySelect" @change="onCategoryChange($event.target.value)">
{% for category in categories %}
<option value="{{ category.id }}" {{ 'selected' if category_id == category.id }}>{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label for="new-entry-date">날짜</label>
<input type="date" id="new-entry-date" name="entry_date" value="{{ today_iso }}" required />
</div>
<div class="field">
<label for="new-entry-title">제목 (선택)</label>
<input type="text" id="new-entry-title" name="title" placeholder="제목을 입력하세요" />
</div>
<div class="field">
<label for="new-entry-content">내용</label>
{% include "partials/journal_editor_toolbar.html" %}
<div x-show="!previewMode">
<textarea
id="new-entry-content"
name="content"
rows="8"
required
placeholder="오늘 하루는 어땠나요?"
class="markdown-editor"
x-model="content"
x-ref="contentField"
hx-post="/journal/preview"
hx-trigger="input changed delay:400ms, load"
hx-target="#new-entry-preview"
hx-swap="innerHTML"
hx-params="content"
></textarea>
</div>
<div id="new-entry-preview" class="journal-preview journal-entry-content" x-show="previewMode" x-cloak></div>
</div>
<div class="field">
<label>기분 (선택, 여러 개 선택 가능)</label>
<input type="hidden" name="moods" :value="moods.join(',')" />
<div class="mood-picker">
{% for value, emoji, label in journal_mood_options %}
<button
type="button"
class="mood-pill"
:class="{ selected: moods.includes('{{ value.value }}') }"
@click="toggleMood('{{ value.value }}')"
>{{ emoji }} {{ label }}</button>
{% endfor %}
</div>
</div>
<div class="field">
<label for="new-entry-tags">태그 (선택, 쉼표로 구분)</label>
<input type="text" id="new-entry-tags" name="tags" placeholder="예: 투자, 회고" />
</div>
<div class="field">
<label for="new-entry-files">사진/영상 첨부 (선택)</label>
<input type="file" id="new-entry-files" name="files" accept="image/*,video/*" multiple />
</div>
<div id="journal-form-error" class="form-error-text"></div>
<button type="submit" class="btn btn-primary btn-block">기록 저장</button>
</form>
</div>
<div class="card">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-2);">
<a href="/journal?year={{ prev_year }}&month={{ prev_month }}{{ '&category_id=' ~ category_id if category_id }}" class="btn btn-secondary"></a>
<h2 style="margin:0;">{{ year }}년 {{ month }}월</h2>
<a href="/journal?year={{ next_year }}&month={{ next_month }}{{ '&category_id=' ~ category_id if category_id }}" class="btn btn-secondary"></a>
</div>
<div class="calendar-grid calendar-grid-header">
{% for wd in ["일", "월", "화", "수", "목", "금", "토"] %}
<div class="calendar-weekday">{{ wd }}</div>
{% endfor %}
</div>
{% for week in weeks %}
<div class="calendar-grid">
{% for d in week %}
{% set day_summary = summary_map.get(d) %}
<div
class="calendar-cell calendar-cell-journal clickable{{ '' if d.month == month else ' muted' }}"
hx-get="/journal/day/{{ d.isoformat() }}"
hx-target="#day-detail"
hx-swap="innerHTML"
>
<span class="calendar-date">{{ d.day }}</span>
{% if day_summary %}
<span class="journal-day-entries">
{% for entry in day_summary.entries %}
<span class="journal-day-entry">
<span class="journal-day-dot" style="background: {{ entry.color }};"></span>
<span class="journal-day-title">{{ entry.title }}</span>
</span>
{% endfor %}
</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<div id="day-detail"></div>
{% endif %}
{% endblock %}