Missing Field Handling

This commit is contained in:
2026-05-01 13:54:05 -05:00
parent 5a6491fa51
commit 09447be473
9 changed files with 252 additions and 105 deletions

View File

@@ -57,28 +57,44 @@ struct ChemicalDetailView: View {
var body: some View {
Form {
Section("Identity") {
LabeledContent("Name", value: chemical.chemicalName)
LabeledContent("CAS Number", value: chemical.casNumber)
if let formula = chemical.chemicalFormula {
LabeledContent("Formula", value: formula)
}
if let mw = chemical.molecularWeight {
LabeledContent("Molecular Weight", value: mw)
}
LabeledContent("Physical State", value: chemical.physicalState.capitalized)
if let conc = chemical.concentration {
LabeledContent("Concentration", value: conc)
let missing = chemical.missingFields
if !missing.isEmpty {
Section {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
VStack(alignment: .leading, spacing: 2) {
Text("Missing \(missing.count) required \(missing.count == 1 ? "field" : "fields")")
.font(.subheadline.weight(.semibold))
Text(missing.joined(separator: ", "))
.font(.caption)
.foregroundStyle(.secondary)
}
}
Text("Tap Edit to fill them in.")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
}
Section("Identity") {
row("Chemical Name", chemical.chemicalName, required: true)
row("CAS #", chemical.casNumber, required: true)
row("Formula", chemical.chemicalFormula)
row("Molecular Weight", chemical.molecularWeight)
row("Physical State", chemical.physicalState?.capitalized, required: true)
row("Concentration", chemical.concentration)
}
Section("Storage") {
LabeledContent("Location", value: chemical.storageLocation)
LabeledContent("Device", value: chemical.storageDevice)
LabeledContent("Building", value: chemical.bldgCode)
LabeledContent("Lab", value: chemical.lab)
LabeledContent("Containers", value: chemical.numberOfContainers)
LabeledContent("Amount / Container", value: "\(chemical.amountPerContainer) \(chemical.unitOfMeasure)")
row("Storage Location", chemical.storageLocation, required: true)
row("Storage Device", chemical.storageDevice, required: true)
row("Building Code", chemical.bldgCode, required: true)
row("Lab", chemical.lab, required: true)
row("# of Containers", chemical.numberOfContainers, required: true)
amountRow
if let pct = chemical.percentageFull {
LabeledContent("% Full") {
HStack(spacing: 8) {
@@ -92,25 +108,15 @@ struct ChemicalDetailView: View {
}
Section("Vendor") {
LabeledContent("PI", value: chemical.piFirstName)
if let vendor = chemical.vendor {
LabeledContent("Vendor", value: vendor)
}
if let catalog = chemical.catalogNumber {
LabeledContent("Catalog #", value: catalog)
}
if let lot = chemical.lotNumber {
LabeledContent("Lot #", value: lot)
}
row("PI First Name", chemical.piFirstName, required: true)
row("Vendor", chemical.vendor)
row("Catalog #", chemical.catalogNumber)
row("Lot #", chemical.lotNumber)
if let exp = chemical.expirationDate, !exp.isEmpty {
LabeledContent("Expiration", value: formatDate(exp))
}
if let barcode = chemical.barcode {
LabeledContent("Barcode", value: barcode)
}
if let contact = chemical.contact {
LabeledContent("Contact", value: contact)
}
row("Barcode", chemical.barcode)
row("Contact", chemical.contact)
}
if let comments = chemical.comments, !comments.isEmpty {
@@ -129,7 +135,7 @@ struct ChemicalDetailView: View {
}
}
}
.navigationTitle(chemical.chemicalName)
.navigationTitle(chemical.displayName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
@@ -157,4 +163,46 @@ struct ChemicalDetailView: View {
chemical = match
}
}
/// Combines "<amount> <unit>" both required, so we show a Missing
/// placeholder if either side is empty. Yellow-tinted when incomplete.
@ViewBuilder
private var amountRow: some View {
let amt = (chemical.amountPerContainer ?? "").trimmingCharacters(in: .whitespaces)
let unit = (chemical.unitOfMeasure ?? "").trimmingCharacters(in: .whitespaces)
if amt.isEmpty || unit.isEmpty {
let parts: [String] = [
amt.isEmpty ? "Amount" : nil,
unit.isEmpty ? "Unit" : nil,
].compactMap { $0 }
LabeledContent("Amount / Container") {
Text("Missing \(parts.joined(separator: " & "))")
.italic()
.foregroundStyle(.orange)
}
.listRowBackground(Color.orange.opacity(0.12))
} else {
LabeledContent("Amount / Container", value: "\(amt) \(unit)")
}
}
/// Render a labelled row.
/// - Optional fields are hidden when empty (matches the web `<Field>` helper).
/// - Required fields always render: present value normally, missing value
/// with a yellow row background and an italic "Missing" placeholder so
/// the user can see exactly which field needs attention and where it lives.
@ViewBuilder
private func row(_ label: String, _ value: String?, required: Bool = false) -> some View {
let trimmed = (value ?? "").trimmingCharacters(in: .whitespaces)
if !trimmed.isEmpty {
LabeledContent(label, value: trimmed)
} else if required {
LabeledContent(label) {
Text("Missing")
.italic()
.foregroundStyle(.orange)
}
.listRowBackground(Color.orange.opacity(0.12))
}
}
}