import SwiftUI import LabWiseKit @Observable final class ChemicalsViewModel { var chemicals: [Chemical] = [] var isLoading = false var errorMessage: String? private let client = ChemicalsClient() func loadChemicals() async { isLoading = true errorMessage = nil defer { isLoading = false } do { chemicals = try await client.list() } catch { errorMessage = "Failed to load chemicals." } } func delete(chemical: Chemical) async { do { try await client.delete(id: chemical.id) chemicals.removeAll { $0.id == chemical.id } } catch { errorMessage = "Failed to delete chemical." } } } struct ChemicalsListView: View { @State private var viewModel = ChemicalsViewModel() var body: some View { NavigationStack { Group { if viewModel.isLoading && viewModel.chemicals.isEmpty { ProgressView("Loading chemicals...") .frame(maxWidth: .infinity, maxHeight: .infinity) } else if viewModel.chemicals.isEmpty { ContentUnavailableView( "No Chemicals", systemImage: "flask", description: Text("Your chemical inventory is empty.") ) } else { List { ForEach(viewModel.chemicals) { chemical in NavigationLink(destination: ChemicalDetailView(chemical: chemical)) { ChemicalRowView(chemical: chemical) } } .onDelete { indexSet in for index in indexSet { let chemical = viewModel.chemicals[index] Task { await viewModel.delete(chemical: chemical) } } } } .refreshable { await viewModel.loadChemicals() } } } .navigationTitle("Chemicals") .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) { Button("OK") { viewModel.errorMessage = nil } } message: { Text(viewModel.errorMessage ?? "") } } .task { await viewModel.loadChemicals() } } } struct ChemicalRowView: View { let chemical: Chemical var body: some View { VStack(alignment: .leading, spacing: 4) { HStack { Text(chemical.chemicalName) .font(.headline) Spacer() PhysicalStateBadge(state: chemical.physicalState) } Text("CAS: \(chemical.casNumber)") .font(.caption) .foregroundStyle(.secondary) if let pct = chemical.percentageFull { PercentageBar(value: pct / 100) .frame(height: 4) .padding(.top, 2) } } .padding(.vertical, 2) } } struct PhysicalStateBadge: View { let state: String var color: Color { switch state.lowercased() { case "liquid": return Color(.brandPrimary) case "solid": return Color(red: 0.42, green: 0.30, blue: 0.18) case "gas": return Color(red: 0.22, green: 0.56, blue: 0.52) default: return Color(.brandMutedForeground) } } var body: some View { Text(state.capitalized) .font(.caption2.weight(.semibold)) .padding(.horizontal, 8) .padding(.vertical, 3) .background(color.opacity(0.15)) .foregroundStyle(color) .clipShape(Capsule()) } } struct PercentageBar: View { let value: Double // 0.0 - 1.0 var body: some View { GeometryReader { geo in ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 2) .fill(Color.secondary.opacity(0.2)) RoundedRectangle(cornerRadius: 2) .fill(barColor) .frame(width: geo.size.width * max(0, min(1, value))) } } } var barColor: Color { if value > 0.6 { return .green } if value > 0.25 { return .yellow } return .red } }