// // ActionTabView.swift // HaruDanim // // Lists all `Action`s and hosts a sheet for creating new ones. // import SwiftUI import SwiftData struct ActionTabView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \Action.createdAt, order: .reverse) private var actions: [Action] @State private var isAddSheetPresented = false var body: some View { NavigationStack { List { ForEach(actions) { action in HStack(spacing: 12) { Image(systemName: action.iconName) .frame(width: 28) .foregroundStyle(Color.brandPrimary) VStack(alignment: .leading, spacing: 4) { Text(action.name) if !action.tags.isEmpty { Text(action.tags.map(\.name).joined(separator: ", ")) .font(.caption) .foregroundStyle(.secondary) } } Spacer() Text(action.type == .time ? "시간" : "횟수") .font(.caption) .foregroundStyle(.secondary) } } .onDelete(perform: deleteActions) } .navigationTitle("행동") .overlay { if actions.isEmpty { ContentUnavailableView( "행동이 없어요", systemImage: "bolt", description: Text("오른쪽 위 + 버튼으로 행동을 추가하세요.") ) } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { isAddSheetPresented = true } label: { Label("행동 추가", systemImage: "plus") } } } .sheet(isPresented: $isAddSheetPresented) { AddActionSheet() } } } private func deleteActions(at offsets: IndexSet) { for index in offsets { modelContext.delete(actions[index]) } } } // MARK: - Add Action Sheet private struct AddActionSheet: View { @Environment(\.modelContext) private var modelContext @Environment(\.dismiss) private var dismiss @Query(sort: \Tag.createdAt, order: .reverse) private var allTags: [Tag] @State private var name = "" @State private var iconName = "star.fill" @State private var type: ActionType = .time @State private var selectedTagIDs: Set = [] private var trimmedName: String { name.trimmingCharacters(in: .whitespacesAndNewlines) } var body: some View { NavigationStack { Form { Section("이름") { TextField("행동 이름", text: $name) } Section("아이콘 (SF Symbol)") { HStack { Image(systemName: iconName.isEmpty ? "questionmark" : iconName) .foregroundStyle(Color.brandPrimary) TextField("star.fill", text: $iconName) .autocorrectionDisabled() .textInputAutocapitalization(.never) } } Section("유형") { Picker("유형", selection: $type) { Text("시간").tag(ActionType.time) Text("횟수").tag(ActionType.count) } .pickerStyle(.segmented) } Section("꼬리표") { if allTags.isEmpty { Text("먼저 꼬리표를 추가하세요.") .foregroundStyle(.secondary) } else { ForEach(allTags) { tag in Button { toggle(tag) } label: { HStack(spacing: 12) { Circle() .fill(Color(hex: tag.colorHex)) .frame(width: 16, height: 16) Text(tag.name) .foregroundStyle(.primary) Spacer() if selectedTagIDs.contains(tag.id) { Image(systemName: "checkmark") .foregroundStyle(Color.brandPrimary) } } } } } } } .navigationTitle("새 행동") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("취소") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("저장") { save() } .disabled(trimmedName.isEmpty) } } } } private func toggle(_ tag: Tag) { if selectedTagIDs.contains(tag.id) { selectedTagIDs.remove(tag.id) } else { selectedTagIDs.insert(tag.id) } } private func save() { let chosenTags = allTags.filter { selectedTagIDs.contains($0.id) } let trimmedIcon = iconName.trimmingCharacters(in: .whitespacesAndNewlines) let action = Action( name: trimmedName, type: type, iconName: trimmedIcon.isEmpty ? "circle" : trimmedIcon, tags: chosenTags ) modelContext.insert(action) dismiss() } } #Preview { ActionTabView() .modelContainer(for: [Action.self, Tag.self, TimeSession.self, CountEntry.self], inMemory: true) }