Missing Field Handling
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user