feat: Gruen ist die Vorgabe fuer Kategorien und Schlagwoerter

Die Farbauswahl der App lag dreimal gleich im Code; sie steht jetzt einmal
in Farbwahl, mit dem Markengruen an erster Stelle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-10-24 17:51:49 +02:00
co-authored by Claude Opus 5
parent a079bf406c
commit 2d1d571b17
8 changed files with 24 additions and 27 deletions
+3 -3
View File
@@ -409,7 +409,7 @@ class CategoryGroup(Base):
name: Mapped[str] = mapped_column(String(120), nullable=False) name: Mapped[str] = mapped_column(String(120), nullable=False)
kind: Mapped[CategoryKind] = mapped_column(_enum(CategoryKind), nullable=False, default=CategoryKind.expense) kind: Mapped[CategoryKind] = mapped_column(_enum(CategoryKind), nullable=False, default=CategoryKind.expense)
icon: Mapped[str] = mapped_column(String(40), nullable=False, default="list") icon: Mapped[str] = mapped_column(String(40), nullable=False, default="list")
color: Mapped[str] = mapped_column(String(9), nullable=False, default="#6b7480") color: Mapped[str] = mapped_column(String(9), nullable=False, default="#3f9c72")
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0) position: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
is_builtin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_builtin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
@@ -431,7 +431,7 @@ class Category(Base):
name: Mapped[str] = mapped_column(String(120), nullable=False) name: Mapped[str] = mapped_column(String(120), nullable=False)
type: Mapped[CategoryKind] = mapped_column(_enum(CategoryKind), nullable=False, default=CategoryKind.expense) type: Mapped[CategoryKind] = mapped_column(_enum(CategoryKind), nullable=False, default=CategoryKind.expense)
icon: Mapped[str] = mapped_column(String(40), nullable=False, default="more-horizontal") icon: Mapped[str] = mapped_column(String(40), nullable=False, default="more-horizontal")
color: Mapped[str] = mapped_column(String(9), nullable=False, default="#6b7480") color: Mapped[str] = mapped_column(String(9), nullable=False, default="#3f9c72")
position: Mapped[int] = mapped_column(Integer, nullable=False, default=0) position: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
is_builtin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_builtin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
is_archived: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) is_archived: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
@@ -1312,7 +1312,7 @@ class Tag(Base):
ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
) )
name: Mapped[str] = mapped_column(String(60), nullable=False) name: Mapped[str] = mapped_column(String(60), nullable=False)
color: Mapped[str] = mapped_column(String(9), nullable=False, default="#6b7480") color: Mapped[str] = mapped_column(String(9), nullable=False, default="#3f9c72")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now)
__table_args__ = ( __table_args__ = (
+3 -3
View File
@@ -361,7 +361,7 @@ class TagOut(BaseModel):
class TagCreate(BaseModel): class TagCreate(BaseModel):
name: str = Field(min_length=1, max_length=60) name: str = Field(min_length=1, max_length=60)
# Ohne Angabe dasselbe Grau wie bei einer neuen Kategorie. # Ohne Angabe dasselbe Grau wie bei einer neuen Kategorie.
color: str = "#6b7480" color: str = "#3f9c72"
class TagUpdate(BaseModel): class TagUpdate(BaseModel):
@@ -406,7 +406,7 @@ class CategoryGroupCreate(BaseModel):
name: str = Field(min_length=1, max_length=120) name: str = Field(min_length=1, max_length=120)
kind: CategoryKind = CategoryKind.expense kind: CategoryKind = CategoryKind.expense
icon: str = "list" icon: str = "list"
color: str = "#6b7480" color: str = "#3f9c72"
class CategoryGroupUpdate(BaseModel): class CategoryGroupUpdate(BaseModel):
@@ -420,7 +420,7 @@ class CategoryCreate(BaseModel):
group_id: int group_id: int
name: str = Field(min_length=1, max_length=120) name: str = Field(min_length=1, max_length=120)
icon: str = "more-horizontal" icon: str = "more-horizontal"
color: str = "#6b7480" color: str = "#3f9c72"
excluded_from_stats: bool = False excluded_from_stats: bool = False
spending_is_good: bool = False spending_is_good: bool = False
+10
View File
@@ -34,6 +34,16 @@ extension Color {
}) })
} }
/// Die Farbauswahl für Kategorien, Schlagwörter und Konten – eine Liste für
/// alle drei Formulare, damit sie nicht auseinanderlaufen. Das Markengrün
/// steht vorn und ist die Vorbelegung; der Rest bleibt bunt, sonst liessen
/// sich die Kategorien in Diagrammen nicht auseinanderhalten.
enum Farbwahl {
static let standard = "#3f9c72"
static let palette = [standard, "#3f51b5", "#b3541e", "#8e24aa",
"#c62828", "#0277bd", "#6b7480", "#5d4037"]
}
/// Übersetzt die Symbolnamen aus der Datenbank in SF Symbols. /// Übersetzt die Symbolnamen aus der Datenbank in SF Symbols.
/// ///
/// Die Weboberfläche benutzt Lucide-Namen und hält dafür ein eigenes /// Die Weboberfläche benutzt Lucide-Namen und hält dafür ein eigenes
+1 -6
View File
@@ -183,11 +183,6 @@ struct SymbolUndFarbeFeld: View {
"film", "music", "wifi", "phone", "gift", "briefcase", "refresh-ccw", "film", "music", "wifi", "phone", "gift", "briefcase", "refresh-ccw",
] ]
private static let farben = [
"#3f51b5", "#2e7d55", "#b3541e", "#8e24aa", "#c62828",
"#0277bd", "#6b7480", "#5d4037",
]
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
auswahl(Self.symbole, aktuell: symbol) { symbol = $0 } inhalt: { name in auswahl(Self.symbole, aktuell: symbol) { symbol = $0 } inhalt: { name in
@@ -196,7 +191,7 @@ struct SymbolUndFarbeFeld: View {
.foregroundStyle(Color(hex: farbe)) .foregroundStyle(Color(hex: farbe))
} }
auswahl(Self.farben, aktuell: farbe) { farbe = $0 } inhalt: { wert in auswahl(Farbwahl.palette, aktuell: farbe) { farbe = $0 } inhalt: { wert in
Circle().fill(Color(hex: wert)).frame(width: 22, height: 22) Circle().fill(Color(hex: wert)).frame(width: 22, height: 22)
} }
} }
+2 -2
View File
@@ -25,7 +25,7 @@ struct KategorieFormular: View {
@State private var name = "" @State private var name = ""
@State private var symbol = "more-horizontal" @State private var symbol = "more-horizontal"
@State private var farbe = "#6b7480" @State private var farbe = Farbwahl.standard
@State private var gruppeId = 0 @State private var gruppeId = 0
@State private var istSparen = false @State private var istSparen = false
@State private var ohneAuswertung = false @State private var ohneAuswertung = false
@@ -203,7 +203,7 @@ struct GruppenFormular: View {
@State private var name = "" @State private var name = ""
@State private var art: Kategorieart = .expense @State private var art: Kategorieart = .expense
@State private var symbol = "list" @State private var symbol = "list"
@State private var farbe = "#6b7480" @State private var farbe = Farbwahl.standard
@State private var fragtNachLoeschen = false @State private var fragtNachLoeschen = false
@State private var vorbelegt = false @State private var vorbelegt = false
+3 -11
View File
@@ -11,11 +11,6 @@ struct TagsAnsicht: View {
@State private var bearbeitet: Schlagwort? @State private var bearbeitet: Schlagwort?
@State private var neuerName = "" @State private var neuerName = ""
/// Dieselbe Auswahl wie im Symbol- und Farbfeld, damit die Etiketten zu
/// den Kategorien passen.
private static let farben = ["#3f51b5", "#2e7d55", "#b3541e", "#8e24aa",
"#c62828", "#0277bd", "#6b7480", "#5d4037"]
var body: some View { var body: some View {
List { List {
Section { Section {
@@ -77,7 +72,7 @@ struct TagsAnsicht: View {
let name = neuerName.trimmingCharacters(in: .whitespacesAndNewlines) let name = neuerName.trimmingCharacters(in: .whitespacesAndNewlines)
guard !name.isEmpty else { return } guard !name.isEmpty else { return }
// Reihum eine Farbe, damit nicht alle grau sind. Ändern geht danach. // Reihum eine Farbe, damit nicht alle grau sind. Ändern geht danach.
let farbe = Self.farben[zustand.nachschlag.schlagwoerter.count % Self.farben.count] let farbe = Farbwahl.palette[zustand.nachschlag.schlagwoerter.count % Farbwahl.palette.count]
let geglueckt = await vorgang.fuehreAus { let geglueckt = await vorgang.fuehreAus {
_ = try await client.legeTagAn(TagAnlegen(name: name, color: farbe)) _ = try await client.legeTagAn(TagAnlegen(name: name, color: farbe))
} }
@@ -104,12 +99,9 @@ struct TagFormular: View {
@Environment(\.dismiss) private var schliesse @Environment(\.dismiss) private var schliesse
@State private var vorgang = Speichervorgang() @State private var vorgang = Speichervorgang()
@State private var name = "" @State private var name = ""
@State private var farbe = "#6b7480" @State private var farbe = Farbwahl.standard
@State private var vorbelegt = false @State private var vorbelegt = false
private static let farben = ["#3f51b5", "#2e7d55", "#b3541e", "#8e24aa",
"#c62828", "#0277bd", "#6b7480", "#5d4037"]
var body: some View { var body: some View {
NavigationStack { NavigationStack {
Form { Form {
@@ -119,7 +111,7 @@ struct TagFormular: View {
Section { Section {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) { HStack(spacing: 12) {
ForEach(Self.farben, id: \.self) { wert in ForEach(Farbwahl.palette, id: \.self) { wert in
Button { farbe = wert } label: { Button { farbe = wert } label: {
Circle() Circle()
.fill(Color(hex: wert)) .fill(Color(hex: wert))
+1 -1
View File
@@ -187,7 +187,7 @@ export default function Categories() {
editing, editing,
form: editing form: editing
? { name: editing.name, kind: editing.kind, icon: editing.icon, color: editing.color } ? { name: editing.name, kind: editing.kind, icon: editing.icon, color: editing.color }
: { name: "", kind: "expense", icon: "list", color: "#3f51b5" }, : { name: "", kind: "expense", icon: "list", color: "#3f9c72" },
}); });
} }
function openCategory(groupId, editing = null) { function openCategory(groupId, editing = null) {
+1 -1
View File
@@ -17,7 +17,7 @@ import { useToast } from "../toast";
// //
// Gesetzt werden Tags nicht hier, sondern an der Buchung – in der Liste und im // Gesetzt werden Tags nicht hier, sondern an der Buchung – in der Liste und im
// Bearbeiten-Dialog. Hier geht es nur um den Vorrat. // Bearbeiten-Dialog. Hier geht es nur um den Vorrat.
const STANDARDFARBE = "#3f51b5"; const STANDARDFARBE = "#3f9c72";
export default function Tags() { export default function Tags() {
const { t } = useTranslation(); const { t } = useTranslation();