iOS: Diagramm-Einstellungen der Web-Karten beim Spiegeln uebernehmen
Die gespiegelte Web-Uebersicht ignorierte bisher die Karten-Props der Diagramme. Jetzt werden uebernommen: - Ablauf-Ring: 'Ohne MHD einbeziehen' (mit_ohne_mhd) - Kategorien-Ring: 'Detailansicht' (alle einzeln statt 'Uebrige') (detail) - Kategorien-Ring: 'Kategorie-Tiefe' (tiefe -> /by-category?depth=N) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -423,8 +423,9 @@ actor APIClient {
|
||||
try await send(try makeRequest("/dashboard/expiry-split"), as: ExpirySplit.self)
|
||||
}
|
||||
|
||||
func byCategory() async throws -> [CategoryShare] {
|
||||
try await send(try makeRequest("/dashboard/by-category"), as: [CategoryShare].self)
|
||||
func byCategory(depth: Int? = nil) async throws -> [CategoryShare] {
|
||||
let suffix = depth.map { "?depth=\($0)" } ?? ""
|
||||
return try await send(try makeRequest("/dashboard/by-category" + suffix), as: [CategoryShare].self)
|
||||
}
|
||||
|
||||
func timeline(days: Int, productId: Int? = nil) async throws -> [TimelinePoint] {
|
||||
|
||||
@@ -76,11 +76,15 @@ private struct ProportionChart: View {
|
||||
struct ChartCardView: View {
|
||||
let type: String
|
||||
let stand: Int
|
||||
// Aus den Web-Karten-Einstellungen (props): werden hier angewandt.
|
||||
var detail: Bool = false
|
||||
var tiefe: Int? = nil
|
||||
var mitOhneMhd: Bool = true
|
||||
|
||||
var body: some View {
|
||||
switch type {
|
||||
case "expiry-donut": ExpiryDonutCard(stand: stand)
|
||||
case "category-donut": CategoryDonutCard(stand: stand)
|
||||
case "expiry-donut": ExpiryDonutCard(stand: stand, mitOhneMhd: mitOhneMhd)
|
||||
case "category-donut": CategoryDonutCard(stand: stand, detail: detail, tiefe: tiefe)
|
||||
case "category-bars": CategoryBarsCard(stand: stand)
|
||||
case "stock-timeline": StockTimelineCard(stand: stand)
|
||||
case "activity-timeline": ActivityTimelineCard(stand: stand)
|
||||
@@ -122,14 +126,19 @@ private struct ChartLoader<Data, Inhalt: View>: View {
|
||||
|
||||
struct ExpiryDonutCard: View {
|
||||
let stand: Int
|
||||
// Web-Karte „Ohne MHD einbeziehen": aus, blendet den grauen Anteil aus.
|
||||
var mitOhneMhd: Bool = true
|
||||
var body: some View {
|
||||
ChartLoader(laden: { try await APIClient.shared.expirySplit() }, stand: stand) { s in
|
||||
ProportionChart(segments: [
|
||||
var segmente = [
|
||||
Segment(label: "In Ordnung", value: s.ok, color: ExpiryColor.ok),
|
||||
Segment(label: "Bald ablaufend", value: s.soon, color: ExpiryColor.soon),
|
||||
Segment(label: "Abgelaufen", value: s.expired, color: ExpiryColor.expired),
|
||||
Segment(label: "Ohne MHD", value: s.noDate, color: ExpiryColor.noDate),
|
||||
], einheit: "Artikeleinheiten")
|
||||
]
|
||||
if mitOhneMhd {
|
||||
segmente.append(Segment(label: "Ohne MHD", value: s.noDate, color: ExpiryColor.noDate))
|
||||
}
|
||||
return ProportionChart(segments: segmente, einheit: "Artikeleinheiten")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,26 +147,46 @@ struct ExpiryDonutCard: View {
|
||||
|
||||
struct CategoryDonutCard: View {
|
||||
let stand: Int
|
||||
// Web-Karte „Detailansicht": alle Kategorien einzeln statt „Übrige".
|
||||
var detail: Bool = false
|
||||
// Web-Karte „Kategorie-Tiefe": auf welche Ebene hochgerollt wird (nil = feinste).
|
||||
var tiefe: Int? = nil
|
||||
|
||||
// Wiederholbare, ruhige Farbfolge fuer die Kategorien.
|
||||
private static let palette: [Color] = [
|
||||
.blue, .green, .orange, .purple, .pink, .teal, .indigo, .brown,
|
||||
]
|
||||
|
||||
// Ab dem Ende der Palette werden Farben ueber den goldenen Winkel erzeugt,
|
||||
// damit auch viele Kategorien (Detailansicht) unterscheidbar bleiben.
|
||||
private static func farbe(_ i: Int) -> Color {
|
||||
if i < palette.count { return palette[i] }
|
||||
let hue = (Double(i) * 0.61803398875).truncatingRemainder(dividingBy: 1)
|
||||
return Color(hue: hue, saturation: 0.55, brightness: 0.78)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ChartLoader(laden: { try await APIClient.shared.byCategory() }, stand: stand) { liste in
|
||||
ChartLoader(laden: { try await APIClient.shared.byCategory(depth: tiefe) }, stand: stand) { liste in
|
||||
let sortiert = liste.filter { $0.articleUnits > 0 }
|
||||
.sorted { $0.articleUnits > $1.articleUnits }
|
||||
// Die groessten sechs einzeln, der Rest gebuendelt - sonst wird der
|
||||
// Ring unlesbar.
|
||||
let kopf = sortiert.prefix(6)
|
||||
let restWert = sortiert.dropFirst(6).reduce(0) { $0 + $1.articleUnits }
|
||||
var segmente = kopf.enumerated().map { i, c in
|
||||
Segment(label: c.name, value: c.articleUnits,
|
||||
color: Self.palette[i % Self.palette.count])
|
||||
}
|
||||
if restWert > 0 {
|
||||
segmente.append(Segment(label: "Übrige", value: restWert, color: .gray))
|
||||
let segmente: [Segment]
|
||||
if detail {
|
||||
// Detailansicht: alle Kategorien einzeln, nichts wird gebuendelt.
|
||||
segmente = sortiert.enumerated().map { i, c in
|
||||
Segment(label: c.name, value: c.articleUnits, color: Self.farbe(i))
|
||||
}
|
||||
} else {
|
||||
// Die groessten sechs einzeln, der Rest gebuendelt - sonst wird der
|
||||
// Ring unlesbar.
|
||||
let kopf = sortiert.prefix(6)
|
||||
let restWert = sortiert.dropFirst(6).reduce(0) { $0 + $1.articleUnits }
|
||||
var s = kopf.enumerated().map { i, c in
|
||||
Segment(label: c.name, value: c.articleUnits, color: Self.farbe(i))
|
||||
}
|
||||
if restWert > 0 {
|
||||
s.append(Segment(label: "Übrige", value: restWert, color: .gray))
|
||||
}
|
||||
segmente = s
|
||||
}
|
||||
return ProportionChart(segments: segmente, einheit: "Artikeleinheiten")
|
||||
}
|
||||
|
||||
@@ -103,7 +103,10 @@ struct MirrorOverview: View {
|
||||
ForEach(karten) { karte in
|
||||
Section {
|
||||
DashboardCardView(type: karte.type, productId: karte.props?.productId,
|
||||
stand: stand) { stand += 1 }
|
||||
stand: stand,
|
||||
detail: karte.props?.detail ?? false,
|
||||
tiefe: karte.props?.tiefe.flatMap { Int($0) },
|
||||
mitOhneMhd: karte.props?.mitOhneMhd ?? true) { stand += 1 }
|
||||
} header: {
|
||||
Text(CardCatalog.titel(karte.type))
|
||||
}
|
||||
@@ -174,6 +177,11 @@ struct DashboardCardView: View {
|
||||
let type: String
|
||||
let productId: Int?
|
||||
let stand: Int
|
||||
// Aus den Web-Karten-Einstellungen (props). Standardwerte = bisheriges Verhalten,
|
||||
// damit die lokale App-Übersicht unverändert bleibt.
|
||||
var detail: Bool = false
|
||||
var tiefe: Int? = nil
|
||||
var mitOhneMhd: Bool = true
|
||||
let onChange: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -198,7 +206,8 @@ struct DashboardCardView: View {
|
||||
ProductTimelineChart(productId: productId, stand: stand)
|
||||
case "expiry-donut", "category-donut", "category-bars",
|
||||
"stock-timeline", "activity-timeline":
|
||||
ChartCardView(type: type, stand: stand)
|
||||
ChartCardView(type: type, stand: stand,
|
||||
detail: detail, tiefe: tiefe, mitOhneMhd: mitOhneMhd)
|
||||
default:
|
||||
Text("Unbekannte Karte „\(type)“.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
|
||||
@@ -1038,9 +1038,17 @@ struct DashboardCard: Codable, Identifiable, Hashable {
|
||||
/// Web-Oberflaeche; hier bestimmen sie allein die Reihenfolge.
|
||||
struct CardProps: Codable, Hashable {
|
||||
let productId: Int?
|
||||
let locationId: String?
|
||||
let detail: Bool? // Kategorien-Ring: alle einzeln statt „Übrige"
|
||||
let tiefe: String? // Kategorie-Tiefe ("" = feinste, "1"/"2"/…)
|
||||
let mitOhneMhd: Bool? // Ablauf-Ring: „Ohne MHD" einbeziehen
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case productId = "product_id"
|
||||
case locationId = "location_id"
|
||||
case detail
|
||||
case tiefe
|
||||
case mitOhneMhd = "mit_ohne_mhd"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user