Files
habit-tracker/app/templates/partials/journal_day_detail.html
T
shinalokandClaude Sonnet 5 54c971875c add journaling feature: categories, tags, attachments, calendar, and multi-select emotions
Adds an 8th development stage that lets users keep a free-form journal alongside
habit tracking, reusing the existing Google OAuth/DB/PWA infrastructure instead
of a separate project. Users organize entries into custom categories, filter by
a month calendar with day-detail drill-down, attach photos/videos (served via an
authenticated route, never /static), tag entries, and pick multiple emotions per
entry from a curated 9-option set. Includes a global journal prompt bank for
lightweight guided journaling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 18:40:07 +09:00

136 lines
5.7 KiB
HTML

{% set weekday_names = ["월", "화", "수", "목", "금", "토", "일"] %}
<div class="card day-detail-card">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-2);">
<h3 style="margin:0;">{{ entry_date.strftime("%Y.%m.%d") }} ({{ weekday_names[entry_date.weekday()] }})</h3>
<button type="button" class="btn btn-secondary" onclick="document.getElementById('day-detail').innerHTML=''">닫기</button>
</div>
{% if items %}
<div class="day-detail-list">
{% for item in items %}
<div
class="journal-entry-item"
x-data="{
editing: {{ 'true' if editing_entry_id == item.id else 'false' }},
moods: [{% for m in item.moods %}'{{ m.value }}'{{ ',' if not loop.last }}{% endfor %}],
toggleMood(v) { this.moods.includes(v) ? this.moods = this.moods.filter(m => m !== v) : this.moods.push(v) },
}"
>
<div x-show="!editing">
<div style="display:flex; align-items:center; justify-content:space-between;">
<span class="badge" style="border-color: {{ item.category_color or 'var(--color-accent)' }};">{{ item.category_name }}</span>
{% if item.moods %}
<span class="mood-icon">{% for m in item.moods %}{{ journal_mood_emoji[m.value] }}{% endfor %}</span>
{% endif %}
</div>
{% if item.title %}<div class="journal-entry-title">{{ item.title }}</div>{% endif %}
<div class="journal-entry-content">{{ item.content }}</div>
{% if item.tags %}
<div class="journal-entry-tags">
{% for tag in item.tags %}<span class="tag-chip">#{{ tag }}</span>{% endfor %}
</div>
{% endif %}
{% if item.attachments %}
<div class="attachment-grid">
{% for attachment in item.attachments %}
<div class="attachment-thumb">
{% if attachment.media_type.value == "image" %}
<a href="/api/journal/media/{{ attachment.id }}" target="_blank">
<img src="/api/journal/media/{{ attachment.id }}?thumbnail=true" alt="{{ attachment.original_filename }}" />
</a>
{% else %}
<a href="/api/journal/media/{{ attachment.id }}" target="_blank" class="attachment-video-link">▶ {{ attachment.original_filename }}</a>
{% endif %}
<button
type="button"
class="attachment-delete-btn"
hx-post="/journal/{{ item.id }}/attachments/{{ attachment.id }}/delete"
hx-target="#day-detail"
hx-swap="innerHTML"
hx-confirm="이 첨부파일을 삭제할까요?"
aria-label="첨부파일 삭제"
>&times;</button>
</div>
{% endfor %}
</div>
{% endif %}
<div class="habit-item-actions" style="margin-top: 8px;">
<button type="button" class="btn btn-secondary" @click="editing = true">수정</button>
<button
type="button"
class="btn btn-danger-ghost"
hx-post="/journal/{{ item.id }}/delete"
hx-target="#day-detail"
hx-swap="innerHTML"
hx-confirm="이 기록을 삭제할까요?"
>삭제</button>
</div>
</div>
<form
x-show="editing"
x-cloak
style="padding: 12px 4px; border-bottom: 1px solid var(--color-border);"
hx-post="/journal/{{ item.id }}/edit"
hx-target="#day-detail"
hx-swap="innerHTML"
hx-encoding="multipart/form-data"
>
<div class="field">
<label>카테고리</label>
<select name="category_id">
{% for category in categories %}
<option value="{{ category.id }}" {{ 'selected' if category.id == item.category_id }}>{{ category.name }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label>날짜</label>
<input type="date" name="entry_date" value="{{ entry_date.isoformat() }}" required />
</div>
<div class="field">
<label>제목</label>
<input type="text" name="title" value="{{ item.title or '' }}" />
</div>
<div class="field">
<label>내용</label>
<textarea name="content" rows="4" required>{{ item.content }}</textarea>
</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>태그 (쉼표로 구분)</label>
<input type="text" name="tags" value="{{ item.tags|join(', ') }}" />
</div>
<div class="field">
<label>사진/영상 추가</label>
<input type="file" name="files" accept="image/*,video/*" multiple />
</div>
{% if edit_error and editing_entry_id == item.id %}
<div class="form-error-text">{{ edit_error }}</div>
{% endif %}
<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>
{% else %}
<div class="empty-state">이 날 작성한 기록이 없어요.</div>
{% endif %}
</div>