mycode/myApp/HaruDanim/IOS/Views/GoalTabView.swift
songyc macbook c35f50fa73 feat: add Goal(목표), Quest(다짐), and history tracking features
- Implement Goal(목표) and Quest(다짐) functionality
- Add a new feature for tracking and recording history
- TODO: Fix an issue in 횟수측정 Action(행동) where count and time are recorded but not reflected in the icon
2026-07-07 02:37:16 +09:00

147 lines
4.5 KiB
Swift

//
// GoalTabView.swift
// HaruDanim
//
// Lists all `Goal`s and hosts a sheet for creating new ones.
//
import SwiftUI
import SwiftData
struct GoalTabView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \Goal.createdAt, order: .reverse) private var goals: [Goal]
@State private var isAddSheetPresented = false
private var dateRangeFormatter: Date.FormatStyle {
.dateTime.year().month().day()
}
var body: some View {
NavigationStack {
List {
ForEach(goals) { goal in
NavigationLink {
GoalDetailView(goal: goal)
} label: {
HStack(spacing: 12) {
Circle()
.fill(Color(hex: goal.colorHex))
.frame(width: 20, height: 20)
VStack(alignment: .leading, spacing: 4) {
Text(goal.title)
Text("\(goal.startDate.formatted(dateRangeFormatter)) ~ \(goal.endDate.formatted(dateRangeFormatter))")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Text("다짐 \(goal.quests.count)")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.onDelete(perform: deleteGoals)
}
.navigationTitle("목표")
.overlay {
if goals.isEmpty {
ContentUnavailableView(
"목표가 없어요",
systemImage: "flag",
description: Text("오른쪽 위 + 버튼으로 목표를 추가하세요.")
)
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
isAddSheetPresented = true
} label: {
Label("목표 추가", systemImage: "plus")
}
}
}
.sheet(isPresented: $isAddSheetPresented) {
AddGoalSheet()
}
}
}
private func deleteGoals(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(goals[index])
}
}
}
// MARK: - Add Goal Sheet
private struct AddGoalSheet: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@State private var title = ""
@State private var color = Color(hex: "#2F6B4F")
@State private var startDate = Date.now
@State private var endDate = Date.now
private var trimmedTitle: String {
title.trimmingCharacters(in: .whitespacesAndNewlines)
}
private var isValid: Bool {
!trimmedTitle.isEmpty && endDate >= startDate
}
var body: some View {
NavigationStack {
Form {
Section("이름") {
TextField("목표 이름", text: $title)
}
Section("색상") {
ColorPicker("색상 선택", selection: $color, supportsOpacity: false)
}
Section("기간") {
DatePicker("시작일", selection: $startDate, displayedComponents: .date)
DatePicker("종료일", selection: $endDate, in: startDate..., displayedComponents: .date)
}
}
.navigationTitle("새 목표")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("취소") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("저장") { save() }
.disabled(!isValid)
}
}
}
}
private func save() {
let goal = Goal(
title: trimmedTitle,
colorHex: color.toHex(),
startDate: startDate,
endDate: endDate
)
modelContext.insert(goal)
dismiss()
}
}
#Preview {
GoalTabView()
.modelContainer(for: [Goal.self, Quest.self, Action.self, Tag.self], inMemory: true)
}