Files
RAPPORT/src/constants.js
T
karim 00f07d76f6 Rapport 0.6 — Initial Public Release
Sicherheits-Hardening
- Passwort-Hashing mit PBKDF2 (SHA-256, 100k Iterationen) inkl. transparenter
  Migration bestehender Klartext-Passwörter beim ersten Login
- Login Brute-Force-Schutz (5 Fehlversuche → 60s Lockout), Constant-Time-Compare,
  Mindestpasswortlänge 8 Zeichen
- HTML-Sanitizer für Brieftexte (Allowlist, entfernt javascript:/data:/vbscript:-URLs,
  Event-Handler, Script-Tags; rel=noopener für target=_blank)
- Datenexport entfernt Legacy-Klartextpasswörter (Hashes bleiben)
- Kryptografische IDs via crypto.randomUUID statt Math.random
- sessionStorage speichert keine Credentials mehr

GUI & Performance
- Code-Splitting pro View via React.lazy + Suspense (Initial-Bundle 86 KB gzipped)
- swissqrbill als lokale Dependency — QR-Rechnungen offline-fähig
- Spesenbelege (Bild/PDF) direkt in der Tageserfassung mit Bildkomprimierung
- Avatar-Upload: 256px-Skalierung + JPEG-Kompression, Typprüfung
- Über-Rapport-Modal, einheitliche Bearbeiten-Icons, Pinnwand-Kategorien als Pills

Bug-Fixes
- Auto-überfällig-Routine läuft nur noch einmal pro Tag (verhindert Re-Render-Loop)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 01:16:26 +02:00

