intern-dashboard / index.html
banao-tech's picture
Update index.html
c6ac5c3 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intern Pipeline</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg: #f5f5f7;
--white: #ffffff;
--border: #e5e5e7;
--border2: #d1d1d6;
--text: #1c1c1e;
--text2: #6c6c70;
--text3: #aeaeb2;
--accent: #5b4ef8;
--accent-light: #ede9fe;
--green: #34c759;
--green-light: #e8f8ec;
--amber: #ff9f0a;
--amber-light: #fff4e0;
--red: #ff3b30;
--red-light: #ffe5e3;
--blue: #007aff;
--radius: 10px;
--radius-lg: 14px;
--shadow: 0 1px 3px rgba(0,0,0,.08);
--shadow-md: 0 4px 12px rgba(0,0,0,.1);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
/* LOGIN */
#login-screen { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 100; }
.login-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 40px 36px; width: 360px; box-shadow: var(--shadow-md); }
.login-card h1 { font-size: 22px; font-weight: 700; margin-bottom: 6px; letter-spacing: -.3px; }
.login-card p { font-size: 13px; color: var(--text2); margin-bottom: 28px; }
.login-card input { width: 100%; padding: 11px 14px; font-size: 14px; border: 1px solid var(--border2); border-radius: var(--radius); outline: none; font-family: 'JetBrains Mono', monospace; margin-bottom: 12px; transition: border-color .15s; color: var(--text); }
.login-card input:focus { border-color: var(--accent); }
.login-error { font-size: 12px; color: var(--red); margin-top: 8px; display: none; text-align: center; }
/* APP */
#app { display: none; }
.layout { display: flex; min-height: 100vh; }
/* SIDEBAR */
.sidebar { width: 240px; min-width: 240px; background: var(--white); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding: 20px 0; position: sticky; top: 0; height: 100vh; }
.sidebar-brand { padding: 4px 20px 20px; border-bottom: 1px solid var(--border); margin-bottom: 12px; }
.sidebar-brand h2 { font-size: 17px; font-weight: 700; letter-spacing: -.3px; }
.sidebar-brand p { font-size: 12px; color: var(--text2); margin-top: 2px; font-family: 'JetBrains Mono', monospace; }
.nav-label { font-size: 11px; font-weight: 600; color: var(--text3); letter-spacing: .06em; text-transform: uppercase; padding: 0 20px; margin: 12px 0 4px; }
.nav-item { display: flex; align-items: center; gap: 10px; padding: 9px 20px; font-size: 14px; font-weight: 500; color: var(--text2); cursor: pointer; border: none; background: none; width: 100%; text-align: left; transition: color .12s, background .12s; font-family: 'Inter', sans-serif; }
.nav-item:hover { color: var(--text); background: var(--bg); }
.nav-item.active { color: var(--accent); background: var(--accent-light); font-weight: 600; }
.sidebar-footer { margin-top: auto; padding: 16px 20px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text3); font-family: 'JetBrains Mono', monospace; line-height: 1.6; word-break: break-all; }
/* MAIN */
.main { flex: 1; overflow-y: auto; }
.topbar { background: var(--white); border-bottom: 1px solid var(--border); padding: 14px 28px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 10; }
.topbar h1 { font-size: 17px; font-weight: 700; letter-spacing: -.3px; }
.topbar-right { display: flex; gap: 8px; align-items: center; }
/* BUTTONS */
.btn { padding: 8px 16px; font-size: 13px; font-weight: 600; border-radius: var(--radius); border: 1px solid var(--border2); background: var(--white); color: var(--text2); cursor: pointer; font-family: 'Inter', sans-serif; transition: all .12s; }
.btn:hover { color: var(--text); background: var(--bg); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: #4a3ed4; border-color: #4a3ed4; color: #fff; }
.btn-sm { padding: 5px 10px; font-size: 12px; }
.content { padding: 24px 28px; }
/* STATS */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
.stat-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px 18px; box-shadow: var(--shadow); }
.stat-card .label { font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 8px; }
.stat-card .value { font-size: 30px; font-weight: 700; letter-spacing: -.5px; }
.stat-card .sub { font-size: 11px; color: var(--text3); margin-top: 3px; font-family: 'JetBrains Mono', monospace; }
/* FILTERS */
.filters { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; }
.filters input, .filters select { padding: 8px 12px; font-size: 13px; border: 1px solid var(--border2); border-radius: var(--radius); outline: none; background: var(--white); color: var(--text); font-family: 'Inter', sans-serif; transition: border-color .12s; }
.filters input { width: 200px; }
.filters input:focus, .filters select:focus { border-color: var(--accent); }
/* TABLE */
.table-card { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow); }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th { background: var(--bg); padding: 10px 16px; text-align: left; font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid var(--border); }
tbody tr { border-bottom: 1px solid var(--border); transition: background .1s; }
tbody tr:last-child { border-bottom: none; }
tbody tr:hover { background: var(--bg); }
tbody td { padding: 12px 16px; color: var(--text2); vertical-align: middle; }
tbody td.name { color: var(--text); font-weight: 600; font-size: 14px; }
/* PILLS */
.pill { display: inline-flex; padding: 3px 9px; border-radius: 20px; font-size: 11px; font-weight: 600; font-family: 'JetBrains Mono', monospace; }
.pill-week0 { background: var(--accent-light); color: var(--accent); }
.pill-intern { background: var(--green-light); color: #1a7a33; }
.pill-probation_w1, .pill-probation_w2 { background: var(--amber-light); color: #7a4d00; }
.status-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; }
.status-badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--green); flex-shrink: 0; }
.status-badge.warned_1::before, .status-badge.warned_2::before { background: var(--amber); }
.status-badge.eliminated::before { background: var(--red); }
.miss-count { font-family: 'JetBrains Mono', monospace; font-size: 13px; }
.miss-0 { color: var(--text3); } .miss-1 { color: var(--amber); font-weight: 600; } .miss-2 { color: var(--red); font-weight: 600; }
.actions { display: flex; gap: 6px; }
.empty-state { text-align: center; padding: 60px 20px; color: var(--text3); font-size: 13px; }
.empty-state .icon { font-size: 32px; margin-bottom: 12px; }
/* MODAL */
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,.4); backdrop-filter: blur(4px); display: none; align-items: center; justify-content: center; z-index: 50; }
.overlay.open { display: flex; }
.modal { background: var(--white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 28px; width: 500px; max-width: 95vw; max-height: 90vh; overflow-y: auto; position: relative; box-shadow: var(--shadow-md); }
.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
.modal-header h2 { font-size: 17px; font-weight: 700; letter-spacing: -.3px; }
.modal-close { background: none; border: none; font-size: 20px; color: var(--text2); cursor: pointer; padding: 2px 6px; border-radius: 6px; line-height: 1; }
.modal-close:hover { background: var(--bg); }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.form-field { display: flex; flex-direction: column; gap: 5px; }
.form-field.full { grid-column: 1 / -1; }
.form-field label { font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; }
.form-field input, .form-field select { padding: 9px 12px; font-size: 13px; border: 1px solid var(--border2); border-radius: var(--radius); outline: none; font-family: 'Inter', sans-serif; color: var(--text); background: var(--white); transition: border-color .12s; }
.form-field input:focus, .form-field select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(91,78,248,.1); }
.form-hint { font-size: 11px; color: var(--text3); font-family: 'JetBrains Mono', monospace; }
.modal-footer { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border); }
/* TOAST */
.toast { position: fixed; bottom: 24px; right: 24px; background: var(--text); color: var(--white); border-radius: var(--radius); padding: 12px 18px; font-size: 13px; font-weight: 500; z-index: 200; transform: translateY(20px); opacity: 0; transition: all .25s; box-shadow: var(--shadow-md); }
.toast.show { transform: translateY(0); opacity: 1; }
.toast.success { background: #1a7a33; }
.toast.error { background: var(--red); }
/* PANEL */
.panel { display: none; }
.panel.active { display: block; }
/* ── DOCS PAGE ── */
.docs-wrap { max-width: 760px; }
.docs-hero {
background: var(--white);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 28px 32px;
margin-bottom: 20px;
box-shadow: var(--shadow);
}
.docs-hero h2 { font-size: 22px; font-weight: 700; letter-spacing: -.4px; margin-bottom: 8px; }
.docs-hero p { font-size: 14px; color: var(--text2); line-height: 1.7; }
.docs-section {
background: var(--white);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
margin-bottom: 14px;
box-shadow: var(--shadow);
overflow: hidden;
}
.docs-section-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
user-select: none;
}
.docs-section-header:hover { background: var(--bg); }
.docs-section-icon {
width: 36px; height: 36px;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.docs-section-header h3 { font-size: 15px; font-weight: 600; letter-spacing: -.2px; flex: 1; }
.docs-section-header .toggle { font-size: 12px; color: var(--text3); font-family: 'JetBrains Mono', monospace; }
.docs-section-body { padding: 20px 20px 24px; display: none; }
.docs-section-body.open { display: block; }
.docs-section-body p { font-size: 14px; color: var(--text2); line-height: 1.75; margin-bottom: 14px; }
.docs-section-body p:last-child { margin-bottom: 0; }
.docs-section-body strong { color: var(--text); font-weight: 600; }
.step-list { display: flex; flex-direction: column; gap: 0; }
.step { display: flex; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.step:last-child { border-bottom: none; }
.step-num { width: 26px; height: 26px; border-radius: 50%; background: var(--accent-light); color: var(--accent); font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px; }
.step-body { flex: 1; }
.step-title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 3px; }
.step-desc { font-size: 13px; color: var(--text2); line-height: 1.6; }
.code-block {
background: #1c1c1e;
color: #e8e8ec;
border-radius: var(--radius);
padding: 14px 16px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
line-height: 1.8;
margin: 12px 0;
overflow-x: auto;
}
.code-block .comment { color: #636366; }
.code-block .key { color: #bf85fa; }
.code-block .val { color: #63d05a; }
.rule-list { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.rule { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; color: var(--text2); line-height: 1.6; }
.rule-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; margin-top: 6px; }
.rule strong { color: var(--text); }
.stage-flow { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 14px 0; }
.stage-box { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; text-align: center; }
.stage-box .stage-name { font-size: 12px; font-weight: 700; margin-bottom: 4px; }
.stage-box .stage-dur { font-size: 11px; color: var(--text3); font-family: 'JetBrains Mono', monospace; }
.stage-box.s1 { background: var(--accent-light); border-color: #c4b8fd; }
.stage-box.s1 .stage-name { color: var(--accent); }
.stage-box.s2 { background: var(--amber-light); border-color: #fcd47a; }
.stage-box.s2 .stage-name { color: #7a4d00; }
.stage-box.s3 { background: var(--amber-light); border-color: #fcd47a; }
.stage-box.s3 .stage-name { color: #7a4d00; }
.stage-box.s4 { background: var(--green-light); border-color: #86efac; }
.stage-box.s4 .stage-name { color: #1a7a33; }
.warn-table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 12px 0; }
.warn-table th { text-align: left; padding: 8px 12px; font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: .05em; border-bottom: 2px solid var(--border); }
.warn-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); color: var(--text2); vertical-align: top; line-height: 1.5; }
.warn-table tr:last-child td { border-bottom: none; }
.warn-table td:first-child { font-weight: 600; color: var(--text); white-space: nowrap; }
.signal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0; }
.signal-card { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; }
.signal-card .signal-name { font-size: 12px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.signal-card .signal-desc { font-size: 12px; color: var(--text2); line-height: 1.5; }
.highlight-box { background: var(--accent-light); border: 1px solid #c4b8fd; border-radius: var(--radius); padding: 14px 16px; margin: 12px 0; font-size: 13px; color: #3730a3; line-height: 1.7; }
.highlight-box strong { color: #2e27a0; }
</style>
</head>
<body>
<!-- LOGIN -->
<div id="login-screen">
<div class="login-card">
<h1>Intern Pipeline</h1>
<p>Enter your password to continue</p>
<input type="password" id="pw" placeholder="Password" onkeydown="if(event.key==='Enter')login()">
<button class="btn btn-primary" style="width:100%;padding:11px" onclick="login()">Sign in</button>
<div class="login-error" id="login-err">Incorrect password</div>
</div>
</div>
<!-- APP -->
<div id="app">
<div class="layout">
<!-- SIDEBAR -->
<div class="sidebar">
<div class="sidebar-brand">
<h2>Intern Pipeline</h2>
<p>banao-tech</p>
</div>
<div class="nav-label">Management</div>
<button class="nav-item active" onclick="showPanel('interns')" id="nav-interns">👥 All Interns</button>
<div class="nav-label">Actions</div>
<button class="nav-item" onclick="openAdd()">+ Add Intern</button>
<div class="sidebar-footer">
API<br>banao-tech-interns-manager.hf.space
</div>
</div>
<!-- MAIN -->
<div class="main">
<div class="topbar">
<h1 id="panel-title">All Interns</h1>
<div class="topbar-right">
<button class="btn" id="refresh-btn" onclick="load()" style="display:block">↻ Refresh</button>
<button class="btn btn-primary" id="add-btn" onclick="openAdd()" style="display:block">+ Add Intern</button>
</div>
</div>
<div class="content">
<!-- INTERNS PANEL -->
<div class="panel active" id="panel-interns">
<div class="stats">
<div class="stat-card"><div class="label">Total Active</div><div class="value" id="s-total"></div><div class="sub">all departments</div></div>
<div class="stat-card"><div class="label">Week 0</div><div class="value" id="s-w0"></div><div class="sub">onboarding</div></div>
<div class="stat-card"><div class="label">Probation</div><div class="value" id="s-prob"></div><div class="sub">W1 + W2</div></div>
<div class="stat-card"><div class="label">Intern</div><div class="value" id="s-intern"></div><div class="sub">regular</div></div>
</div>
<div class="filters">
<input type="text" id="q" placeholder="Search name or ID…" oninput="filter()">
<select id="f-dept" onchange="load()">
<option value="">All departments</option>
<option value="ai">AI</option>
<option value="hr">HR</option>
<option value="sales">Sales</option>
<option value="product">Product</option>
<option value="general">General</option>
</select>
<select id="f-stage" onchange="filter()">
<option value="">All stages</option>
<option value="week0">Week 0</option>
<option value="intern">Intern</option>
<option value="probation_w1">Probation W1</option>
<option value="probation_w2">Probation W2</option>
</select>
</div>
<div class="table-card">
<table>
<thead>
<tr>
<th>Name</th><th>Candidate ID</th><th>Department</th><th>Stage</th>
<th>Status</th><th>Start Date</th><th>Misses</th><th>Actions</th>
</tr>
</thead>
<tbody id="tbody">
<tr><td colspan="8" style="text-align:center;padding:48px;color:var(--text3)">Loading…</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-title">Probation Week 1 — Observe</div>
<div class="step-desc">FT observes without intervening. Bot sends the FT a daily focus brief each morning and signal questions each evening. Signals logged: proactive start, thinking before building, debugging posture, comms rhythm, depth under questioning.</div>
</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<div class="step-title">Probation Week 2 — Hold</div>
<div class="step-desc">FT holds the intern to commitments made on Day 6. Specific deliverables with specific dates. Bot tracks Wednesday and Friday deadlines. Miss a deadline without flagging it first → formal warning.</div>
</div>
</div>
<div class="step">
<div class="step-num">4</div>
<div class="step-body">
<div class="step-title">Intern — Ongoing</div>
<div class="step-desc">Regular interns still submit EOD reports. Same miss logic applies — miss one gets a warning, miss two and HR is notified. No elimination recommendation — HR follows up directly.</div>
</div>
</div>
</div>
</div>
</div>
<!-- SECTION 2 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s2')">
<div class="docs-section-icon" style="background:#fff4e0">📋</div>
<h3>EOD Report Format</h3>
<span class="toggle" id="s2-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s2-body">
<p>Every intern posts their EOD report in their department Slack channel. The message <strong>must start with EOD: or REPORT:</strong> — otherwise the bot ignores it.</p>
<div class="code-block">
<span class="key">EOD:</span>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<span class="val">EOD REPORT — Your Name — DD Mon YYYY</span>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
<span class="key">TASKS COMPLETED</span>
<span class="comment">- Task 1 with PR/ticket ID if any</span>
<span class="comment">- Task 2</span>
<span class="key">TASKS IN PROGRESS</span>
<span class="comment">- What you started but didn't finish</span>
<span class="comment">- Expected completion: tomorrow / specific time</span>
<span class="key">BLOCKERS</span>
<span class="comment">- What is stopping you / None</span>
<span class="key">WHAT I LEARNED TODAY</span>
<span class="comment">- Concept or tool you understood today</span>
<span class="key">AI USAGE TODAY</span>
<span class="comment">Tool used: Claude / ChatGPT / Copilot</span>
<span class="comment">Chat link: https://...</span>
<span class="key">PLAN FOR TOMORROW</span>
<span class="comment">- Task 1 with expected output</span>
<span class="key">CONFIDENCE LEVEL TODAY</span>
<span class="comment">> [x] On track [ ] Crushing it [ ] Need help [ ] Stuck</span>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</div>
<p>The bot uses Claude to extract all fields from the report. If any required field is missing, it replies with exactly which fields are missing and the correct format.</p>
<div class="highlight-box">
<strong>The AI chat link is required.</strong> Interns must share the actual conversation link so their thinking can be verified. If they used AI but have no link, that is flagged as a quality issue.
</div>
</div>
</div>
<!-- SECTION 3 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s3')">
<div class="docs-section-icon" style="background:#ffe5e3">⚠️</div>
<h3>Miss and Warning Logic</h3>
<span class="toggle" id="s3-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s3-body">
<p>The bot checks for missed reports 30 minutes after each report window closes. Before firing a warning, it checks the intern's Slack status — if they have set a leave-related status, the miss is skipped.</p>
<p><strong>Leave keywords detected:</strong> leave, sick, away, ooo, out of office, on leave, medical, emergency, holiday, unavailable, off today.</p>
<table class="warn-table">
<thead><tr><th>Stage</th><th>Miss 1</th><th>Miss 2</th></tr></thead>
<tbody>
<tr>
<td>Week 0</td>
<td>Warning posted in channel. References commitment. States consequence.</td>
<td>Elimination recommendation sent to HR. HR confirms before outcome is posted.</td>
</tr>
<tr>
<td>Probation W1 / W2</td>
<td>Warning posted. Logged as a signal flag in the evaluation.</td>
<td>Formal warning posted. FT and HR notified via DM. Reflected in end-of-week eval.</td>
</tr>
<tr>
<td>Intern</td>
<td>Warning posted in channel. Softer tone.</td>
<td>HR notified via DM. No elimination — HR follows up directly.</td>
</tr>
</tbody>
</table>
<p><strong>Sundays are skipped.</strong> No warnings fire on Sunday. Day numbers also skip Sundays — so a week is 6 working days, not 7.</p>
</div>
</div>
<!-- SECTION 4 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s4')">
<div class="docs-section-icon" style="background:#e8f8ec">📊</div>
<h3>Evaluations — What the Bot Reads</h3>
<span class="toggle" id="s4-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s4-body">
<p>At the end of each stage, the bot generates a full evaluation using Claude. It reads all reports, all FT signal answers, and prior eval outputs — and produces a recommendation.</p>
<p><strong>Week 0 signals (4):</strong></p>
<div class="signal-grid">
<div class="signal-card"><div class="signal-name">Report cadence</div><div class="signal-desc">Did they submit on time without being chased?</div></div>
<div class="signal-card"><div class="signal-name">Technical engagement</div><div class="signal-desc">Are reports showing real AI tool usage or surface-level activity?</div></div>
<div class="signal-card"><div class="signal-name">Honesty</div><div class="signal-desc">Do they admit blockers? Any signs of fabricated progress?</div></div>
<div class="signal-card"><div class="signal-name">Friction response</div><div class="signal-desc">When tools broke or APIs failed — what did they do?</div></div>
</div>
<p style="margin-top:14px"><strong>Probation W1 signals (5):</strong></p>
<div class="signal-grid">
<div class="signal-card"><div class="signal-name">Proactive start</div><div class="signal-desc">Clarifying questions Day 1, architecture Day 2 without being asked.</div></div>
<div class="signal-card"><div class="signal-name">Thinking before building</div><div class="signal-desc">Documented plan before code. Trade-offs named. Failure cases considered.</div></div>
<div class="signal-card"><div class="signal-name">Debugging posture</div><div class="signal-desc">Blockers surfaced with specific detail and prior attempts.</div></div>
<div class="signal-card"><div class="signal-name">Comms rhythm</div><div class="signal-desc">Daily reports on cadence showing real reflection, not just activity.</div></div>
<div class="signal-card" style="grid-column:1/-1"><div class="signal-name">Depth under questioning</div><div class="signal-desc">Day 4 check — explained decisions, held reasoning under pushback, knew where system fails.</div></div>
</div>
<p style="margin-top:14px"><strong>Probation W2 signals (6):</strong> delivery reliability, proactive comms, ownership of hard parts, depth under questioning, end-of-probation document quality, and two-week trajectory.</p>
<div class="highlight-box">
<strong>HR confirms before anything is communicated.</strong> Every evaluation recommendation — offer, no offer, extend, end relationship — goes to HR first. The bot never announces an outcome without HR approval.
</div>
</div>
</div>
<!-- SECTION 5 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s5')">
<div class="docs-section-icon" style="background:#ede9fe"></div>
<h3>Automated Schedule — What Fires When</h3>
<span class="toggle" id="s5-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s5-body">
<table class="warn-table">
<thead><tr><th>Time (IST)</th><th>What happens</th></tr></thead>
<tbody>
<tr><td>9:00 AM</td><td>FT receives daily focus brief for each probation intern they manage</td></tr>
<tr><td>12:30 PM</td><td>Missed report check — noon window closes</td></tr>
<tr><td>5:30 PM</td><td>Missed report check — 5pm window closes</td></tr>
<tr><td>6:00 PM</td><td>FT receives signal questions for today's evaluation</td></tr>
<tr><td>6:30 PM (Wed)</td><td>Wednesday deliverable deadline check — W2 interns</td></tr>
<tr><td>12:00 PM (Fri)</td><td>Friday deliverable deadline check — W2 interns</td></tr>
<tr><td>8:00 PM</td><td>End-of-stage evaluation generated on Day 5 and Day 10</td></tr>
<tr><td>10:30 PM</td><td>Missed report check — 10pm window closes</td></tr>
</tbody>
</table>
<p>All jobs skip Sunday automatically. Day numbers also skip Sunday in the count.</p>
</div>
</div>
<!-- SECTION 6 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s6')">
<div class="docs-section-icon" style="background:#fff4e0">👤</div>
<h3>FT Responsibilities in This System</h3>
<span class="toggle" id="s6-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s6-body">
<p>The bot handles tracking and logistics. The FT handles judgment and accountability. Here is what the FT does and does not do.</p>
<div class="rule-list">
<div class="rule"><div class="rule-dot"></div><div><strong>Morning brief (9 AM):</strong> The bot sends the FT a focus note for the day — what to observe, what not to do. The FT reads it and acts accordingly.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>Evening signal questions (6 PM):</strong> The bot asks 2–3 specific questions about today's intern behaviour. FT replies in the DM. Bot logs the answers as signals.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>Day 4 depth check:</strong> FT runs a 15-minute check-in with the intern. Bot pre-generates the questions based on the architecture the intern submitted on Day 2.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>Day 6 commitments:</strong> FT sets specific deliverables with specific dates. Bot logs them. Bot enforces them on Wednesday EOD and Friday noon.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>Do NOT coach report quality mid-week.</strong> The bot notes quality. The FT does not comment on it. That removes the signal the report quality provides.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>Do NOT chase missed reports.</strong> The bot handles warnings. The FT chasing defeats the purpose of the discipline filter.</div></div>
</div>
</div>
</div>
<!-- SECTION 7 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s7')">
<div class="docs-section-icon" style="background:#e8f8ec">🏢</div>
<h3>Adding a New Department</h3>
<span class="toggle" id="s7-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s7-body">
<p>The system supports multiple departments. Each department gets its own Slack channel. All interns across all departments are tracked in the same Google Sheet.</p>
<div class="step-list">
<div class="step">
<div class="step-num">1</div>
<div class="step-body">
<div class="step-title">Create a Slack channel</div>
<div class="step-desc">e.g. #sales-eod or #hr-eod. Invite the intern-management-agent bot to it.</div>
</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-title">Get the channel ID</div>
<div class="step-desc">Right-click the channel in Slack → View channel details → Copy ID at the bottom (starts with C).</div>
</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<div class="step-title">Add interns via this dashboard</div>
<div class="step-desc">Use the Add Intern button. Set department to the new department name. Set channel_id to the channel ID from step 2. The bot will start listening to that channel immediately.</div>
</div>
</div>
</div>
</div>
</div>
<!-- SECTION 8 -->
<div class="docs-section">
<div class="docs-section-header" onclick="toggleSection('s8')">
<div class="docs-section-icon" style="background:#ffe5e3">🗄️</div>
<h3>What Is Stored in Google Sheets</h3>
<span class="toggle" id="s8-toggle">▼ expand</span>
</div>
<div class="docs-section-body" id="s8-body">
<p>All data lives in a Google Sheet with 7 tabs. Nothing is deleted — only marked as eliminated or cleared. This gives you a full audit trail.</p>
<div class="rule-list">
<div class="rule"><div class="rule-dot"></div><div><strong>candidates</strong> — every intern's full record, stage, status, miss count.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>reports_log</strong> — every EOD report submitted, with timestamp, quality score, and flags.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>signals_log</strong> — every FT signal answer logged per day per intern.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>warnings_log</strong> — every warning issued, when, what triggered it, exact message sent.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>commitments</strong> — Week 2 deliverables, due dates, whether delivered on time.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>evaluations</strong> — end-of-stage evaluations with signals, recommendations, HR confirmation status.</div></div>
<div class="rule"><div class="rule-dot"></div><div><strong>decisions_log</strong> — every final decision made — offer, elimination, extension — with who confirmed it and when.</div></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ADD / EDIT MODAL -->
<div class="overlay" id="modal-overlay" onclick="if(event.target===this)closeModal()">
<div class="modal">
<div class="modal-header">
<h2 id="modal-title">Add Intern</h2>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<input type="hidden" id="m-id">
<div class="form-grid">
<div class="form-field full"><label>Full Name *</label><input type="text" id="m-name" placeholder="Roshesh Shah"></div>
<div class="form-field"><label>Slack User ID *</label><input type="text" id="m-slack" placeholder="U0AFNM2H88G"><span class="form-hint">Profile → More → Copy member ID</span></div>
<div class="form-field"><label>Department</label><input type="text" id="m-dept" placeholder="e.g. AI, HR, Sales"><span class="form-hint">Type your department name</span></div>
<div class="form-field"><label>Stage</label><select id="m-stage"><option value="week0">Week 0</option><option value="intern">Regular Intern</option><option value="probation_w1">Probation W1</option><option value="probation_w2">Probation W2</option></select></div>
<div class="form-field"><label>Cohort / Batch</label><input type="text" id="m-cohort" placeholder="BATCH_001"></div>
<div class="form-field"><label>Start Date *</label><input type="date" id="m-start"></div>
<div class="form-field"><label>FT / Mentor Slack ID *</label><input type="text" id="m-ft" placeholder="U083B055EA0"></div>
<div class="form-field"><label>HR Slack ID *</label><input type="text" id="m-hr" placeholder="U083B055EA0"></div>
<div class="form-field full"><label>Channel ID</label><input type="text" id="m-channel" placeholder="C08L1CFEF40"><span class="form-hint">Right-click channel → View details → Copy ID</span></div>
</div>
<div class="modal-footer">
<button class="btn" onclick="closeModal()">Cancel</button>
<button class="btn btn-primary" id="m-submit" onclick="submit()">Add Intern</button>
</div>
</div>
</div>
<!-- CONFIRM DELETE -->
<div class="overlay" id="confirm-overlay" onclick="if(event.target===this)closeConfirm()">
<div class="modal" style="width:380px">
<div class="modal-header">
<h2>Remove Intern</h2>
<button class="modal-close" onclick="closeConfirm()">×</button>
</div>
<p style="font-size:14px;color:var(--text2);line-height:1.6">Remove <strong id="confirm-name" style="color:var(--text)"></strong> from the pipeline?</p>
<p style="font-size:12px;color:var(--text3);margin-top:8px">Their record is kept for audit. Status is set to eliminated.</p>
<div class="modal-footer">
<button class="btn" onclick="closeConfirm()">Cancel</button>
<button class="btn" style="background:var(--red);border-color:var(--red);color:#fff" onclick="confirmDelete()">Remove</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
const API = 'https://banao-tech-interns-manager.hf.space';
const PASSWORD = 'banao2026';
let allInterns = [], deleteId = null;
function login() {
if (document.getElementById('pw').value === PASSWORD) {
document.getElementById('login-screen').style.display = 'none';
document.getElementById('app').style.display = 'block';
load();
} else {
document.getElementById('login-err').style.display = 'block';
document.getElementById('pw').value = '';
}
}
document.getElementById('pw').addEventListener('keydown', e => { if(e.key==='Enter') login(); });
function toast(msg, type='') {
const el = document.getElementById('toast');
el.textContent = msg;
el.className = `toast ${type} show`;
setTimeout(() => el.className = 'toast', 3000);
}
async function api(path, opts={}) {
const r = await fetch(API + path, { headers: {'Content-Type':'application/json'}, ...opts });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
async function load() {
const dept = document.getElementById('f-dept').value;
const url = dept ? `/interns?department=${dept}` : '/interns';
try {
const data = await api(url);
allInterns = data.interns || [];
updateStats(); filter();
} catch(e) {
document.getElementById('tbody').innerHTML =
`<tr><td colspan="8"><div class="empty-state"><div class="icon">⚠️</div>Could not load. Check API or CORS.</div></td></tr>`;
}
}
function updateStats() {
document.getElementById('s-total').textContent = allInterns.length;
document.getElementById('s-w0').textContent = allInterns.filter(i=>i.stage==='week0').length;
document.getElementById('s-prob').textContent = allInterns.filter(i=>i.stage?.includes('probation')).length;
document.getElementById('s-intern').textContent = allInterns.filter(i=>i.stage==='intern').length;
}
function filter() {
const q = document.getElementById('q').value.toLowerCase();
const stage = document.getElementById('f-stage').value;
render(allInterns.filter(i => {
const mq = !q || (i.name||'').toLowerCase().includes(q) || (i.candidate_id||'').toLowerCase().includes(q);
return mq && (!stage || i.stage === stage);
}));
}
function stageLabel(s) { return {week0:'Week 0',intern:'Intern',probation_w1:'Prob W1',probation_w2:'Prob W2'}[s]||s||'—'; }
function missClass(n) { n=parseInt(n)||0; return n===0?'miss-0':n===1?'miss-1':'miss-2'; }
function statusCls(s) { if(!s||s==='active') return ''; return s.replace(/_/g,'-'); }
function render(interns) {
const tbody = document.getElementById('tbody');
if (!interns.length) { tbody.innerHTML=`<tr><td colspan="8"><div class="empty-state"><div class="icon">○</div>No interns found</div></td></tr>`; return; }
tbody.innerHTML = interns.map(i=>`
<tr>
<td class="name">${i.name||'—'}</td>
<td style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text3)">${i.candidate_id||'—'}</td>
<td style="text-transform:uppercase;font-size:12px">${i.department||'general'}</td>
<td><span class="pill pill-${i.stage||'intern'}">${stageLabel(i.stage)}</span></td>
<td><span class="status-badge ${statusCls(i.status)}">${i.status||'active'}</span></td>
<td style="font-family:'JetBrains Mono',monospace;font-size:12px">${i.week0_start_date||'—'}</td>
<td><span class="miss-count ${missClass(i.miss_count)}">${i.miss_count??0}</span></td>
<td><div class="actions">
<button class="btn btn-sm" onclick='openEdit(${JSON.stringify(i)})'>Edit</button>
<button class="btn btn-sm" style="color:var(--red);border-color:var(--red)" onclick="openConfirm('${i.candidate_id}','${(i.name||'').replace(/'/g,"\\'")}')">Remove</button>
</div></td>
</tr>`).join('');
}
function showPanel(id) {
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
document.getElementById('panel-'+id).classList.add('active');
document.getElementById('nav-'+id).classList.add('active');
const titles = {interns:'All Interns'};
document.getElementById('panel-title').textContent = titles[id]||id;
}
function toggleSection(id) {
const body = document.getElementById(id+'-body');
const toggle = document.getElementById(id+'-toggle');
const isOpen = body.classList.contains('open');
body.classList.toggle('open', !isOpen);
toggle.textContent = isOpen ? '▼ expand' : '▲ collapse';
}
function openAdd() {
showPanel('interns');
document.getElementById('modal-title').textContent='Add Intern';
document.getElementById('m-submit').textContent='Add Intern';
document.getElementById('m-id').value='';
['m-name','m-slack','m-ft','m-hr','m-channel'].forEach(id=>document.getElementById(id).value='');
document.getElementById('m-dept').value='';
document.getElementById('m-stage').value='week0';
document.getElementById('m-cohort').value='BATCH_001';
document.getElementById('m-start').value=new Date().toISOString().split('T')[0];
document.getElementById('modal-overlay').classList.add('open');
setTimeout(()=>document.getElementById('m-name').focus(),100);
}
function openEdit(i) {
document.getElementById('modal-title').textContent='Edit Intern';
document.getElementById('m-submit').textContent='Save Changes';
document.getElementById('m-id').value=i.candidate_id;
document.getElementById('m-name').value=i.name||'';
document.getElementById('m-slack').value=i.slack_user_id||'';
document.getElementById('m-dept').value=i.department||'';
document.getElementById('m-stage').value=i.stage||'intern';
document.getElementById('m-cohort').value=i.cohort_id||'';
document.getElementById('m-start').value=i.week0_start_date||'';
document.getElementById('m-ft').value=i.ft_slack_id||'';
document.getElementById('m-hr').value=i.hr_slack_id||'';
document.getElementById('m-channel').value=i.channel_id||'';
document.getElementById('modal-overlay').classList.add('open');
}
function closeModal() { document.getElementById('modal-overlay').classList.remove('open'); }
async function submit() {
const cid = document.getElementById('m-id').value;
const isEdit = !!cid;
const name = document.getElementById('m-name').value.trim();
const slack = document.getElementById('m-slack').value.trim();
const start = document.getElementById('m-start').value;
const ft = document.getElementById('m-ft').value.trim();
const hr = document.getElementById('m-hr').value.trim();
if (!name || (!isEdit && (!slack||!start||!ft||!hr))) { toast('Fill in all required fields','error'); return; }
const btn = document.getElementById('m-submit');
btn.disabled=true; btn.textContent=isEdit?'Saving…':'Adding…';
try {
const payload = { name, stage:document.getElementById('m-stage').value, department:document.getElementById('m-dept').value, cohort_id:document.getElementById('m-cohort').value.trim()||'BATCH_001', week0_start_date:start, ft_slack_id:ft, hr_slack_id:hr, channel_id:document.getElementById('m-channel').value.trim() };
if (!isEdit) { payload.slack_user_id=slack; }
const data = await api(isEdit?`/interns/${cid}`:`/admin/add_candidate`, { method:isEdit?'PATCH':'POST', body:JSON.stringify(payload) });
if (data.ok) {
closeModal();
toast(isEdit?`${name} updated ✓`:`${name} added ✓`,'success');
await load(); // reload from API so next edit shows fresh data
}
else toast(data.error||'Something went wrong','error');
} catch(e) { toast('Request failed','error'); }
btn.disabled=false; btn.textContent=isEdit?'Save Changes':'Add Intern';
}
function openConfirm(id,name) { deleteId=id; document.getElementById('confirm-name').textContent=name; document.getElementById('confirm-overlay').classList.add('open'); }
function closeConfirm() { deleteId=null; document.getElementById('confirm-overlay').classList.remove('open'); }
async function confirmDelete() {
if (!deleteId) return;
try {
const data = await api(`/interns/${deleteId}`,{method:'DELETE'});
if (data.ok) { closeConfirm(); toast('Intern removed ✓','success'); load(); }
else toast(data.error||'Could not remove','error');
} catch(e) { toast('Request failed','error'); }
}
</script>
</body>
</html>