import SwiftUI import SwiftData struct TasksTabView: View { @Environment(\.modelContext) private var context @Query(sort: \TaskItem.sortOrder) private var tasks: [TaskItem] @State private var showAddSheet = false @State private var editTarget: TaskItem? private var activeTasks: [TaskItem] { tasks.filter { !$0.isArchived } } var body: some View { NavigationStack { Group { if activeTasks.isEmpty { EmptyStateView( icon: "checkmark.circle", message: String(localized: "tasks.empty") ) } else { List { ForEach(activeTasks) { task in TaskRowView(task: task) .swipeActions(edge: .trailing, allowsFullSwipe: false) { Button(role: .destructive) { context.delete(task) } label: { Label("삭제", systemImage: "trash") } Button { editTarget = task } label: { Label("편집", systemImage: "pencil") } .tint(.blue) } } } .scrollContentBackground(.hidden) } } .background(Color.tfBackground) .navigationTitle(String(localized: "tab.tasks")) .toolbar { ToolbarItem(placement: .primaryAction) { Button { showAddSheet = true } label: { Image(systemName: "plus") } .tint(Color.tfPrimary) } } .sheet(isPresented: $showAddSheet) { TaskFormView() } .sheet(item: $editTarget) { task in TaskFormView(existing: task) } } } } // MARK: – Row private struct TaskRowView: View { let task: TaskItem private var primaryColor: Color { task.tags.first.map { Color(hex: $0.colorHex) } ?? Color.tfPrimary } var body: some View { HStack(spacing: 12) { Image(systemName: task.icon) .foregroundStyle(primaryColor) .frame(width: 28) VStack(alignment: .leading, spacing: 2) { Text(task.name) .foregroundStyle(Color.tfOnBackground) HStack(spacing: 4) { Text(task.taskType == .timer ? "타이머" : "카운터") .font(.caption) .foregroundStyle(Color.tfMuted) if let firstTag = task.tags.first { Text("·") .font(.caption) .foregroundStyle(Color.tfMuted) Text(firstTag.name) .font(.caption) .foregroundStyle(Color(hex: firstTag.colorHex)) } } } Spacer() Image(systemName: task.taskType == .timer ? "timer" : "number") .foregroundStyle(primaryColor) } .padding(.vertical, 4) } } // MARK: – Form struct TaskFormView: View { @Environment(\.modelContext) private var context @Environment(\.dismiss) private var dismiss @Query(sort: \TagEntity.sortOrder) private var allTags: [TagEntity] var existing: TaskItem? = nil @State private var name = "" @State private var icon = "star" @State private var taskType: TaskType = .timer @State private var selectedTagIDs: Set = [] private let iconPresets = [ "star", "heart", "bolt", "flame", "book.fill", "pencil", "laptopcomputer", "dumbbell.fill", "music.note", "camera.fill", "gamecontroller.fill", "fork.knife", "figure.walk", "bicycle", "car.fill", "airplane", "moon.stars.fill", "sun.max.fill", "drop.fill", "leaf.fill" ] private var isEditing: Bool { existing != nil } private var isSaveDisabled: Bool { name.trimmingCharacters(in: .whitespaces).isEmpty } var body: some View { NavigationStack { Form { Section("이름") { TextField("작업 이름", text: $name) } Section("아이콘") { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 12) { ForEach(iconPresets, id: \.self) { sym in Image(systemName: sym) .font(.title2) .foregroundStyle(sym == icon ? Color.tfPrimary : Color.tfMuted) .frame(width: 44, height: 44) .background( RoundedRectangle(cornerRadius: 8) .fill(sym == icon ? Color.tfPrimary.opacity(0.15) : Color.clear) ) .onTapGesture { icon = sym } } } .padding(.vertical, 4) } Section("유형") { Picker("유형", selection: $taskType) { Text("타이머").tag(TaskType.timer) Text("카운터").tag(TaskType.counter) } .pickerStyle(.segmented) } if !allTags.isEmpty { Section("태그") { ForEach(allTags) { tag in TagSelectionRow( tag: tag, isSelected: selectedTagIDs.contains(tag.id), onToggle: { toggleTag(tag) } ) } } } } .navigationTitle(isEditing ? "작업 편집" : "작업 추가") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("취소") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("저장") { save() } .disabled(isSaveDisabled) } } .onAppear { loadExisting() } } } private func loadExisting() { guard let task = existing else { return } name = task.name icon = task.icon taskType = task.taskType selectedTagIDs = Set(task.tags.map { $0.id }) } private func toggleTag(_ tag: TagEntity) { if selectedTagIDs.contains(tag.id) { selectedTagIDs.remove(tag.id) } else { selectedTagIDs.insert(tag.id) // Selecting a sub tag implicitly includes its parent super tag if let parent = tag.parent { selectedTagIDs.insert(parent.id) } } } private func save() { let trimmed = name.trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { return } let selectedTags = allTags.filter { selectedTagIDs.contains($0.id) } if let task = existing { task.name = trimmed task.icon = icon task.taskType = taskType task.tags = selectedTags } else { let newTask = TaskItem(name: trimmed, icon: icon, taskType: taskType) newTask.tags = selectedTags context.insert(newTask) } dismiss() } } private struct TagSelectionRow: View { let tag: TagEntity let isSelected: Bool let onToggle: () -> Void var body: some View { HStack(spacing: 8) { if tag.isSubTag { Spacer().frame(width: 14) } Circle() .fill(Color(hex: tag.colorHex)) .frame(width: 10, height: 10) Text(tag.name) .foregroundStyle(Color.tfOnBackground) if tag.isSubTag, let parent = tag.parent { Text("(\(parent.name))") .font(.caption) .foregroundStyle(Color.tfMuted) } Spacer() if isSelected { Image(systemName: "checkmark") .foregroundStyle(Color.tfPrimary) } } .contentShape(Rectangle()) .onTapGesture { onToggle() } } } #Preview { TasksTabView() .modelContainer(for: [TaskItem.self, TagEntity.self, TrackingRecord.self], inMemory: true) .environment(AppPreferences()) }