139 lines
5.5 KiB
Swift
139 lines
5.5 KiB
Swift
import SwiftUI
|
|
import SwiftData
|
|
|
|
/// 「写日记 · 用药」:记一次服用流水 —— 选药(药品库或手输)+ 剂量 + 时间
|
|
/// → 带 `DiaryEntry.medicationTag` 的日记,进「记录」时间线的「用药」分类。
|
|
///
|
|
/// 与药品库(`Medication`,master 清单)分层:这里是「某次吃了多少、什么时候吃的」。
|
|
/// 嵌套 sheet,自带保存 / 取消(同 `SymptomStartSheet`),关闭后回到写日记页。
|
|
struct MedicationLogSheet: View {
|
|
@Environment(\.modelContext) private var ctx
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@Query(sort: \Medication.updatedAt, order: .reverse)
|
|
private var library: [Medication]
|
|
|
|
/// 选中的药品库药;手输模式为 nil。
|
|
@State private var selectedMed: Medication?
|
|
/// 手输药名(药品库为空,或想记不在库里的药)。与 selectedMed 互斥。
|
|
@State private var manualName = ""
|
|
@State private var dosage = ""
|
|
@State private var takenAt: Date = .now
|
|
|
|
/// 从药品库某个药直接拉起时预选它(药品库详情页「记录一次服用」)。默认 nil = 自由选择。
|
|
init(preselected: Medication? = nil) {
|
|
_selectedMed = State(initialValue: preselected)
|
|
}
|
|
|
|
private var resolvedName: String {
|
|
(selectedMed?.name ?? manualName).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
private var canSave: Bool { !resolvedName.isEmpty }
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section {
|
|
if library.isEmpty {
|
|
TextField(String(appLoc: "药名,如:缬沙坦胶囊"), text: $manualName)
|
|
.foregroundStyle(Tj.Palette.text)
|
|
} else {
|
|
ForEach(library) { m in
|
|
Button { select(m) } label: { medRow(m) }
|
|
.buttonStyle(.plain)
|
|
}
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "pencil")
|
|
.foregroundStyle(Tj.Palette.text3)
|
|
TextField(String(appLoc: "或手动输入药名"), text: $manualName)
|
|
.foregroundStyle(Tj.Palette.text)
|
|
.onChange(of: manualName) { _, v in
|
|
if !v.trimmingCharacters(in: .whitespaces).isEmpty {
|
|
selectedMed = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} header: {
|
|
Text("吃了哪个药")
|
|
} footer: {
|
|
if library.isEmpty {
|
|
Text("药品库还没有药,可在「记录 · 药品库」拍药盒或手动添加。这里直接手输也行。")
|
|
}
|
|
}
|
|
|
|
Section {
|
|
TextField(String(appLoc: "剂量,如:1 片 / 80mg"), text: $dosage)
|
|
.foregroundStyle(Tj.Palette.text)
|
|
} header: {
|
|
Text("剂量")
|
|
}
|
|
|
|
Section {
|
|
DatePicker(String(appLoc: "时间"), selection: $takenAt, in: ...Date.now)
|
|
} header: {
|
|
Text("时间")
|
|
}
|
|
}
|
|
.scrollContentBackground(.hidden)
|
|
.background(Tj.Palette.sand.ignoresSafeArea())
|
|
.navigationTitle("记录用药")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Button(String(appLoc: "取消")) { dismiss() }
|
|
}
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button(String(appLoc: "保存")) { save() }
|
|
.fontWeight(.semibold)
|
|
.disabled(!canSave)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func medRow(_ m: Medication) -> some View {
|
|
let on = selectedMed === m
|
|
return HStack(spacing: 10) {
|
|
Image(systemName: on ? "checkmark.circle.fill" : "circle")
|
|
.foregroundStyle(on ? Tj.Palette.ink : Tj.Palette.text3)
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(m.name)
|
|
.foregroundStyle(Tj.Palette.text)
|
|
if !m.detailLine.isEmpty {
|
|
Text(m.detailLine)
|
|
.font(.tjScaled( 11))
|
|
.foregroundStyle(Tj.Palette.text3)
|
|
}
|
|
}
|
|
Spacer(minLength: 0)
|
|
}
|
|
.contentShape(Rectangle())
|
|
}
|
|
|
|
private func select(_ m: Medication) {
|
|
selectedMed = m
|
|
manualName = ""
|
|
}
|
|
|
|
private func save() {
|
|
guard canSave else { return }
|
|
// content 单行:「药名 [规格] · 剂量」。剂量进正文,时间用 createdAt 承载。
|
|
// 与 TimelineEntry.firstLine / TimelineEntryDetailView.medicationLines 单行解析兼容。
|
|
var line = resolvedName
|
|
if let s = selectedMed?.strength, !s.isEmpty { line += " \(s)" }
|
|
let dose = dosage.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if !dose.isEmpty { line += " · \(dose)" }
|
|
|
|
let entry = DiaryEntry(content: line, createdAt: takenAt, tags: [DiaryEntry.medicationTag])
|
|
ctx.insert(entry)
|
|
try? ctx.save()
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
MedicationLogSheet()
|
|
.modelContainer(for: [Medication.self, DiaryEntry.self, Asset.self], inMemory: true)
|
|
}
|