import { useState, useRef } from "react";
// ─── CONSTANTS ────────────────────────────────────────────────────────────────
const USERS = [
{ id: 0, name: "Manager", role: "manager", avatar: "MG", pin: "0000", color: "#111827" },
{ id: 1, name: "Asha Mensah", role: "employee", avatar: "AM", pin: "1111", color: "#6366f1", trustBattery: 87, handle: "@brand_asha" },
{ id: 2, name: "Jordan Blake", role: "employee", avatar: "JB", pin: "2222", color: "#0891b2", trustBattery: 62, handle: "@brand_jordan" },
{ id: 3, name: "Priya Nair", role: "employee", avatar: "PN", pin: "3333", color: "#059669", trustBattery: 95, handle: "@brand_priya" },
];
const TASKS = [
{ label: "Content Creation", icon: "✏️", points: 5 },
{ label: "Content Scheduling", icon: "📅", points: 2 },
{ label: "Community Engagement", icon: "💬", points: 3 },
{ label: "Reporting & Analytics", icon: "📊", points: 2 },
{ label: "Inbox Management", icon: "📥", points: 1 },
{ label: "Ad Campaign Management", icon: "📣", points: 4 },
{ label: "Strategy", icon: "🧠", points: 5 },
{ label: "Research", icon: "🔍", points: 4 },
{ label: "Brand Development", icon: "🎨", points: 5 },
{ label: "Competitor Analysis", icon: "🔭", points: 4 },
];
const SAMPLE_HISTORY = {
1: [
{ date: "Mon", points: 8, status: "verified", trustDelta: 5 },
{ date: "Tue", points: 10, status: "verified", trustDelta: 6 },
{ date: "Wed", points: 6, status: "flagged", trustDelta: -3 },
{ date: "Thu", points: 9, status: "verified", trustDelta: 5 },
{ date: "Fri", points: 0, status: "pending", trustDelta: 0 },
],
2: [
{ date: "Mon", points: 6, status: "verified", trustDelta: 3 },
{ date: "Tue", points: 4, status: "flagged", trustDelta: -5 },
{ date: "Wed", points: 7, status: "verified", trustDelta: 4 },
{ date: "Thu", points: 5, status: "flagged", trustDelta: -4 },
{ date: "Fri", points: 0, status: "pending", trustDelta: 0 },
],
3: [
{ date: "Mon", points: 10, status: "verified", trustDelta: 6 },
{ date: "Tue", points: 12, status: "verified", trustDelta: 7 },
{ date: "Wed", points: 9, status: "verified", trustDelta: 5 },
{ date: "Thu", points: 11, status: "verified", trustDelta: 6 },
{ date: "Fri", points: 0, status: "pending", trustDelta: 0 },
],
};
// ─── UTILS ────────────────────────────────────────────────────────────────────
const batteryColor = v => v >= 80 ? "#059669" : v >= 55 ? "#d97706" : "#dc2626";
const batteryLabel = v => v >= 80 ? "Elite" : v >= 55 ? "Solid" : "At risk";
const getHour = () => new Date().getHours();
const isPlanOpen = () => getHour() < 16; // 4 PM cutoff (test mode)
const isFriday = () => new Date().getDay() === 5;
async function callGemini(apiKey, prompt) {
const res = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.7, maxOutputTokens: 500 },
}),
}
);
if (!res.ok) throw new Error("API error " + res.status);
const d = await res.json();
return d.candidates?.[0]?.content?.parts?.[0]?.text || "";
}
// ─── SHARED UI ────────────────────────────────────────────────────────────────
function Avatar({ initials, size = 40, color = "#6366f1" }) {
return (
{initials}
);
}
function Pill({ children, color = "#6366f1", bg }) {
return {children} ;
}
function Card({ children, style, onClick }) {
return (
{ if (onClick) e.currentTarget.style.boxShadow = "0 4px 16px rgba(0,0,0,0.1)"; }}
onMouseLeave={e => { if (onClick) e.currentTarget.style.boxShadow = "0 1px 4px rgba(0,0,0,0.06)"; }}
>{children}
);
}
function Spinner() {
return (
);
}
function TrustBar({ value }) {
const color = batteryColor(value);
return (
Trust Battery
{batteryLabel(value)}
{value}%
);
}
// ─── SETUP ────────────────────────────────────────────────────────────────────
function SetupScreen({ onDone }) {
const [key, setKey] = useState("");
const [testing, setTesting] = useState(false);
const [result, setResult] = useState(null);
async function test() {
setTesting(true); setResult(null);
try { await callGemini(key, "Reply with just: OK"); setResult("ok"); }
catch { setResult("fail"); }
setTesting(false);
}
return (
⚡
TYHID
Track Your Hours, I Dare You
Gemini API Key
setKey(e.target.value)} placeholder="AIza..."
style={{ width: "100%", padding: "11px 14px", borderRadius: 10, border: "1.5px solid #e5e7eb", fontSize: 14, outline: "none", marginBottom: 10, boxSizing: "border-box" }} />
{result && (
{result === "ok" ? "✓ Connected! Ready to go." : "✗ Invalid key — check and try again."}
)}
{testing ? "Testing..." : "Test"}
onDone(key)} disabled={!key} style={{ flex: 2, padding: 11, borderRadius: 10, border: "none", background: key ? "#6366f1" : "#e5e7eb", color: key ? "#fff" : "#9ca3af", fontSize: 13, fontWeight: 700, cursor: key ? "pointer" : "not-allowed" }}>
Enter Portal →
🧪 Test mode: plan submission open until 4 PM
);
}
// ─── LOGIN ────────────────────────────────────────────────────────────────────
function LoginScreen({ onLogin }) {
const [selected, setSelected] = useState(null);
const [pin, setPin] = useState("");
const [error, setError] = useState("");
const [shaking, setShaking] = useState(false);
function handleDigit(d) {
if (pin.length >= 4) return;
const next = pin + d;
setPin(next); setError("");
if (next.length === 4) {
setTimeout(() => {
if (selected?.pin === next) { onLogin(selected); }
else { setShaking(true); setError("Wrong PIN"); setTimeout(() => { setPin(""); setShaking(false); }, 600); }
}, 100);
}
}
return (
{!selected ? (
{USERS.map(u => (
setSelected(u)} style={{ textAlign: "center", padding: "1.25rem 1rem" }}>
{u.name}
{u.role}
))}
) : (
{selected.name}
Enter your PIN
{[0,1,2,3].map(i => (
))}
{error && {error}
}
{[1,2,3,4,5,6,7,8,9,"",0,"⌫"].map((d, i) => (
d === "⌫" ? setPin(p => p.slice(0,-1)) : d !== "" && handleDigit(String(d))} disabled={d === ""}
style={{ padding: "13px 0", borderRadius: 9, border: "1.5px solid " + (d === "" ? "transparent" : "#f0f0f0"), background: d === "" ? "transparent" : "#f9fafb", fontSize: d === "⌫" ? 16 : 17, fontWeight: 600, cursor: d === "" ? "default" : "pointer" }}>{d}
))}
{ setSelected(null); setPin(""); setError(""); }} style={{ marginTop: "1rem", background: "none", border: "none", color: "#9ca3af", fontSize: 13, cursor: "pointer" }}>← Back
Demo PINs: 0000 / 1111 / 2222 / 3333
)}
);
}
// ─── CLOCK-IN (PLAN) ──────────────────────────────────────────────────────────
function ClockIn({ user, apiKey, onPlanLocked }) {
const open = isPlanOpen();
const [goals, setGoals] = useState([{ id: 1, taskText: "", hours: "", notes: "", points: 0, aiResult: null, aiLoading: false }]);
const [submitting, setSubmitting] = useState(false);
const totalPoints = goals.reduce((s, g) => s + (Number(g.points) || 0), 0);
function addGoal() {
setGoals(p => [...p, { id: Date.now(), taskText: "", hours: "", notes: "", points: 0, aiResult: null, aiLoading: false }]);
}
function update(id, patch) { setGoals(p => p.map(g => g.id === id ? { ...g, ...patch } : g)); }
function removeGoal(id) { setGoals(p => p.filter(g => g.id !== id)); }
function pickTemplate(goalId, t) { update(goalId, { taskText: t.label, points: t.points, hours: "", aiResult: null }); }
async function getAIFeedback(goal) {
if (!goal.taskText) return;
update(goal.id, { aiLoading: true, aiResult: null });
try {
const prompt = `You are an AI Strategic Coach for a marketing team. Evaluate this task:
Task: ${goal.taskText}
Planned hours: ${goal.hours || "not given"}
Details: ${goal.notes || "none"}
Reply ONLY in this format:
SCORE: [1-10]
POINTS: [1, 2, 3, 5, 8, or 13]
ROI: [HIGH, MEDIUM, or LOW]
FEEDBACK: [2 direct, specific coaching sentences]`;
const r = await callGemini(apiKey, prompt);
const score = parseInt(r.match(/SCORE:\s*(\d+)/)?.[1] || "7");
const pts = parseInt(r.match(/POINTS:\s*(\d+)/)?.[1] || "3");
const roi = r.match(/ROI:\s*(HIGH|MEDIUM|LOW)/)?.[1] || "MEDIUM";
const feedback = r.match(/FEEDBACK:\s*(.+)/s)?.[1]?.trim() || r;
update(goal.id, { aiLoading: false, aiResult: { score, pts, roi, feedback }, points: pts });
} catch (e) {
update(goal.id, { aiLoading: false, aiResult: { error: e.message } });
}
}
async function handleSubmit() {
setSubmitting(true);
await new Promise(r => setTimeout(r, 900));
onPlanLocked(goals);
}
return (
🕘 Clock In — Today's Plan
Min 8 story points · Locks at 4:00 PM
{open ? "OPEN" : "LOCKED"}
{!open && (
🔒 Submission closed at 4:00 PM. Contact your manager for an override.
)}
{goals.map((goal, idx) => (
Goal {idx + 1}
{goal.points > 0 &&
{goal.points} pts }
{goals.length > 1 &&
removeGoal(goal.id)} style={{ background: "none", border: "none", color: "#d1d5db", cursor: "pointer", fontSize: 20, lineHeight: 1 }}>× }
What are you working on?
update(goal.id, { taskText: e.target.value, points: 0, aiResult: null })}
placeholder="Type your task, or pick a suggestion below..." disabled={!open}
style={{ width: "100%", padding: "10px 13px", borderRadius: 9, border: "1.5px solid #e5e7eb", fontSize: 14, outline: "none", boxSizing: "border-box" }}
onFocus={e => e.target.style.borderColor = user.color} onBlur={e => e.target.style.borderColor = "#e5e7eb"} />
{TASKS.map(t => {
const active = goal.taskText === t.label;
return (
open && pickTemplate(goal.id, t)} style={{ padding: "5px 11px", borderRadius: 99, border: "1.5px solid " + (active ? user.color : "#e5e7eb"), background: active ? user.color + "18" : "#f9fafb", color: active ? user.color : "#6b7280", fontSize: 12, fontWeight: active ? 700 : 500, cursor: open ? "pointer" : "not-allowed" }}>
{t.icon} {t.label}
);
})}
Story points
{goal.points || "via template or AI"}
))}
{open && (
+ Add goal
)}
= 8 ? "#059669" : "#d97706" }}>{totalPoints}
/8 pts
= 8 ? user.color : "#e5e7eb", color: open && totalPoints >= 8 ? "#fff" : "#9ca3af", fontSize: 13, fontWeight: 700, cursor: open && totalPoints >= 8 ? "pointer" : "not-allowed" }}>
{submitting ? "Locking..." : "🔒 Lock Plan & Clock In"}
);
}
// ─── CLOCK-OUT ────────────────────────────────────────────────────────────────
function EvidenceUploader({ goalId, evidence, onUpdate, color }) {
const fileRef = useRef();
const [urlInput, setUrlInput] = useState("");
const [showUrlBox, setShowUrlBox] = useState(false);
function handleFile(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = ev => {
onUpdate(goalId, { type: "file", name: file.name, size: file.size, dataUrl: ev.target.result });
};
reader.readAsDataURL(file);
}
function addUrl() {
if (!urlInput.trim()) return;
onUpdate(goalId, { type: "url", url: urlInput.trim() });
setUrlInput(""); setShowUrlBox(false);
}
function removeEvidence() { onUpdate(goalId, null); }
if (evidence) {
const isImg = evidence.type === "file" && evidence.dataUrl?.startsWith("data:image");
return (
{evidence.type === "url" ? "🔗" : isImg ? "🖼️" : "📎"}
Evidence attached
{evidence.type === "url" ? evidence.url : evidence.name}
×
{isImg &&
}
);
}
return (
{showUrlBox ? (
setUrlInput(e.target.value)} placeholder="https://..." onKeyDown={e => e.key === "Enter" && addUrl()}
style={{ flex: 1, padding: "8px 11px", borderRadius: 8, border: "1.5px solid #e5e7eb", fontSize: 13, outline: "none", boxSizing: "border-box" }}
onFocus={e => e.target.style.borderColor = color} onBlur={e => e.target.style.borderColor = "#e5e7eb"} autoFocus />
Add
setShowUrlBox(false)} style={{ padding: "8px 10px", borderRadius: 8, border: "1px solid #e5e7eb", background: "#f9fafb", fontSize: 12, cursor: "pointer" }}>✕
) : (
fileRef.current.click()} style={{ padding: "6px 12px", borderRadius: 8, border: "1.5px dashed #d1d5db", background: "#fafafa", fontSize: 12, color: "#6b7280", cursor: "pointer" }}>
📎 Upload file
setShowUrlBox(true)} style={{ padding: "6px 12px", borderRadius: 8, border: "1.5px dashed #d1d5db", background: "#fafafa", fontSize: 12, color: "#6b7280", cursor: "pointer" }}>
🔗 Add URL
)}
);
}
function ClockOut({ user, lockedGoals, apiKey, onClockOutDone }) {
const [completions, setCompletions] = useState(() =>
lockedGoals.map(g => ({ id: g.id, done: false, partial: false, notes: "", evidence: null }))
);
const [submitting, setSubmitting] = useState(false);
const [aiSummary, setAiSummary] = useState(null);
const [aiLoading, setAiLoading] = useState(false);
const [clockedOut, setClockedOut] = useState(false);
function toggle(id, field) {
setCompletions(p => p.map(c => {
if (c.id !== id) return c;
if (field === "done") return { ...c, done: !c.done, partial: false };
if (field === "partial") return { ...c, partial: !c.partial, done: false };
return c;
}));
}
function updateNotes(id, notes) { setCompletions(p => p.map(c => c.id === id ? { ...c, notes } : c)); }
function updateEvidence(id, evidence) { setCompletions(p => p.map(c => c.id === id ? { ...c, evidence } : c)); }
const doneCount = completions.filter(c => c.done).length;
const partialCount = completions.filter(c => c.partial).length;
const totalGoals = lockedGoals.length;
async function generateSummary() {
setAiLoading(true);
const lines = lockedGoals.map((g, i) => {
const c = completions[i];
const status = c.done ? "COMPLETED" : c.partial ? "PARTIAL" : "NOT DONE";
return `- ${g.taskText} (${g.points} pts): ${status}${c.notes ? " — " + c.notes : ""}`;
}).join("\n");
const prompt = `Marketing employee ${user.name} is clocking out. Here's their day:
${lines}
Write a 2-sentence end-of-day coaching note: one sentence acknowledging what went well, one forward-looking suggestion. Be direct, warm, and specific.`;
try {
const r = await callGemini(apiKey, prompt);
setAiSummary(r.trim());
} catch (e) { setAiSummary("Could not generate summary: " + e.message); }
setAiLoading(false);
}
async function handleClockOut() {
setSubmitting(true);
await new Promise(r => setTimeout(r, 800));
setClockedOut(true);
setSubmitting(false);
onClockOutDone(completions);
}
if (clockedOut) {
return (
🏁
Clocked out!
Your day is logged. Manager reconciliation runs at 5 PM.
{[{ l: "Completed", v: doneCount, c: "#059669" }, { l: "Partial", v: partialCount, c: "#d97706" }, { l: "Missed", v: totalGoals - doneCount - partialCount, c: "#dc2626" }].map(s => (
{s.v}
{s.l}
))}
);
}
return (
🕔 Clock Out — End of Day
Tick what you did · upload evidence where needed
{lockedGoals.map((goal, idx) => {
const c = completions.find(x => x.id === goal.id);
const status = c.done ? "done" : c.partial ? "partial" : "none";
const borderColor = status === "done" ? "#059669" : status === "partial" ? "#d97706" : "#f0f0f0";
const bgColor = status === "done" ? "#f0fdf4" : status === "partial" ? "#fffbeb" : "#fff";
return (
{/* Goal header */}
{/* Done checkbox */}
toggle(goal.id, "done")} title="Mark as completed" style={{ width: 26, height: 26, borderRadius: 7, border: "2px solid " + (c.done ? "#059669" : "#d1d5db"), background: c.done ? "#059669" : "#fff", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", flexShrink: 0, transition: "all 0.15s" }}>
{c.done && ✓ }
{/* Partial checkbox */}
toggle(goal.id, "partial")} title="Mark as partially done" style={{ width: 26, height: 26, borderRadius: 7, border: "2px solid " + (c.partial ? "#d97706" : "#d1d5db"), background: c.partial ? "#d97706" : "#fff", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", flexShrink: 0, transition: "all 0.15s" }}>
{c.partial && ~ }
{TASKS.find(t => t.label === goal.taskText)?.icon || "📌"} {goal.taskText}
{goal.points} pts
{status === "done" &&
✓ Done }
{status === "partial" &&
~ Partial }
{goal.hours &&
Planned: {goal.hours}h
}
✓ = complete · ~ = partial
{/* Notes */}
{/* Evidence uploader */}
);
})}
{/* AI summary */}
🤖 Generate AI End-of-Day Summary
{aiLoading &&
}
{aiSummary && (
🤖 {aiSummary}
)}
{/* Submit */}
{doneCount} done · {partialCount} partial
{submitting ? "Submitting..." : "🏁 Clock Out"}
);
}
// ─── MY STATS ─────────────────────────────────────────────────────────────────
function MyStats({ user }) {
const history = SAMPLE_HISTORY[user.id] || [];
const done = history.filter(d => d.status !== "pending");
const verifiedDays = done.filter(d => d.status === "verified").length;
const totalPts = done.reduce((s, d) => s + d.points, 0);
const netTrust = done.reduce((s, d) => s + d.trustDelta, 0);
const maxPts = Math.max(...done.map(d => d.points), 1);
return (
My Performance
{user.name}
{user.handle}
{user.trustBattery}
Trust Battery
{[{ l: "Days verified", v: verifiedDays, c: "#059669" }, { l: "Story points", v: totalPts, c: user.color }, { l: "Trust change", v: (netTrust > 0 ? "+" : "") + netTrust, c: netTrust >= 0 ? "#059669" : "#dc2626" }].map(s => (
{s.v}
{s.l}
))}
This Week
{history.map(d => (
{d.status !== "pending"
?
:
}
{d.date}
{d.status === "verified" ? "✅" : d.status === "flagged" ? "⚠️" : "⏳"}
{d.trustDelta !== 0 &&
0 ? "#059669" : "#dc2626" }}>{d.trustDelta > 0 ? "+" : ""}{d.trustDelta}
}
))}
= 70 ? "#f0fdf4" : "#fef2f2", border: "1px solid " + (user.trustBattery >= 70 ? "#bbf7d0" : "#fecaca") }}>
{user.trustBattery >= 70 ? "💚" : "🔴"}
= 70 ? "#059669" : "#dc2626" }}>Performance pay: {user.trustBattery >= 70 ? "cleared ✓" : "at risk ✗"}
{user.trustBattery >= 70 ? "Keep your Trust Battery above 70% to maintain performance pay." : "Trust Battery below threshold. Review flagged days with your manager."}
);
}
// ─── SCORECARD ────────────────────────────────────────────────────────────────
function EmployeeScorecard({ user, scorecardApproved }) {
const history = SAMPLE_HISTORY[user.id] || [];
if (!scorecardApproved) {
return (
🔐
Scorecard Locked
{!isFriday() ? "Your weekly scorecard is released every Friday after your manager reviews and approves it." : "Your manager hasn't approved this week's scorecard yet."}
{!isFriday() ? "📅 Available on Friday" : "⏳ Awaiting manager approval"}
);
}
return (
Weekly Scorecard
✓ Approved
{history.filter(d => d.status !== "pending").map(d => (
{d.status === "verified" ? "✅" : "⚠️"}
{d.date}
{d.points} story points · {d.status}
= 0 ? "#059669" : "#dc2626" }}>{d.trustDelta > 0 ? "+" : ""}{d.trustDelta}
))}
);
}
// ─── MANAGER DASHBOARD ────────────────────────────────────────────────────────
function ManagerDashboard({ apiKey, scorecardApproved, onApproveScorecard }) {
const [tab, setTab] = useState("overview");
const [generatingFor, setGeneratingFor] = useState(null);
const [reports, setReports] = useState({});
const [reconciling, setReconciling] = useState(false);
const [reconProgress, setReconProgress] = useState(0);
const [reconDone, setReconDone] = useState(false);
const employees = USERS.filter(u => u.role === "employee");
async function generateReport(emp) {
setGeneratingFor(emp.id);
const h = SAMPLE_HISTORY[emp.id] || [];
const prompt = `Write a brief professional weekly performance review (3 short paragraphs, no bullets) for ${emp.name}.
Stats: Trust Battery ${emp.trustBattery}%, verified days: ${h.filter(d=>d.status==="verified").length}/4, flagged: ${h.filter(d=>d.status==="flagged").length}, total points: ${h.reduce((s,d)=>s+d.points,0)}.
Be specific, constructive, and direct.`;
try { const r = await callGemini(apiKey, prompt); setReports(p => ({ ...p, [emp.id]: r })); }
catch (e) { setReports(p => ({ ...p, [emp.id]: "Error: " + e.message })); }
setGeneratingFor(null);
}
async function runRecon() {
setReconciling(true); setReconProgress(0); setReconDone(false);
for (let i = 0; i <= 100; i += 5) { await new Promise(r => setTimeout(r, 70)); setReconProgress(i); }
setReconciling(false); setReconDone(true);
}
const tabs = [{ id: "overview", label: "Overview" }, { id: "reconciliation", label: "Reconciliation" }, { id: "scorecards", label: "Scorecards" }, { id: "trust", label: "Trust" }];
return (
Manager Dashboard
Full team visibility
{tabs.map(t => (
setTab(t.id)} style={{ flex: 1, padding: "8px 4px", borderRadius: 7, border: "none", background: tab === t.id ? "#fff" : "transparent", color: tab === t.id ? "#111" : "#6b7280", fontSize: 12, fontWeight: tab === t.id ? 700 : 500, cursor: "pointer", boxShadow: tab === t.id ? "0 1px 3px rgba(0,0,0,0.1)" : "none" }}>{t.label}
))}
{tab === "overview" && (
{[{ l: "Team avg trust", v: Math.round(employees.reduce((s,e)=>s+e.trustBattery,0)/employees.length)+"%", c: "#6366f1" }, { l: "Verified today", v: "4/5", c: "#059669" }, { l: "Flags pending", v: "2", c: "#dc2626" }].map(s => (
{s.v}
{s.l}
))}
{employees.map(emp => {
const h = SAMPLE_HISTORY[emp.id] || [];
const hasFlag = h.some(d => d.status === "flagged");
return (
{emp.name}
{hasFlag &&
⚠ flagged }
{emp.trustBattery}%
trust
);
})}
)}
{tab === "reconciliation" && (
Social API verification + AI quality audits
{reconciling ? "Running " + reconProgress + "%..." : reconDone ? "▶ Re-run" : "▶ Run Job"}
{reconciling && (
{reconProgress < 35 ? "Connecting to Meta Graph API..." : reconProgress < 65 ? "Verifying post timestamps..." : reconProgress < 88 ? "Counting interactions..." : "Compiling results..."}
{reconProgress}%
)}
{employees.map(emp => {
const h = SAMPLE_HISTORY[emp.id] || [];
const flagged = h.filter(d => d.status === "flagged").length;
const verified = h.filter(d => d.status === "verified").length;
return (
{emp.name}
✓ {verified}
{flagged > 0 &&
⚠ {flagged} flagged }
{h.filter(d => d.status !== "pending").map(d => (
))}
);
})}
)}
{tab === "scorecards" && (
{!scorecardApproved
?
✓ Approve All Scorecards & Notify Team
:
✅ Scorecards approved — employees can now view their results.
}
{employees.map(emp => (
{emp.name}
Trust Battery: {emp.trustBattery}%
= 70 ? "#059669" : "#dc2626"}>{emp.trustBattery >= 70 ? "Pay cleared ✓" : "Pay withheld ✗"}
generateReport(emp)} disabled={generatingFor === emp.id} style={{ padding: "8px 14px", borderRadius: 8, border: "1.5px solid #6366f1", background: "#eef2ff", color: "#6366f1", fontSize: 12, fontWeight: 700, cursor: "pointer" }}>
{generatingFor === emp.id ? "Generating..." : "🤖 AI Performance Report"}
{generatingFor === emp.id &&
}
{reports[emp.id] && {reports[emp.id]}
}
))}
)}
{tab === "trust" && (
{employees.map(emp => {
const h = SAMPLE_HISTORY[emp.id] || [];
return (
{h.map(d => (
0 ? "#f0fdf4" : d.trustDelta < 0 ? "#fef2f2" : "#f9fafb", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 700, color: d.trustDelta > 0 ? "#059669" : d.trustDelta < 0 ? "#dc2626" : "#9ca3af" }}>
{d.trustDelta > 0 ? "+" : ""}{d.trustDelta || "–"}
{d.date}
))}
);
})}
)}
);
}
// ─── EMPLOYEE SHELL ───────────────────────────────────────────────────────────
function EmployeeShell({ user, apiKey, scorecardApproved, onLogout }) {
// "clockin" | "clockout" | "stats" | "scorecard"
const [tab, setTab] = useState("clockin");
const [lockedGoals, setLockedGoals] = useState(null); // null = not clocked in yet
const [clockedOut, setClockedOut] = useState(false);
function handlePlanLocked(goals) { setLockedGoals(goals); setTab("clockout"); }
function handleClockOutDone() { setClockedOut(true); }
const tabs = [
{ id: "clockin", label: "🕘 Clock In", disabled: !!lockedGoals },
{ id: "clockout", label: "🕔 Clock Out", disabled: !lockedGoals || clockedOut },
{ id: "stats", label: "📈 Stats" },
{ id: "scorecard", label: "🏆 Scorecard" },
];
return (
⚡ TYHID
{lockedGoals && !clockedOut &&
Clocked in ✓ }
{clockedOut &&
Clocked out ✓ }
{user.name.split(" ")[0]}
Out
{tabs.map(t => (
!t.disabled && setTab(t.id)} style={{ flex: 1, padding: "11px 4px", border: "none", background: "transparent", borderBottom: "2.5px solid " + (tab === t.id ? user.color : "transparent"), color: tab === t.id ? user.color : t.disabled ? "#d1d5db" : "#9ca3af", fontSize: 12, fontWeight: tab === t.id ? 700 : 500, cursor: t.disabled ? "not-allowed" : "pointer" }}>{t.label}
))}
{tab === "clockin" && }
{tab === "clockout" && lockedGoals && }
{tab === "stats" && }
{tab === "scorecard" && }
);
}
// ─── MANAGER SHELL ────────────────────────────────────────────────────────────
function ManagerShell({ apiKey, onLogout, scorecardApproved, onApproveScorecard }) {
return (
);
}
// ─── ROOT ─────────────────────────────────────────────────────────────────────
export default function App() {
const [apiKey, setApiKey] = useState("");
const [screen, setScreen] = useState("setup");
const [user, setUser] = useState(null);
const [scorecardApproved, setScorecardApproved] = useState(false);
return (
<>
{screen === "setup" && { setApiKey(k); setScreen("login"); }} />}
{screen === "login" && { setUser(u); setScreen("app"); }} />}
{screen === "app" && user?.role === "manager" && { setUser(null); setScreen("login"); }} scorecardApproved={scorecardApproved} onApproveScorecard={() => setScorecardApproved(true)} />}
{screen === "app" && user?.role === "employee" && { setUser(null); setScreen("login"); }} />}
>
);
}