iOS: Kassenzettel in den Scannen-Tab, Markenblau statt System-Blau, weisse Kacheln, luftigere Pruefen-Liste
- Kassenzettel-Kachel vom Listen- in den Scannen-Tab verschoben (passt thematisch). - App-Akzent auf Markenblau (#3F51B5/#7C8AE8, wie Web/App-Icon) via .tint; alle Color.accentColor -> Color.marke. System-Blau verschwindet. - Kachel-Buttons/Links auf .buttonStyle(.plain): weisse Schrift/Symbole statt getoentem Blau. - Kassenzettel-Pruefen-Liste: je Artikel eine eigene Section (insetGrouped) mit Artikel/Menge/Lagerort in eigenen Zeilen - klar gruppiert, nicht mehr gequetscht. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ struct AssignScanView: View {
|
|||||||
.font(.caption).padding(.top, 2)
|
.font(.caption).padding(.top, 2)
|
||||||
}
|
}
|
||||||
.padding().frame(maxWidth: .infinity)
|
.padding().frame(maxWidth: .infinity)
|
||||||
.background(Color.accentColor.opacity(0.15))
|
.background(Color.marke.opacity(0.15))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12)).padding(.horizontal)
|
.clipShape(RoundedRectangle(cornerRadius: 12)).padding(.horizontal)
|
||||||
} else {
|
} else {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
|
|||||||
@@ -238,9 +238,9 @@ private struct LinienDiagramm: View {
|
|||||||
Chart(punkte) { p in
|
Chart(punkte) { p in
|
||||||
if let datum = DisplaySettings.parseTimestamp(p.at) {
|
if let datum = DisplaySettings.parseTimestamp(p.at) {
|
||||||
LineMark(x: .value("Zeit", datum), y: .value("Bestand", p.articleUnits))
|
LineMark(x: .value("Zeit", datum), y: .value("Bestand", p.articleUnits))
|
||||||
.foregroundStyle(Color.accentColor)
|
.foregroundStyle(Color.marke)
|
||||||
AreaMark(x: .value("Zeit", datum), y: .value("Bestand", p.articleUnits))
|
AreaMark(x: .value("Zeit", datum), y: .value("Bestand", p.articleUnits))
|
||||||
.foregroundStyle(Color.accentColor.opacity(0.12))
|
.foregroundStyle(Color.marke.opacity(0.12))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 170)
|
.frame(height: 170)
|
||||||
|
|||||||
@@ -503,12 +503,12 @@ struct KassenzettelMarkierenView: View {
|
|||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
Image(uiImage: bild).resizable().scaledToFit()
|
Image(uiImage: bild).resizable().scaledToFit()
|
||||||
.frame(width: geo.size.width, height: geo.size.height)
|
.frame(width: geo.size.width, height: geo.size.height)
|
||||||
Rectangle().stroke(Color.accentColor, lineWidth: 2)
|
Rectangle().stroke(Color.marke, lineWidth: 2)
|
||||||
.background(Color.accentColor.opacity(0.15))
|
.background(Color.marke.opacity(0.15))
|
||||||
.frame(width: rect.width, height: rect.height)
|
.frame(width: rect.width, height: rect.height)
|
||||||
.offset(x: rect.minX, y: rect.minY)
|
.offset(x: rect.minX, y: rect.minY)
|
||||||
.gesture(zieh(resize: false))
|
.gesture(zieh(resize: false))
|
||||||
Rectangle().fill(Color.accentColor)
|
Rectangle().fill(Color.marke)
|
||||||
.frame(width: 26, height: 26)
|
.frame(width: 26, height: 26)
|
||||||
.offset(x: rect.maxX - 13, y: rect.maxY - 13)
|
.offset(x: rect.maxX - 13, y: rect.maxY - 13)
|
||||||
.gesture(zieh(resize: true))
|
.gesture(zieh(resize: true))
|
||||||
@@ -608,7 +608,14 @@ struct KassenzettelPruefenView: View {
|
|||||||
}.padding()
|
}.padding()
|
||||||
} else {
|
} else {
|
||||||
List {
|
List {
|
||||||
ForEach($zeilen) { $z in zeile($z) }
|
ForEach($zeilen) { $z in
|
||||||
|
Section {
|
||||||
|
zeile($z)
|
||||||
|
} header: {
|
||||||
|
Text(z.text)
|
||||||
|
.textCase(nil).font(.footnote).foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
Section {
|
Section {
|
||||||
Button { Task { await einlagern() } } label: {
|
Button { Task { await einlagern() } } label: {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -620,6 +627,7 @@ struct KassenzettelPruefenView: View {
|
|||||||
Text("Nicht zugeordnete Zeilen werden übersprungen. Neue Artikel werden nicht angelegt.")
|
Text("Nicht zugeordnete Zeilen werden übersprungen. Neue Artikel werden nicht angelegt.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.listStyle(.insetGrouped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear(perform: aufbauen)
|
.onAppear(perform: aufbauen)
|
||||||
@@ -637,10 +645,11 @@ struct KassenzettelPruefenView: View {
|
|||||||
} message: { Text(meldung ?? "") }
|
} message: { Text(meldung ?? "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drei Zeilen (Rows) je Artikel: Auswahl, Menge, Lagerort. In einer eigenen
|
||||||
|
/// Section gruppiert – so ist klar, was zusammengehört, und nichts ist gequetscht.
|
||||||
@ViewBuilder private func zeile(_ z: Binding<KassenzettelZeile>) -> some View {
|
@ViewBuilder private func zeile(_ z: Binding<KassenzettelZeile>) -> some View {
|
||||||
let zeile = z.wrappedValue
|
let zeile = z.wrappedValue
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
// Artikel wählen
|
||||||
Text(zeile.text).font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(zeile.kandidaten) { k in
|
ForEach(zeile.kandidaten) { k in
|
||||||
Button("\(k.name) · \(k.score) %") {
|
Button("\(k.name) · \(k.score) %") {
|
||||||
@@ -664,20 +673,21 @@ struct KassenzettelPruefenView: View {
|
|||||||
Image(systemName: "chevron.up.chevron.down").font(.caption2).foregroundStyle(.secondary)
|
Image(systemName: "chevron.up.chevron.down").font(.caption2).foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HStack(spacing: 12) {
|
// Menge
|
||||||
Stepper("Menge \(Int(zeile.menge))", value: z.menge, in: 1...99).fixedSize()
|
Stepper("Menge: \(Int(zeile.menge))", value: z.menge, in: 1...99)
|
||||||
Spacer()
|
// Lagerort + QR-Scan
|
||||||
|
HStack {
|
||||||
Menu {
|
Menu {
|
||||||
Button("– ohne –") { z.wrappedValue.ortId = nil }
|
Button("– ohne –") { z.wrappedValue.ortId = nil }
|
||||||
ForEach(locations) { loc in Button(loc.name) { z.wrappedValue.ortId = loc.id } }
|
ForEach(locations) { loc in Button(loc.name) { z.wrappedValue.ortId = loc.id } }
|
||||||
} label: {
|
} label: {
|
||||||
Label(ortName(zeile.ortId), systemImage: "mappin.and.ellipse").font(.callout).lineLimit(1)
|
Label(ortName(zeile.ortId), systemImage: "mappin.and.ellipse")
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
Button { scanZiel = ZeilenRef(id: zeile.id) } label: {
|
Button { scanZiel = ZeilenRef(id: zeile.id) } label: {
|
||||||
Image(systemName: "qrcode.viewfinder")
|
Label("QR", systemImage: "qrcode.viewfinder")
|
||||||
}.buttonStyle(.borderless)
|
}.buttonStyle(.borderless)
|
||||||
}
|
}
|
||||||
}.padding(.vertical, 4)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ortName(_ id: String?) -> String {
|
private func ortName(_ id: String?) -> String {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ struct QuickBookingCard: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.tint(einlagern ? Color.accentColor : .orange)
|
.tint(einlagern ? Color.marke : .orange)
|
||||||
.disabled(!bereit)
|
.disabled(!bereit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,10 +171,10 @@ struct StatusCard: View {
|
|||||||
switch art {
|
switch art {
|
||||||
case "kpi-products":
|
case "kpi-products":
|
||||||
Kennzahl(wert: String(stats.productsInStock), label: "Artikel mit Bestand",
|
Kennzahl(wert: String(stats.productsInStock), label: "Artikel mit Bestand",
|
||||||
detail: "von \(stats.productsTotal)", tint: .accentColor)
|
detail: "von \(stats.productsTotal)", tint: .marke)
|
||||||
case "kpi-units":
|
case "kpi-units":
|
||||||
Kennzahl(wert: formatAmount(stats.articleUnits), label: "Artikeleinheiten",
|
Kennzahl(wert: formatAmount(stats.articleUnits), label: "Artikeleinheiten",
|
||||||
detail: "", tint: .accentColor)
|
detail: "", tint: .marke)
|
||||||
default:
|
default:
|
||||||
HStack(alignment: .top, spacing: 12) {
|
HStack(alignment: .top, spacing: 12) {
|
||||||
Kennzahl(wert: String(stats.expiringSoon), label: "Bald ablaufend",
|
Kennzahl(wert: String(stats.expiringSoon), label: "Bald ablaufend",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ struct DateRow: View {
|
|||||||
Text(label).foregroundStyle(.primary)
|
Text(label).foregroundStyle(.primary)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(Self.fmt.string(from: date))
|
Text(Self.fmt.string(from: date))
|
||||||
.foregroundStyle(open ? Color.accentColor : .secondary)
|
.foregroundStyle(open ? Color.marke : .secondary)
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ struct MinStockView: View {
|
|||||||
if r.isGroup {
|
if r.isGroup {
|
||||||
Text("Gruppe").font(.caption2)
|
Text("Gruppe").font(.caption2)
|
||||||
.padding(.horizontal, 6).padding(.vertical, 1)
|
.padding(.horizontal, 6).padding(.vertical, 1)
|
||||||
.background(Color.accentColor.opacity(0.2), in: Capsule())
|
.background(Color.marke.opacity(0.2), in: Capsule())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text("\(r.ort) · Soll \(r.soll)" + (r.bestand.map { " · Bestand \($0)" } ?? ""))
|
Text("\(r.ort) · Soll \(r.soll)" + (r.bestand.map { " · Bestand \($0)" } ?? ""))
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ struct LocalOverview: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text(produkte.first { $0.id == karte.productId }?.name ?? "Artikel wählen")
|
Text(produkte.first { $0.id == karte.productId }?.name ?? "Artikel wählen")
|
||||||
.font(.caption).foregroundStyle(Color.accentColor)
|
.font(.caption).foregroundStyle(Color.marke)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ struct CardPickerView: View {
|
|||||||
Text("· Artikel").font(.caption).foregroundStyle(.secondary)
|
Text("· Artikel").font(.caption).foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Image(systemName: "plus.circle").foregroundStyle(Color.accentColor)
|
Image(systemName: "plus.circle").foregroundStyle(Color.marke)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|||||||
@@ -99,14 +99,25 @@ struct HomeView: View {
|
|||||||
.tabItem { Label("Verwaltung", systemImage: "gearshape") }
|
.tabItem { Label("Verwaltung", systemImage: "gearshape") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tint(Color.marke)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
/// Markenblau (wie App-Icon und Web-Akzent) statt des System-Blaus.
|
||||||
|
static let marke = Color(UIColor { trait in
|
||||||
|
trait.userInterfaceStyle == .dark
|
||||||
|
? UIColor(red: 0.486, green: 0.541, blue: 0.910, alpha: 1) // #7C8AE8
|
||||||
|
: UIColor(red: 0.247, green: 0.318, blue: 0.710, alpha: 1) // #3F51B5
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Die beiden Kacheln, die frueher die Startseite waren.
|
/// Die beiden Kacheln, die frueher die Startseite waren.
|
||||||
struct ScanTabView: View {
|
struct ScanTabView: View {
|
||||||
@EnvironmentObject private var router: Router
|
@EnvironmentObject private var router: Router
|
||||||
@EnvironmentObject private var session: Session
|
@EnvironmentObject private var session: Session
|
||||||
@State private var showNew = false
|
@State private var showNew = false
|
||||||
|
@State private var showKassenzettel = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -132,6 +143,13 @@ struct ScanTabView: View {
|
|||||||
subtitle: "Barcode/QR scannen und nur ansehen",
|
subtitle: "Barcode/QR scannen und nur ansehen",
|
||||||
systemImage: "doc.text.magnifyingglass")
|
systemImage: "doc.text.magnifyingglass")
|
||||||
}
|
}
|
||||||
|
Button {
|
||||||
|
showKassenzettel = true
|
||||||
|
} label: {
|
||||||
|
ActionTile(title: "Kassenzettel",
|
||||||
|
subtitle: "Bon fotografieren und Lebensmittel einlagern",
|
||||||
|
systemImage: "doc.text.viewfinder")
|
||||||
|
}
|
||||||
// Ohne Barcode: gerade bei Gegenstaenden (Kleidung, Werkzeug) gibt
|
// Ohne Barcode: gerade bei Gegenstaenden (Kleidung, Werkzeug) gibt
|
||||||
// es keinen Code zum Scannen. Deshalb hier ein offensichtlicher Weg.
|
// es keinen Code zum Scannen. Deshalb hier ein offensichtlicher Weg.
|
||||||
if session.isAdmin {
|
if session.isAdmin {
|
||||||
@@ -152,8 +170,10 @@ struct ScanTabView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
.padding()
|
.padding()
|
||||||
.navigationTitle("Scannen")
|
.navigationTitle("Scannen")
|
||||||
|
.fullScreenCover(isPresented: $showKassenzettel) { KassenzettelView() }
|
||||||
.sheet(isPresented: $showNew) {
|
.sheet(isPresented: $showNew) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ProductFormView(prefillBarcode: nil, groupId: nil) { _ in
|
ProductFormView(prefillBarcode: nil, groupId: nil) { _ in
|
||||||
@@ -166,8 +186,6 @@ struct ScanTabView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ListsTabView: View {
|
struct ListsTabView: View {
|
||||||
@State private var kassenzettelAn = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
@@ -215,18 +233,11 @@ struct ListsTabView: View {
|
|||||||
ActionTile(title: "Verlauf", subtitle: "Wer hat wann was ein- und ausgelagert",
|
ActionTile(title: "Verlauf", subtitle: "Wer hat wann was ein- und ausgelagert",
|
||||||
systemImage: "clock.arrow.circlepath")
|
systemImage: "clock.arrow.circlepath")
|
||||||
}
|
}
|
||||||
Button {
|
|
||||||
kassenzettelAn = true
|
|
||||||
} label: {
|
|
||||||
ActionTile(title: "Kassenzettel", subtitle: "Bon fotografieren und Lebensmittel einlagern",
|
|
||||||
systemImage: "doc.text.viewfinder")
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
.padding()
|
.padding()
|
||||||
.navigationTitle("Listen")
|
.navigationTitle("Listen")
|
||||||
.fullScreenCover(isPresented: $kassenzettelAn) { KassenzettelView() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,7 +253,7 @@ struct CompactTile: View {
|
|||||||
Image(systemName: systemImage)
|
Image(systemName: systemImage)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.frame(width: 44, height: 44)
|
.frame(width: 44, height: 44)
|
||||||
.background(Color.accentColor.opacity(0.15))
|
.background(Color.marke.opacity(0.15))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
Text(title).font(.headline)
|
Text(title).font(.headline)
|
||||||
}
|
}
|
||||||
@@ -264,7 +275,7 @@ struct ActionTile: View {
|
|||||||
Image(systemName: systemImage)
|
Image(systemName: systemImage)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.frame(width: 44, height: 44)
|
.frame(width: 44, height: 44)
|
||||||
.background(Color.accentColor.opacity(0.15))
|
.background(Color.marke.opacity(0.15))
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(title).font(.headline)
|
Text(title).font(.headline)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ struct TorchButton: View {
|
|||||||
isOn.toggle()
|
isOn.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: isOn ? "bolt.fill" : "bolt.slash.fill")
|
Image(systemName: isOn ? "bolt.fill" : "bolt.slash.fill")
|
||||||
.foregroundStyle(locked ? Color.yellow : Color.accentColor)
|
.foregroundStyle(locked ? Color.yellow : Color.marke)
|
||||||
}
|
}
|
||||||
.simultaneousGesture(
|
.simultaneousGesture(
|
||||||
LongPressGesture(minimumDuration: 0.4).onEnded { _ in
|
LongPressGesture(minimumDuration: 0.4).onEnded { _ in
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ struct ServerListView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Image(systemName: NotificationStore.config(for: profile.id).enabled
|
Image(systemName: NotificationStore.config(for: profile.id).enabled
|
||||||
? "bell.fill" : "bell")
|
? "bell.fill" : "bell")
|
||||||
.foregroundStyle(Color.accentColor)
|
.foregroundStyle(Color.marke)
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ struct ServerListView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
if profile.id == session.activeProfileID {
|
if profile.id == session.activeProfileID {
|
||||||
Image(systemName: "checkmark").foregroundStyle(Color.accentColor)
|
Image(systemName: "checkmark").foregroundStyle(Color.marke)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|||||||
Reference in New Issue
Block a user