Skip to content

Commit 7ba6107

Browse files
committed
add github pages top-down learning dashboard
1 parent 909e50a commit 7ba6107

6 files changed

Lines changed: 1642 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Deploy GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: false
16+
17+
jobs:
18+
deploy:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Upload static site artifact
25+
uses: actions/upload-pages-artifact@v3
26+
with:
27+
path: docs
28+
29+
- name: Deploy to GitHub Pages
30+
id: deployment
31+
uses: actions/deploy-pages@v4

code/architect-code/docs/.nojekyll

Whitespace-only changes.

code/architect-code/docs/app.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
const partFilter = document.getElementById("partFilter");
2+
const langFilter = document.getElementById("langFilter");
3+
const searchInput = document.getElementById("searchInput");
4+
const partsRoot = document.getElementById("parts");
5+
const statsRoot = document.getElementById("stats");
6+
const tpl = document.getElementById("problemTemplate");
7+
8+
const MEMORY_SCHEDULE = [1, 3, 7];
9+
10+
function getReviewTag(problemId) {
11+
const key = `review:${problemId}`;
12+
const lastRaw = localStorage.getItem(key);
13+
if (!lastRaw) {
14+
return { label: "Not Started", tone: "new", next: "Start today" };
15+
}
16+
const last = new Date(lastRaw);
17+
const days = Math.floor((Date.now() - last.getTime()) / (1000 * 60 * 60 * 24));
18+
let next = MEMORY_SCHEDULE.find((d) => d > days);
19+
if (!next) {
20+
return { label: "Review Due", tone: "due", next: "Review now" };
21+
}
22+
return { label: "In Loop", tone: "loop", next: `D+${next}` };
23+
}
24+
25+
function retrievalPrompt(problem) {
26+
return [
27+
`Q1. ${problem.algorithm}를 떠올리지 않고 30초 내로 접근 전략을 말할 수 있나요?`,
28+
`Q2. ${problem.name}를 실제 시스템에서 어디에 적용할지 1개 사례를 답해보세요.`,
29+
`Q3. ${problem.architectView}를 시간/공간 복잡도와 연결해 설명해보세요.`
30+
].join(" ");
31+
}
32+
33+
function toGitHubLink(path) {
34+
if (!path) return null;
35+
return `../${path}`;
36+
}
37+
38+
function problemMatches(problem, filters) {
39+
const q = filters.query;
40+
const matchesQuery = !q
41+
|| `${problem.id} ${problem.name} ${problem.algorithm} ${problem.architectView}`.toLowerCase().includes(q);
42+
43+
if (!matchesQuery) return false;
44+
if (filters.part !== "all" && String(problem.part) !== filters.part) return false;
45+
if (filters.lang === "python" && !problem.pythonAvailable) return false;
46+
if (filters.lang === "java" && !problem.javaAvailable) return false;
47+
return true;
48+
}
49+
50+
function statCard(label, value) {
51+
const el = document.createElement("article");
52+
const strong = document.createElement("strong");
53+
strong.textContent = value;
54+
const p = document.createElement("p");
55+
p.textContent = label;
56+
p.style.margin = "0.15rem 0 0";
57+
p.style.color = "var(--muted)";
58+
p.style.fontSize = "0.86rem";
59+
el.append(strong, p);
60+
return el;
61+
}
62+
63+
function renderStats(data) {
64+
statsRoot.innerHTML = "";
65+
const s = data.stats;
66+
const values = [
67+
["Total Problems", String(s.problemCount)],
68+
["Parts", String(s.partCount)],
69+
["Python Coverage", `${s.pythonCount}/${s.problemCount}`],
70+
["Java Coverage", `${s.javaCount}/${s.problemCount}`],
71+
];
72+
values.forEach(([label, value]) => statsRoot.appendChild(statCard(label, value)));
73+
}
74+
75+
function renderPartOptions(parts) {
76+
parts.forEach((part) => {
77+
const opt = document.createElement("option");
78+
opt.value = String(part.part);
79+
opt.textContent = `Part ${part.part} - ${part.title}`;
80+
partFilter.appendChild(opt);
81+
});
82+
}
83+
84+
function render(data) {
85+
const filters = {
86+
part: partFilter.value,
87+
lang: langFilter.value,
88+
query: searchInput.value.trim().toLowerCase(),
89+
};
90+
91+
partsRoot.innerHTML = "";
92+
data.parts.forEach((part) => {
93+
const enriched = part.problems.map((p) => ({ ...p, part: part.part }));
94+
const filtered = enriched.filter((p) => problemMatches(p, filters));
95+
if (!filtered.length) return;
96+
97+
const details = document.createElement("details");
98+
details.className = "part";
99+
details.open = true;
100+
101+
const summary = document.createElement("summary");
102+
const left = document.createElement("div");
103+
const right = document.createElement("div");
104+
left.className = "part-title";
105+
right.className = "part-meta";
106+
left.textContent = `Part ${part.part}. ${part.title}`;
107+
right.textContent = `${filtered.length} / ${part.problems.length} problems`;
108+
summary.append(left, right);
109+
110+
const grid = document.createElement("div");
111+
grid.className = "problem-grid";
112+
113+
filtered.forEach((problem) => {
114+
const node = tpl.content.firstElementChild.cloneNode(true);
115+
node.querySelector(".pid").textContent = problem.id;
116+
node.querySelector("h4").textContent = problem.name;
117+
node.querySelector(".algo").textContent = `Pattern: ${problem.algorithm}`;
118+
119+
const rt = getReviewTag(problem.id);
120+
node.querySelector(".arch").textContent = `${problem.architectView} | ${rt.label} (${rt.next})`;
121+
node.querySelector(".prompt").textContent = retrievalPrompt(problem);
122+
123+
const links = node.querySelector(".links");
124+
const py = toGitHubLink(problem.pythonPath);
125+
const ja = toGitHubLink(problem.javaPath);
126+
127+
if (py) {
128+
const a = document.createElement("a");
129+
a.href = py;
130+
a.textContent = "Python";
131+
a.className = "active";
132+
a.target = "_blank";
133+
a.rel = "noopener";
134+
links.appendChild(a);
135+
}
136+
137+
if (ja) {
138+
const a = document.createElement("a");
139+
a.href = ja;
140+
a.textContent = "Java";
141+
a.className = "active";
142+
a.target = "_blank";
143+
a.rel = "noopener";
144+
links.appendChild(a);
145+
}
146+
147+
const mark = document.createElement("a");
148+
mark.href = "#";
149+
mark.textContent = "Mark Reviewed";
150+
mark.addEventListener("click", (e) => {
151+
e.preventDefault();
152+
localStorage.setItem(`review:${problem.id}`, new Date().toISOString());
153+
render(data);
154+
});
155+
links.appendChild(mark);
156+
157+
grid.appendChild(node);
158+
});
159+
160+
details.append(summary, grid);
161+
partsRoot.appendChild(details);
162+
});
163+
}
164+
165+
async function init() {
166+
const res = await fetch("data/problems.json");
167+
const data = await res.json();
168+
renderStats(data);
169+
renderPartOptions(data.parts);
170+
render(data);
171+
172+
[partFilter, langFilter, searchInput].forEach((el) => {
173+
el.addEventListener("input", () => render(data));
174+
el.addEventListener("change", () => render(data));
175+
});
176+
}
177+
178+
init().catch((error) => {
179+
partsRoot.innerHTML = `<p>Failed to load data: ${error.message}</p>`;
180+
});

0 commit comments

Comments
 (0)