253 lines
11 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ─── CONSTANTS ──────────────────────────────────────────────────
export const STORAGE_KEY = "studio_data_v1";
// SIA-Phasen nach SIA 102/103 (Architektur)
export const SIA_PHASES = [
{ id: "11", label: "11 Strategische Planung" },
{ id: "21", label: "21 Vorstudien" },
{ id: "22", label: "22 Machbarkeitsstudie" },
{ id: "31", label: "31 Vorprojekt" },
{ id: "32", label: "32 Bauprojekt" },
{ id: "33", label: "33 Bewilligungsverfahren" },
{ id: "41", label: "41 Ausschreibung" },
{ id: "51", label: "51 Ausführungsprojekt" },
{ id: "52", label: "52 Ausführung" },
{ id: "53", label: "53 Inbetriebnahme / Abschluss" },
];
// SIA 102 Teilleistungen mit Standard-Prozentanteilen
export const SIA_PHASE_WEIGHTS = [
{ id: "31", label: "Vorprojekt", items: [
{ label: "Lösungsstudien / Grobschätzung", pct: 3 },
{ label: "Vorprojekt / Kostenschätzung", pct: 6 },
]},
{ id: "32", label: "Bauprojekt", items: [
{ label: "Bauprojekt", pct: 13 },
{ label: "Detailstudien", pct: 4 },
{ label: "Kostenvoranschlag", pct: 4 },
]},
{ id: "33", label: "Bewilligungsverfahren", items: [
{ label: "Bewilligungsverfahren", pct: 2.5 },
]},
{ id: "41", label: "Ausschreibung", items: [
{ label: "Ausschreibungspläne", pct: 10 },
{ label: "Ausschreibung / Offertvergleich", pct: 8 },
]},
{ id: "51", label: "Ausführungsplanung", items: [
{ label: "Ausführungspläne", pct: 15 },
{ label: "Werkverträge", pct: 1 },
]},
{ id: "52", label: "Ausführung", items: [
{ label: "Gestalterische Leitung", pct: 6 },
{ label: "Bauleitung / Kostenkontrolle", pct: 23 },
]},
{ id: "53", label: "Inbetriebnahme / Abschluss", items: [
{ label: "Inbetriebnahme", pct: 1 },
{ label: "Dokumentation", pct: 1 },
{ label: "Garantiearbeiten", pct: 1.5 },
{ label: "Schlussabrechnung", pct: 1 },
]},
];
// Projekt-Typen
export const PROJECT_TYPES = [
"Wettbewerb",
"Studienauftrag",
"Direktauftrag",
"Machbarkeitsstudie",
"Gutachten",
"Grafik",
"Sonstiges",
];
export const EXPENSE_CATEGORIES = [
"Reise / Fahrt", "Drucken / Reprografie", "Modellbau / Material",
"Büromaterial", "Weiterbildung",
"Unterauftrag / Freelancer", "Verpflegung / Geschäftsessen",
"Sonstiges",
];
export const INTERNAL_EXPENSE_CATEGORIES = [
"Miete / Raumkosten", "Software / Lizenzen", "Hardware / IT",
"Telefon / Internet", "Versicherung", "Steuern / Abgaben",
"Büromaterial", "Marketing / Werbung", "Weiterbildung",
"Unterauftrag / Freelancer", "Bankgebühren", "Sonstiges",
];
export const NAV_ITEMS = [
{ id: "dashboard", label: "Übersicht", icon: "grid_view" },
{ id: "pinnwand", label: "Pinnwand", icon: "campaign" },
{ id: "projects", label: "Projekte", icon: "work" },
{ id: "time", label: "Zeiterfassung", icon: "schedule" },
{ id: "quotes", label: "Offerten", icon: "request_quote" },
{ id: "buchhaltung", label: "Buchhaltung", icon: "account_balance", children: [
{ id: "invoices", label: "Rechnungen" },
{ id: "internal-expenses", label: "Ausgaben" },
{ id: "expenses", label: "Spesen" },
{ id: "loehne", label: "Löhne" },
{ id: "studio-budget", label: "Budget" },
]},
{ id: "dokumente", label: "Dokumente", icon: "folder", children: [
{ id: "protokolle", label: "Protokolle" },
{ id: "lieferscheine", label: "Lieferscheine" },
{ id: "letters", label: "Briefe" },
]},
{ id: "personen", label: "Personen", icon: "group" },
{ id: "mitarbeiter", label: "Mitarbeiter", icon: "badge" },
{ id: "settings", label: "Einstellungen", icon: "settings" },
];
export const STATUS_COLORS = {
aktiv: "#2d6a4f", abgeschlossen: "#555", pausiert: "#b5621e",
entwurf: "#7a6a00", gesendet: "#1a4e8a", bezahlt: "#2d6a4f", überfällig: "#8a1a1a",
offen: "#7a6a00", angenommen: "#2d6a4f", abgelehnt: "#8a1a1a", abgelaufen: "#888",
genehmigt: "#1a4e8a", "auf nächsten Lohn": "#b5621e", ausbezahlt: "#2d6a4f",
};
export const STATUS_BG = {
aktiv: "#e8f5ee", abgeschlossen: "#f0f0ee", pausiert: "#fdf0e8",
entwurf: "#fffbe6", gesendet: "#e8f0fa", bezahlt: "#e8f5ee", überfällig: "#fdf2f2",
offen: "#fffbe6", angenommen: "#e8f5ee", abgelehnt: "#fdf2f2", abgelaufen: "#f2f2f2",
genehmigt: "#e8f0fa", "auf nächsten Lohn": "#fdf0e8", ausbezahlt: "#e8f5ee",
};
export const defaultData = {
settings: {
setupCompleted: false,
name: "Mein Studio",
address: "Musterstrasse 1\n8001 Zürich",
street: "",
zip: "",
city: "",
country: "CH",
email: "mail@studio.ch",
phone: "+41 79 000 00 00",
iban: "CH00 0000 0000 0000 0000 0",
ibanType: "qr", // "qr" | "normal"
mwst: "CHE-000.000.000 MWST",
mwstRate: 8.1,
defaultHourlyRate: 120,
autoPrint: false,
logoSize: 60,
expenseCategories: [...EXPENSE_CATEGORIES],
internalExpenseCategories: [...INTERNAL_EXPENSE_CATEGORIES],
projectNumberFormat: "YYYY/NN",
invoiceNumberFormat: "YYYY-NNN",
protokollNumberFormat: "YYYY-TT-NN",
protokollTypeAbbreviations: {
"Bausitzung": "BS",
"Planungssitzung": "PS",
"Baubesprechung": "BB",
"Jour fixe": "JF",
"Interne Sitzung": "IS",
"Kundensitzung": "KS",
"Abnahme": "AB",
"Sonstiges": "SO",
},
pdfNameFormat: "{studio}_{typ}_{nummer}",
qrNewPage: true,
pageMarginTop: 20,
pageMarginBottom: 20,
pageMarginLeft: 20,
pageMarginRight: 20,
defaultWochenstunden: 35,
defaultFerienWochen: 5,
closedMonths: [],
blockMaiTag: true,
roles: [
{ id: "PL", label: "Projektleiter/in", rate: 140 },
{ id: "TS", label: "Technischer Support", rate: 120 },
{ id: "BL", label: "Bauleiter/in", rate: 135 },
{ id: "AS", label: "Administrativer Support", rate: 120 },
],
},
persons: [],
projects: [],
timeEntries: [],
invoices: [],
quotes: [],
expenses: [],
internalExpenses: [],
deliveryNotes: [],
protocols: [],
employees: [],
feiertage: [],
absences: [],
ferienEntries: [],
absenzTypes: [],
lohnEntries: [],
uberstundenAbschluss: [],
dashboardTemplates: [
{ id: "tpl-admin", name: "Administrator", isPublic: true, layout: [
{ id: "dw-a1", cols: 4, minH: 0, widgets: ["kpi-projekte","kpi-stunden","kpi-ausstehend","kpi-umsatz"] },
{ id: "dw-a2", cols: 1, minH: 0, widgets: ["warnungen"] },
{ id: "dw-a3", cols: 2, minH: 0, widgets: ["aktive-projekte","unverrechnete-stunden"] },
{ id: "dw-a4", cols: 2, minH: 0, widgets: ["umsatz-sparkline","offene-offerten"] },
{ id: "dw-a5", cols: 1, minH: 0, widgets: ["letzte-zeiteintraege"] },
]},
{ id: "tpl-projektleiter", name: "Projektleiter", isPublic: true, layout: [
{ id: "dw-p1", cols: 2, minH: 0, widgets: ["kpi-projekte","kpi-stunden"] },
{ id: "dw-p2", cols: 1, minH: 0, widgets: ["warnungen"] },
{ id: "dw-p3", cols: 3, minH: 0, widgets: ["meine-projekte","team-auslastung","offene-offerten"] },
{ id: "dw-p4", cols: 1, minH: 0, widgets: ["letzte-zeiteintraege"] },
]},
{ id: "tpl-mitarbeiter", name: "Mitarbeiter", isPublic: true, layout: [
{ id: "dw-m1", cols: 3, minH: 0, widgets: ["kpi-stunden","ueberstunden","meine-ferien"] },
{ id: "dw-m2", cols: 2, minH: 0, widgets: ["meine-projekte","stunden-woche"] },
{ id: "dw-m3", cols: 1, minH: 0, widgets: ["meine-zeiteintraege"] },
]},
],
appRoles: [
{ id: "r-admin", name: "Administrator", permissions: null, dashboardTemplateId: "tpl-admin" },
{ id: "r-projektleiter", name: "Projektleiter", permissions: ["dashboard","projects","time","quotes","personen","mitarbeiter","settings"], dashboardTemplateId: "tpl-projektleiter" },
{ id: "r-mitarbeiter", name: "Mitarbeiter", permissions: ["dashboard","projects","time","personen","settings"], dashboardTemplateId: "tpl-mitarbeiter" },
],
users: [
{ id: "admin", username: "admin", password: "admin", role: "admin", displayName: "Administrator", appRoleId: "r-admin" },
],
blogPosts: [],
letterTemplates: [
{ id: "offer", name: "Offerte", body: "Sehr geehrte/r {{client}}\n\nGerne unterbreiten wir Ihnen die Offerte für das Projekt «{{project}}».\n\n[Leistungsumfang]\n\nHonorar: CHF [Betrag]\n\nWir freuen uns auf die Zusammenarbeit.\n\nFreundliche Grüsse" },
{ id: "reminder", name: "Zahlungserinnerung", body: "Sehr geehrte/r {{client}}\n\nBei einer Überprüfung unserer Buchhaltung stellen wir fest, dass die Rechnung [Nr.] vom [Datum] über CHF [Betrag] noch nicht beglichen ist.\n\nWir bitten Sie höflich, den offenen Betrag innert 10 Tagen zu überweisen.\n\nFreundliche Grüsse" },
],
};
export const PROTOKOLL_TYPES = ["Bausitzung", "Planungssitzung", "Baubesprechung", "Jour fixe", "Interne Sitzung", "Kundensitzung", "Abnahme", "Sonstiges"];
export const PROTOKOLL_ENTRY_TYPES = [
{ id: "beschluss", label: "Beschluss", color: "#1a4e8a", bg: "#e8f0fa", icon: "⬡" },
{ id: "info", label: "Info", color: "#2d6a4f", bg: "#e8f5ee", icon: "" },
{ id: "aufgabe", label: "Aufgabe", color: "#b5621e", bg: "#fdf0e8", icon: "◎" },
];
export const DASHBOARD_WIDGETS = [
{ id: "kpi-projekte", label: "Aktive Projekte (KPI)", span: 3 },
{ id: "kpi-stunden", label: "Stunden diesen Monat (KPI)", span: 3 },
{ id: "kpi-ausstehend", label: "Ausstehend (KPI)", span: 3 },
{ id: "kpi-umsatz", label: "Jahresumsatz (KPI)", span: 3 },
{ id: "warnungen", label: "Warnungen", span: 12 },
{ id: "aktive-projekte", label: "Projektliste mit Budget", span: 4 },
{ id: "unverrechnete-stunden", label: "Unverrechnete Stunden", span: 4 },
{ id: "umsatz-sparkline", label: "Umsatz Sparkline", span: 4 },
{ id: "offene-offerten", label: "Offene Offerten", span: 4 },
{ id: "letzte-zeiteintraege", label: "Letzte Zeiteinträge", span: 12 },
{ id: "meine-zeiteintraege", label: "Meine Zeiteinträge", span: 12 },
{ id: "meine-projekte", label: "Meine Projekte", span: 6 },
{ id: "meine-ferien", label: "Ferienstand", span: 4 },
{ id: "ueberstunden", label: "Stundensaldo", span: 4 },
{ id: "stunden-woche", label: "Stunden pro Woche", span: 6 },
{ id: "team-auslastung", label: "Team-Auslastung", span: 6 },
{ id: "interner-blog", label: "Pinnwand", span: 6 },
];
export const DEFAULT_ABSENZ_TYPES = [
{ id: "krankheit", label: "Krankheit", color: "#8a1a1a" },
{ id: "unfall", label: "Unfall", color: "#b5621e" },
{ id: "intern", label: "Intern", color: "#1a4e8a" },
{ id: "informatik", label: "Informatik", color: "#555" },
{ id: "rechnungswesen", label: "Rechnungswesen", color: "#7a6a00" },
{ id: "weiterbildung", label: "Weiterbildung", color: "#2d6a4f" },
{ id: "militaer", label: "Militär / Zivildienst", color: "#3d3d38" },
];