Einstellungen: Tab «Updates & Support» + Bump auf 0.7.0
Neuer Settings-Tab mit manueller Update-Suche (ignoriert die Skip- Markierung), Zeitstempel der letzten Prüfung, Link zur Dokumentation auf rapport.kgva.ch. Update-Helper sind in src/utils/updater.js zentralisiert; UpdateNotifier schreibt jetzt auch beim Auto-Check das Datum der letzten Prüfung mit. Version 0.6.0 → 0.7.0 in package.json, tauri.conf.json, Cargo.toml und allen UI-Referenzen. Changelog-Eintrag 0.7 mit den drei Highlights dieses Releases ergänzt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -121,8 +121,8 @@ Zusätzlich im UI-Changelog: [`src/App.jsx`](src/App.jsx) — Konstante `CHANGEL
|
|||||||
# 1. Versionen anheben (package.json, tauri.conf.json, Cargo.toml)
|
# 1. Versionen anheben (package.json, tauri.conf.json, Cargo.toml)
|
||||||
# 2. Changelog in src/App.jsx ergänzen
|
# 2. Changelog in src/App.jsx ergänzen
|
||||||
# 3. Commit + Tag
|
# 3. Commit + Tag
|
||||||
git tag -a v0.6.0 -m "Rapport 0.6"
|
git tag -a v0.7.0 -m "Rapport 0.7"
|
||||||
git push origin main v0.6.0
|
git push origin main v0.7.0
|
||||||
|
|
||||||
# 4. Bundle bauen
|
# 4. Bundle bauen
|
||||||
npx tauri build
|
npx tauri build
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "rapport",
|
"name": "rapport",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Generated
+1
-1
@@ -2880,7 +2880,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rapport"
|
name = "rapport"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rapport"
|
name = "rapport"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
description = "Rapport — Studio-Management für Architekturbüros"
|
description = "Rapport — Studio-Management für Architekturbüros"
|
||||||
authors = ["Karim Gabriele Varano <karim@gabrielevarano.ch>"]
|
authors = ["Karim Gabriele Varano <karim@gabrielevarano.ch>"]
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "RAPPORT PRE-RELEASE",
|
"productName": "RAPPORT PRE-RELEASE",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"identifier": "com.karimgabrielevarano.rapport",
|
"identifier": "com.karimgabrielevarano.rapport",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
+15
-8
@@ -231,8 +231,8 @@ export default function App() {
|
|||||||
const [modal, setModal] = useState(null);
|
const [modal, setModal] = useState(null);
|
||||||
const [printContent, setPrintContent] = useState(null);
|
const [printContent, setPrintContent] = useState(null);
|
||||||
const [darkMode, setDarkMode] = useState(() => localStorage.getItem("rapport_dark") === "1");
|
const [darkMode, setDarkMode] = useState(() => localStorage.getItem("rapport_dark") === "1");
|
||||||
const [showChangelog, setShowChangelog] = useState(() => localStorage.getItem("rapport_changelog_seen") !== "0.6");
|
const [showChangelog, setShowChangelog] = useState(() => localStorage.getItem("rapport_changelog_seen") !== "0.7");
|
||||||
const [changelogVersion, setChangelogVersion] = useState("0.6");
|
const [changelogVersion, setChangelogVersion] = useState("0.7");
|
||||||
const [showAbout, setShowAbout] = useState(false);
|
const [showAbout, setShowAbout] = useState(false);
|
||||||
const [navOpen, setNavOpen] = useState(false);
|
const [navOpen, setNavOpen] = useState(false);
|
||||||
const [expandedNav, setExpandedNav] = useState(new Set(["buchhaltung"]));
|
const [expandedNav, setExpandedNav] = useState(new Set(["buchhaltung"]));
|
||||||
@@ -334,7 +334,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
return <Login verifyLogin={verifyLogin} settings={data.settings} version="0.6" />;
|
return <Login verifyLogin={verifyLogin} settings={data.settings} version="0.7" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (printContent) {
|
if (printContent) {
|
||||||
@@ -607,8 +607,8 @@ export default function App() {
|
|||||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
<button onClick={() => setShowAbout(true)} style={{ background: "none", border: "none", padding: 0, color: "#555", fontSize: 10, letterSpacing: "0.08em", cursor: "pointer", fontFamily: "inherit", textAlign: "left" }}
|
<button onClick={() => setShowAbout(true)} style={{ background: "none", border: "none", padding: 0, color: "#555", fontSize: 10, letterSpacing: "0.08em", cursor: "pointer", fontFamily: "inherit", textAlign: "left" }}
|
||||||
onMouseEnter={e => e.currentTarget.style.color = "#aaa"} onMouseLeave={e => e.currentTarget.style.color = "#555"}>ÜBER RAPPORT</button>
|
onMouseEnter={e => e.currentTarget.style.color = "#aaa"} onMouseLeave={e => e.currentTarget.style.color = "#555"}>ÜBER RAPPORT</button>
|
||||||
<button onClick={() => { setChangelogVersion("0.6"); setShowChangelog(true); }} style={{ background: "none", border: "none", padding: 0, color: "#aaa", fontSize: 10, letterSpacing: "0.08em", cursor: "pointer", fontFamily: "inherit" }}
|
<button onClick={() => { setChangelogVersion("0.7"); setShowChangelog(true); }} style={{ background: "none", border: "none", padding: 0, color: "#aaa", fontSize: 10, letterSpacing: "0.08em", cursor: "pointer", fontFamily: "inherit" }}
|
||||||
onMouseEnter={e => e.currentTarget.style.color = "#f0ede8"} onMouseLeave={e => e.currentTarget.style.color = "#aaa"}>0.6</button>
|
onMouseEnter={e => e.currentTarget.style.color = "#f0ede8"} onMouseLeave={e => e.currentTarget.style.color = "#aaa"}>0.7</button>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
@@ -673,6 +673,13 @@ export default function App() {
|
|||||||
|
|
||||||
{showChangelog && (() => {
|
{showChangelog && (() => {
|
||||||
const CHANGELOGS = {
|
const CHANGELOGS = {
|
||||||
|
"0.7": {
|
||||||
|
items: [
|
||||||
|
["Automatische Updates", "Rapport prüft beim Start, ob eine neue Version unter git.kgva.ch verfügbar ist, und installiert sie auf Knopfdruck — kein manuelles DMG-Download mehr nötig. Updates lassen sich überspringen oder verschieben; Pakete werden vor der Installation per Signaturprüfung verifiziert."],
|
||||||
|
["System-Tray-Icon", "Rapport läuft im Hintergrund weiter, wenn das Fenster geschlossen wird, und ist über ein Menüleisten-Icon erreichbar. Schnellzugriff auf Dashboard, Zeiterfassung, Projekte und Buchhaltung; Cmd+Q beendet die App vollständig."],
|
||||||
|
["Einstellungen: Updates & Support", "Neuer Tab «Updates & Support» mit manueller Update-Suche, Zeitstempel der letzten Prüfung und Link zur Dokumentation auf rapport.kgva.ch."],
|
||||||
|
],
|
||||||
|
},
|
||||||
"0.6": {
|
"0.6": {
|
||||||
items: [
|
items: [
|
||||||
["Sicherheit: Passwort-Hashing", "Passwörter werden jetzt mit PBKDF2 (SHA-256, 100 000 Iterationen) und einem zufälligen Salt gespeichert. Bestehende Klartext-Passwörter werden beim ersten erfolgreichen Login transparent migriert."],
|
["Sicherheit: Passwort-Hashing", "Passwörter werden jetzt mit PBKDF2 (SHA-256, 100 000 Iterationen) und einem zufälligen Salt gespeichert. Bestehende Klartext-Passwörter werden beim ersten erfolgreichen Login transparent migriert."],
|
||||||
@@ -730,7 +737,7 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
const versions = Object.keys(CHANGELOGS);
|
const versions = Object.keys(CHANGELOGS);
|
||||||
const current = CHANGELOGS[changelogVersion] || CHANGELOGS["0.6"];
|
const current = CHANGELOGS[changelogVersion] || CHANGELOGS["0.7"];
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.55)", zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
|
<div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.55)", zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
|
||||||
<div style={{ background: "#fff", borderRadius: 10, width: "100%", maxWidth: 480, boxShadow: "0 8px 40px rgba(0,0,0,0.18)", overflow: "hidden" }}>
|
<div style={{ background: "#fff", borderRadius: 10, width: "100%", maxWidth: 480, boxShadow: "0 8px 40px rgba(0,0,0,0.18)", overflow: "hidden" }}>
|
||||||
@@ -759,7 +766,7 @@ export default function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: "12px 32px 24px" }}>
|
<div style={{ padding: "12px 32px 24px" }}>
|
||||||
<button className="btn btn-primary" style={{ width: "100%", fontSize: 13 }} onClick={() => { setShowChangelog(false); localStorage.setItem("rapport_changelog_seen", "0.6"); }}>
|
<button className="btn btn-primary" style={{ width: "100%", fontSize: 13 }} onClick={() => { setShowChangelog(false); localStorage.setItem("rapport_changelog_seen", "0.7"); }}>
|
||||||
Schliessen
|
Schliessen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -774,7 +781,7 @@ export default function App() {
|
|||||||
<div style={{ background: "#1a1a18", padding: "28px 32px 24px" }}>
|
<div style={{ background: "#1a1a18", padding: "28px 32px 24px" }}>
|
||||||
<div style={{ fontSize: 10, letterSpacing: "0.18em", color: "#b07848", marginBottom: 8, fontWeight: 600 }}>ÜBER RAPPORT</div>
|
<div style={{ fontSize: 10, letterSpacing: "0.18em", color: "#b07848", marginBottom: 8, fontWeight: 600 }}>ÜBER RAPPORT</div>
|
||||||
<div style={{ fontFamily: "'Playfair Display', serif", fontSize: 28, color: "#f0ede8", fontWeight: 400, lineHeight: 1.1 }}>Rapport</div>
|
<div style={{ fontFamily: "'Playfair Display', serif", fontSize: 28, color: "#f0ede8", fontWeight: 400, lineHeight: 1.1 }}>Rapport</div>
|
||||||
<div style={{ fontSize: 11, color: "#888", marginTop: 6, letterSpacing: "0.04em" }}>Alpha 0.6 · Studio-Management für Architekturbüros</div>
|
<div style={{ fontSize: 11, color: "#888", marginTop: 6, letterSpacing: "0.04em" }}>Alpha 0.7 · Studio-Management für Architekturbüros</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: "20px 32px 8px" }}>
|
<div style={{ padding: "20px 32px 8px" }}>
|
||||||
<div style={{ fontSize: 11, fontWeight: 600, color: "#888", letterSpacing: "0.1em", marginBottom: 12 }}>LIZENZ</div>
|
<div style={{ fontSize: 11, fontWeight: 600, color: "#888", letterSpacing: "0.1em", marginBottom: 12 }}>LIZENZ</div>
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
import {
|
||||||
|
checkForAppUpdate,
|
||||||
|
installAppUpdate,
|
||||||
|
skipUpdateVersion,
|
||||||
|
getLastUpdateCheck,
|
||||||
|
formatLastCheck,
|
||||||
|
isTauri,
|
||||||
|
} from "../utils/updater.js";
|
||||||
|
|
||||||
|
function Section({ title, children }) {
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 28 }}>
|
||||||
|
<div style={{ fontSize: 10, letterSpacing: "0.12em", color: "#aaa", fontWeight: 600, marginBottom: 14, paddingBottom: 8, borderBottom: "1px solid #ece8e2" }}>{title}</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UpdatesSupport({ fallbackVersion }) {
|
||||||
|
const [version, setVersion] = useState(fallbackVersion || "");
|
||||||
|
const [lastCheck, setLastCheck] = useState(() => getLastUpdateCheck());
|
||||||
|
const [state, setState] = useState("idle");
|
||||||
|
const [update, setUpdate] = useState(null);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [downloaded, setDownloaded] = useState(0);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isTauri()) return;
|
||||||
|
import("@tauri-apps/api/app").then(({ getVersion }) => {
|
||||||
|
getVersion().then(setVersion).catch(() => {});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const runCheck = useCallback(async () => {
|
||||||
|
setError(null);
|
||||||
|
if (!isTauri()) {
|
||||||
|
setState("not-tauri");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState("checking");
|
||||||
|
try {
|
||||||
|
const res = await checkForAppUpdate({ respectSkip: false });
|
||||||
|
setLastCheck(getLastUpdateCheck());
|
||||||
|
if (res.available) {
|
||||||
|
setUpdate(res.update);
|
||||||
|
setState("available");
|
||||||
|
} else {
|
||||||
|
setUpdate(null);
|
||||||
|
setState("no-update");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Update-Check fehlgeschlagen:", e);
|
||||||
|
setError(String(e?.message || e));
|
||||||
|
setState("idle");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const install = async () => {
|
||||||
|
if (!update) return;
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
setState("downloading");
|
||||||
|
setDownloaded(0);
|
||||||
|
setTotal(0);
|
||||||
|
await installAppUpdate(update, (event) => {
|
||||||
|
if (event.event === "Started") {
|
||||||
|
setTotal(event.data.contentLength || 0);
|
||||||
|
} else if (event.event === "Progress") {
|
||||||
|
setDownloaded((d) => d + (event.data.chunkLength || 0));
|
||||||
|
} else if (event.event === "Finished") {
|
||||||
|
setState("installing");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Update-Installation fehlgeschlagen:", e);
|
||||||
|
setError(String(e?.message || e));
|
||||||
|
setState("available");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const skipVersion = () => {
|
||||||
|
skipUpdateVersion(update?.version);
|
||||||
|
setUpdate(null);
|
||||||
|
setState("idle");
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBusy = state === "downloading" || state === "installing";
|
||||||
|
const pct = total > 0 ? Math.min(100, Math.round((downloaded / total) * 100)) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }} className="responsive-grid-2">
|
||||||
|
<div className="card">
|
||||||
|
<Section title="UPDATES">
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0", fontSize: 12, borderBottom: "1px solid #f0ede8" }}>
|
||||||
|
<span style={{ color: "#888" }}>Aktuelle Version</span>
|
||||||
|
<strong style={{ color: "#555" }}>{version || "—"}</strong>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0", fontSize: 12, borderBottom: "1px solid #f0ede8", marginBottom: 16 }}>
|
||||||
|
<span style={{ color: "#888" }}>Letzte Prüfung</span>
|
||||||
|
<span style={{ color: "#555" }}>{formatLastCheck(lastCheck)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
style={{ width: "100%", marginBottom: 10 }}
|
||||||
|
onClick={runCheck}
|
||||||
|
disabled={state === "checking" || isBusy}
|
||||||
|
>
|
||||||
|
{state === "checking" ? "Wird geprüft …" : "Nach Updates suchen"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{state === "no-update" && (
|
||||||
|
<div style={{ marginTop: 6, padding: "10px 12px", background: "#f0f7f0", border: "1px solid #c8e0c8", borderRadius: 8, fontSize: 12, color: "#2d6a4f", lineHeight: 1.5 }}>
|
||||||
|
✓ Rapport ist auf dem neuesten Stand.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === "available" && update && (
|
||||||
|
<div style={{ marginTop: 10, padding: "14px 14px 12px", background: "#fbf6ef", border: "1px solid #e6d4b3", borderRadius: 8 }}>
|
||||||
|
<div style={{ fontSize: 10, letterSpacing: "0.12em", color: "#b07848", fontWeight: 600, marginBottom: 4 }}>UPDATE VERFÜGBAR</div>
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 600, color: "#1a1a18", marginBottom: 6 }}>Rapport {update.version}</div>
|
||||||
|
{update.body && (
|
||||||
|
<div style={{ fontSize: 12, color: "#666", lineHeight: 1.5, whiteSpace: "pre-wrap", marginBottom: 10, maxHeight: 160, overflowY: "auto" }}>
|
||||||
|
{update.body}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button className="btn btn-primary" style={{ width: "100%", marginBottom: 8 }} onClick={install} disabled={isBusy}>
|
||||||
|
{isBusy ? "Bitte warten …" : "Installieren und neu starten"}
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-ghost" style={{ width: "100%", fontSize: 12 }} onClick={skipVersion} disabled={isBusy}>
|
||||||
|
Diese Version überspringen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isBusy && (
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<div style={{ fontSize: 11, color: "#888", marginBottom: 6, letterSpacing: "0.04em" }}>
|
||||||
|
{state === "downloading"
|
||||||
|
? (pct !== null ? `Wird heruntergeladen … ${pct}%` : "Wird heruntergeladen …")
|
||||||
|
: "Wird installiert …"}
|
||||||
|
</div>
|
||||||
|
<div style={{ height: 4, background: "#eee", borderRadius: 2, overflow: "hidden" }}>
|
||||||
|
<div style={{
|
||||||
|
height: "100%",
|
||||||
|
width: pct !== null ? `${pct}%` : "100%",
|
||||||
|
background: "#b07848",
|
||||||
|
transition: "width 0.2s",
|
||||||
|
animation: pct === null ? "rapport-us-pulse 1.2s ease-in-out infinite" : undefined,
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
<style>{`@keyframes rapport-us-pulse { 0%,100% { opacity: 0.5; } 50% { opacity: 1; } }`}</style>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{state === "not-tauri" && (
|
||||||
|
<div style={{ marginTop: 6, padding: "10px 12px", background: "#f5f5f0", border: "1px solid #e0dbd4", borderRadius: 8, fontSize: 12, color: "#666" }}>
|
||||||
|
Updates sind nur in der Desktop-App verfügbar.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div style={{ marginTop: 10, padding: "10px 12px", background: "#fdf0f0", border: "1px solid #f3c4c4", borderRadius: 6, fontSize: 12, color: "#8a1a1a" }}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p style={{ fontSize: 11, color: "#888", lineHeight: 1.6, marginTop: 16 }}>
|
||||||
|
Updates werden automatisch beim Start der App geprüft. Hier kannst du manuell suchen — z.B. wenn die App selten geschlossen wird.
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card">
|
||||||
|
<Section title="SUPPORT & DOKUMENTATION">
|
||||||
|
<p style={{ fontSize: 13, color: "#666", lineHeight: 1.7, marginBottom: 16 }}>
|
||||||
|
Anleitungen, Dokumentation und Support findest du auf der offiziellen Website:
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://rapport.kgva.ch/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="btn btn-ghost"
|
||||||
|
style={{ width: "100%", textDecoration: "none", marginBottom: 10 }}
|
||||||
|
>
|
||||||
|
rapport.kgva.ch ↗
|
||||||
|
</a>
|
||||||
|
<p style={{ fontSize: 11, color: "#888", lineHeight: 1.6, marginTop: 16 }}>
|
||||||
|
Dort findest du auch das Changelog, Fehlerberichte und Kontaktmöglichkeiten für direkten Support.
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { STORAGE_KEY, DEFAULT_ABSENZ_TYPES } from "../constants.js";
|
import { STORAGE_KEY, DEFAULT_ABSENZ_TYPES } from "../constants.js";
|
||||||
import { formatIban, isQRIban, applyProjectNumberFormat, applyProtoNumberFormat, generateId, getFeiertageForYear, getAbsenzTypes } from "../utils.js";
|
import { formatIban, isQRIban, applyProjectNumberFormat, applyProtoNumberFormat, generateId, getFeiertageForYear, getAbsenzTypes } from "../utils.js";
|
||||||
import { Header, FormField, Modal, DateInput, useConfirm } from "../components/UI.jsx";
|
import { Header, FormField, Modal, DateInput, useConfirm } from "../components/UI.jsx";
|
||||||
|
import UpdatesSupport from "../components/UpdatesSupport.jsx";
|
||||||
|
|
||||||
const PERMISSION_GROUPS = [
|
const PERMISSION_GROUPS = [
|
||||||
{ label: "Grundmodule", items: [
|
{ label: "Grundmodule", items: [
|
||||||
@@ -37,6 +38,7 @@ const TABS = [
|
|||||||
{ id: "team", label: "Team & Rollen" },
|
{ id: "team", label: "Team & Rollen" },
|
||||||
{ id: "kalender", label: "Feiertage & Absenzen" },
|
{ id: "kalender", label: "Feiertage & Absenzen" },
|
||||||
{ id: "system", label: "System" },
|
{ id: "system", label: "System" },
|
||||||
|
{ id: "support", label: "Updates & Support" },
|
||||||
{ id: "profil", label: "Mein Profil" },
|
{ id: "profil", label: "Mein Profil" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -858,6 +860,9 @@ export default function Settings({ data, update, currentUser, uiZoom, setUiZoom
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ── Tab: Updates & Support ── */}
|
||||||
|
{tab === "support" && <UpdatesSupport />}
|
||||||
</>}
|
</>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user