Design: Pflicht-Fixes gegen helle Schrift und dunkle Flaechen, Diagrammfarben, Chat-Variablen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-06 06:02:54 +00:00
1 parent 47a26fb9b2
commit f911dbcb03
17 files changed
+143 -103

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ 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: '#10b981', red: '#ef4444', grey: '#a0aec0' }
const COLORS = { green: 'var(--green-fg)', red: 'var(--red-fg)', grey: 'var(--text-2)' }
// Kompakte Zeile "Website-Analyse" je Projekt (lädt beim Einbinden; mit key=Projekt-ID verwenden)
export default function AnalyseRow({ projectId }) {
+12 -12
View File
@@ -10,21 +10,21 @@ import { needsPreviewButton, assignSummary } from '../lib/projectAssign'
import { updateWebsiteProject, backfillProjectPushes } from '../lib/projectAdminApi'
const STATUS_COLORS = {
ready: '#10b981',
live: '#10b981',
online: '#10b981',
provisioning: '#f59e0b',
pending: '#f59e0b',
building: '#3b82f6',
error: '#ef4444',
failed: '#ef4444',
ready: 'var(--green-fg)',
live: 'var(--green-fg)',
online: 'var(--green-fg)',
provisioning: 'var(--orange-fg)',
pending: 'var(--orange-fg)',
building: 'var(--blue-fg)',
error: 'var(--red-fg)',
failed: 'var(--red-fg)',
}
function StatusBadge({ label, value }) {
if (!value) return null
const color = STATUS_COLORS[String(value).toLowerCase()] || '#a0aec0'
const color = STATUS_COLORS[String(value).toLowerCase()] || 'var(--text-2)'
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#cbd5e0', marginRight: 12 }}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-2)', marginRight: 12 }}>
{label}:
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, color }}>
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
@@ -79,7 +79,7 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
}
return (
<div style={{ background: 'rgba(30,41,59,0.5)', border: '1px solid rgba(59,130,246,0.25)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
<div style={{ background: 'var(--bg-subtle)', border: '1px solid var(--line)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'space-between' }}>
<div style={{ minWidth: 0 }}>
<strong>{project.projectName}</strong>
@@ -99,7 +99,7 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
title="Vom Ticket lösen"
aria-label="Vom Ticket lösen"
onClick={() => onUnassign(project.$id)}
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 6, background: 'rgba(239, 68, 68, 0.12)', border: '1px solid rgba(239, 68, 68, 0.35)', color: '#f87171', cursor: 'pointer' }}
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 6, background: 'var(--red-bg)', border: '1px solid var(--line)', color: 'var(--red-fg)', cursor: 'pointer' }}
>
<FaLinkSlash size={16} />
</button>
+2 -2
View File
@@ -191,7 +191,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
))}
</select>
{isAcquisition && (
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
<small style={{ color: 'var(--text-2)', fontSize: '12px' }}>
"Zugesagt" macht den Lead automatisch zum festen Kunden.
</small>
)}
@@ -259,7 +259,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
onChange={(value) => handleChange('totalTime', value)}
disabled={formData.isComment}
/>
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
<small style={{ color: 'var(--text-2)', fontSize: '12px' }}>
{autoCalculate && formData.startTime && formData.endTime
? '✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten'
: autoCalculate ? 'Beginn und Ende eintragen oder Zeit wählen' : 'Von Hand gewählt'}
+3 -3
View File
@@ -17,9 +17,9 @@ export default function GiteaLinkButton({ href, title = 'In Gitea oeffnen', size
width: 32,
height: 32,
borderRadius: 6,
background: 'rgba(99, 102, 241, 0.15)',
border: '1px solid rgba(99, 102, 241, 0.35)',
color: '#818cf8',
background: 'var(--violet-bg)',
border: '1px solid var(--line)',
color: 'var(--violet-fg)',
textDecoration: 'none',
flexShrink: 0,
}}
+5 -5
View File
@@ -208,13 +208,13 @@ export default function PreviewBlock({ projectId }) {
<span style={{ overflowWrap: 'anywhere' }}>{status.text}</span>
</div>
{data.state === 'failed' && data.error && !isDevUnavailableError(data.error) && (
<div style={{ color: '#f87171', ...small, marginTop: 4, overflowWrap: 'anywhere' }}>{data.error}</div>
<div style={{ color: 'var(--red-fg)', ...small, marginTop: 4, overflowWrap: 'anywhere' }}>{data.error}</div>
)}
</>
)}
{inactive && <div className="text-grey" style={{ ...small, marginTop: 8 }}>{NOT_ACTIVE}.</div>}
{error && <div style={{ color: '#f87171', ...small, marginTop: 8, overflowWrap: 'anywhere' }}>{error}</div>}
{error && <div style={{ color: 'var(--red-fg)', ...small, marginTop: 8, overflowWrap: 'anywhere' }}>{error}</div>}
{data && !inactive && (
<>
@@ -251,7 +251,7 @@ export default function PreviewBlock({ projectId }) {
{logs.map((l) => <option key={l.runFile} value={l.runFile}>{logDate(l.at)}</option>)}
</select>
{logFile && (
<pre style={{ marginTop: 6, maxHeight: 260, overflow: 'auto', background: 'rgba(0,0,0,0.35)', padding: 8, borderRadius: 6, fontFamily: 'monospace', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
<pre style={{ marginTop: 6, maxHeight: 260, overflow: 'auto', background: 'var(--bg-subtle)', padding: 8, borderRadius: 6, fontFamily: 'monospace', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
{logText || '…'}
</pre>
)}
@@ -263,7 +263,7 @@ export default function PreviewBlock({ projectId }) {
<div style={{ marginTop: 6 }}>
{names.length === 0 && <div className="text-grey" style={small}>Keine Variablen.</div>}
{names.map((n) => (
<div key={n} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', padding: '4px 0', borderBottom: '1px solid rgba(255,255,255,0.06)' }}>
<div key={n} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', padding: '4px 0', borderBottom: '1px solid var(--line)' }}>
<code style={{ flex: '1 1 140px', overflowWrap: 'anywhere' }}>{n}</code>
<span className="text-grey">•••</span>
{editName === n ? (
@@ -282,7 +282,7 @@ export default function PreviewBlock({ projectId }) {
<input type="password" autoComplete="off" className="form-control" style={{ flex: '1 1 140px', minWidth: 0 }} placeholder="Wert" onKeyDown={enter(handleAdd)} value={newValue} onChange={(e) => setNewValue(e.target.value)} />
<button type="button" className="btn btn-sm" disabled={busy} onClick={handleAdd}>Hinzufügen</button>
</div>
{envError && <div style={{ color: '#f87171', ...small, marginTop: 4 }}>{envError}</div>}
{envError && <div style={{ color: 'var(--red-fg)', ...small, marginTop: 4 }}>{envError}</div>}
{dirty && (
<div style={{ marginTop: 8 }}>
<button type="button" className="btn btn-sm" disabled={busy} onClick={handleSaveEnv}>Variablen speichern</button>
+3 -3
View File
@@ -30,9 +30,9 @@ export default function PreviewLinkButton({ href, subdomain, title = 'Preview oe
width: 32,
height: 32,
borderRadius: 6,
background: 'rgba(16, 185, 129, 0.15)',
border: '1px solid rgba(16, 185, 129, 0.35)',
color: '#34d399',
background: 'var(--green-bg)',
border: '1px solid var(--line)',
color: 'var(--green-fg)',
textDecoration: 'none',
flexShrink: 0,
}}
+34 -33
View File
@@ -4,21 +4,22 @@ import { serviceTypeLabel, needsTimeEntry, worksheetTone } from '../lib/ticketFo
import { statusLabel } from '../lib/ticketStatus'
import { parseSupporters, supportMinutes, firstName } from '../lib/support'
// Kopffarbe je Zustand (worksheetTone): orange = Git-Zeit fehlt, gruen = Git-Zeit eingetragen
// Randfarbe je Zustand (worksheetTone): orange = Git-Zeit fehlt, gruen = Git-Zeit eingetragen. Der Kopf ist fuer alle Zustaende
// grau (Schrift --text auf --bg-subtle); P2 baut die Liste um.
const TONES = {
'git-open': { border: '#f97316', base: 'linear-gradient(135deg, #f97316 0%, #ea580c 100%)', hover: 'linear-gradient(135deg, #ea580c 0%, #c2410c 100%)' },
'git-done': { border: '#10b981', base: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', hover: 'linear-gradient(135deg, #059669 0%, #047857 100%)' },
comment: { border: '#3b82f6', base: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', hover: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)' },
work: { border: '#4a5568', base: 'linear-gradient(135deg, #4a5568 0%, #2d3748 100%)', hover: 'linear-gradient(135deg, #2d3748 0%, #1a202c 100%)' },
'git-open': { border: 'var(--orange-fg)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
'git-done': { border: 'var(--green-fg)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
comment: { border: 'var(--blue-fg)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
work: { border: 'var(--text-2)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
}
// Hinweise im farbigen Kopf: mit der grauen Badge-Schrift waeren sie kaum lesbar
const headerBadgeStyle = { background: 'rgba(255,255,255,0.3)', color: 'white', marginLeft: '8px' }
// Hinweise im Kopf: graue Badge-Toene
const headerBadgeStyle = { background: 'var(--grey-bg)', color: 'var(--grey-fg)', marginLeft: '8px' }
const headerButtonStyle = {
background: 'rgba(255,255,255,0.25)',
color: 'white',
border: '1px solid rgba(255,255,255,0.4)',
background: 'var(--bg)',
color: 'var(--text)',
border: '1px solid var(--line-strong)',
borderRadius: '6px',
padding: '2px 10px',
fontSize: '0.8rem',
@@ -88,10 +89,10 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
transition: 'transform 0.2s ease, box-shadow 0.2s ease'
}} onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-2px)'
e.currentTarget.style.boxShadow = '0 8px 16px rgba(0,0,0,0.1)'
e.currentTarget.style.boxShadow = 'var(--shadow-btn)'
}} onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)'
e.currentTarget.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'
e.currentTarget.style.boxShadow = 'none'
}}>
{/* Header - Immer sichtbar, klickbar */}
<div
@@ -99,7 +100,7 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
onClick={() => toggleWorksheet(ws.wsid)}
style={{
background: colors.base,
color: 'white',
color: 'var(--text)',
border: 'none',
cursor: 'pointer',
userSelect: 'none',
@@ -141,7 +142,7 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
</span>
)}
{ws.countsToPlan && !ws.isComment && (
<span className="badge ms-2" title="Wird vom Stundenguthaben des Wartungs-Abos abgezogen" style={{ ...headerBadgeStyle, background: '#9d3dfe' }}>
<span className="badge ms-2" title="Wird vom Stundenguthaben des Wartungs-Abos abgezogen" style={{ ...headerBadgeStyle, background: 'var(--violet-bg)', color: 'var(--violet-fg)' }}>
Abo-Stunden
</span>
)}
@@ -160,8 +161,8 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
</div>
)}
<span className="badge" style={{
background: 'rgba(255,255,255,0.2)',
color: 'white',
background: 'var(--grey-bg)',
color: 'var(--grey-fg)',
fontSize: '0.8rem'
}}>{serviceTypeLabel(ws.serviceType)}</span>
</div>
@@ -207,9 +208,9 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
className="btn btn-sm"
title={ws.countsToPlan ? 'Nicht mehr vom Abo-Stundenkonto abziehen' : 'Vom Abo-Stundenkonto des Kunden abziehen'}
style={{
background: ws.countsToPlan ? '#9d3dfe' : 'rgba(255,255,255,0.15)',
color: 'white',
border: '1px solid rgba(255,255,255,0.4)',
background: ws.countsToPlan ? 'var(--violet-bg)' : 'var(--bg)',
color: ws.countsToPlan ? 'var(--violet-fg)' : 'var(--text)',
border: '1px solid var(--line-strong)',
borderRadius: '6px',
padding: '2px 10px',
fontSize: '0.8rem',
@@ -241,12 +242,12 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
<div className="row mb-3">
<div className="col-md-6">
<div className="d-flex align-items-center">
<FaUser className="me-2" style={{ color: '#10b981' }} />
<FaUser className="me-2" style={{ color: 'var(--green-fg)' }} />
<strong>{ws.employeeName}</strong>
{ws.employeeShort && (
<span className="badge ms-2" style={{
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
color: 'white'
background: 'var(--green-bg)',
color: 'var(--green-fg)'
}}>{ws.employeeShort}</span>
)}
</div>
@@ -254,8 +255,8 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
<div className="col-md-6 text-md-end">
{!ws.isComment && (
<div className="d-flex align-items-center justify-content-md-end">
<FaClock className="me-2" style={{ color: '#10b981' }} />
<strong className="fs-5" style={{ color: '#10b981' }}>{formatTime(ws.totalTime)}</strong>
<FaClock className="me-2" style={{ color: 'var(--green-fg)' }} />
<strong className="fs-5" style={{ color: 'var(--green-fg)' }}>{formatTime(ws.totalTime)}</strong>
</div>
)}
</div>
@@ -275,8 +276,8 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
{/* Service Type */}
<div className="mb-3">
<span className="badge px-3 py-2" style={{
background: 'linear-gradient(135deg, #4a5568 0%, #2d3748 100%)',
color: 'white',
background: 'var(--grey-bg)',
color: 'var(--grey-fg)',
fontSize: '0.85rem'
}}>{serviceTypeLabel(ws.serviceType)}</span>
</div>
@@ -284,20 +285,20 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
{/* Status-Änderung */}
{ws.oldStatus !== ws.newStatus && (
<div className="mb-3 p-3 rounded-3" style={{
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%)'
background: 'var(--bg-subtle)'
}}>
<FaExchangeAlt className="me-2" style={{ color: '#10b981' }} />
<FaExchangeAlt className="me-2" style={{ color: 'var(--green-fg)' }} />
<span className="text-muted">Status:</span>{' '}
<span className="badge" style={{ background: '#6b7280', color: 'white' }}>{statusLabel(ws.oldStatus)}</span>
<span className="mx-2" style={{ color: '#10b981' }}>→</span>
<span className="badge" style={{ background: '#10b981', color: 'white' }}>{statusLabel(ws.newStatus)}</span>
<span className="badge" style={{ background: 'var(--grey-bg)', color: 'var(--grey-fg)' }}>{statusLabel(ws.oldStatus)}</span>
<span className="mx-2" style={{ color: 'var(--green-fg)' }}>→</span>
<span className="badge" style={{ background: 'var(--green-bg)', color: 'var(--green-fg)' }}>{statusLabel(ws.newStatus)}</span>
</div>
)}
{/* Details */}
<div className="mt-3 p-3 rounded-3" style={{
background: 'rgba(16, 185, 129, 0.05)',
border: '1px solid rgba(16, 185, 129, 0.1)'
background: 'var(--bg-subtle)',
border: '1px solid var(--line)'
}}>
<small className="text-dark" style={{ whiteSpace: 'pre-wrap', lineHeight: '1.6' }}>
{ws.details}
+1 -1
View File
@@ -140,7 +140,7 @@ export default function WorksheetTimesModal({ worksheet, currentUserId, onClose,
value={totalTime}
onChange={(value) => setManualTotal(value)}
/>
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
<small style={{ color: 'var(--text-2)', fontSize: '12px' }}>
{manualTotal === null && calculatedTotal !== null
? '✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten'
: 'Von Hand gewählt'}
+8 -7
View File
@@ -1,6 +1,7 @@
import { useEffect, useRef } from 'react'
import uPlot from 'uplot'
import { formatAxisTick, formatLegendTime } from '../../lib/serverpulsFormat'
import { CHART } from './chartColors'
import 'uplot/dist/uPlot.min.css'
/**
@@ -25,15 +26,15 @@ export default function Chart({ data, series, height = 280, yFmt, yRange }) {
scales: { y: { range: yr || ((u, min, max) => [Math.min(0, min), max === min ? min + 1 : max + (max - min) * 0.08]) } },
axes: [
{
stroke: '#9aa7b4',
grid: { stroke: 'rgba(255,255,255,0.06)' },
ticks: { stroke: 'rgba(255,255,255,0.1)' },
stroke: CHART.axis,
grid: { show: false },
ticks: { stroke: CHART.tick },
values: (u, splits, axisIdx, foundSpace, foundIncr) => splits.map((t) => formatAxisTick(t, foundIncr)),
},
{
stroke: '#9aa7b4',
grid: { stroke: 'rgba(255,255,255,0.06)' },
ticks: { stroke: 'rgba(255,255,255,0.1)' },
stroke: CHART.axis,
grid: { stroke: CHART.grid },
ticks: { stroke: CHART.tick },
size: 70,
values: (u, vals) => vals.map(fmtVal),
},
@@ -42,7 +43,7 @@ export default function Chart({ data, series, height = 280, yFmt, yRange }) {
{ label: 'Zeit', value: (u, v) => formatLegendTime(v) },
...s.map((x) => ({
label: x.label,
stroke: x.color || '#10b981',
stroke: x.color || CHART.line,
width: 2,
spanGaps: false,
points: { show: false },
+11
View File
@@ -0,0 +1,11 @@
// Canvas-Farben fuer uPlot (Serverpuls). Canvas versteht kein var(--...), deshalb stehen die Werte hier als Literale;
// sie entsprechen den Tokens: line = --accent, peak = --text-3, axis = --text-2, grid = --line, tick = --line-strong.
// Einzige Stelle im Quelltext, an der Farbliterale ausserhalb von CSS erlaubt sind.
export const CHART = {
line: '#111111',
fill: 'rgba(17,17,17,.06)',
peak: '#8a93a3',
axis: '#5e6678',
grid: '#ebeef1',
tick: '#d8dde3',
}