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>
141 lines
5.8 KiB
React
141 lines
5.8 KiB
React
import { useState, useEffect } from 'react'
|
|
import { fetchAnalyse, setAnalyse } from '../lib/analyseApi'
|
|
import { analyseStatus, textformLabel, canSubmitTextform, channelLabel, legalLinks, chatTemplate, privacyHint } from '../lib/analyseView'
|
|
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 }) {
|
|
const [data, setData] = useState(null)
|
|
const [loadError, setLoadError] = useState('')
|
|
const [mode, setMode] = useState(null) // null | 'on' | 'off'
|
|
const [confirmed, setConfirmed] = useState(false)
|
|
const [date, setDate] = useState('')
|
|
const [channel, setChannel] = useState('')
|
|
const [copied, setCopied] = useState(false)
|
|
const [busy, setBusy] = useState(false)
|
|
const [error, setError] = useState('')
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
fetchAnalyse(projectId)
|
|
.then((r) => { if (!cancelled) setData(r) })
|
|
.catch((err) => { if (!cancelled) setLoadError(err.message) })
|
|
return () => { cancelled = true }
|
|
}, [projectId])
|
|
|
|
const apply = async (on) => {
|
|
setBusy(true)
|
|
setError('')
|
|
try {
|
|
await setAnalyse(projectId, { on, textformDate: date, textformChannel: channel, audienceConfirmed: on })
|
|
setData(await fetchAnalyse(projectId))
|
|
setMode(null)
|
|
setConfirmed(false)
|
|
setDate('')
|
|
setChannel('')
|
|
} catch (err) {
|
|
setError(err.message)
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
const cancel = () => { setMode(null); setConfirmed(false); setDate(''); setChannel(''); setError('') }
|
|
const today = new Date().toLocaleDateString('sv-SE', { timeZone: 'Europe/Berlin' })
|
|
const minDate = data?.avvVersion || ''
|
|
const privacyBlocked = data?.privacy?.ok === false
|
|
const canSubmit = !privacyBlocked && canSubmitTextform({ confirmed, date, channel, today, minDate })
|
|
const copyTemplate = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(chatTemplate(data.avvVersion))
|
|
setCopied(true)
|
|
setTimeout(() => setCopied(false), 2000)
|
|
} 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 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 && (
|
|
<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>
|
|
))}
|
|
</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>
|
|
)}
|
|
</>
|
|
)
|
|
}
|