import React, { useState, useEffect, useCallback } from 'react';
import { useAuth } from '../../contexts/AuthContext';
import { AppLayout } from '../../components/layout/AppLayout';
import {
getCoachingScorecards,
createCoachingScorecard,
updateCoachingScorecard,
deleteCoachingScorecard,
} from '../../api/coachingScorecards';
import type { CoachingScorecard, CoachingScorecardCreate } from '../../types';
import {
Plus, X, Search, Filter, Star, Award, Edit, Trash2, Calendar,
TrendingUp, MessageSquare, Briefcase, Phone, Target,
} from 'lucide-react';
const scoreColor = (score: number | null) => {
if (score === null) return 'text-surface-400';
if (score >= 80) return 'text-emerald-600';
if (score >= 60) return 'text-amber-600';
return 'text-red-600';
};
const scoreBadge = (score: number | null) => {
if (score === null) return '';
if (score >= 80) return 'bg-emerald-100 text-emerald-700';
if (score >= 60) return 'bg-amber-100 text-amber-700';
return 'bg-red-100 text-red-700';
};
export function CoachingScorecardsPage() {
const { user } = useAuth();
const companyId = user?.tenantId as string;
const [scorecards, setScorecards] = useState<CoachingScorecard[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [showCreate, setShowCreate] = useState(false);
const [editingScorecard, setEditingScorecard] = useState<CoachingScorecard | null>(null);
const [search, setSearch] = useState('');
const [form, setForm] = useState<CoachingScorecardCreate>({
assignment_id: '',
rep_id: '',
evaluation_date: null,
overall_score: null,
communication_score: null,
technical_score: null,
closing_score: null,
follow_up_score: null,
strengths: '',
improvement_areas: '',
notes: '',
});
const loadScorecards = useCallback(async () => {
if (!companyId) return;
try {
setLoading(true);
const data = await getCoachingScorecards(companyId);
setScorecards(data);
setError(null);
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to load coaching scorecards');
} finally {
setLoading(false);
}
}, [companyId]);
useEffect(() => {
loadScorecards();
}, [loadScorecards]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!companyId) return;
try {
if (editingScorecard) {
await updateCoachingScorecard(companyId, editingScorecard.id, form);
} else {
await createCoachingScorecard(companyId, form);
}
setShowCreate(false);
setEditingScorecard(null);
setForm({ assignment_id: '', rep_id: '', evaluation_date: null, overall_score: null, communication_score: null, technical_score: null, closing_score: null, follow_up_score: null, strengths: '', improvement_areas: '', notes: '' });
await loadScorecards();
} catch (e) {
setError(e instanceof Error ? e.message : 'Failed to save scorecard');
}
};
const handleDelete = async (scorecard: CoachingScorecard) => {
if (!companyId) return;
if (!confirm('Delete this scorecard?')) return;
try { await deleteCoachingScorecard(companyId, scorecard.id); await loadScorecards(); }
catch (e) { setError(e instanceof Error ? e.message : 'Failed to delete scorecard'); }
};
const startEdit = (scorecard: CoachingScorecard) => {
setEditingScorecard(scorecard);
setForm({
assignment_id: scorecard.assignment_id,
rep_id: scorecard.rep_id,
evaluation_date: scorecard.evaluation_date,
overall_score: scorecard.overall_score,
communication_score: scorecard.communication_score,
technical_score: scorecard.technical_score,
closing_score: scorecard.closing_score,
follow_up_score: scorecard.follow_up_score,
strengths: scorecard.strengths,
improvement_areas: scorecard.improvement_areas,
notes: scorecard.notes,
});
setShowCreate(true);
};
const filtered = scorecards
.filter(s => search === '' || s.rep_name.toLowerCase().includes(search.toLowerCase()));
const avgScore = scorecards.filter(s => s.overall_score !== null).length > 0
? (scorecards.filter(s => s.overall_score !== null).reduce((sum, s) => sum + (s.overall_score ?? 0), 0) / scorecards.filter(s => s.overall_score !== null).length)
: 0;
return (
<AppLayout title="Coaching Scorecards">
{/* Summary */}
<div className="mb-6 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="rounded-xl border border-surface-200 bg-white p-5">
<p className="text-sm font-medium text-surface-500">Total Scorecards</p>
<p className="mt-2 text-3xl font-bold text-surface-900">{scorecards.length}</p>
</div>
<div className="rounded-xl border border-surface-200 bg-white p-5">
<p className="text-sm font-medium text-surface-500">Average Score</p>
<p className="mt-2 text-3xl font-bold text-blue-600">{avgScore.toFixed(1)}%</p>
</div>
<div className="rounded-xl border border-surface-200 bg-white p-5">
<p className="text-sm font-medium text-surface-500">Evaluations This Month</p>
<p className="mt-2 text-3xl font-bold text-emerald-600">
{scorecards.filter(s => s.evaluation_date && s.evaluation_date.substring(0, 7) === new Date().toISOString().substring(0, 7)).length}
</p>
</div>
</div>
{/* Toolbar */}
<div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="relative flex-1 sm:flex-initial">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-surface-400" />
<input type="text" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search by rep name..."
className="w-full rounded-lg border border-surface-300 py-2 pl-9 pr-4 text-sm outline-none focus:border-brand-500 sm:w-64" />
</div>
<button onClick={() => { setShowCreate(true); setEditingScorecard(null); setForm({ assignment_id: '', rep_id: '', evaluation_date: null, overall_score: null, communication_score: null, technical_score: null, closing_score: null, follow_up_score: null, strengths: '', improvement_areas: '', notes: '' }); }}
className="inline-flex items-center gap-2 rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-700 min-h-[44px]">
<Plus className="h-4 w-4" /> New Scorecard
</button>
</div>
{error && (
<div className="mb-4 rounded-xl border border-red-200 bg-red-50 p-4">
<p className="text-sm font-medium text-red-800">{error}</p>
<button onClick={() => setError(null)} className="mt-1 text-sm text-red-600 underline">Dismiss</button>
</div>
)}
{/* Modal */}
{showCreate && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div className="w-full max-w-lg max-h-[90vh] overflow-y-auto rounded-xl bg-white p-6 shadow-xl">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-surface-900">{editingScorecard ? 'Edit Scorecard' : 'New Coaching Scorecard'}</h2>
<button onClick={() => setShowCreate(false)} className="rounded-lg p-1.5 text-surface-400 hover:bg-surface-100">
<X className="h-5 w-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-surface-700">Assignment ID</label>
<input type="text" required value={form.assignment_id} onChange={(e) => setForm({ ...form, assignment_id: e.target.value })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
<div>
<label className="block text-sm font-medium text-surface-700">Rep ID</label>
<input type="text" required value={form.rep_id} onChange={(e) => setForm({ ...form, rep_id: e.target.value })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
</div>
<div>
<label className="block text-sm font-medium text-surface-700">Evaluation Date</label>
<input type="date" value={form.evaluation_date ?? ''} onChange={(e) => setForm({ ...form, evaluation_date: e.target.value || null })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
{/* Scores */}
<div className="space-y-3 rounded-lg border border-surface-200 p-4">
<h3 className="text-sm font-medium text-surface-700">Scores (0-100)</h3>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-surface-500">Overall</label>
<input type="number" min="0" max="100" value={form.overall_score ?? ''} onChange={(e) => setForm({ ...form, overall_score: e.target.value ? Number(e.target.value) : null })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
<div>
<label className="block text-xs font-medium text-surface-500">Communication</label>
<input type="number" min="0" max="100" value={form.communication_score ?? ''} onChange={(e) => setForm({ ...form, communication_score: e.target.value ? Number(e.target.value) : null })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
<div>
<label className="block text-xs font-medium text-surface-500">Technical</label>
<input type="number" min="0" max="100" value={form.technical_score ?? ''} onChange={(e) => setForm({ ...form, technical_score: e.target.value ? Number(e.target.value) : null })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
<div>
<label className="block text-xs font-medium text-surface-500">Closing</label>
<input type="number" min="0" max="100" value={form.closing_score ?? ''} onChange={(e) => setForm({ ...form, closing_score: e.target.value ? Number(e.target.value) : null })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
<div>
<label className="block text-xs font-medium text-surface-500">Follow-up</label>
<input type="number" min="0" max="100" value={form.follow_up_score ?? ''} onChange={(e) => setForm({ ...form, follow_up_score: e.target.value ? Number(e.target.value) : null })}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500" />
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-surface-700">Strengths</label>
<textarea value={form.strengths} onChange={(e) => setForm({ ...form, strengths: e.target.value })} rows={2}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500 resize-none" />
</div>
<div>
<label className="block text-sm font-medium text-surface-700">Areas for Improvement</label>
<textarea value={form.improvement_areas} onChange={(e) => setForm({ ...form, improvement_areas: e.target.value })} rows={2}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500 resize-none" />
</div>
<div>
<label className="block text-sm font-medium text-surface-700">Notes</label>
<textarea value={form.notes} onChange={(e) => setForm({ ...form, notes: e.target.value })} rows={2}
className="mt-1 w-full rounded-lg border border-surface-300 py-2 px-3 text-sm outline-none focus:border-brand-500 resize-none" />
</div>
<div className="flex justify-end gap-2 pt-2">
<button type="button" onClick={() => setShowCreate(false)}
className="rounded-lg border border-surface-300 px-4 py-2 text-sm font-medium text-surface-700 hover:bg-surface-50 min-h-[44px]">Cancel</button>
<button type="submit" className="rounded-lg bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700 min-h-[44px]">
{editingScorecard ? 'Update' : 'Create'}
</button>
</div>
</form>
</div>
</div>
)}
{/* Scorecards List */}
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-brand-600 border-t-transparent" />
<span className="ml-3 text-sm text-surface-500">Loading scorecards…</span>
</div>
) : filtered.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-xl border border-surface-200 bg-white py-16">
<Award className="h-12 w-12 text-surface-300" />
<p className="mt-4 text-sm font-medium text-surface-700">
{search ? 'No scorecards match your search' : 'No coaching scorecards yet'}
</p>
</div>
) : (
<div className="space-y-3">
{filtered.map((scorecard) => (
<div key={scorecard.id} className="rounded-xl border border-surface-200 bg-white p-5">
<div className="flex items-start justify-between">
<div>
<div className="flex items-center gap-2">
<h3 className="font-semibold text-surface-900">{scorecard.rep_name}</h3>
{scorecard.evaluation_date && (
<span className="flex items-center gap-1 text-xs text-surface-400">
<Calendar className="h-3 w-3" /> {scorecard.evaluation_date.substring(0, 10)}
</span>
)}
</div>
{/* Scores Grid */}
<div className="mt-3 grid grid-cols-5 gap-2">
{[
{ label: 'Overall', value: scorecard.overall_score, icon: Star },
{ label: 'Comms', value: scorecard.communication_score, icon: MessageSquare },
{ label: 'Technical', value: scorecard.technical_score, icon: Briefcase },
{ label: 'Closing', value: scorecard.closing_score, icon: Target },
{ label: 'Follow-up', value: scorecard.follow_up_score, icon: Phone },
].map(({ label, value, icon: Icon }) => (
<div key={label} className="rounded-lg border border-surface-200 p-2 text-center">
<Icon className="mx-auto h-3 w-3 text-surface-400" />
<div className={`mt-1 text-sm font-bold ${scoreColor(value)}`}>
{value !== null ? `${value}` : '—'}
</div>
<div className="text-[10px] text-surface-400">{label}</div>
</div>
))}
</div>
{/* Details */}
{(scorecard.strengths || scorecard.improvement_areas || scorecard.notes) && (
<div className="mt-3 space-y-1 text-xs text-surface-500">
{scorecard.strengths && <p className="text-emerald-600">✓ {scorecard.strengths}</p>}
{scorecard.improvement_areas && <p className="text-amber-600">→ {scorecard.improvement_areas}</p>}
{scorecard.notes && <p className="text-surface-500">📝 {scorecard.notes}</p>}
</div>
)}
</div>
<div className="flex items-center gap-1">
{scorecard.overall_score !== null && (
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-bold ${scoreBadge(scorecard.overall_score)}`}>
{scorecard.overall_score}%
</span>
)}
<button onClick={() => startEdit(scorecard)} className="rounded-lg p-1.5 text-surface-400 hover:bg-surface-100 hover:text-surface-600" title="Edit">
<Edit className="h-4 w-4" />
</button>
<button onClick={() => handleDelete(scorecard)} className="rounded-lg p-1.5 text-surface-400 hover:bg-red-50 hover:text-red-600" title="Delete">
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
</div>
))}
</div>
)}
</AppLayout>
);
}