From 345aa44dc21406b19f2abf5d2cff59ded8440ab4 Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 15:43:59 +0900 Subject: [PATCH 01/17] =?UTF-8?q?feat:=20=EA=B8=B0=EB=A1=9D=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/templates/diary/list.html | 481 +++++++++++++++++++++++++++++++- 1 file changed, 480 insertions(+), 1 deletion(-) diff --git a/diary/templates/diary/list.html b/diary/templates/diary/list.html index 991f6b9..6f40002 100644 --- a/diary/templates/diary/list.html +++ b/diary/templates/diary/list.html @@ -1,3 +1,482 @@ + + {% extends "base.html" %} -{% block content %}{% endblock %} +{% load static %} + +{% block title %}다이어리 목록 | HeartMark{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+ + + {% include "diary/partials/diary_tab.html" %} + + +
+
+
+

전체 기록

+ + {{ records|length }}개 + +
+ + +
+ + + + + + + + + +
+
+ + +
+ + +
+
+
+
+

감정별 기록

+

+ 감정을 선택하면 해당 감정으로 남긴 기록을 확인할 수 있어요. +

+
+ + + {{ records|length }}개 + +
+
+ +
+ + +
+ + + {% for emotion in emotions %} + + {% endfor %} +
+
+ + +
+ + +
+
+
+
+

장소별 기록

+

+ 장소를 선택하면 그곳에서 남긴 기록을 확인할 수 있어요. +

+
+ + + {{ records|length }}개 + +
+
+ +
+ + +
+ + + {% for location in locations %} + + {% endfor %} +
+
+ + +
+ +
+
+{% endblock %} +{% block extra_js %} + +{% endblock %} \ No newline at end of file From 3d4628dcc77b6fcca99db9b5d4703b7784280775 Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 15:44:36 +0900 Subject: [PATCH 02/17] =?UTF-8?q?feat:=20diary/views.py=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/diary/views.py b/diary/views.py index 6b25e2b..611517c 100644 --- a/diary/views.py +++ b/diary/views.py @@ -1 +1,7 @@ # 담당 D: 캘린더 및 다이어리 목록 조회 + +from django.shortcuts import render + + +def diary_list(request): + return render(request, "diary/list.html") \ No newline at end of file From 02205138d6971f6881ed7644b100238e710a9383 Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 15:45:17 +0900 Subject: [PATCH 03/17] =?UTF-8?q?feat:=20diary/urls.py=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/urls.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/diary/urls.py b/diary/urls.py index 2e12300..3189f9d 100644 --- a/diary/urls.py +++ b/diary/urls.py @@ -1,5 +1,7 @@ from django.urls import path +from . import views app_name = "diary" -urlpatterns = [] - +urlpatterns = [ + path("", views.diary_list, name="list"), +] \ No newline at end of file From e6eea35c1619b42ac515121f9db0351ff9196d2e Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 21:09:39 +0900 Subject: [PATCH 04/17] =?UTF-8?q?feat:=20=EB=8B=A4=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EB=A6=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/templates/diary/list.html | 229 +++++++++++++++----------------- 1 file changed, 108 insertions(+), 121 deletions(-) diff --git a/diary/templates/diary/list.html b/diary/templates/diary/list.html index 6f40002..cc14f2c 100644 --- a/diary/templates/diary/list.html +++ b/diary/templates/diary/list.html @@ -129,7 +129,7 @@

전체 기록

{% for record in records %}
@@ -193,7 +193,7 @@

기록 작성하기 @@ -204,143 +204,52 @@

-
장소별 기록

{% for record in records %}
@@ -475,6 +384,84 @@

+
{% endblock %} {% block extra_js %} From f085df6f1c88e6ae0607ee7955a7e3fcb338c7ae Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 21:10:05 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4/=EA=B0=90?= =?UTF-8?q?=EC=A0=95=EB=B3=84/=EC=9E=A5=EC=86=8C=EB=B3=84=20=ED=83=AD=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/templates/diary/partials/diary_tab.html | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/diary/templates/diary/partials/diary_tab.html b/diary/templates/diary/partials/diary_tab.html index 818d995..ed3c594 100644 --- a/diary/templates/diary/partials/diary_tab.html +++ b/diary/templates/diary/partials/diary_tab.html @@ -1,2 +1,24 @@ + \ No newline at end of file From 30016047fcd28e1f4759e51cff2178a614f301f9 Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 21:10:58 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat:=20=EA=B8=B0=EB=A1=9D=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20js,=20css=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/static/diary/css/diary.css | 717 +++++++++++++++++++++++++++++++ diary/static/diary/js/diary.js | 91 ++++ 2 files changed, 808 insertions(+) diff --git a/diary/static/diary/css/diary.css b/diary/static/diary/css/diary.css index abbbe7b..f2a7f47 100644 --- a/diary/static/diary/css/diary.css +++ b/diary/static/diary/css/diary.css @@ -1,2 +1,719 @@ /* 담당 D 전용 */ +.diary-list-container { + width: min(1200px, calc(100% - 80px)); + margin: 0 auto; + padding: 40px 0 80px; +} + +.diary-tabs { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 36px; +} + +.diary-tab { + display: inline-flex; + min-width: 72px; + height: 40px; + align-items: center; + justify-content: center; + + padding: 0 18px; + border: 1px solid #e4ddd1; + border-radius: 999px; + + background: #ffffff; + color: #555555; + font-size: 14px; + text-decoration: none; + + box-shadow: 0 2px 6px rgb(0 0 0 / 6%); +} + +.diary-tab:hover { + background: #fffaf0; +} + +.diary-tab.active { + border-color: #e2c36b; + background: #fff4cb; + color: #222222; + font-weight: 700; +} +.diary-list-header { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + margin-bottom: 24px; +} + +.diary-list-summary { + display: flex; + align-items: center; + gap: 10px; +} + +.diary-list-title { + margin: 0; + font-size: 26px; +} + +.diary-list-count { + padding: 5px 10px; + border-radius: 999px; + background: #fff1ba; + font-size: 13px; + font-weight: 700; +} + +.diary-filter-form { + position: relative; + display: flex; + align-items: center; + gap: 8px; +} + +.diary-sort-select, +.period-toggle-button { + min-height: 42px; + padding: 0 16px; + border: 1px solid #e6ded0; + border-radius: 12px; + background: #ffffff; + color: #333333; + font: inherit; + cursor: pointer; +} + +.diary-sort-select:focus, +.period-toggle-button:focus, +.period-filter input:focus { + outline: 2px solid #e9c767; + outline-offset: 2px; +} + +.period-toggle-button.active { + border-color: #e1bd55; + background: #fff7d9; + font-weight: 700; +} + +.period-filter { + position: absolute; + z-index: 10; + top: calc(100% + 10px); + right: 0; + + display: flex; + align-items: flex-end; + gap: 10px; + + min-width: 500px; + padding: 18px; + + border: 1px solid #e6ded0; + border-radius: 16px; + background: #ffffff; + box-shadow: 0 10px 30px rgb(0 0 0 / 12%); +} + +.period-filter[hidden] { + display: none; +} + +.period-input-group { + display: flex; + flex-direction: column; + gap: 7px; +} + +.period-input-group label { + color: #666666; + font-size: 12px; +} + +.period-input-group input { + min-height: 40px; + padding: 0 10px; + border: 1px solid #ded7cb; + border-radius: 9px; + font: inherit; +} + +.period-separator { + padding-bottom: 10px; + color: #999999; +} + +.period-apply-button, +.period-reset-button { + display: inline-flex; + min-height: 40px; + align-items: center; + justify-content: center; + padding: 0 14px; + border-radius: 9px; + font: inherit; + text-decoration: none; + cursor: pointer; +} + +.period-apply-button { + border: 1px solid #e0bd59; + background: #ffe99c; + color: #222222; +} + +.period-reset-button { + border: 1px solid #ded7cb; + background: #ffffff; + color: #555555; +} + +@media (max-width: 760px) { + .diary-list-header { + align-items: flex-start; + flex-direction: column; + } + + .diary-filter-form { + width: 100%; + } +} + +/* ========================= + 감정별 화면 +========================= */ + +.emotion-page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 30px; + margin-bottom: 30px; +} + +.emotion-month-summary { + display: flex; + align-items: center; + gap: 10px; + + padding: 12px 18px; + border: 1px solid #eadfce; + border-radius: 14px; + background: #ffffff; + + font-size: 13px; +} + +.emotion-month-summary strong { + color: #d98d36; + font-size: 18px; +} + +.emotion-bubble-area { + position: relative; + + width: 100%; + min-height: 560px; + + border-radius: 24px; + background: rgba(255, 251, 238, 0.5); +} + +.emotion-bubble { + position: absolute; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + + border: 2px solid #e8c578; + background: rgba(255, 250, 231, 0.95); + color: #333333; + font: inherit; + cursor: pointer; + + /* emotion-bubble의 각 꼭짓점의 둥근 정도 다르게 */ + border-radius: 42% 58% 53% 47% / 45% 41% 59% 49%; + + transition: + transform 0.2s ease, + box-shadow 0.2s ease; +} + +.emotion-bubble:hover { + z-index: 5; + + transform: scale(1.08); + + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12); +} + +.emotion-bubble-name { + font-size: 14px; + font-weight: 700; +} + +.emotion-bubble-count { + color: #8e877c; + font-size: 11px; +} + +/* 기록 개수에 따른 크기 */ + +.emotion-bubble--large { + width: 130px; + height: 130px; +} + +.emotion-bubble--medium { + width: 92px; + height: 92px; +} + +.emotion-bubble--small { + width: 64px; + height: 64px; +} + +/* 임시 위치 배치 */ + +.emotion-bubble:nth-child(1) { + top: 30px; + left: 9%; +} + +.emotion-bubble:nth-child(2) { + top: 70px; + left: 25%; +} + +.emotion-bubble:nth-child(3) { + top: 160px; + left: 15%; +} + +.emotion-bubble:nth-child(4) { + top: 110px; + left: 44%; +} + +.emotion-bubble:nth-child(5) { + top: 260px; + left: 34%; +} + +.emotion-bubble:nth-child(6) { + top: 80px; + left: 68%; +} + +.emotion-bubble:nth-child(7) { + top: 210px; + left: 75%; +} + +.emotion-bubble:nth-child(8) { + top: 340px; + left: 60%; +} + +.emotion-bubble:nth-child(9) { + top: 390px; + left: 20%; +} + +.emotion-bubble:nth-child(10) { + top: 370px; + left: 80%; +} + +/* ========================= + 감정 팝업 +========================= */ + +body.modal-open { + overflow: hidden; +} + +.emotion-modal { + position: fixed; + z-index: 1000; + inset: 0; +} + +.emotion-modal[hidden] { + display: none; +} + +.emotion-modal-backdrop { + position: absolute; + inset: 0; + + background: rgba(35, 30, 25, 0.45); + backdrop-filter: blur(3px); +} + +.emotion-modal-content { + position: relative; + z-index: 1; + + width: min(1040px, calc(100% - 60px)); + max-height: calc(100vh - 80px); + margin: 40px auto; + padding: 30px; + + overflow-y: auto; + + border: 1px solid #eadfce; + border-radius: 24px; + background: #fffdf7; + + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.2); +} + +.emotion-modal-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + + margin-bottom: 24px; +} + +.emotion-modal-label { + margin: 0 0 6px; + + color: #8d857b; + font-size: 13px; +} + +.emotion-modal-header h2 { + margin: 0; + + font-size: 28px; +} + +.emotion-modal-close { + width: 42px; + height: 42px; + + border: 1px solid #e5ddd1; + border-radius: 50%; + background: #ffffff; + + font-size: 26px; + cursor: pointer; +} + +.emotion-modal-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 20px; +} + +.emotion-modal-empty { + padding: 80px 20px; + text-align: center; +} + +@media (max-width: 900px) { + .emotion-modal-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 650px) { + .emotion-page-header { + flex-direction: column; + } + + .emotion-bubble-area { + min-height: 720px; + } + + .emotion-modal-content { + width: calc(100% - 28px); + padding: 20px; + } + + .emotion-modal-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +/* ========================= + 기록 카드 목록 +========================= */ + +.diary-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 22px; + width: 100%; +} + +.diary-card { + min-width: 0; + overflow: hidden; + + border: 1px solid #ebe5da; + border-radius: 14px; + background-color: #ffffff; + + box-shadow: + 0 3px 8px rgba(0, 0, 0, 0.08), + 0 1px 2px rgba(0, 0, 0, 0.04); + + transition: + transform 0.2s ease, + box-shadow 0.2s ease; +} + +.diary-card:hover { + transform: translateY(-5px); + + box-shadow: + 0 10px 20px rgba(0, 0, 0, 0.12), + 0 3px 6px rgba(0, 0, 0, 0.06); +} + +.diary-card-link { + display: block; + height: 100%; + + color: inherit; + text-decoration: none; +} + + +/* ========================= + 카드 이미지 +========================= */ + +.diary-card-image-wrapper { + position: relative; + + width: 100%; + aspect-ratio: 4 / 3; + + overflow: hidden; + background-color: #f3efe8; +} + +.diary-card-image { + display: block; + + width: 100%; + height: 100%; + + object-fit: cover; + + transition: transform 0.3s ease; +} + +.diary-card:hover .diary-card-image { + transform: scale(1.04); +} + +.diary-card-image-placeholder { + display: flex; + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; + + background-color: #f4f0e8; + color: #aaa397; + + font-size: 13px; +} + + +/* ========================= + 감정 표시 +========================= */ + +.diary-card-emotion { + position: absolute; + right: 10px; + bottom: 10px; + + display: flex; + align-items: center; + justify-content: center; + + min-width: 34px; + height: 34px; + padding: 0 9px; + + border: 2px solid #ffffff; + border-radius: 999px; + background-color: #fff3f3; + + color: #d65d69; + font-size: 12px; + font-weight: 700; + + box-shadow: 0 2px 7px rgba(0, 0, 0, 0.12); +} + +.diary-card-emotion img { + display: block; + + width: 28px; + height: 28px; + + object-fit: contain; +} + + +/* ========================= + 카드 내용 +========================= */ + +.diary-card-content { + display: flex; + flex-direction: column; + + min-height: 130px; + padding: 15px 14px 16px; +} + +.diary-card-date { + margin-bottom: 8px; + + color: #7f7a72; + font-size: 12px; + line-height: 1.3; +} + +.diary-card-title { + display: -webkit-box; + overflow: hidden; + + min-height: 44px; + margin: 0; + + color: #222222; + font-size: 16px; + font-weight: 700; + line-height: 1.4; + + word-break: keep-all; + + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.diary-card-location { + display: flex; + align-items: center; + gap: 5px; + + overflow: hidden; + + margin: auto 0 0; + padding-top: 13px; + + color: #7c776f; + font-size: 12px; + + text-overflow: ellipsis; + white-space: nowrap; +} + + +/* ========================= + 기록 없음 +========================= */ + +.diary-empty { + grid-column: 1 / -1; + + padding: 80px 20px; + + text-align: center; +} + +.diary-empty-title { + margin: 0 0 10px; + + color: #333333; + font-size: 20px; + font-weight: 700; +} + +.diary-empty-description { + margin: 0 0 24px; + + color: #777777; + font-size: 14px; +} + +.diary-empty-button { + display: inline-flex; + align-items: center; + justify-content: center; + + min-height: 44px; + padding: 0 20px; + + border: 1px solid #e3c566; + border-radius: 12px; + background-color: #fff1b8; + + color: #2e2a20; + font-size: 14px; + font-weight: 700; + text-decoration: none; + + transition: + transform 0.2s ease, + background-color 0.2s ease; +} + +.diary-empty-button:hover { + transform: translateY(-2px); + background-color: #ffe894; +} + + +/* ========================= + 반응형 +========================= */ + +@media (max-width: 1200px) { + .diary-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +@media (max-width: 900px) { + .diary-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 650px) { + .diary-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; + } + + .diary-card-content { + min-height: 118px; + padding: 12px; + } + + .diary-card-title { + font-size: 14px; + } +} \ No newline at end of file diff --git a/diary/static/diary/js/diary.js b/diary/static/diary/js/diary.js index 5aaf506..dd16be2 100644 --- a/diary/static/diary/js/diary.js +++ b/diary/static/diary/js/diary.js @@ -1,2 +1,93 @@ // 담당 D 전용 +document.addEventListener("DOMContentLoaded", () => { + const sortSelect = document.querySelector("#diary-sort"); + const filterForm = document.querySelector("#diary-filter-form"); + + const periodToggleButton = document.querySelector( + "#period-toggle-button" + ); + const periodFilter = document.querySelector("#period-filter"); + + if (sortSelect && filterForm) { + sortSelect.addEventListener("change", () => { + filterForm.submit(); + }); + } + + if (periodToggleButton && periodFilter) { + periodToggleButton.addEventListener("click", () => { + const isOpen = !periodFilter.hidden; + + periodFilter.hidden = isOpen; + periodToggleButton.classList.toggle("active", !isOpen); + periodToggleButton.setAttribute( + "aria-expanded", + String(!isOpen) + ); + }); + } + const emotionButtons = + document.querySelectorAll(".emotion-bubble"); + + const modal = + document.querySelector("#emotion-modal"); + + const modalName = + document.querySelector("#emotion-modal-name"); + + const modalRecords = + document.querySelectorAll(".emotion-modal-record"); + + const modalEmpty = + document.querySelector("#emotion-modal-empty"); + + const closeButtons = + document.querySelectorAll("[data-modal-close]"); + + function openEmotionModal(emotionId, emotionName) { + let visibleRecordCount = 0; + + modalName.textContent = emotionName; + + modalRecords.forEach((record) => { + const isMatched = + record.dataset.recordEmotion === emotionId; + + record.hidden = !isMatched; + + if (isMatched) { + visibleRecordCount += 1; + } + }); + + modalEmpty.hidden = visibleRecordCount !== 0; + modal.hidden = false; + + document.body.classList.add("modal-open"); + } + + function closeEmotionModal() { + modal.hidden = true; + document.body.classList.remove("modal-open"); + } + + emotionButtons.forEach((button) => { + button.addEventListener("click", () => { + openEmotionModal( + button.dataset.emotionId, + button.dataset.emotionName + ); + }); + }); + + closeButtons.forEach((button) => { + button.addEventListener("click", closeEmotionModal); + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && !modal.hidden) { + closeEmotionModal(); + } + }); +}); \ No newline at end of file From fd80ff79c346b69d38c58a6de1c494b5ec6969ef Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Fri, 31 Jul 2026 21:13:19 +0900 Subject: [PATCH 07/17] =?UTF-8?q?test:=20=20=EA=B0=90=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=9D=B4=20=EC=95=84=EC=A7=81=20=EC=97=86=EC=96=B4?= =?UTF-8?q?=EC=84=9C=20diary/views.py=EC=97=90=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/views.py | 210 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 1 deletion(-) diff --git a/diary/views.py b/diary/views.py index 611517c..735b0f4 100644 --- a/diary/views.py +++ b/diary/views.py @@ -1,7 +1,215 @@ # 담당 D: 캘린더 및 다이어리 목록 조회 +from datetime import date from django.shortcuts import render def diary_list(request): - return render(request, "diary/list.html") \ No newline at end of file + selected_tab = request.GET.get("tab", "all") + selected_sort = request.GET.get("sort", "latest") + selected_start_date = request.GET.get("start_date", "") + selected_end_date = request.GET.get("end_date", "") + + records = [ + { + "id": 1, + "title": "2차 회의한 날", + "created_at": date(2026, 7, 30), + "image": None, + "emotion": { + "id": 1, + "name": "설렘", + "image": None, + }, + "location": { + "id": 1, + "name": "성북구", + }, + }, + { + "id": 2, + "title": "친구랑 카페에서 수다", + "created_at": date(2026, 7, 29), + "image": None, + "emotion": { + "id": 2, + "name": "행복", + "image": None, + }, + "location": { + "id": 2, + "name": "마포구", + }, + }, + { + "id": 3, + "title": "산책하면서 많은 생각을 했다", + "created_at": date(2026, 7, 28), + "image": None, + "emotion": { + "id": 3, + "name": "평온", + "image": None, + }, + "location": { + "id": 3, + "name": "성동구", + }, + }, + { + "id": 4, + "title": "작은 성공! 뿌듯한 하루", + "created_at": date(2026, 7, 27), + "image": None, + "emotion": { + "id": 4, + "name": "뿌듯함", + "image": None, + }, + "location": { + "id": 1, + "name": "성북구", + }, + }, + { + "id": 5, + "title": "노을이 너무 예뻤다", + "created_at": date(2026, 7, 26), + "image": None, + "emotion": { + "id": 2, + "name": "행복", + "image": None, + }, + "location": { + "id": 4, + "name": "강남구", + }, + }, + { + "id": 6, + "title": "마음이 편안했던 날", + "created_at": date(2026, 7, 25), + "image": None, + "emotion": { + "id": 3, + "name": "평온", + "image": None, + }, + "location": { + "id": 4, + "name": "강남구", + }, + }, + { + "id": 7, + "title": "집에서 푹 쉬었다", + "created_at": date(2026, 7, 23), + "image": None, + "emotion": { + "id": 5, + "name": "지침", + "image": None, + }, + "location": { + "id": 2, + "name": "마포구", + }, + }, + { + "id": 8, + "title": "왜인지 모르게 우울했다", + "created_at": date(2026, 7, 22), + "image": None, + "emotion": { + "id": 6, + "name": "슬픔", + "image": None, + }, + "location": { + "id": 5, + "name": "명동", + }, + }, + ] + + emotions = [ + { + "id": 1, + "name": "감사", + "record_count": 8, + "size": "large", + }, + { + "id": 2, + "name": "설렘", + "record_count": 6, + "size": "large", + }, + { + "id": 3, + "name": "행복", + "record_count": 4, + "size": "medium", + }, + { + "id": 4, + "name": "기대", + "record_count": 3, + "size": "medium", + }, + { + "id": 5, + "name": "불안", + "record_count": 2, + "size": "small", + }, + { + "id": 6, + "name": "평온", + "record_count": 4, + "size": "medium", + }, + { + "id": 7, + "name": "뿌듯", + "record_count": 3, + "size": "medium", + }, + { + "id": 8, + "name": "지침", + "record_count": 2, + "size": "small", + }, + ] + + locations = [ + {"id": 1, "name": "성북구", "record_count": 2}, + {"id": 2, "name": "마포구", "record_count": 2}, + {"id": 3, "name": "성동구", "record_count": 1}, + {"id": 4, "name": "강남구", "record_count": 2}, + {"id": 5, "name": "명동", "record_count": 1}, + ] + + if selected_sort == "oldest": + records.sort(key=lambda record: record["created_at"]) + else: + records.sort( + key=lambda record: record["created_at"], + reverse=True, + ) + + context = { + "records": records, + "emotions": emotions, + "locations": locations, + "selected_tab": selected_tab, + "selected_sort": selected_sort, + "selected_start_date": selected_start_date, + "selected_end_date": selected_end_date, + "selected_emotion": request.GET.get("emotion", ""), + "selected_location": request.GET.get("location", ""), + } + + return render(request, "diary/list.html", context) \ No newline at end of file From 781f88f022199a92c99c37648caa40fa21598ec9 Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Sat, 1 Aug 2026 11:32:52 +0900 Subject: [PATCH 08/17] =?UTF-8?q?feat:=20=EA=B0=90=EC=A0=95=EB=B3=84=20?= =?UTF-8?q?=EB=B2=84=EB=B8=94=20=EC=83=89=EC=83=81=20=EB=B6=84=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/static/diary/css/diary.css | 27 ++++++++++++++++++++++++++- diary/templates/diary/list.html | 21 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/diary/static/diary/css/diary.css b/diary/static/diary/css/diary.css index f2a7f47..e8cff96 100644 --- a/diary/static/diary/css/diary.css +++ b/diary/static/diary/css/diary.css @@ -186,7 +186,7 @@ } /* ========================= - 감정별 화면 + 감정별 화면 ========================= */ .emotion-page-header { @@ -248,6 +248,31 @@ box-shadow 0.2s ease; } +.emotion-bubble--yellow { + background: #FEE8B3; + border-color: #F9D472; +} + +.emotion-bubble--purple { + background: #ECEDEE; + border-color: #879ABC; +} + +.emotion-bubble--blue { + background: #a9ccec; + border-color: #82add5; +} + +.emotion-bubble--green { + background: #c8e69a; + border-color: #a9ce72; +} + +.emotion-bubble--default { + background: #dedede; + border-color: #c5c5c5; +} + .emotion-bubble:hover { z-index: 5; diff --git a/diary/templates/diary/list.html b/diary/templates/diary/list.html index cc14f2c..936d794 100644 --- a/diary/templates/diary/list.html +++ b/diary/templates/diary/list.html @@ -229,7 +229,26 @@

감정별 기록

{% for emotion in emotions %} + + +

장소별 기록 - diff --git a/diary/views.py b/diary/views.py index 735b0f4..6b7a88e 100644 --- a/diary/views.py +++ b/diary/views.py @@ -2,7 +2,7 @@ from datetime import date from django.shortcuts import render - +from collections import Counter def diary_list(request): selected_tab = request.GET.get("tab", "all") @@ -48,7 +48,7 @@ def diary_list(request): "image": None, "emotion": { "id": 3, - "name": "평온", + "name": "슬픔", "image": None, }, "location": { @@ -78,7 +78,7 @@ def diary_list(request): "image": None, "emotion": { "id": 2, - "name": "행복", + "name": "분노", "image": None, }, "location": { @@ -133,6 +133,58 @@ def diary_list(request): }, ] + district_records = {} + + for record in records: + location = record.get("location") + emotion = record.get("emotion") + created_at = record.get("created_at") + + if not location or not emotion: + continue + + district_name = location.get("name") + emotion_name = emotion.get("name") + + if not district_name or not emotion_name: + continue + + # 서울 자치구만 지도에 표시 + if not district_name.endswith("구"): + continue + + if district_name not in district_records: + district_records[district_name] = [] + + district_records[district_name].append({ + "emotion_name": emotion_name, + "created_at": created_at, + }) + + + district_map_data = [] + + for district_name, district_record_list in district_records.items(): + emotion_names = [ + item["emotion_name"] + for item in district_record_list + ] + + emotion_counts = Counter(emotion_names) + dominant_emotion = emotion_counts.most_common(1)[0][0] + + latest_record = max( + district_record_list, + key=lambda item: item["created_at"] + ) + + district_map_data.append({ + "district_name": district_name, + "visit_count": len(district_record_list), + "dominant_emotion": dominant_emotion, + "latest_emotion": latest_record["emotion_name"], + }) + emotions = [ { "id": 1, @@ -202,6 +254,7 @@ def diary_list(request): context = { "records": records, + "district_map_data": district_map_data, "emotions": emotions, "locations": locations, "selected_tab": selected_tab, From 4e7f6166fb41e199913392387215436942bce91e Mon Sep 17 00:00:00 2001 From: 1wltn <1jisoo0120@gmail.com> Date: Sun, 2 Aug 2026 13:14:32 +0900 Subject: [PATCH 17/17] =?UTF-8?q?feat:=20=EC=A7=80=EB=8F=84=20=EC=9C=84=20?= =?UTF-8?q?=EC=A7=80=EC=97=AD=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=EB=B3=84=20=EA=B8=B0=EB=A1=9D=20=ED=8C=9D=EC=97=85?= =?UTF-8?q?=EC=B0=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- diary/static/diary/css/diary.css | 126 ++++++++++++++++++++- diary/static/diary/js/diary.js | 187 ++++++++++++++++++++++++++++--- diary/templates/diary/list.html | 108 ++++++++++++++++++ 3 files changed, 403 insertions(+), 18 deletions(-) diff --git a/diary/static/diary/css/diary.css b/diary/static/diary/css/diary.css index 1cc3afd..6d2182c 100644 --- a/diary/static/diary/css/diary.css +++ b/diary/static/diary/css/diary.css @@ -560,6 +560,106 @@ body.modal-open { } } +/* ========================= + 장소별 기록 팝업 +========================= */ + +.location-modal { + position: fixed; + z-index: 1000; + inset: 0; +} + +.location-modal[hidden] { + display: none; +} + +.location-modal-backdrop { + position: absolute; + inset: 0; + + background: rgba(35, 30, 25, 0.45); + backdrop-filter: blur(3px); +} + +.location-modal-content { + position: relative; + z-index: 1; + + width: min(1040px, calc(100% - 60px)); + max-height: calc(100vh - 80px); + margin: 40px auto; + padding: 30px; + + overflow-y: auto; + + border: 1px solid #eadfce; + border-radius: 24px; + background: #fffdf7; + + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.2); +} + +.location-modal-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + + margin-bottom: 24px; +} + +.location-modal-label { + margin: 0 0 6px; + + color: #8d857b; + font-size: 13px; +} + +.location-modal-header h2 { + margin: 0; + font-size: 28px; +} + +.location-modal-close { + width: 42px; + height: 42px; + + border: 1px solid #e5ddd1; + border-radius: 50%; + background: #ffffff; + + font-size: 26px; + cursor: pointer; +} + +.location-modal-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 20px; +} + +.location-modal-empty { + padding: 80px 20px; + text-align: center; +} + +@media (max-width: 900px) { + .location-modal-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (max-width: 650px) { + .location-modal-content { + width: calc(100% - 28px); + padding: 20px; + } + + .location-modal-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + .location-map-wrapper { position: relative; width: min(580px, 100%); @@ -584,9 +684,16 @@ body.modal-open { /* 방문한 장소 채우기 */ .seoul-map path.has-record { - /*stroke: #789447;*/ + stroke: #789447; + stroke-width: 2; + cursor: pointer; +} + +.seoul-map path.has-record:hover { + filter: brightness(0.94); stroke-width: 3; } + /* 지도 위 구 이름 */ .district-labels { pointer-events: none; @@ -605,6 +712,23 @@ body.modal-open { stroke-linejoin: round; } +.seoul-map path { + pointer-events: all; +} + +.seoul-map path:focus { + outline: none; +} + +.seoul-map path.has-record { + cursor: pointer; +} + +.seoul-map path.has-record:hover { + filter: brightness(0.94); + stroke-width: 3; +} + /* ========================= 기록 카드 목록 ========================= */ diff --git a/diary/static/diary/js/diary.js b/diary/static/diary/js/diary.js index a218483..7f39669 100644 --- a/diary/static/diary/js/diary.js +++ b/diary/static/diary/js/diary.js @@ -64,7 +64,31 @@ document.addEventListener("DOMContentLoaded", () => { "#district-map-data [data-district]" ); - /* HTML 데이터를 JavaScripts 객체로 변환 */ + + /* ========================= + 장소 팝업 요소 + ========================= */ + + const locationModal = + document.querySelector("#location-modal"); + + const locationModalName = + document.querySelector("#location-modal-name"); + + const locationModalRecords = + document.querySelectorAll(".location-modal-record"); + + const locationModalEmpty = + document.querySelector("#location-modal-empty"); + + const locationModalCloseButtons = + document.querySelectorAll( + "[data-location-modal-close]" + ); + + + /* HTML 데이터를 JavaScript 객체로 변환 */ + function getDistrictMapData() { return Array.from(districtDataElements).map((element) => ({ district: element.dataset.district.trim(), @@ -76,7 +100,9 @@ document.addEventListener("DOMContentLoaded", () => { })); } - /* 기존 지도 색상 초기화하는 함수 */ + + /* 기존 지도 색상 초기화 */ + function clearDistrictStyles() { document .querySelectorAll(".seoul-map path") @@ -86,7 +112,9 @@ document.addEventListener("DOMContentLoaded", () => { }); } - /* 색상 방식 적용 함수 추가 */ + + /* 선택한 지도 색상 방식 적용 */ + function applyMapMode(mode) { clearDistrictStyles(); @@ -135,7 +163,6 @@ document.addEventListener("DOMContentLoaded", () => { minimumOpacity + ratio * (1 - minimumOpacity); - /* 노란색 */ path.style.fill = `rgba(245, 196, 80, ${opacity})`; } @@ -158,29 +185,125 @@ document.addEventListener("DOMContentLoaded", () => { localStorage.setItem("diaryMapMode", mode); } - /* 점 세 개 메뉴 여닫는 코드 */ + + /* ========================= + 장소 팝업 + ========================= */ + + function openLocationModal(locationName) { + if (!locationModal || !locationModalName) { + return; + } + + let visibleRecordCount = 0; + + locationModalName.textContent = locationName; + + locationModalRecords.forEach((record) => { + const recordLocation = + record.dataset.recordLocation.trim(); + + const isMatched = + recordLocation === locationName; + + record.hidden = !isMatched; + + if (isMatched) { + visibleRecordCount += 1; + } + }); + + if (locationModalEmpty) { + locationModalEmpty.hidden = + visibleRecordCount !== 0; + } + + locationModal.hidden = false; + document.body.classList.add("modal-open"); + } + + + function closeLocationModal() { + if (!locationModal) { + return; + } + + locationModal.hidden = true; + document.body.classList.remove("modal-open"); + } + + + /* 기록이 있는 구에 클릭 이벤트 연결 */ + + districtDataElements.forEach((element) => { + const districtName = + element.dataset.district.trim(); + + const districtPath = + document.getElementById(districtName); + + if (!districtPath) { + console.warn( + `클릭 이벤트를 연결할 구를 찾지 못했습니다: ${districtName}` + ); + return; + } + + /* + * tabindex를 넣지 않습니다. + * SVG에 직사각형 포커스 테두리가 나타나는 것을 방지합니다. + */ + districtPath.addEventListener("click", () => { + openLocationModal(districtName); + }); + }); + + + /* 장소 팝업 닫기 */ + + locationModalCloseButtons.forEach((button) => { + button.addEventListener( + "click", + closeLocationModal + ); + }); + + + /* ========================= + 점 세 개 메뉴 + ========================= */ + if (mapSettingsButton && mapSettingsMenu) { - mapSettingsButton.addEventListener("click", () => { - const isOpen = !mapSettingsMenu.hidden; + mapSettingsButton.addEventListener("click", (event) => { + event.stopPropagation(); - mapSettingsMenu.hidden = isOpen; + const willOpen = mapSettingsMenu.hidden; + + mapSettingsMenu.hidden = !willOpen; mapSettingsButton.setAttribute( "aria-expanded", - String(!isOpen) + String(willOpen) ); }); - } - if (districtDataElements.length > 0) { - const savedMapMode = - localStorage.getItem("diaryMapMode") - || "dominant"; + mapSettingsMenu.addEventListener("click", (event) => { + event.stopPropagation(); + }); - applyMapMode(savedMapMode); + document.addEventListener("click", () => { + mapSettingsMenu.hidden = true; + + mapSettingsButton.setAttribute( + "aria-expanded", + "false" + ); + }); } - /* 색상 방식 클릭 */ + + /* 지도 색상 방식 선택 */ + mapModeButtons.forEach((button) => { button.addEventListener("click", () => { const selectedMode = @@ -201,6 +324,17 @@ document.addEventListener("DOMContentLoaded", () => { }); }); + + /* 페이지 진입 시 저장된 색상 모드 적용 */ + + if (districtDataElements.length > 0) { + const savedMapMode = + localStorage.getItem("diaryMapMode") + || "dominant"; + + applyMapMode(savedMapMode); + } + const sortSelect = document.querySelector("#diary-sort"); const filterForm = document.querySelector("#diary-filter-form"); @@ -286,8 +420,27 @@ document.addEventListener("DOMContentLoaded", () => { }); document.addEventListener("keydown", (event) => { - if (event.key === "Escape" && !modal.hidden) { + if (event.key !== "Escape") { + return; + } + + if (modal && !modal.hidden) { closeEmotionModal(); } + + if (locationModal && !locationModal.hidden) { + closeLocationModal(); + } + + if (mapSettingsMenu && !mapSettingsMenu.hidden) { + mapSettingsMenu.hidden = true; + + if (mapSettingsButton) { + mapSettingsButton.setAttribute( + "aria-expanded", + "false" + ); + } + } }); }); \ No newline at end of file diff --git a/diary/templates/diary/list.html b/diary/templates/diary/list.html index 263ce3f..1d229f0 100644 --- a/diary/templates/diary/list.html +++ b/diary/templates/diary/list.html @@ -820,6 +820,114 @@

+ + + {% endblock %} {% block extra_js %}