This commit is contained in:
2026-02-18 21:35:32 +01:00
parent 4b0cfaa699
commit 4e49741dfa
47 changed files with 3672 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
import SwiftUI
struct ClientsView: View {
@StateObject private var viewModel = ClientsViewModel()
@State private var showAddClient = false
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading && viewModel.clients.isEmpty {
LoadingView()
} else if let error = viewModel.error, viewModel.clients.isEmpty {
ErrorView(message: error) {
Task { await viewModel.loadClients() }
}
} else if viewModel.clients.isEmpty {
EmptyView(
icon: "person.2",
title: "No Clients",
message: "Create a client to organize your projects."
)
} else {
clientsList
}
}
.navigationTitle("Clients")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
showAddClient = true
} label: {
Image(systemName: "plus")
}
}
}
.task {
await viewModel.loadClients()
}
.sheet(isPresented: $showAddClient) {
ClientFormView(onSave: { name, description in
Task {
await viewModel.createClient(name: name, description: description)
}
})
}
}
}
private var clientsList: some View {
List {
ForEach(viewModel.clients) { client in
ClientRow(client: client)
}
.onDelete { indexSet in
Task {
for index in indexSet {
await viewModel.deleteClient(viewModel.clients[index])
}
}
}
}
.refreshable {
await viewModel.loadClients()
}
}
}
struct ClientRow: View {
let client: Client
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(client.name)
.font(.headline)
if let description = client.description {
Text(description)
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(2)
}
}
}
}
struct ClientFormView: View {
@Environment(\.dismiss) private var dismiss
let onSave: (String, String?) -> Void
@State private var name = ""
@State private var description = ""
var body: some View {
NavigationStack {
Form {
Section("Name") {
TextField("Client name", text: $name)
}
Section("Description (Optional)") {
TextField("Description", text: $description, axis: .vertical)
.lineLimit(3...6)
}
}
.navigationTitle("New Client")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
onSave(name, description.isEmpty ? nil : description)
dismiss()
}
.disabled(name.isEmpty)
}
}
}
}
}