refactor: zentralisiere Konfiguration in src/config/constants.ts
- RATING_OPTIONS, INST_COLORS, AI_CONFIG, RATING_NUM_MAP aus Komponenten extrahiert - #0070AD als @theme-Farbe 'brand' in Tailwind v4 (index.css) - Alle ~70 hardcoded #0070AD-Vorkommen in Pages durch 'brand' ersetzt - BRAND_COLOR als JS-Konstante für style-Props (App.tsx)
This commit is contained in:
parent
ece111c0a7
commit
97e3eeb8a6
|
|
@ -1,4 +1,5 @@
|
||||||
import { Routes, Route, NavLink } from 'react-router-dom'
|
import { Routes, Route, NavLink } from 'react-router-dom'
|
||||||
|
import { BRAND_COLOR } from './config/constants'
|
||||||
import Dashboard from './pages/Dashboard'
|
import Dashboard from './pages/Dashboard'
|
||||||
import Configuration from './pages/Configuration'
|
import Configuration from './pages/Configuration'
|
||||||
import Consultants from './pages/Consultants'
|
import Consultants from './pages/Consultants'
|
||||||
|
|
@ -13,7 +14,7 @@ import Evaluation from './pages/Evaluation'
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex flex-col">
|
<div className="min-h-screen bg-gray-50 flex flex-col">
|
||||||
<header className="bg-[#0070AD] text-white px-4 py-3 flex items-center gap-3 shadow">
|
<header className="text-white px-4 py-3 flex items-center gap-3 shadow" style={{ backgroundColor: BRAND_COLOR }}>
|
||||||
<span className="font-bold text-lg tracking-tight">Assignment Monitor</span>
|
<span className="font-bold text-lg tracking-tight">Assignment Monitor</span>
|
||||||
</header>
|
</header>
|
||||||
<main className="flex-1 p-4 max-w-2xl mx-auto w-full pb-20">
|
<main className="flex-1 p-4 max-w-2xl mx-auto w-full pb-20">
|
||||||
|
|
@ -42,8 +43,9 @@ export default function App() {
|
||||||
to={to}
|
to={to}
|
||||||
end
|
end
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`text-xs px-3 py-1 rounded ${isActive ? 'text-[#0070AD] font-semibold' : 'text-gray-500'}`
|
`text-xs px-3 py-1 rounded ${isActive ? 'font-semibold' : 'text-gray-500'}`
|
||||||
}
|
}
|
||||||
|
style={({ isActive }) => isActive ? { color: BRAND_COLOR } : undefined}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
|
||||||
46
src/config/constants.ts
Normal file
46
src/config/constants.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import type { FeedbackRating } from '../db'
|
||||||
|
|
||||||
|
export const BRAND_COLOR = '#0070AD'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kanonische Reihenfolge der Rating-Skala (nicht ändern — DB-Werte sind stabil).
|
||||||
|
* `color` = Badge/Display-State (inaktiv)
|
||||||
|
* `active` = ausgewählter Button-State
|
||||||
|
*/
|
||||||
|
export const RATING_OPTIONS: ReadonlyArray<{
|
||||||
|
value: FeedbackRating
|
||||||
|
short: string
|
||||||
|
label: string
|
||||||
|
color: string
|
||||||
|
active: string
|
||||||
|
}> = [
|
||||||
|
{ value: 'na', short: 'N/A', label: 'N/A (0)', color: 'bg-gray-100 text-gray-500 border-gray-300', active: 'bg-gray-400 text-white border-gray-400' },
|
||||||
|
{ value: 'not_client_ready', short: 'Not', label: 'Not (1)', color: 'bg-red-100 text-red-700 border-red-300', active: 'bg-red-500 text-white border-red-500' },
|
||||||
|
{ value: 'partially_client_ready', short: 'Partially', label: 'Partially (2)', color: 'bg-orange-100 text-orange-700 border-orange-300', active: 'bg-orange-500 text-white border-orange-500' },
|
||||||
|
{ value: 'nearly_client_ready', short: 'Nearly', label: 'Nearly (3)', color: 'bg-green-100 text-green-600 border-green-300', active: 'bg-green-400 text-white border-green-400' },
|
||||||
|
{ value: 'client_ready', short: 'Fully', label: 'Fully (4)', color: 'bg-green-100 text-green-800 border-green-400', active: 'bg-green-700 text-white border-green-700' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/** Farb-Schemata für Teilnehmer-Badges (Index = Reihenfolge im Assignment). */
|
||||||
|
export const INST_COLORS = [
|
||||||
|
{ bg: 'bg-blue-50', border: 'border-blue-300', tag: 'bg-blue-500', text: 'text-blue-700' },
|
||||||
|
{ bg: 'bg-purple-50', border: 'border-purple-300', tag: 'bg-purple-500', text: 'text-purple-700' },
|
||||||
|
{ bg: 'bg-emerald-50', border: 'border-emerald-300', tag: 'bg-emerald-500', text: 'text-emerald-700' },
|
||||||
|
{ bg: 'bg-orange-50', border: 'border-orange-300', tag: 'bg-orange-500', text: 'text-orange-700' },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const AI_CONFIG = {
|
||||||
|
localStorageKeyApiKey: 'ai_api_key',
|
||||||
|
localStorageKeyModel: 'ai_model',
|
||||||
|
defaultModel: 'openai/gpt-4o-mini',
|
||||||
|
openRouterBaseUrl: 'https://openrouter.ai/api/v1',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** Numerische Gewichte für Rating-Aggregation. N/A wird aus Berechnungen ausgeschlossen. */
|
||||||
|
export const RATING_NUM_MAP: Record<FeedbackRating, number> = {
|
||||||
|
na: 0,
|
||||||
|
not_client_ready: 1,
|
||||||
|
partially_client_ready: 2,
|
||||||
|
nearly_client_ready: 3,
|
||||||
|
client_ready: 4,
|
||||||
|
}
|
||||||
|
|
@ -1 +1,6 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-brand: #0070AD;
|
||||||
|
--color-brand-dark: #005a8e;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,13 +88,13 @@ export default function AssignmentCreate() {
|
||||||
|
|
||||||
{/* Basisdaten */}
|
{/* Basisdaten */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 space-y-3">
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 space-y-3">
|
||||||
<input className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
<input className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Titel des Assignments" value={form.title}
|
placeholder="Titel des Assignments" value={form.title}
|
||||||
onChange={e => setForm(p => ({ ...p, title: e.target.value }))} />
|
onChange={e => setForm(p => ({ ...p, title: e.target.value }))} />
|
||||||
<input className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
<input className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Kunde / Unternehmen" value={form.client}
|
placeholder="Kunde / Unternehmen" value={form.client}
|
||||||
onChange={e => setForm(p => ({ ...p, client: e.target.value }))} />
|
onChange={e => setForm(p => ({ ...p, client: e.target.value }))} />
|
||||||
<input className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
<input className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Lead-Name (dein Name)" value={form.leadName}
|
placeholder="Lead-Name (dein Name)" value={form.leadName}
|
||||||
onChange={e => setForm(p => ({ ...p, leadName: e.target.value }))} />
|
onChange={e => setForm(p => ({ ...p, leadName: e.target.value }))} />
|
||||||
<select className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm"
|
<select className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm"
|
||||||
|
|
@ -125,7 +125,7 @@ export default function AssignmentCreate() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="font-semibold text-gray-700 text-sm">Institutees (Berater)</h2>
|
<h2 className="font-semibold text-gray-700 text-sm">Institutees (Berater)</h2>
|
||||||
<button onClick={() => setShowAddInstitutee(p => !p)}
|
<button onClick={() => setShowAddInstitutee(p => !p)}
|
||||||
className="text-xs text-[#0070AD] border border-[#0070AD] px-2.5 py-1 rounded-lg">
|
className="text-xs text-brand border border-brand px-2.5 py-1 rounded-lg">
|
||||||
+ Neu anlegen
|
+ Neu anlegen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -135,11 +135,11 @@ export default function AssignmentCreate() {
|
||||||
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3 space-y-2">
|
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3 space-y-2">
|
||||||
<div className="text-xs font-semibold text-blue-600 mb-1">Neuen Berater anlegen</div>
|
<div className="text-xs font-semibold text-blue-600 mb-1">Neuen Berater anlegen</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
<input className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Vorname" value={newFirst}
|
placeholder="Vorname" value={newFirst}
|
||||||
onChange={e => setNewFirst(e.target.value)}
|
onChange={e => setNewFirst(e.target.value)}
|
||||||
onKeyDown={e => e.key === 'Enter' && addInstituteeInline()} />
|
onKeyDown={e => e.key === 'Enter' && addInstituteeInline()} />
|
||||||
<input className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
<input className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Nachname" value={newLast}
|
placeholder="Nachname" value={newLast}
|
||||||
onChange={e => setNewLast(e.target.value)}
|
onChange={e => setNewLast(e.target.value)}
|
||||||
onKeyDown={e => e.key === 'Enter' && addInstituteeInline()} />
|
onKeyDown={e => e.key === 'Enter' && addInstituteeInline()} />
|
||||||
|
|
@ -155,7 +155,7 @@ export default function AssignmentCreate() {
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button onClick={addInstituteeInline}
|
<button onClick={addInstituteeInline}
|
||||||
disabled={!newFirst.trim() || !newLast.trim()}
|
disabled={!newFirst.trim() || !newLast.trim()}
|
||||||
className="flex-1 py-2 rounded-lg text-sm font-medium bg-[#0070AD] text-white disabled:bg-gray-200 disabled:text-gray-400">
|
className="flex-1 py-2 rounded-lg text-sm font-medium bg-brand text-white disabled:bg-gray-200 disabled:text-gray-400">
|
||||||
Hinzufügen
|
Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => { setShowAddInstitutee(false); setNewFirst(''); setNewLast('') }}
|
<button onClick={() => { setShowAddInstitutee(false); setNewFirst(''); setNewLast('') }}
|
||||||
|
|
@ -182,7 +182,7 @@ export default function AssignmentCreate() {
|
||||||
{groupConsultants.map(c => (
|
{groupConsultants.map(c => (
|
||||||
<label key={c.id} className="flex items-center gap-3 py-1.5 cursor-pointer">
|
<label key={c.id} className="flex items-center gap-3 py-1.5 cursor-pointer">
|
||||||
<input type="checkbox" checked={form.instituteeIds.includes(c.id!)}
|
<input type="checkbox" checked={form.instituteeIds.includes(c.id!)}
|
||||||
onChange={() => toggleInstitutee(c.id!)} className="w-4 h-4 accent-[#0070AD]" />
|
onChange={() => toggleInstitutee(c.id!)} className="w-4 h-4 accent-brand" />
|
||||||
<span className="text-sm">{c.firstName} {c.lastName}</span>
|
<span className="text-sm">{c.firstName} {c.lastName}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
|
|
@ -192,7 +192,7 @@ export default function AssignmentCreate() {
|
||||||
{/* Gewählte Berater aus anderen Gruppen */}
|
{/* Gewählte Berater aus anderen Gruppen */}
|
||||||
{selectedInstitutees.filter(c => c.groupId !== form.selectedGroupId).map(c => (
|
{selectedInstitutees.filter(c => c.groupId !== form.selectedGroupId).map(c => (
|
||||||
<div key={c.id} className="flex items-center gap-2 py-1">
|
<div key={c.id} className="flex items-center gap-2 py-1">
|
||||||
<span className="w-4 h-4 rounded bg-[#0070AD] flex items-center justify-center">
|
<span className="w-4 h-4 rounded bg-brand flex items-center justify-center">
|
||||||
<span className="text-white text-xs">✓</span>
|
<span className="text-white text-xs">✓</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm">{c.firstName} {c.lastName}</span>
|
<span className="text-sm">{c.firstName} {c.lastName}</span>
|
||||||
|
|
@ -203,7 +203,7 @@ export default function AssignmentCreate() {
|
||||||
|
|
||||||
<button onClick={create} disabled={!canCreate}
|
<button onClick={create} disabled={!canCreate}
|
||||||
className={`w-full py-3 rounded-xl text-sm font-medium shadow transition ${
|
className={`w-full py-3 rounded-xl text-sm font-medium shadow transition ${
|
||||||
canCreate ? 'bg-[#0070AD] text-white hover:bg-[#005a8e]' : 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
canCreate ? 'bg-brand text-white hover:bg-brand-dark' : 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
||||||
}`}>
|
}`}>
|
||||||
Assignment anlegen
|
Assignment anlegen
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -150,17 +150,17 @@ export default function AssignmentDetail() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => navigate('/')} className="text-sm text-[#0070AD] mb-2 block">← Zurück</button>
|
<button onClick={() => navigate('/')} className="text-sm text-brand mb-2 block">← Zurück</button>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
{assignment.assignmentNumber && (
|
{assignment.assignmentNumber && (
|
||||||
<div className="text-xs font-semibold text-[#0070AD] uppercase tracking-wide">{assignment.assignmentNumber}</div>
|
<div className="text-xs font-semibold text-brand uppercase tracking-wide">{assignment.assignmentNumber}</div>
|
||||||
)}
|
)}
|
||||||
<h1 className="text-xl font-bold text-gray-800 leading-tight">{assignment.title}</h1>
|
<h1 className="text-xl font-bold text-gray-800 leading-tight">{assignment.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/assignment/${assignmentId}/edit`)}
|
onClick={() => navigate(`/assignment/${assignmentId}/edit`)}
|
||||||
className="flex-shrink-0 text-xs px-3 py-1.5 border border-gray-200 rounded-lg text-gray-500 hover:border-[#0070AD] hover:text-[#0070AD] transition">
|
className="flex-shrink-0 text-xs px-3 py-1.5 border border-gray-200 rounded-lg text-gray-500 hover:border-brand hover:text-brand transition">
|
||||||
✏️ Bearbeiten
|
✏️ Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -175,7 +175,7 @@ export default function AssignmentDetail() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-semibold text-gray-700">Ad-hoc Meeting starten</span>
|
<span className="text-sm font-semibold text-gray-700">Ad-hoc Meeting starten</span>
|
||||||
<button onClick={() => setShowAdHoc(p => !p)}
|
<button onClick={() => setShowAdHoc(p => !p)}
|
||||||
className="text-xs bg-[#0070AD] text-white px-3 py-1.5 rounded-lg font-medium">
|
className="text-xs bg-brand text-white px-3 py-1.5 rounded-lg font-medium">
|
||||||
{showAdHoc ? 'Abbrechen' : '+ Ad-hoc'}
|
{showAdHoc ? 'Abbrechen' : '+ Ad-hoc'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,7 +187,7 @@ export default function AssignmentDetail() {
|
||||||
const count = phaseRows.find(r => r.template.id === t.id)?.instances.length ?? 0
|
const count = phaseRows.find(r => r.template.id === t.id)?.instances.length ?? 0
|
||||||
startMeeting(t.id!, count)
|
startMeeting(t.id!, count)
|
||||||
}}
|
}}
|
||||||
className="py-2.5 px-3 text-left rounded-lg border border-gray-200 hover:border-[#0070AD] hover:bg-blue-50 transition">
|
className="py-2.5 px-3 text-left rounded-lg border border-gray-200 hover:border-brand hover:bg-blue-50 transition">
|
||||||
<div className="text-sm font-medium text-gray-700">{t.label}</div>
|
<div className="text-sm font-medium text-gray-700">{t.label}</div>
|
||||||
<div className={`text-xs mt-0.5 ${t.withWhom === 'client' ? 'text-blue-500' : 'text-purple-500'}`}>
|
<div className={`text-xs mt-0.5 ${t.withWhom === 'client' ? 'text-blue-500' : 'text-purple-500'}`}>
|
||||||
{t.withWhom === 'client' ? 'Kundentermin' : 'Lead-intern'}
|
{t.withWhom === 'client' ? 'Kundentermin' : 'Lead-intern'}
|
||||||
|
|
@ -237,7 +237,7 @@ export default function AssignmentDetail() {
|
||||||
<button
|
<button
|
||||||
onClick={() => canStart && startMeeting(template.id!, instances.length)}
|
onClick={() => canStart && startMeeting(template.id!, instances.length)}
|
||||||
className={`ml-2 flex-shrink-0 text-xs px-3 py-1.5 rounded-lg font-medium ${
|
className={`ml-2 flex-shrink-0 text-xs px-3 py-1.5 rounded-lg font-medium ${
|
||||||
canStart ? 'bg-[#0070AD] text-white' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
canStart ? 'bg-brand text-white' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||||
}`}
|
}`}
|
||||||
disabled={!canStart}>
|
disabled={!canStart}>
|
||||||
{instances.length === 0 ? 'Starten' : template.isRepeatable ? '+ Wiederholen' : 'Erledigt'}
|
{instances.length === 0 ? 'Starten' : template.isRepeatable ? '+ Wiederholen' : 'Erledigt'}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export default function AssignmentEdit() {
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
|
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
|
||||||
className="text-sm text-[#0070AD] mb-2 block">← Zurück</button>
|
className="text-sm text-brand mb-2 block">← Zurück</button>
|
||||||
<h1 className="text-xl font-bold text-gray-800">Assignment bearbeiten</h1>
|
<h1 className="text-xl font-bold text-gray-800">Assignment bearbeiten</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Assignment-Nummer</label>
|
<label className="text-xs font-medium text-gray-500">Assignment-Nummer</label>
|
||||||
<input
|
<input
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="z. B. CGI-2024-001"
|
placeholder="z. B. CGI-2024-001"
|
||||||
value={assignmentNumber}
|
value={assignmentNumber}
|
||||||
onChange={e => setAssignmentNumber(e.target.value)}
|
onChange={e => setAssignmentNumber(e.target.value)}
|
||||||
|
|
@ -114,7 +114,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Titel</label>
|
<label className="text-xs font-medium text-gray-500">Titel</label>
|
||||||
<input
|
<input
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => setTitle(e.target.value)}
|
onChange={e => setTitle(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -122,7 +122,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Kunde</label>
|
<label className="text-xs font-medium text-gray-500">Kunde</label>
|
||||||
<input
|
<input
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
value={client}
|
value={client}
|
||||||
onChange={e => setClient(e.target.value)}
|
onChange={e => setClient(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -130,7 +130,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Beschreibung</label>
|
<label className="text-xs font-medium text-gray-500">Beschreibung</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
rows={3}
|
rows={3}
|
||||||
value={description}
|
value={description}
|
||||||
onChange={e => setDescription(e.target.value)}
|
onChange={e => setDescription(e.target.value)}
|
||||||
|
|
@ -139,7 +139,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Assignment-Typ</label>
|
<label className="text-xs font-medium text-gray-500">Assignment-Typ</label>
|
||||||
<select
|
<select
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
value={assignmentTypeId}
|
value={assignmentTypeId}
|
||||||
onChange={e => handleTypeChange(Number(e.target.value))}>
|
onChange={e => handleTypeChange(Number(e.target.value))}>
|
||||||
{assignmentTypes.map(t => (
|
{assignmentTypes.map(t => (
|
||||||
|
|
@ -155,7 +155,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Lead</label>
|
<label className="text-xs font-medium text-gray-500">Lead</label>
|
||||||
<input
|
<input
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
value={leadName}
|
value={leadName}
|
||||||
onChange={e => setLeadName(e.target.value)}
|
onChange={e => setLeadName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -163,7 +163,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-medium text-gray-500">Status</label>
|
<label className="text-xs font-medium text-gray-500">Status</label>
|
||||||
<select
|
<select
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
value={status}
|
value={status}
|
||||||
onChange={e => setStatus(e.target.value as 'active' | 'done')}>
|
onChange={e => setStatus(e.target.value as 'active' | 'done')}>
|
||||||
<option value="active">Aktiv</option>
|
<option value="active">Aktiv</option>
|
||||||
|
|
@ -193,7 +193,7 @@ export default function AssignmentEdit() {
|
||||||
<span className="text-xs text-gray-400">Von</span>
|
<span className="text-xs text-gray-400">Von</span>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-[#0070AD]"
|
className="border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-brand"
|
||||||
value={enrollment?.from ?? ''}
|
value={enrollment?.from ?? ''}
|
||||||
onChange={e => updateEnrollment(cid, 'from', e.target.value)}
|
onChange={e => updateEnrollment(cid, 'from', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -202,7 +202,7 @@ export default function AssignmentEdit() {
|
||||||
<span className="text-xs text-gray-400">Bis</span>
|
<span className="text-xs text-gray-400">Bis</span>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-[#0070AD]"
|
className="border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-brand"
|
||||||
value={enrollment?.to ?? ''}
|
value={enrollment?.to ?? ''}
|
||||||
onChange={e => updateEnrollment(cid, 'to', e.target.value)}
|
onChange={e => updateEnrollment(cid, 'to', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -220,7 +220,7 @@ export default function AssignmentEdit() {
|
||||||
{availableConsultants.length > 0 && (
|
{availableConsultants.length > 0 && (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<select
|
<select
|
||||||
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
value={addInstituteeId}
|
value={addInstituteeId}
|
||||||
onChange={e => setAddInstituteeId(Number(e.target.value))}>
|
onChange={e => setAddInstituteeId(Number(e.target.value))}>
|
||||||
<option value={0}>Institutee auswählen…</option>
|
<option value={0}>Institutee auswählen…</option>
|
||||||
|
|
@ -231,7 +231,7 @@ export default function AssignmentEdit() {
|
||||||
<button
|
<button
|
||||||
onClick={addInstitutee}
|
onClick={addInstitutee}
|
||||||
disabled={!addInstituteeId}
|
disabled={!addInstituteeId}
|
||||||
className="px-4 py-2 bg-[#0070AD] text-white text-sm rounded-lg font-medium disabled:opacity-40">
|
className="px-4 py-2 bg-brand text-white text-sm rounded-lg font-medium disabled:opacity-40">
|
||||||
Hinzufügen
|
Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -242,7 +242,7 @@ export default function AssignmentEdit() {
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="flex-1 py-3 bg-[#0070AD] text-white rounded-xl text-sm font-semibold">
|
className="flex-1 py-3 bg-brand text-white rounded-xl text-sm font-semibold">
|
||||||
Speichern
|
Speichern
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -12,40 +12,24 @@ import {
|
||||||
type FeedbackDimensionText,
|
type FeedbackDimensionText,
|
||||||
type FeedbackRating,
|
type FeedbackRating,
|
||||||
} from '../db'
|
} from '../db'
|
||||||
|
import { RATING_OPTIONS, RATING_NUM_MAP, AI_CONFIG } from '../config/constants'
|
||||||
|
|
||||||
const ratingToNum = (r: FeedbackRating | null | undefined): number => {
|
const ratingToNum = (r: FeedbackRating | null | undefined): number =>
|
||||||
const m: Record<FeedbackRating, number> = { na: 0, not_client_ready: 1, partially_client_ready: 2, nearly_client_ready: 3, client_ready: 4 }
|
r ? (RATING_NUM_MAP[r] ?? 0) : 0
|
||||||
return r ? (m[r] ?? 0) : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Rating-Skala ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const RATINGS: { value: FeedbackRating; short: string; label: string; color: string }[] = [
|
|
||||||
{ value: 'na', short: 'N/A', label: 'N/A (0)', color: 'bg-gray-100 text-gray-500 border-gray-300' },
|
|
||||||
{ value: 'not_client_ready', short: 'Not', label: 'Not (1)', color: 'bg-red-100 text-red-700 border-red-300' },
|
|
||||||
{ value: 'partially_client_ready', short: 'Partially', label: 'Partially (2)', color: 'bg-orange-100 text-orange-700 border-orange-300' },
|
|
||||||
{ value: 'nearly_client_ready', short: 'Nearly', label: 'Nearly (3)', color: 'bg-green-100 text-green-600 border-green-300' },
|
|
||||||
{ value: 'client_ready', short: 'Fully', label: 'Fully (4)', color: 'bg-green-100 text-green-800 border-green-400' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const ratingColor = (r: FeedbackRating | undefined) =>
|
const ratingColor = (r: FeedbackRating | undefined) =>
|
||||||
RATINGS.find(x => x.value === r)?.color ?? 'bg-gray-50 text-gray-400 border-gray-200'
|
RATING_OPTIONS.find(x => x.value === r)?.color ?? 'bg-gray-50 text-gray-400 border-gray-200'
|
||||||
|
|
||||||
// ── KI-Einstellungen aus localStorage ────────────────────────────────────────
|
|
||||||
|
|
||||||
const AI_KEY = 'ai_api_key'
|
|
||||||
const AI_MODEL = 'ai_model'
|
|
||||||
|
|
||||||
function getAiSettings() {
|
function getAiSettings() {
|
||||||
return {
|
return {
|
||||||
apiKey: localStorage.getItem(AI_KEY) ?? '',
|
apiKey: localStorage.getItem(AI_CONFIG.localStorageKeyApiKey) ?? '',
|
||||||
model: localStorage.getItem(AI_MODEL) ?? 'openai/gpt-4o-mini',
|
model: localStorage.getItem(AI_CONFIG.localStorageKeyModel) ?? AI_CONFIG.defaultModel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveAiSettings(apiKey: string, model: string) {
|
function saveAiSettings(apiKey: string, model: string) {
|
||||||
localStorage.setItem(AI_KEY, apiKey)
|
localStorage.setItem(AI_CONFIG.localStorageKeyApiKey, apiKey)
|
||||||
localStorage.setItem(AI_MODEL, model)
|
localStorage.setItem(AI_CONFIG.localStorageKeyModel, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Prompt-Builder ────────────────────────────────────────────────────────────
|
// ── Prompt-Builder ────────────────────────────────────────────────────────────
|
||||||
|
|
@ -95,7 +79,7 @@ async function buildAiPrompt(
|
||||||
const dimCats = categories.filter(c => c.dimensionId === dim.id)
|
const dimCats = categories.filter(c => c.dimensionId === dim.id)
|
||||||
const lines = dimCats.map(cat => {
|
const lines = dimCats.map(cat => {
|
||||||
const r = ratings.find(r => r.feedbackCategoryId === cat.id)
|
const r = ratings.find(r => r.feedbackCategoryId === cat.id)
|
||||||
const rating = RATINGS.find(x => x.value === r?.rating)?.label ?? 'nicht bewertet'
|
const rating = RATING_OPTIONS.find(x => x.value === r?.rating)?.label ?? 'nicht bewertet'
|
||||||
return ` ${cat.name}: ${rating}`
|
return ` ${cat.name}: ${rating}`
|
||||||
}).join('\n')
|
}).join('\n')
|
||||||
return ` Dimension "${dim.name}":\n${lines}`
|
return ` Dimension "${dim.name}":\n${lines}`
|
||||||
|
|
@ -338,7 +322,7 @@ export default function AssignmentFeedbackPage() {
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
|
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
|
||||||
className="text-xs text-[#0070AD] mb-0.5 block">← Assignment</button>
|
className="text-xs text-brand mb-0.5 block">← Assignment</button>
|
||||||
<h1 className="text-xl font-bold text-gray-800">
|
<h1 className="text-xl font-bold text-gray-800">
|
||||||
Feedback: {institutee.firstName} {institutee.lastName}
|
Feedback: {institutee.firstName} {institutee.lastName}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -369,7 +353,7 @@ export default function AssignmentFeedbackPage() {
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
className="w-full border border-blue-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD] bg-white"
|
className="w-full border border-blue-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand bg-white"
|
||||||
placeholder="sk-or-..."
|
placeholder="sk-or-..."
|
||||||
value={aiKey}
|
value={aiKey}
|
||||||
onChange={e => setAiKey(e.target.value)}
|
onChange={e => setAiKey(e.target.value)}
|
||||||
|
|
@ -386,7 +370,7 @@ export default function AssignmentFeedbackPage() {
|
||||||
</select>
|
</select>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button onClick={() => { saveAiSettings(aiKey, aiModel); setShowAiSettings(false) }}
|
<button onClick={() => { saveAiSettings(aiKey, aiModel); setShowAiSettings(false) }}
|
||||||
className="flex-1 py-2 rounded-lg text-sm font-medium bg-[#0070AD] text-white">
|
className="flex-1 py-2 rounded-lg text-sm font-medium bg-brand text-white">
|
||||||
Speichern
|
Speichern
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setShowAiSettings(false)}
|
<button onClick={() => setShowAiSettings(false)}
|
||||||
|
|
@ -405,7 +389,7 @@ export default function AssignmentFeedbackPage() {
|
||||||
className={`flex-1 py-3 rounded-xl text-sm font-medium transition ${
|
className={`flex-1 py-3 rounded-xl text-sm font-medium transition ${
|
||||||
generating
|
generating
|
||||||
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
||||||
: 'bg-[#0070AD] text-white hover:bg-[#005a8e]'
|
: 'bg-brand text-white hover:bg-brand-dark'
|
||||||
}`}>
|
}`}>
|
||||||
{generating ? 'Generiere Entwurf…' : '✦ KI-Feedback-Entwurf generieren'}
|
{generating ? 'Generiere Entwurf…' : '✦ KI-Feedback-Entwurf generieren'}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -446,7 +430,7 @@ export default function AssignmentFeedbackPage() {
|
||||||
<span className="font-semibold text-gray-800 text-sm">{dim.name}</span>
|
<span className="font-semibold text-gray-800 text-sm">{dim.name}</span>
|
||||||
{worstRating && (
|
{worstRating && (
|
||||||
<span className={`text-xs px-2 py-0.5 rounded-full border font-medium ${summaryColor}`}>
|
<span className={`text-xs px-2 py-0.5 rounded-full border font-medium ${summaryColor}`}>
|
||||||
{RATINGS.find(r => r.value === worstRating)?.short}
|
{RATING_OPTIONS.find(r => r.value === worstRating)?.short}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -465,7 +449,7 @@ export default function AssignmentFeedbackPage() {
|
||||||
|
|
||||||
{/* Rating-Buttons */}
|
{/* Rating-Buttons */}
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex gap-1 flex-wrap">
|
||||||
{RATINGS.map(r => (
|
{RATING_OPTIONS.map(r => (
|
||||||
<button key={r.value}
|
<button key={r.value}
|
||||||
onClick={() => setRating(cat.id!, r.value)}
|
onClick={() => setRating(cat.id!, r.value)}
|
||||||
className={`px-2.5 py-1.5 rounded-lg text-xs font-semibold border transition ${
|
className={`px-2.5 py-1.5 rounded-lg text-xs font-semibold border transition ${
|
||||||
|
|
|
||||||
|
|
@ -117,14 +117,14 @@ export default function AssignmentTypeConfig() {
|
||||||
{/* Neuer Typ */}
|
{/* Neuer Typ */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Neuer Assignment-Typ…"
|
placeholder="Neuer Assignment-Typ…"
|
||||||
value={newTypeName}
|
value={newTypeName}
|
||||||
onChange={e => setNewTypeName(e.target.value)}
|
onChange={e => setNewTypeName(e.target.value)}
|
||||||
onKeyDown={e => e.key === 'Enter' && addType()}
|
onKeyDown={e => e.key === 'Enter' && addType()}
|
||||||
/>
|
/>
|
||||||
<button onClick={addType}
|
<button onClick={addType}
|
||||||
className="px-4 py-2 bg-[#0070AD] text-white text-sm rounded-lg font-medium">
|
className="px-4 py-2 bg-brand text-white text-sm rounded-lg font-medium">
|
||||||
+ Typ
|
+ Typ
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -145,7 +145,7 @@ export default function AssignmentTypeConfig() {
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-2 px-4 py-3 bg-gray-50 border-b border-gray-100">
|
<div className="flex items-center gap-2 px-4 py-3 bg-gray-50 border-b border-gray-100">
|
||||||
<input
|
<input
|
||||||
className="flex-1 bg-transparent text-sm font-semibold text-gray-800 focus:outline-none border-b border-transparent focus:border-[#0070AD] py-0.5"
|
className="flex-1 bg-transparent text-sm font-semibold text-gray-800 focus:outline-none border-b border-transparent focus:border-brand py-0.5"
|
||||||
value={type.name}
|
value={type.name}
|
||||||
onChange={e => updateTypeName(type.id!, e.target.value)}
|
onChange={e => updateTypeName(type.id!, e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -170,7 +170,7 @@ export default function AssignmentTypeConfig() {
|
||||||
<button key={t}
|
<button key={t}
|
||||||
onClick={() => setActiveTab(prev => ({ ...prev, [type.id!]: t }))}
|
onClick={() => setActiveTab(prev => ({ ...prev, [type.id!]: t }))}
|
||||||
className={`flex-1 py-2 text-xs font-medium transition ${
|
className={`flex-1 py-2 text-xs font-medium transition ${
|
||||||
tab === t ? 'text-[#0070AD] border-b-2 border-[#0070AD]' : 'text-gray-400'
|
tab === t ? 'text-brand border-b-2 border-brand' : 'text-gray-400'
|
||||||
}`}>
|
}`}>
|
||||||
{t === 'phases' ? `Phasen (${typPhases.length})` : `Kriterien (${selected.length || 'alle'})`}
|
{t === 'phases' ? `Phasen (${typPhases.length})` : `Kriterien (${selected.length || 'alle'})`}
|
||||||
|
|
||||||
|
|
@ -185,14 +185,14 @@ export default function AssignmentTypeConfig() {
|
||||||
<div key={phase.id} className="border border-gray-100 rounded-lg px-3 py-2 space-y-1.5">
|
<div key={phase.id} className="border border-gray-100 rounded-lg px-3 py-2 space-y-1.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
className="flex-1 text-sm text-gray-700 border-b border-transparent focus:border-[#0070AD] focus:outline-none bg-transparent"
|
className="flex-1 text-sm text-gray-700 border-b border-transparent focus:border-brand focus:outline-none bg-transparent"
|
||||||
value={phase.label}
|
value={phase.label}
|
||||||
onChange={e => updatePhase(phase.id!, { label: e.target.value })}
|
onChange={e => updatePhase(phase.id!, { label: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
value={phase.withWhom}
|
value={phase.withWhom}
|
||||||
onChange={e => updatePhase(phase.id!, { withWhom: e.target.value as 'lead' | 'client' })}
|
onChange={e => updatePhase(phase.id!, { withWhom: e.target.value as 'lead' | 'client' })}
|
||||||
className="text-xs border border-gray-200 rounded px-1.5 py-1 focus:outline-none focus:border-[#0070AD]">
|
className="text-xs border border-gray-200 rounded px-1.5 py-1 focus:outline-none focus:border-brand">
|
||||||
<option value="lead">Lead</option>
|
<option value="lead">Lead</option>
|
||||||
<option value="client">Kunde</option>
|
<option value="client">Kunde</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -224,7 +224,7 @@ export default function AssignmentTypeConfig() {
|
||||||
<div className="text-xs font-medium text-gray-500">Neue Phase</div>
|
<div className="text-xs font-medium text-gray-500">Neue Phase</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
className="flex-1 border border-gray-200 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-[#0070AD] bg-white"
|
className="flex-1 border border-gray-200 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-brand bg-white"
|
||||||
placeholder="Phasenname…"
|
placeholder="Phasenname…"
|
||||||
value={np.label}
|
value={np.label}
|
||||||
onChange={e => setNewPhase(prev => ({ ...prev, [type.id!]: { ...np, label: e.target.value } }))}
|
onChange={e => setNewPhase(prev => ({ ...prev, [type.id!]: { ...np, label: e.target.value } }))}
|
||||||
|
|
@ -233,7 +233,7 @@ export default function AssignmentTypeConfig() {
|
||||||
<select
|
<select
|
||||||
value={np.withWhom}
|
value={np.withWhom}
|
||||||
onChange={e => setNewPhase(prev => ({ ...prev, [type.id!]: { ...np, withWhom: e.target.value as 'lead' | 'client' } }))}
|
onChange={e => setNewPhase(prev => ({ ...prev, [type.id!]: { ...np, withWhom: e.target.value as 'lead' | 'client' } }))}
|
||||||
className="border border-gray-200 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-[#0070AD] bg-white">
|
className="border border-gray-200 rounded px-2 py-1.5 text-sm focus:outline-none focus:border-brand bg-white">
|
||||||
<option value="lead">Lead</option>
|
<option value="lead">Lead</option>
|
||||||
<option value="client">Kunde</option>
|
<option value="client">Kunde</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -256,7 +256,7 @@ export default function AssignmentTypeConfig() {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => addPhase(type.id!)}
|
<button onClick={() => addPhase(type.id!)}
|
||||||
className="px-4 py-1.5 bg-[#0070AD] text-white text-xs rounded-lg font-medium">
|
className="px-4 py-1.5 bg-brand text-white text-xs rounded-lg font-medium">
|
||||||
+ Hinzufügen
|
+ Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -41,15 +41,15 @@ export default function Configuration() {
|
||||||
{/* Tab-Switcher */}
|
{/* Tab-Switcher */}
|
||||||
<div className="flex bg-gray-100 rounded-xl p-1 gap-0.5">
|
<div className="flex bg-gray-100 rounded-xl p-1 gap-0.5">
|
||||||
<button onClick={() => setTab('feedback')}
|
<button onClick={() => setTab('feedback')}
|
||||||
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${tab === 'feedback' ? 'bg-white text-[#0070AD] shadow-sm' : 'text-gray-500'}`}>
|
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${tab === 'feedback' ? 'bg-white text-brand shadow-sm' : 'text-gray-500'}`}>
|
||||||
Feedback
|
Feedback
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setTab('types')}
|
<button onClick={() => setTab('types')}
|
||||||
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${tab === 'types' ? 'bg-white text-[#0070AD] shadow-sm' : 'text-gray-500'}`}>
|
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${tab === 'types' ? 'bg-white text-brand shadow-sm' : 'text-gray-500'}`}>
|
||||||
Ass.-Typen
|
Ass.-Typen
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setTab('backup')}
|
<button onClick={() => setTab('backup')}
|
||||||
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${tab === 'backup' ? 'bg-white text-[#0070AD] shadow-sm' : 'text-gray-500'}`}>
|
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${tab === 'backup' ? 'bg-white text-brand shadow-sm' : 'text-gray-500'}`}>
|
||||||
Backup
|
Backup
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -76,7 +76,7 @@ export default function Configuration() {
|
||||||
Diese Datei kannst du sicher aufbewahren und bei Bedarf wiederherstellen.
|
Diese Datei kannst du sicher aufbewahren und bei Bedarf wiederherstellen.
|
||||||
</p>
|
</p>
|
||||||
<button onClick={handleExport}
|
<button onClick={handleExport}
|
||||||
className="w-full py-3 bg-[#0070AD] text-white rounded-xl text-sm font-semibold">
|
className="w-full py-3 bg-brand text-white rounded-xl text-sm font-semibold">
|
||||||
↓ Vollständiges Backup herunterladen
|
↓ Vollständiges Backup herunterladen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export default function Consultants() {
|
||||||
onChange={e => setNewGroup(p => ({ ...p, name: e.target.value }))}
|
onChange={e => setNewGroup(p => ({ ...p, name: e.target.value }))}
|
||||||
onKeyDown={e => e.key === 'Enter' && addGroup()}
|
onKeyDown={e => e.key === 'Enter' && addGroup()}
|
||||||
/>
|
/>
|
||||||
<button onClick={addGroup} className="bg-[#0070AD] text-white px-4 py-2 rounded-lg text-sm">+</button>
|
<button onClick={addGroup} className="bg-brand text-white px-4 py-2 rounded-lg text-sm">+</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ export default function Consultants() {
|
||||||
<input className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm" placeholder="Nachname"
|
<input className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm" placeholder="Nachname"
|
||||||
value={newCons.lastName} onChange={e => setNewCons(p => ({ ...p, lastName: e.target.value }))} />
|
value={newCons.lastName} onChange={e => setNewCons(p => ({ ...p, lastName: e.target.value }))} />
|
||||||
</div>
|
</div>
|
||||||
<button onClick={addConsultant} className="w-full bg-[#0070AD] text-white py-2 rounded-lg text-sm font-medium">Berater hinzufügen</button>
|
<button onClick={addConsultant} className="w-full bg-brand text-white py-2 rounded-lg text-sm font-medium">Berater hinzufügen</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{groups.map(g => {
|
{groups.map(g => {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default function Dashboard() {
|
||||||
<h1 className="text-xl font-bold text-gray-800">Assignments</h1>
|
<h1 className="text-xl font-bold text-gray-800">Assignments</h1>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/assignment/new')}
|
onClick={() => navigate('/assignment/new')}
|
||||||
className="bg-[#0070AD] text-white px-4 py-2 rounded-lg text-sm font-medium"
|
className="bg-brand text-white px-4 py-2 rounded-lg text-sm font-medium"
|
||||||
>
|
>
|
||||||
+ Neues
|
+ Neues
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -66,13 +66,13 @@ export default function Dashboard() {
|
||||||
placeholder="Suche nach Nummer, Titel, Kunde, Berater…"
|
placeholder="Suche nach Nummer, Titel, Kunde, Berater…"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={e => setQuery(e.target.value)}
|
onChange={e => setQuery(e.target.value)}
|
||||||
className="w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD] bg-white"
|
className="w-full border border-gray-200 rounded-xl px-3 py-2 text-sm focus:outline-none focus:border-brand bg-white"
|
||||||
/>
|
/>
|
||||||
<div className="flex bg-gray-100 rounded-xl p-1 gap-0.5">
|
<div className="flex bg-gray-100 rounded-xl p-1 gap-0.5">
|
||||||
{([['all', 'Alle'], ['active', 'Aktiv'], ['done', 'Abgeschlossen']] as const).map(([val, label]) => (
|
{([['all', 'Alle'], ['active', 'Aktiv'], ['done', 'Abgeschlossen']] as const).map(([val, label]) => (
|
||||||
<button key={val} onClick={() => setStatusFilter(val)}
|
<button key={val} onClick={() => setStatusFilter(val)}
|
||||||
className={`flex-1 py-1.5 rounded-lg text-xs font-medium transition ${
|
className={`flex-1 py-1.5 rounded-lg text-xs font-medium transition ${
|
||||||
statusFilter === val ? 'bg-white text-[#0070AD] shadow-sm' : 'text-gray-500'
|
statusFilter === val ? 'bg-white text-brand shadow-sm' : 'text-gray-500'
|
||||||
}`}>
|
}`}>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -96,7 +96,7 @@ export default function Dashboard() {
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{a.assignmentNumber && (
|
{a.assignmentNumber && (
|
||||||
<div className="text-xs font-semibold text-[#0070AD] uppercase tracking-wide leading-none mb-0.5">
|
<div className="text-xs font-semibold text-brand uppercase tracking-wide leading-none mb-0.5">
|
||||||
{a.assignmentNumber}
|
{a.assignmentNumber}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ export default function FeedbackDraftPage() {
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
|
<button onClick={() => navigate(`/assignment/${assignmentId}`)}
|
||||||
className="text-sm text-[#0070AD] mb-2 block">
|
className="text-sm text-brand mb-2 block">
|
||||||
← Assignment
|
← Assignment
|
||||||
</button>
|
</button>
|
||||||
<h1 className="text-xl font-bold text-gray-800">
|
<h1 className="text-xl font-bold text-gray-800">
|
||||||
|
|
@ -191,12 +191,12 @@ export default function FeedbackDraftPage() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wide">Feedback-Entwurf</div>
|
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wide">Feedback-Entwurf</div>
|
||||||
<button onClick={generateDraft}
|
<button onClick={generateDraft}
|
||||||
className="text-xs bg-[#0070AD] text-white px-3 py-1.5 rounded-lg font-medium">
|
className="text-xs bg-brand text-white px-3 py-1.5 rounded-lg font-medium">
|
||||||
{generated ? 'Neu generieren' : 'Automatisch generieren'}
|
{generated ? 'Neu generieren' : 'Automatisch generieren'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:border-brand"
|
||||||
rows={18}
|
rows={18}
|
||||||
placeholder="Tippe deinen Feedback-Entwurf hier, oder nutze 'Automatisch generieren'…"
|
placeholder="Tippe deinen Feedback-Entwurf hier, oder nutze 'Automatisch generieren'…"
|
||||||
value={content}
|
value={content}
|
||||||
|
|
|
||||||
|
|
@ -198,14 +198,14 @@ export default function FeedbackStructureConfig() {
|
||||||
{/* Neue Dimension */}
|
{/* Neue Dimension */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Neue Dimension…"
|
placeholder="Neue Dimension…"
|
||||||
value={newDimName}
|
value={newDimName}
|
||||||
onChange={e => setNewDimName(e.target.value)}
|
onChange={e => setNewDimName(e.target.value)}
|
||||||
onKeyDown={e => e.key === 'Enter' && addDimension()}
|
onKeyDown={e => e.key === 'Enter' && addDimension()}
|
||||||
/>
|
/>
|
||||||
<button onClick={addDimension}
|
<button onClick={addDimension}
|
||||||
className="px-4 py-2 bg-[#0070AD] text-white text-sm rounded-lg font-medium">
|
className="px-4 py-2 bg-brand text-white text-sm rounded-lg font-medium">
|
||||||
+ Dimension
|
+ Dimension
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -226,7 +226,7 @@ export default function FeedbackStructureConfig() {
|
||||||
className="text-xs text-gray-400 hover:text-gray-600 leading-none disabled:opacity-30">▼</button>
|
className="text-xs text-gray-400 hover:text-gray-600 leading-none disabled:opacity-30">▼</button>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
className="flex-1 bg-transparent font-semibold text-sm text-gray-800 focus:outline-none border-b border-transparent focus:border-[#0070AD]"
|
className="flex-1 bg-transparent font-semibold text-sm text-gray-800 focus:outline-none border-b border-transparent focus:border-brand"
|
||||||
value={dim.name}
|
value={dim.name}
|
||||||
onChange={e => updateDimension(dim.id!, e.target.value)}
|
onChange={e => updateDimension(dim.id!, e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -275,7 +275,7 @@ export default function FeedbackStructureConfig() {
|
||||||
value={item.weight ?? 1}
|
value={item.weight ?? 1}
|
||||||
onChange={e => updateItemWeight(item.id!, Number(e.target.value))}
|
onChange={e => updateItemWeight(item.id!, Number(e.target.value))}
|
||||||
title="Gewichtung"
|
title="Gewichtung"
|
||||||
className="text-xs border border-gray-200 rounded px-1 py-0.5 text-gray-500 bg-white focus:outline-none focus:border-[#0070AD]">
|
className="text-xs border border-gray-200 rounded px-1 py-0.5 text-gray-500 bg-white focus:outline-none focus:border-brand">
|
||||||
{[1,2,3,4,5].map(w => <option key={w} value={w}>×{w}</option>)}
|
{[1,2,3,4,5].map(w => <option key={w} value={w}>×{w}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<button onClick={() => deleteItem(item.id!)}
|
<button onClick={() => deleteItem(item.id!)}
|
||||||
|
|
@ -284,7 +284,7 @@ export default function FeedbackStructureConfig() {
|
||||||
))}
|
))}
|
||||||
<div className="flex gap-1.5 mt-1">
|
<div className="flex gap-1.5 mt-1">
|
||||||
<input
|
<input
|
||||||
className="flex-1 border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-[#0070AD]"
|
className="flex-1 border border-gray-200 rounded px-2 py-1 text-xs focus:outline-none focus:border-brand"
|
||||||
placeholder="Neues Kriterium…"
|
placeholder="Neues Kriterium…"
|
||||||
value={newItemName[cat.id!] ?? ''}
|
value={newItemName[cat.id!] ?? ''}
|
||||||
onChange={e => setNewItemName(p => ({ ...p, [cat.id!]: e.target.value }))}
|
onChange={e => setNewItemName(p => ({ ...p, [cat.id!]: e.target.value }))}
|
||||||
|
|
@ -302,7 +302,7 @@ export default function FeedbackStructureConfig() {
|
||||||
{/* Neue Kategorie */}
|
{/* Neue Kategorie */}
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
<input
|
<input
|
||||||
className="flex-1 border border-gray-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="flex-1 border border-gray-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:border-brand"
|
||||||
placeholder="Neue Kategorie…"
|
placeholder="Neue Kategorie…"
|
||||||
value={newCatName[dim.id!] ?? ''}
|
value={newCatName[dim.id!] ?? ''}
|
||||||
onChange={e => setNewCatName(p => ({ ...p, [dim.id!]: e.target.value }))}
|
onChange={e => setNewCatName(p => ({ ...p, [dim.id!]: e.target.value }))}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,7 @@ import {
|
||||||
} from '../db'
|
} from '../db'
|
||||||
import { exportMeetingAsMarkdown, exportMeetingAsJson, downloadFile } from '../utils/meetingExport'
|
import { exportMeetingAsMarkdown, exportMeetingAsJson, downloadFile } from '../utils/meetingExport'
|
||||||
import { NotationText } from '../utils/notationParser'
|
import { NotationText } from '../utils/notationParser'
|
||||||
|
import { INST_COLORS, RATING_OPTIONS } from '../config/constants'
|
||||||
const INST_COLORS = [
|
|
||||||
{ bg: 'bg-blue-50', border: 'border-blue-300', tag: 'bg-blue-500', text: 'text-blue-700' },
|
|
||||||
{ bg: 'bg-purple-50', border: 'border-purple-300', tag: 'bg-purple-500', text: 'text-purple-700' },
|
|
||||||
{ bg: 'bg-emerald-50', border: 'border-emerald-300', tag: 'bg-emerald-500', text: 'text-emerald-700' },
|
|
||||||
{ bg: 'bg-orange-50', border: 'border-orange-300', tag: 'bg-orange-500', text: 'text-orange-700' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const NOTATIONS = [
|
const NOTATIONS = [
|
||||||
{ sym: '+', tip: 'Gut' },
|
{ sym: '+', tip: 'Gut' },
|
||||||
|
|
@ -32,14 +26,6 @@ const NOTATIONS = [
|
||||||
{ sym: '[…]', tip: 'Slide-Referenz' },
|
{ sym: '[…]', tip: 'Slide-Referenz' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const RATING_OPTIONS: { value: FeedbackRating; short: string; color: string; active: string }[] = [
|
|
||||||
{ value: 'na', short: 'N/A', color: 'bg-gray-50 text-gray-400 border-gray-200', active: 'bg-gray-400 text-white border-gray-400' },
|
|
||||||
{ value: 'not_client_ready', short: 'Not', color: 'bg-red-50 text-red-400 border-red-200', active: 'bg-red-500 text-white border-red-500' },
|
|
||||||
{ value: 'partially_client_ready', short: 'Partially', color: 'bg-orange-50 text-orange-400 border-orange-200', active: 'bg-orange-500 text-white border-orange-500' },
|
|
||||||
{ value: 'nearly_client_ready', short: 'Nearly', color: 'bg-green-50 text-green-500 border-green-200', active: 'bg-green-400 text-white border-green-400' },
|
|
||||||
{ value: 'client_ready', short: 'Fully', color: 'bg-green-50 text-green-800 border-green-300', active: 'bg-green-700 text-white border-green-700' },
|
|
||||||
]
|
|
||||||
|
|
||||||
type Tab = 'conversation' | 'assessment'
|
type Tab = 'conversation' | 'assessment'
|
||||||
|
|
||||||
export default function MeetingView() {
|
export default function MeetingView() {
|
||||||
|
|
@ -328,9 +314,9 @@ export default function MeetingView() {
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<button onClick={() => navigate(`/assignment/${assignment?.id}`)}
|
<button onClick={() => navigate(`/assignment/${assignment?.id}`)}
|
||||||
className="text-xs text-[#0070AD] mb-0.5 block">← Assignment</button>
|
className="text-xs text-brand mb-0.5 block">← Assignment</button>
|
||||||
{assignment?.assignmentNumber && (
|
{assignment?.assignmentNumber && (
|
||||||
<div className="text-xs font-semibold text-[#0070AD] uppercase tracking-wide leading-none mb-0.5">{assignment.assignmentNumber}</div>
|
<div className="text-xs font-semibold text-brand uppercase tracking-wide leading-none mb-0.5">{assignment.assignmentNumber}</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-sm font-semibold text-gray-600 truncate">{assignment?.title}</div>
|
<div className="text-sm font-semibold text-gray-600 truncate">{assignment?.title}</div>
|
||||||
<div className="font-bold text-gray-800 truncate">{template.label}</div>
|
<div className="font-bold text-gray-800 truncate">{template.label}</div>
|
||||||
|
|
@ -392,7 +378,7 @@ export default function MeetingView() {
|
||||||
{editingGeneralNotes ? (
|
{editingGeneralNotes ? (
|
||||||
<textarea
|
<textarea
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full border border-[#0070AD] rounded-xl px-3 py-2 text-sm bg-white focus:outline-none resize-none"
|
className="w-full border border-brand rounded-xl px-3 py-2 text-sm bg-white focus:outline-none resize-none"
|
||||||
style={{ minHeight: '4rem', fieldSizing: 'content' } as React.CSSProperties}
|
style={{ minHeight: '4rem', fieldSizing: 'content' } as React.CSSProperties}
|
||||||
placeholder="Allgemeine Notizen zum Meeting… (+ gut, ! negativ, > Kundeninput)"
|
placeholder="Allgemeine Notizen zum Meeting… (+ gut, ! negativ, > Kundeninput)"
|
||||||
value={generalNotes}
|
value={generalNotes}
|
||||||
|
|
@ -416,7 +402,7 @@ export default function MeetingView() {
|
||||||
{hasConv && (
|
{hasConv && (
|
||||||
<button onClick={() => setTab('conversation')}
|
<button onClick={() => setTab('conversation')}
|
||||||
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${
|
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${
|
||||||
tab === 'conversation' ? 'bg-white text-[#0070AD] shadow-sm' : 'text-gray-500'
|
tab === 'conversation' ? 'bg-white text-brand shadow-sm' : 'text-gray-500'
|
||||||
}`}>
|
}`}>
|
||||||
Gesprächsprotokoll
|
Gesprächsprotokoll
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -424,7 +410,7 @@ export default function MeetingView() {
|
||||||
{hasAssess && (
|
{hasAssess && (
|
||||||
<button onClick={() => setTab('assessment')}
|
<button onClick={() => setTab('assessment')}
|
||||||
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${
|
className={`flex-1 py-2 rounded-lg text-sm font-medium transition ${
|
||||||
tab === 'assessment' ? 'bg-white text-[#0070AD] shadow-sm' : 'text-gray-500'
|
tab === 'assessment' ? 'bg-white text-brand shadow-sm' : 'text-gray-500'
|
||||||
}`}>
|
}`}>
|
||||||
Gesamtbewertung
|
Gesamtbewertung
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -438,7 +424,7 @@ export default function MeetingView() {
|
||||||
|
|
||||||
{/* Aktiver Sprecher-Bereich */}
|
{/* Aktiver Sprecher-Bereich */}
|
||||||
<div className={`bg-white rounded-xl shadow-sm border-2 p-4 space-y-3 ${
|
<div className={`bg-white rounded-xl shadow-sm border-2 p-4 space-y-3 ${
|
||||||
activeInstId !== null ? (activeColor?.border ?? 'border-[#0070AD]') : 'border-gray-200'
|
activeInstId !== null ? (activeColor?.border ?? 'border-brand') : 'border-gray-200'
|
||||||
}`}>
|
}`}>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-2">Wer spricht?</div>
|
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-2">Wer spricht?</div>
|
||||||
|
|
@ -502,7 +488,7 @@ export default function MeetingView() {
|
||||||
{/* Notiz */}
|
{/* Notiz */}
|
||||||
<textarea
|
<textarea
|
||||||
ref={noteRef}
|
ref={noteRef}
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[#0070AD]"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-brand"
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder="Notizen… (+ gut, (!) negativ mit Abstrichen, > Kundeninput, [Slide 3])"
|
placeholder="Notizen… (+ gut, (!) negativ mit Abstrichen, > Kundeninput, [Slide 3])"
|
||||||
value={entryNote}
|
value={entryNote}
|
||||||
|
|
@ -576,7 +562,7 @@ export default function MeetingView() {
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full border border-gray-300 rounded-lg px-2 py-1.5 text-sm focus:outline-none focus:border-[#0070AD] bg-white"
|
className="w-full border border-gray-300 rounded-lg px-2 py-1.5 text-sm focus:outline-none focus:border-brand bg-white"
|
||||||
rows={3}
|
rows={3}
|
||||||
value={editNote}
|
value={editNote}
|
||||||
onChange={e => setEditNote(e.target.value)}
|
onChange={e => setEditNote(e.target.value)}
|
||||||
|
|
@ -592,7 +578,7 @@ export default function MeetingView() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button onClick={() => saveEdit(entry.id!)}
|
<button onClick={() => saveEdit(entry.id!)}
|
||||||
className="text-xs bg-[#0070AD] text-white px-3 py-1.5 rounded-lg">Speichern</button>
|
className="text-xs bg-brand text-white px-3 py-1.5 rounded-lg">Speichern</button>
|
||||||
<button onClick={() => setEditingId(null)}
|
<button onClick={() => setEditingId(null)}
|
||||||
className="text-xs border border-gray-200 text-gray-500 px-3 py-1.5 rounded-lg">Abbrechen</button>
|
className="text-xs border border-gray-200 text-gray-500 px-3 py-1.5 rounded-lg">Abbrechen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -707,7 +693,7 @@ export default function MeetingView() {
|
||||||
<span className={`text-sm font-medium ${headerRo ? 'text-white' : 'text-gray-700'}`}>{cat.name}</span>
|
<span className={`text-sm font-medium ${headerRo ? 'text-white' : 'text-gray-700'}`}>{cat.name}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{!headerRo && ratedCount > 0 && (
|
{!headerRo && ratedCount > 0 && (
|
||||||
<span className="text-xs text-[#0070AD] font-medium">{ratedCount}/{catItems.length}</span>
|
<span className="text-xs text-brand font-medium">{ratedCount}/{catItems.length}</span>
|
||||||
)}
|
)}
|
||||||
<span className={`text-xs ${headerRo ? 'text-white/70' : 'text-gray-300'}`}>{isOpen ? '▲' : '▼'}</span>
|
<span className={`text-xs ${headerRo ? 'text-white/70' : 'text-gray-300'}`}>{isOpen ? '▲' : '▼'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -737,7 +723,7 @@ export default function MeetingView() {
|
||||||
})}
|
})}
|
||||||
<div className="px-3 py-2 bg-gray-50">
|
<div className="px-3 py-2 bg-gray-50">
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-1.5 text-xs focus:outline-none focus:border-[#0070AD] bg-white"
|
className="w-full border border-gray-200 rounded-lg px-3 py-1.5 text-xs focus:outline-none focus:border-brand bg-white"
|
||||||
rows={1}
|
rows={1}
|
||||||
placeholder="Kommentar zur Kategorie"
|
placeholder="Kommentar zur Kategorie"
|
||||||
defaultValue={assessments.find(a => a.instituteeId === inst.id && a.criteriaId === cat.id)?.note ?? ''}
|
defaultValue={assessments.find(a => a.instituteeId === inst.id && a.criteriaId === cat.id)?.note ?? ''}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user