From dde53d36a150b214dd446685e5f92d88faec1fc6 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 23:05:12 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=ED=86=A0=EA=B8=80=EB=A1=9C=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/ViewModel/TodoEditorViewModel.swift | 12 ++++++++++-- DevLog/UI/Home/TodoEditorView.swift | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 397a508..43427ff 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -11,6 +11,7 @@ import OrderedCollections @Observable final class TodoEditorViewModel: Store { private struct Draft: Equatable { + let isCompleted: Bool let isPinned: Bool let title: String let content: String @@ -19,6 +20,7 @@ final class TodoEditorViewModel: Store { let kind: TodoKind init(todo: Todo) { + self.isCompleted = todo.isCompleted self.isPinned = todo.isPinned self.title = todo.title self.content = todo.content @@ -28,6 +30,7 @@ final class TodoEditorViewModel: Store { } init(state: State) { + self.isCompleted = state.isCompleted self.isPinned = state.isPinned self.title = state.title self.content = state.content @@ -38,6 +41,7 @@ final class TodoEditorViewModel: Store { } struct State: Equatable { + var isCompleted: Bool = false var isPinned: Bool = false var title: String = "" var content: String = "" @@ -61,6 +65,7 @@ final class TodoEditorViewModel: Store { case addTag(String) case removeTag(String) case setContent(String) + case setCompleted(Bool) case setDueDate(Date?) case setKind(TodoKind) case setPinned(Bool) @@ -117,6 +122,7 @@ final class TodoEditorViewModel: Store { self.createdAt = todo.createdAt self.completedAt = todo.completedAt self.originalDraft = Draft(todo: todo) + state.isCompleted = todo.isCompleted state.isPinned = todo.isPinned state.title = todo.title state.content = todo.content @@ -145,6 +151,8 @@ final class TodoEditorViewModel: Store { } else { state.dueDate = nil } + case .setCompleted(let isCompleted): + state.isCompleted = isCompleted case .setKind(let todoKind): state.kind = todoKind case .setPinned(let isPinned): @@ -183,13 +191,13 @@ extension TodoEditorViewModel { return Todo( id: self.id, isPinned: state.isPinned, - isCompleted: self.isCompleted, + isCompleted: state.isCompleted, isChecked: self.isChecked, title: state.title, content: state.content, createdAt: self.createdAt ?? date, updatedAt: date, - completedAt: self.completedAt, + completedAt: state.isCompleted ? (self.completedAt ?? date) : nil, dueDate: state.dueDate, tags: state.tags.map { $0 }, kind: state.kind diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index f8534a1..f221dc6 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -192,6 +192,15 @@ private struct TodoEditorInfoSheetView: View { } } + Toggle( + "완료", + isOn: Binding( + get: { viewModel.state.isCompleted }, + set: { viewModel.send(.setCompleted($0)) } + ) + ) + .tint(.blue) + Toggle( "중요 표시", isOn: Binding( From 768359811fe0f3d36de30cf7148c5ae8cabbbb28 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 23:11:53 +0900 Subject: [PATCH 2/3] =?UTF-8?q?ui:=20TodoDetailView=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20TodoEditorView=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=ED=8A=B8=20=ED=98=95=ED=83=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DevLog/UI/Home/TodoDetailView.swift | 102 ++++++++++++++++++++++++++-- DevLog/UI/Home/TodoEditorView.swift | 2 - 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/DevLog/UI/Home/TodoDetailView.swift b/DevLog/UI/Home/TodoDetailView.swift index 250ee91..6f08909 100644 --- a/DevLog/UI/Home/TodoDetailView.swift +++ b/DevLog/UI/Home/TodoDetailView.swift @@ -70,14 +70,104 @@ struct TodoDetailView: View { @ViewBuilder private var sheetContent: some View { if let todo = viewModel.state.todo { - TodoInfoSheetView( - createdAt: todo.createdAt, - completedAt: todo.completedAt, - dueDate: todo.dueDate, - tags: todo.tags - ) { + TodoDetailInfoSheetView(todo: todo) { viewModel.send(.setShowInfo(false)) } } } } + +private struct TodoDetailInfoSheetView: View { + let todo: Todo + let onClose: () -> Void + private let calendar = Calendar.current + + var body: some View { + NavigationStack { + List { + Section("옵션") { + HStack { + Text("카테고리") + Spacer() + Text(todo.kind.localizedName) + .foregroundStyle(.secondary) + } + + statusRow( + title: "완료", + systemImage: todo.isCompleted ? "checkmark.circle.fill" : "circle", + color: todo.isCompleted ? .green : .secondary + ) + + statusRow( + title: "중요 표시", + systemImage: todo.isPinned ? "star.fill" : "star", + color: todo.isPinned ? .orange : .secondary + ) + + HStack { + Text("마감일") + + Spacer() + + if let dueDate = todo.dueDate { + Tag(dueDateText(for: dueDate), isEditing: false) + .padding(.vertical, -4) + } else { + Text("없음") + .foregroundStyle(.secondary) + } + } + } + + Section("태그") { + if todo.tags.isEmpty { + Text("태그 없음") + .foregroundStyle(.secondary) + .padding(.vertical, 4) + } else { + TagList(todo.tags) + } + } + } + .navigationTitle("세부 정보") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarLeadingButton { + onClose() + } + } + } + } + + @ViewBuilder + private func statusRow( + title: String, + systemImage: String, + color: Color + ) -> some View { + HStack { + Text(title) + + Spacer() + + Image(systemName: systemImage) + .foregroundStyle(color) + } + } + + private func dueDateText(for dueDate: Date) -> String { + let currentYear = calendar.component(.year, from: Date()) + let dueDateYear = calendar.component(.year, from: dueDate) + + if currentYear == dueDateYear { + return dueDate.formatted( + .dateTime.month(.defaultDigits).day(.defaultDigits) + ) + } + + return dueDate.formatted( + .dateTime.year(.twoDigits).month(.defaultDigits).day(.defaultDigits) + ) + } +} diff --git a/DevLog/UI/Home/TodoEditorView.swift b/DevLog/UI/Home/TodoEditorView.swift index f221dc6..ebae63b 100644 --- a/DevLog/UI/Home/TodoEditorView.swift +++ b/DevLog/UI/Home/TodoEditorView.swift @@ -272,9 +272,7 @@ private struct TodoEditorInfoSheetView: View { HStack { Text("마감일") .foregroundStyle(.primary) - Spacer() - if let dueDate = viewModel.state.dueDate { Tag(dueDateText(for: dueDate), isEditing: true) { viewModel.send(.setDueDate(nil)) From ecd499788f6f93b7dc0df081f97ceea7a1befac5 Mon Sep 17 00:00:00 2001 From: opficdev Date: Tue, 17 Mar 2026 23:22:29 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EA=B8=B0=EC=A1=B4=EC=97=90=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=90=9C=20=ED=9B=84=20=ED=95=B4=EC=A0=9C?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=83=88=EB=A1=9C=20=EC=99=84=EB=A3=8C?= =?UTF-8?q?=ED=96=88=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=EC=9D=98=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=EA=B0=84=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/TodoEditorViewModel.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift index 43427ff..8125cce 100644 --- a/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift +++ b/DevLog/Presentation/ViewModel/TodoEditorViewModel.swift @@ -12,6 +12,7 @@ import OrderedCollections final class TodoEditorViewModel: Store { private struct Draft: Equatable { let isCompleted: Bool + let completedAt: Date? let isPinned: Bool let title: String let content: String @@ -21,6 +22,7 @@ final class TodoEditorViewModel: Store { init(todo: Todo) { self.isCompleted = todo.isCompleted + self.completedAt = todo.completedAt self.isPinned = todo.isPinned self.title = todo.title self.content = todo.content @@ -31,6 +33,7 @@ final class TodoEditorViewModel: Store { init(state: State) { self.isCompleted = state.isCompleted + self.completedAt = state.completedAt self.isPinned = state.isPinned self.title = state.title self.content = state.content @@ -42,6 +45,7 @@ final class TodoEditorViewModel: Store { struct State: Equatable { var isCompleted: Bool = false + var completedAt: Date? var isPinned: Bool = false var title: String = "" var content: String = "" @@ -83,7 +87,6 @@ final class TodoEditorViewModel: Store { private let isCompleted: Bool private let isChecked: Bool private let createdAt: Date? - private let completedAt: Date? private let originalDraft: Draft? var navigationTitle: String { @@ -109,7 +112,6 @@ final class TodoEditorViewModel: Store { self.isCompleted = false self.isChecked = false self.createdAt = nil - self.completedAt = nil self.originalDraft = nil state.kind = kind } @@ -120,9 +122,9 @@ final class TodoEditorViewModel: Store { self.isCompleted = todo.isCompleted self.isChecked = todo.isChecked self.createdAt = todo.createdAt - self.completedAt = todo.completedAt self.originalDraft = Draft(todo: todo) state.isCompleted = todo.isCompleted + state.completedAt = todo.completedAt state.isPinned = todo.isPinned state.title = todo.title state.content = todo.content @@ -152,6 +154,9 @@ final class TodoEditorViewModel: Store { state.dueDate = nil } case .setCompleted(let isCompleted): + if state.isCompleted != isCompleted { + state.completedAt = isCompleted ? Date() : nil + } state.isCompleted = isCompleted case .setKind(let todoKind): state.kind = todoKind @@ -197,7 +202,7 @@ extension TodoEditorViewModel { content: state.content, createdAt: self.createdAt ?? date, updatedAt: date, - completedAt: state.isCompleted ? (self.completedAt ?? date) : nil, + completedAt: state.completedAt, dueDate: state.dueDate, tags: state.tags.map { $0 }, kind: state.kind