Refactor form structure and add collapsible components

Improved the user interface on the TargetsPage by replacing static form headers with collapsible sections, enhancing maintainability and user experience. Updated styles for consistency, added hover effects, and ensured accessibility. Also replaced German special characters for uniform encoding.
This commit is contained in:
2026-02-12 11:18:15 +01:00
parent 834c5b42b0
commit 6c660239d0
2 changed files with 109 additions and 71 deletions

View File

@@ -51,7 +51,7 @@ export function TargetsPage() {
}; };
const deleteTarget = async (id) => { const deleteTarget = async (id) => {
if (!confirm("Target löschen?")) return; if (!confirm("Target loeschen?")) return;
try { try {
await apiFetch(`/targets/${id}`, { method: "DELETE" }, tokens, refresh); await apiFetch(`/targets/${id}`, { method: "DELETE" }, tokens, refresh);
await load(); await load();
@@ -64,12 +64,18 @@ export function TargetsPage() {
<div className="targets-page"> <div className="targets-page">
<h2>Targets Management</h2> <h2>Targets Management</h2>
{error && <div className="card error">{error}</div>} {error && <div className="card error">{error}</div>}
{canManage && ( {canManage && (
<form className="card target-form grid two" onSubmit={createTarget}> <details className="card collapsible" open>
<div className="target-form-header"> <summary className="collapse-head">
<div>
<h3>Neues Target</h3> <h3>Neues Target</h3>
<p>Verbindungsdaten fuer eine PostgreSQL-Instanz.</p> <p>Verbindungsdaten fuer eine PostgreSQL-Instanz.</p>
</div> </div>
<span className="collapse-chevron" aria-hidden="true">?</span>
</summary>
<form className="target-form grid two" onSubmit={createTarget}>
<div className="field"> <div className="field">
<label>Name</label> <label>Name</label>
<input placeholder="z.B. Prod-DB" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required /> <input placeholder="z.B. Prod-DB" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required />
@@ -88,7 +94,7 @@ export function TargetsPage() {
<div className="field"> <div className="field">
<label>DB Name</label> <label>DB Name</label>
<input placeholder="z.B. postgres oder appdb" value={form.dbname} onChange={(e) => setForm({ ...form, dbname: e.target.value })} required /> <input placeholder="z.B. postgres oder appdb" value={form.dbname} onChange={(e) => setForm({ ...form, dbname: e.target.value })} required />
<small>Name der Datenbank, die überwacht werden soll.</small> <small>Name der Datenbank, die ueberwacht werden soll.</small>
</div> </div>
<div className="field"> <div className="field">
<label>Username</label> <label>Username</label>
@@ -98,7 +104,7 @@ export function TargetsPage() {
<div className="field"> <div className="field">
<label>Password</label> <label>Password</label>
<input placeholder="Passwort" type="password" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} required /> <input placeholder="Passwort" type="password" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} required />
<small>Wird verschlüsselt in der Core-DB gespeichert.</small> <small>Wird verschluesselt in der Core-DB gespeichert.</small>
</div> </div>
<div className="field"> <div className="field">
<label>SSL Mode</label> <label>SSL Mode</label>
@@ -116,10 +122,18 @@ export function TargetsPage() {
<button className="primary-btn">Target anlegen</button> <button className="primary-btn">Target anlegen</button>
</div> </div>
</form> </form>
</details>
)} )}
{canManage && ( {canManage && (
<div className="card tips"> <details className="card collapsible tips">
<strong>Troubleshooting</strong> <summary className="collapse-head">
<div>
<h3>Troubleshooting</h3>
<p>Typische Verbindungsfehler schnell erkennen.</p>
</div>
<span className="collapse-chevron" aria-hidden="true">?</span>
</summary>
<p> <p>
<code>Connection refused</code>: Host/Port falsch oder DB nicht erreichbar. <code>Connection refused</code>: Host/Port falsch oder DB nicht erreichbar.
</p> </p>
@@ -129,8 +143,9 @@ export function TargetsPage() {
<p> <p>
<code>localhost</code> im Target zeigt aus Backend-Container-Sicht auf den Container selbst. <code>localhost</code> im Target zeigt aus Backend-Container-Sicht auf den Container selbst.
</p> </p>
</div> </details>
)} )}
<div className="card targets-table"> <div className="card targets-table">
{loading ? ( {loading ? (
<p>Lade Targets...</p> <p>Lade Targets...</p>

View File

@@ -195,36 +195,59 @@ button {
} }
.target-form { .target-form {
position: relative; margin-top: 12px;
padding-top: 56px;
} }
.target-form-header { .primary-btn {
position: absolute; font-weight: 650;
top: 14px; letter-spacing: 0.01em;
left: 16px; border-color: #4467ab;
background: linear-gradient(180deg, #1a2f56, #142643);
box-shadow: inset 0 1px 0 #5f8de144;
} }
.target-form-header h3 { .primary-btn:hover {
border-color: #6b9ee9;
background: linear-gradient(180deg, #1d3766, #183053);
}
.collapsible {
padding-top: 12px;
}
.collapse-head {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
margin: -2px 0 0 0;
padding: 0 2px 8px 2px;
}
.collapse-head::-webkit-details-marker {
display: none;
}
.collapse-head h3 {
margin: 0; margin: 0;
font-size: 16px; font-size: 24px;
} }
.target-form-header p { .collapse-head p {
margin: 2px 0 0 0; margin: 2px 0 0 0;
color: #92a7cc; color: #92a7cc;
font-size: 12px; font-size: 12px;
} }
.primary-btn { .collapse-chevron {
font-weight: 700; font-size: 22px;
border-color: #3e73d4; color: #89a7d8;
background: linear-gradient(90deg, #2e7cd4, #265fb4); transition: transform 0.2s ease;
} }
.primary-btn:hover { details[open] .collapse-chevron {
border-color: #6aa8ff; transform: rotate(180deg);
filter: brightness(1.05);
} }
.targets-table table tbody tr:hover { .targets-table table tbody tr:hover {