| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- //
- // ProjectPanelView.swift
- // Todos
- //
- // Created by Sam Jaffe on 2/28/26.
- //
- import SwiftUI
- import SwiftData
- struct ProjectPanelView: View {
- @Environment(\.modelContext) private var modelContext
- @AppStorage(UserDefaultsKeys.Category) var allGroups = CodableArray<Category>()
- @Bindable var item: Project
- @State private var empty = Category()
- @State private var showDialogue = false
- @State private var move = false
- @State private var taskFilter = ""
- @State private var statuses = StatusList()
- var body: some View {
- HStack {
- TextField("Project Name", text: $item.name)
- .font(.title)
- .padding(.leading, 10)
- if let grp = $allGroups.first(where: { $0.name.wrappedValue == item.category }) {
- ColorPicker("", selection: grp.color).disabled(true).scaledToFit()
- }
- Spacer()
- Button(action: addItem) {
- Image(systemName: "plus")
- }
- .help("New Task")
- .padding(.trailing, 10)
- if move {
- Label("", systemImage: "arrow.up.arrow.down")
- .foregroundStyle(.red)
- .font(.title2)
- .help("Re-ordering mode is enabled, text fields will be unresponsive")
- }
- if !taskFilter.isEmpty {
- Label("", systemImage: "text.magnifyingglass")
- .foregroundStyle(.blue)
- .font(.title2)
- .help("Only showing text matching '\(taskFilter)'")
- }
- if !statuses.all {
- Label("", systemImage: "exclamationmark.magnifyingglass")
- .foregroundStyle(.blue)
- .font(.title2)
- .help(statuses.description)
- }
- Button {
- showDialogue = !showDialogue
- } label: {
- Label("", systemImage: "ellipsis.circle")
- .foregroundStyle(.gray)
- .font(.title)
- }
- .buttonStyle(.borderless)
- .popover(isPresented: $showDialogue) {
- List{
- Label("Settings", systemImage: "gearshape")
- .font(.title)
- Picker("Category", selection: $item.category) {
- Text(empty.name).tag("")
- ForEach(allGroups) { group in
- Text(group.name)
- }
- }
- Label("Filters", systemImage: "magnifyingglass")
- .font(.title)
- HStack {
- Label("", systemImage: "arrow.up.arrow.down")
- Toggle("Move Tasks", isOn: $move)
- }
- HStack {
- Label("", systemImage: "text.magnifyingglass")
- TextField("Filter Tasks", text: $taskFilter)
- }
- StatusChecklist(statuses: $statuses)
- }
- }
- Text("")
- }
- TextField("Project Notes", text: $item.notes, axis: .vertical)
- .padding(.leading, 20)
- List {
- ForEach(selected($item.tasks), id: \.id) { task in
- TaskView(task: task)
- .swipeActions {
- Button("Delete", systemImage: "trash", role: .destructive) {
- deleteItem(item: task.wrappedValue, from: item)
- }
- }
- ForEach(selected(task.subtasks), id: \.id) { subtask in
- SubTaskView(task: subtask)
- .swipeActions {
- Button("Delete", systemImage: "trash", role: .destructive) {
- deleteItem(item: subtask.wrappedValue, from: task.wrappedValue)
- }
- }
- }
- .onMove(perform: { moveItem(task.wrappedValue, $0, $1) })
- .moveDisabled(!move)
- }
- .onMove(perform: { moveItem(item, $0, $1) })
- .moveDisabled(!move)
- }
- }
-
- private func selected<T : Ordered & Filterable>(_ items: Binding<[T]>) -> [Binding<T>] {
- return items.sorted(by: T.less).filter({
- let value = $0.wrappedValue
- return value.name.isEmpty ||
- (statuses.test(value.status) &&
- (taskFilter.isEmpty || value.containsText(taskFilter)))
- })
- }
- private func addItem() {
- withAnimation {
- let newTask = Task(parent: item)
- modelContext.insert(newTask)
- item.tasks.append(newTask)
- }
- }
- private func moveItem(_ within: any Aggregate, _ fromOffsets: IndexSet, _ toOffset: Int) {
- withAnimation {
- within.move(fromOffsets: fromOffsets, toOffset: toOffset)
- }
- }
- private func deleteItem<T: Aggregate>(item: T.Element, from: T) where T.Element: PersistentModel {
- withAnimation {
- from.remove(item)
- modelContext.delete(item)
- }
- }
- }
- #Preview {
- @Previewable @State var item = Project()
- ProjectPanelView(item: item)
- }
|