Project

General

Profile

FluentEdgeTracker #1251 » communication-gym-sessions (1).html

Naveena G, 08/25/2026 08:09 AM

 
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@500;600&display=swap');

:root{
--navy:#14213D; --orange:#F5A623; --bg:#F4F6FB; --card:#FFFFFF;
--text:#1B2338; --textSoft:#6B7280; --border:#E7E9F0; --rowAlt:#FAFBFD;
}
*{box-sizing:border-box;}
#cg-root{
width:100%; min-height:100vh; display:flex;
background:var(--bg); font-family:'Sora','Segoe UI',sans-serif; color:var(--text);
}
.icon{display:inline-block; vertical-align:middle;}

/* Sidebar */
.cg-sidebar{
display:none; flex-direction:column; padding:20px 0;
width:220px; background:var(--navy); flex-shrink:0;
}
@media (min-width:1024px){ .cg-sidebar{ display:flex; } }
.cg-logo{ padding:0 20px; margin-bottom:24px; color:#fff; font-weight:600; font-size:13px; letter-spacing:.04em; }
.cg-logo span{ color:var(--orange); }
.cg-navitem{
display:flex; align-items:center; gap:12px; padding:10px 16px; font-size:14px;
border-radius:10px; margin:0 8px; color:#C9D0E3; font-weight:500; cursor:default;
}
.cg-navitem.active{ background:var(--orange); color:#241703; font-weight:600; }
.cg-navsection{ margin-top:16px; margin-bottom:4px; padding:0 20px; font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:#6E7A9A; }
.cg-navsub{ margin-left:32px; margin-top:4px; margin-bottom:8px; font-size:12px; font-weight:500; color:#9FABC9; }

/* Main */
.cg-main{ flex:1; padding:24px; }
@media (min-width:1024px){ .cg-main{ padding:32px; } }

.cg-header{ display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:space-between; gap:16px; margin-bottom:20px; }
.cg-title{ font-size:20px; font-weight:600; color:var(--text); margin:0; }
.cg-subtitle{ font-size:14px; color:var(--textSoft); margin:2px 0 0; }

.cg-toggle{ display:flex; align-items:center; gap:4px; padding:4px; border-radius:999px; background:#E9ECF4; }
.cg-toggle button{
display:flex; align-items:center; gap:6px; padding:6px 14px; border-radius:999px;
font-size:14px; font-weight:500; background:transparent; color:var(--textSoft); border:none; cursor:pointer;
font-family:inherit;
}
.cg-toggle button.active{ background:var(--navy); color:#fff; }

.cg-filters{ display:flex; flex-wrap:wrap; align-items:center; gap:12px; margin-bottom:20px; }
.cg-select-btn, .cg-select-wrap select, .cg-search, .cg-daterange{
background:#fff; border:1px solid var(--border); border-radius:10px; font-size:14px; color:var(--text);
font-family:inherit;
}
.cg-select-btn{ display:flex; align-items:center; gap:8px; padding:8px 12px; cursor:default; }
.cg-select-wrap{ position:relative; }
.cg-select-wrap select{
appearance:none; padding:8px 32px 8px 12px; cursor:pointer;
}
.cg-select-wrap .chev{ position:absolute; right:10px; top:10px; pointer-events:none; color:var(--textSoft); }
.cg-search-box{ display:flex; align-items:center; gap:8px; padding:8px 12px; border-radius:10px; background:#fff; border:1px solid var(--border); }
.cg-search-box input{ font-size:14px; outline:none; width:160px; color:var(--text); border:none; font-family:inherit; background:transparent; }
.cg-daterange{ margin-left:auto; display:flex; align-items:center; gap:8px; padding:8px 12px; }
.cg-daterange span{ font-size:14px; color:var(--text); }

.cg-table{ border-radius:16px; overflow:hidden; background:var(--card); border:1px solid var(--border); }
.cg-thead{
display:grid; grid-template-columns:28px 2fr 1.4fr 1fr 1fr; padding:12px 20px;
font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em;
color:var(--textSoft); border-bottom:1px solid var(--border);
}
.cg-row-btn{
width:100%; display:grid; grid-template-columns:28px 2fr 1.4fr 1fr 1fr; align-items:center;
padding:12px 20px; text-align:left; background:none; border:none; cursor:pointer; font-family:inherit;
}
.cg-row-btn:hover{ filter:brightness(0.98); }
.cg-chevcell{ color:var(--textSoft); }
.cg-teacher{ font-weight:500; font-size:14px; color:var(--text); }
.cg-institution{ display:block; font-size:12px; font-weight:400; color:var(--textSoft); }
.cg-grade{ font-size:14px; color:var(--textSoft); }
.cg-count{ font-size:14px; font-weight:600; color:var(--text); font-family:'IBM Plex Mono','Courier New',monospace; }
.cg-last{ font-size:14px; color:var(--textSoft); }

.cg-detail-wrap{ padding:0 20px 16px; }
.cg-detail{ margin-left:32px; border-radius:12px; overflow:hidden; border:1px solid var(--border); }
.cg-detail-head{
display:grid; grid-template-columns:1fr 1.6fr 1.2fr; padding:8px 16px;
font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.04em;
background:var(--bg); color:var(--textSoft);
}
.cg-detail-row{
display:grid; grid-template-columns:1fr 1.6fr 1.2fr; align-items:center; padding:10px 16px;
font-size:14px; border-top:1px solid var(--border); color:var(--text);
}
.cg-detail-date{ font-family:'IBM Plex Mono','Courier New',monospace; }
.cg-detail-chapter{ color:var(--textSoft); }

.cg-skilltag{ display:inline-flex; align-items:center; gap:6px; padding:4px 8px; border-radius:6px; font-size:12px; font-weight:500; }

.cg-empty{ padding:40px 20px; text-align:center; font-size:14px; color:var(--textSoft); }
</style>

<div id="cg-root"></div>

<script>
(function(){
const C = {
navy: "#14213D", orange: "#F5A623", bg: "#F4F6FB", card: "#FFFFFF",
text: "#1B2338", textSoft: "#6B7280", border: "#E7E9F0", rowAlt: "#FAFBFD",
};

const SKILLS = {
Speaking: { color: "#E8746B", bg: "#FCE4E4", icon: "mic" },
Vocabulary: { color: "#38A169", bg: "#E1F3E6", icon: "bookOpen" },
Grammar: { color: "#4C7FE0", bg: "#E3ECFB", icon: "penLine" },
Listening: { color: "#DB9A2E", bg: "#FDEBD3", icon: "headphones" },
Reading: { color: "#8B6FD9", bg: "#EAE4FB", icon: "book" },
};

const RECORDS = [
{ teacher: "Aisha Rahman", grade: "Grade 1 - A", institution: "Riverside Public School", sessions: [
{ date: "2026-08-06", chapter: "1. In the Park", skill: "Speaking" },
{ date: "2026-08-05", chapter: "1. In the Park", skill: "Vocabulary" },
{ date: "2026-08-04", chapter: "1. In the Park", skill: "Grammar" },
{ date: "2026-08-03", chapter: "1. In the Park", skill: "Listening" },
{ date: "2026-08-01", chapter: "1. In the Park", skill: "Speaking" },
]},
{ teacher: "Aisha Rahman", grade: "Grade 2 - A", institution: "Riverside Public School", sessions: [
{ date: "2026-08-06", chapter: "2. At the Market", skill: "Vocabulary" },
{ date: "2026-08-02", chapter: "1. In the Park", skill: "Reading" },
]},
{ teacher: "Rohan Mehta", grade: "Grade 2 - C", institution: "Riverside Public School", sessions: [
{ date: "2026-08-06", chapter: "2. At the Market", skill: "Grammar" },
{ date: "2026-08-05", chapter: "2. At the Market", skill: "Speaking" },
{ date: "2026-08-04", chapter: "2. At the Market", skill: "Speaking" },
{ date: "2026-08-01", chapter: "1. In the Park", skill: "Listening" },
]},
{ teacher: "Sara Ali", grade: "Grade 1 - B", institution: "Greenfield Academy", sessions: [
{ date: "2026-08-07", chapter: "1. In the Park", skill: "Reading" },
{ date: "2026-08-05", chapter: "1. In the Park", skill: "Reading" },
{ date: "2026-08-03", chapter: "1. In the Park", skill: "Vocabulary" },
]},
{ teacher: "Daniel Cruz", grade: "Grade 3 - A", institution: "Greenfield Academy", sessions: [
{ date: "2026-08-06", chapter: "3. My Family", skill: "Grammar" },
{ date: "2026-08-04", chapter: "3. My Family", skill: "Grammar" },
]},
{ teacher: "Meera Nair", grade: "Grade 2 - A", institution: "St. Xavier's High", sessions: [
{ date: "2026-08-07", chapter: "2. At the Market", skill: "Listening" },
{ date: "2026-08-06", chapter: "2. At the Market", skill: "Vocabulary" },
{ date: "2026-08-02", chapter: "1. In the Park", skill: "Speaking" },
]},
{ teacher: "James Okafor", grade: "Grade 1 - C", institution: "St. Xavier's High", sessions: [
{ date: "2026-08-01", chapter: "1. In the Park", skill: "Vocabulary" },
]},
];

// ---- inline icon set (lucide-style strokes) ----
function icon(name, size, color){
size = size || 16; color = color || "currentColor";
const common = `width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"`;
const paths = {
home: `<path d="M3 11l9-8 9 8"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/>`,
building2: `<rect x="6" y="3" width="12" height="18" rx="1"/><path d="M9 7h.01M9 11h.01M9 15h.01M15 7h.01M15 11h.01M15 15h.01"/>`,
user: `<circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-7 8-7s8 2.6 8 7"/>`,
layoutDashboard: `<rect x="3" y="3" width="8" height="8" rx="1"/><rect x="13" y="3" width="8" height="5" rx="1"/><rect x="13" y="10" width="8" height="11" rx="1"/><rect x="3" y="13" width="8" height="8" rx="1"/>`,
bookOpenText: `<path d="M2 5c2-1 5-1.5 8-.5v14c-3-1-6-.5-8 .5V5z"/><path d="M22 5c-2-1-5-1.5-8-.5v14c3-1 6-.5 8 .5V5z"/>`,
dumbbell: `<path d="M4 9v6"/><path d="M2 9v6"/><path d="M20 9v6"/><path d="M22 9v6"/><path d="M6 12h12"/><path d="M6 8v8"/><path d="M18 8v8"/>`,
clipboardList: `<rect x="6" y="3" width="12" height="18" rx="2"/><path d="M9 3v3h6V3"/><path d="M9 10h6M9 14h6M9 18h3"/>`,
settings: `<circle cx="12" cy="12" r="3"/><path d="M12 3v2M12 19v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M3 12h2M19 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/>`,
chevronDown: `<path d="M6 9l6 6 6-6"/>`,
chevronRight: `<path d="M9 6l6 6-6 6"/>`,
search: `<circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/>`,
school: `<path d="M12 3l10 5-10 5L2 8l10-5z"/><path d="M6 11v5c0 1.5 2.7 3 6 3s6-1.5 6-3v-5"/>`,
mic: `<rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10a7 7 0 0014 0"/><path d="M12 19v3M8 22h8"/>`,
bookOpen: `<path d="M2 4c2-1 5-1.5 8-.5v13c-3-1-6-.5-8 .5V4z"/><path d="M22 4c-2-1-5-1.5-8-.5v13c3-1 6-.5 8 .5V4z"/>`,
penLine: `<path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 013 3L7 19l-4 1 1-4 12.5-12.5z"/>`,
headphones: `<path d="M3 14v-2a9 9 0 0118 0v2"/><rect x="2" y="14" width="5" height="7" rx="1.5"/><rect x="17" y="14" width="5" height="7" rx="1.5"/>`,
book: `<path d="M4 4.5A2.5 2.5 0 016.5 2H20v18H6.5A2.5 2.5 0 004 17.5v-13z"/><path d="M4 17.5A2.5 2.5 0 016.5 15H20"/>`,
calendar: `<rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/>`,
};
return `<svg ${common}>${paths[name] || ""}</svg>`;
}

function fmtDate(d){
const dt = new Date(d + "T00:00:00");
return dt.toLocaleDateString("en-US", { day: "numeric", month: "short", year: "numeric" });
}

// ---- state ----
const state = {
level: "school",
expanded: new Set(),
query: "",
gradeFilter: "All Grades",
};

function getGrades(){
return ["All Grades", ...Array.from(new Set(RECORDS.map(r => r.grade))).sort()];
}

function getRows(){
return RECORDS
.filter(r => state.gradeFilter === "All Grades" || r.grade === state.gradeFilter)
.filter(r => r.teacher.toLowerCase().includes(state.query.toLowerCase()))
.map(r => ({
...r,
id: `${r.teacher}__${r.grade}`,
count: r.sessions.length,
last: r.sessions.map(s => s.date).sort().at(-1),
}))
.sort((a, b) => b.count - a.count);
}

function navItem(iconName, label, active){
return `
<div class="cg-navitem${active ? " active" : ""}">
${icon(iconName, 16)}
<span>${label}</span>
</div>`;
}

function skillTag(skill){
const s = SKILLS[skill];
return `
<span class="cg-skilltag" style="background:${s.bg};color:${s.color}">
${icon(s.icon, 12, s.color)}
${skill}
</span>`;
}

function render(){
const rows = getRows();
const totalSessions = rows.reduce((sum, r) => sum + r.count, 0);
const grades = getGrades();

const root = document.getElementById("cg-root");
root.innerHTML = `
<aside class="cg-sidebar">
<div class="cg-logo">MYENGLISH<span>PATH</span></div>
${navItem("home", "Home")}
${navItem("building2", "My Institutions")}
${navItem("user", "My Organization")}
${navItem("layoutDashboard", "My Dashboard")}
<div class="cg-navsection">English Enrichment</div>
${navItem("bookOpenText", "My ELP")}
${navItem("dumbbell", "Communication GYM", true)}
<div class="cg-navsub">Session Log</div>
${navItem("clipboardList", "Tests &amp; Evaluations")}
${navItem("settings", "Administration")}
</aside>

<main class="cg-main">
<div class="cg-header">
<div>
<h1 class="cg-title">Communication GYM — Session Log</h1>
<p class="cg-subtitle">${rows.length} teachers · ${totalSessions} sessions in view</p>
</div>
<div class="cg-toggle">
<button data-level="school" class="${state.level === "school" ? "active" : ""}">
${icon("school", 14)} School
</button>
<button data-level="org" class="${state.level === "org" ? "active" : ""}">
${icon("building2", 14)} Organization
</button>
</div>
</div>

<div class="cg-filters">
${state.level === "org" ? `
<div class="cg-select-btn">
All Institutions
${icon("chevronDown", 14, C.textSoft)}
</div>` : ""}

<div class="cg-select-wrap">
<select id="cg-grade-select">
${grades.map(g => `<option value="${g}" ${g === state.gradeFilter ? "selected" : ""}>${g}</option>`).join("")}
</select>
<span class="chev">${icon("chevronDown", 14, C.textSoft)}</span>
</div>

<div class="cg-search-box">
${icon("search", 14, C.textSoft)}
<input id="cg-search-input" placeholder="Search teacher..." value="${state.query.replace(/"/g, "&quot;")}" />
</div>

<div class="cg-daterange">
${icon("calendar", 14, C.textSoft)}
<span>Aug 1 – Aug 7, 2026</span>
</div>
</div>

<div class="cg-table">
<div class="cg-thead">
<span></span><span>Teacher</span><span>Grade</span><span>Sessions</span><span>Last Session</span>
</div>

${rows.map((r, i) => {
const isOpen = state.expanded.has(r.id);
const rowBg = i % 2 ? C.rowAlt : "#fff";
const sortedSessions = [...r.sessions].sort((a, b) => b.date.localeCompare(a.date));
return `
<div style="background:${rowBg}">
<button class="cg-row-btn" data-toggle="${r.id}" style="border-bottom:${isOpen ? "none" : `1px solid ${C.border}`}">
<span class="cg-chevcell">${icon(isOpen ? "chevronDown" : "chevronRight", 16)}</span>
<span class="cg-teacher">
${r.teacher}
${state.level === "org" ? `<span class="cg-institution">${r.institution}</span>` : ""}
</span>
<span class="cg-grade">${r.grade}</span>
<span class="cg-count">${r.count}</span>
<span class="cg-last">${fmtDate(r.last)}</span>
</button>

${isOpen ? `
<div class="cg-detail-wrap" style="border-bottom:1px solid ${C.border}">
<div class="cg-detail">
<div class="cg-detail-head">
<span>Date</span><span>Chapter</span><span>Skill</span>
</div>
${sortedSessions.map(s => `
<div class="cg-detail-row">
<span class="cg-detail-date">${fmtDate(s.date)}</span>
<span class="cg-detail-chapter">${s.chapter}</span>
<span>${skillTag(s.skill)}</span>
</div>
`).join("")}
</div>
</div>
` : ""}
</div>
`;
}).join("")}

${rows.length === 0 ? `<div class="cg-empty">No teachers match this filter.</div>` : ""}
</div>
</main>
`;

// wire up events
root.querySelectorAll(".cg-toggle button").forEach(btn => {
btn.addEventListener("click", () => {
state.level = btn.getAttribute("data-level");
render();
});
});

const gradeSelect = document.getElementById("cg-grade-select");
if (gradeSelect){
gradeSelect.addEventListener("change", (e) => {
state.gradeFilter = e.target.value;
render();
});
}

const searchInput = document.getElementById("cg-search-input");
if (searchInput){
searchInput.addEventListener("input", (e) => {
state.query = e.target.value;
const caret = e.target.selectionStart;
render();
const newInput = document.getElementById("cg-search-input");
if (newInput){
newInput.focus();
newInput.setSelectionRange(caret, caret);
}
});
}

root.querySelectorAll("[data-toggle]").forEach(btn => {
btn.addEventListener("click", () => {
const id = btn.getAttribute("data-toggle");
if (state.expanded.has(id)) state.expanded.delete(id);
else state.expanded.add(id);
render();
});
});
}

render();
})();
</script>
    (1-1/1)