Projekte: Website-Analyse-Zeile ohne Farbliterale, Zustimmung als Dialog (Wortlaut unveraendert)
Zeile mit Statusbadge und Textlinks, Zustimmungsablauf im Dialog mit Fuss (Abbrechen, Einschalten), Ausschalten-Rueckfrage bleibt inline. Drei Zeilen CSS fuer die Textvorlage (details) im Dialog. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
154aea44a8
commit
8807af3f42
2 files changed
+80
-69
No files matched your search
@@ -1,8 +1,8 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { fetchAnalyse, setAnalyse } from '../lib/analyseApi'
|
||||
import { analyseStatus, textformLabel, canSubmitTextform, channelLabel, legalLinks, chatTemplate, privacyHint } from '../lib/analyseView'
|
||||
|
||||
const COLORS = { green: 'var(--green-fg)', red: 'var(--red-fg)', grey: 'var(--text-2)' }
|
||||
import { Badge, Button, Dialog, Field } from './ui'
|
||||
import { analyseColorTone } from './projectTones'
|
||||
|
||||
// Kompakte Zeile "Website-Analyse" je Projekt (lädt beim Einbinden; mit key=Projekt-ID verwenden)
|
||||
export default function AnalyseRow({ projectId }) {
|
||||
@@ -54,80 +54,87 @@ export default function AnalyseRow({ projectId }) {
|
||||
} catch { /* Kopieren nicht möglich: Text bleibt markierbar */ }
|
||||
}
|
||||
const status = analyseStatus(data)
|
||||
const label = data ? textformLabel(data.avvVersion) : null
|
||||
const links = data ? legalLinks(data.avvVersion) : null
|
||||
const dateId = `analyse-datum-${projectId}`
|
||||
const errorLine = error ? <p className="msg err" role="alert">{error}</p> : null
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: 10, fontSize: 13 }}>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<span className="text-grey">Website-Analyse:</span>
|
||||
{loadError ? (
|
||||
<span style={{ color: COLORS.red }}>{loadError}</span>
|
||||
) : data ? (
|
||||
<span style={{ color: COLORS[status.color] }}>{status.text}</span>
|
||||
) : (
|
||||
<span className="text-grey">Lädt …</span>
|
||||
)}
|
||||
{status.hint && <span className="text-grey">({status.hint})</span>}
|
||||
{status.canEnable && !mode && (
|
||||
<button type="button" className="btn btn-sm" onClick={() => setMode('on')}>{status.enableLabel || 'Einschalten'}</button>
|
||||
)}
|
||||
{status.canDisable && !mode && (
|
||||
<button type="button" className="btn btn-sm" onClick={() => setMode('off')}>Ausschalten</button>
|
||||
<>
|
||||
<div className="pv-block">
|
||||
<div className="pv-row">
|
||||
<span className="muted">Website-Analyse:</span>
|
||||
{loadError ? (
|
||||
<span className="msg err" role="alert">{loadError}</span>
|
||||
) : data ? (
|
||||
<Badge tone={analyseColorTone(status.color)} className="wrap">{status.text}</Badge>
|
||||
) : (
|
||||
<span className="muted">Lädt …</span>
|
||||
)}
|
||||
{status.hint && <span className="muted">({status.hint})</span>}
|
||||
{status.canEnable && mode !== 'off' && (
|
||||
<button type="button" className="link" onClick={() => setMode('on')}>{status.enableLabel || 'Einschalten'}</button>
|
||||
)}
|
||||
{status.canDisable && !mode && (
|
||||
<button type="button" className="link" onClick={() => setMode('off')}>Ausschalten</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{mode === 'off' && (
|
||||
<div className="pv-row">
|
||||
<span>Website-Analyse wirklich ausschalten?</span>
|
||||
<button type="button" className="link-danger" disabled={busy} onClick={() => apply(false)}>Ja, ausschalten</button>
|
||||
<button type="button" className="link" disabled={busy} onClick={cancel}>Abbrechen</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode !== 'on' && errorLine}
|
||||
</div>
|
||||
|
||||
{mode === 'on' && data && (() => {
|
||||
const label = textformLabel(data.avvVersion)
|
||||
const links = legalLinks(data.avvVersion)
|
||||
return (
|
||||
<div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<label style={{ display: 'inline-flex', gap: 6, alignItems: 'flex-start' }}>
|
||||
<input type="checkbox" checked={confirmed} onChange={(e) => setConfirmed(e.target.checked)} style={{ marginTop: 3 }} />
|
||||
<span>
|
||||
{label.before}
|
||||
<a href={links.avv} target="_blank" rel="noopener noreferrer">{label.avv}</a>
|
||||
{label.between}
|
||||
<a href={links.lb} target="_blank" rel="noopener noreferrer">{label.lb}</a>
|
||||
{label.after}
|
||||
</span>
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<label>
|
||||
Datum der Zustimmung:{' '}
|
||||
<input type="date" required value={date} min={minDate} max={today} onChange={(e) => setDate(e.target.value)} />
|
||||
{mode === 'on' && data && (
|
||||
<Dialog
|
||||
title="Website-Analyse"
|
||||
onClose={cancel}
|
||||
busy={busy}
|
||||
footer={
|
||||
<>
|
||||
<span className="grow" />
|
||||
<Button type="button" disabled={busy} onClick={cancel}>Abbrechen</Button>
|
||||
<Button type="button" variant="primary" disabled={!canSubmit || busy} onClick={() => apply(true)}>Einschalten</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{errorLine}
|
||||
<label className="check top">
|
||||
<input type="checkbox" checked={confirmed} onChange={(e) => setConfirmed(e.target.checked)} />
|
||||
<span>
|
||||
{label.before}
|
||||
<a className="link" href={links.avv} target="_blank" rel="noopener noreferrer">{label.avv}</a>
|
||||
{label.between}
|
||||
<a className="link" href={links.lb} target="_blank" rel="noopener noreferrer">{label.lb}</a>
|
||||
{label.after}
|
||||
</span>
|
||||
</label>
|
||||
<Field label="Datum der Zustimmung:" htmlFor={dateId}>
|
||||
<input id={dateId} className="input" type="date" required value={date} min={minDate} max={today} onChange={(e) => setDate(e.target.value)} />
|
||||
</Field>
|
||||
<div className="field" role="radiogroup" aria-label="Weg der Zustimmung">
|
||||
<span className="strong">Weg:</span>
|
||||
{['chat', 'email'].map((c) => (
|
||||
<label key={c} className="check">
|
||||
<input type="radio" name={`analyse-weg-${projectId}`} value={c} checked={channel === c} onChange={() => setChannel(c)} />
|
||||
{channelLabel(c)}
|
||||
</label>
|
||||
<span role="radiogroup" aria-label="Weg der Zustimmung" style={{ display: 'inline-flex', gap: 10, alignItems: 'center' }}>
|
||||
<span>Weg:</span>
|
||||
{['chat', 'email'].map((c) => (
|
||||
<label key={c} style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>
|
||||
<input type="radio" name={`analyse-weg-${projectId}`} value={c} checked={channel === c} onChange={() => setChannel(c)} />
|
||||
{channelLabel(c)}
|
||||
</label>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
{privacyBlocked && <div style={{ color: COLORS.red }}>{privacyHint(data.privacy)}</div>}
|
||||
<details>
|
||||
<summary style={{ cursor: 'pointer' }}>Textvorlage für den Chat</summary>
|
||||
<p style={{ margin: '6px 0', whiteSpace: 'pre-wrap' }}>{chatTemplate(data.avvVersion)}</p>
|
||||
<button type="button" className="btn btn-sm" onClick={copyTemplate}>{copied ? 'Kopiert' : 'Kopieren'}</button>
|
||||
</details>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<button type="button" className="btn btn-sm" disabled={!canSubmit || busy} onClick={() => apply(true)}>Einschalten</button>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={cancel}>Abbrechen</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
||||
{mode === 'off' && (
|
||||
<div style={{ marginTop: 8, display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<span>Website-Analyse wirklich ausschalten?</span>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={() => apply(false)}>Ja, ausschalten</button>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={cancel}>Abbrechen</button>
|
||||
</div>
|
||||
{privacyBlocked && <p className="msg err">{privacyHint(data.privacy)}</p>}
|
||||
<details>
|
||||
<summary>Textvorlage für den Chat</summary>
|
||||
<p className="prose">{chatTemplate(data.avvVersion)}</p>
|
||||
<Button type="button" onClick={copyTemplate}>{copied ? 'Kopiert' : 'Kopieren'}</Button>
|
||||
</details>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
{error && <div style={{ marginTop: 6, color: COLORS.red }}>{error}</div>}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1143,6 +1143,10 @@ button.link { text-align: left; } /* Reset und Gewicht von button.link stehen im
|
||||
|
||||
/* ---------- Dialog-Inhalte ---------- */
|
||||
.dialog-form { display: flex; flex-direction: column; gap: 16px; }
|
||||
/* Textvorlage der Analyse-Zustimmung (details im Dialog): Zeiger wie vorher, Abstaende statt Aussenabstaende im JSX, die Vorlage enthaelt lange Adressen ohne Leerzeichen */
|
||||
.dialog-body details > summary { cursor: pointer; }
|
||||
.dialog-body details[open] > summary { margin-bottom: 8px; }
|
||||
.dialog-body details > .prose { margin-bottom: 8px; overflow-wrap: anywhere; }
|
||||
|
||||
/* ---------- Preview-Block (im Projekt-Dialog, am Ticket und beim Kunden) ---------- */
|
||||
.pv-block { font-size: 13px; display: flex; flex-direction: column; gap: 10px; }
|
||||
|
||||
Reference in new issue
Block a user