Kentlo's picture
Sync from GitHub b098d17dbed1a6386c212d28b4bfa6138ad59d75
88c7275 verified
Raw
History Blame Contribute Delete
8.21 kB
import streamlit as st
from cert_study_app.services.learning_lab_service import (
PRACTICE_TASKS,
certification_for_track,
lessons_for_track,
quizzes_for_track,
certifications_for_track,
active_tracks,
normalize_track_id,
)
from cert_study_app.services.learning_progress_service import (
completed_steps,
save_preferred_track,
spaced_review_count,
spaced_review_due_today,
)
from cert_study_app.ui.common import go_to, selected_lab_track
def render_concept_mode_home():
track_id = selected_lab_track()
certification = certification_for_track(track_id)
lessons = lessons_for_track(track_id)
quizzes_list = quizzes_for_track(track_id)
session_done = completed_steps(track_id)
lesson_done = "lesson" in session_done
quiz_done = "quiz" in session_done
st.subheader("๐Ÿ“– ๊ฐœ๋… ๊ณต๋ถ€")
st.caption(f"{certification.get('name', '')} ๋Œ€๋น„ ยท ์ด๋ก  ์นด๋“œ๋ฅผ ๋ณด๊ณ  ํ™•์ธ ํ€ด์ฆˆ๋กœ ์ดํ•ด๋„๋ฅผ ์ ๊ฒ€ํ•ฉ๋‹ˆ๋‹ค")
st.info("๐Ÿ’ก ์ด ์„น์…˜์€ **์ง์ ‘ ์ œ์ž‘ํ•œ ๊ฐœ๋… ํ•™์Šต ์ฝ˜ํ…์ธ **์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ '์‹œํ—˜ ์ค€๋น„'์˜ ๋คํ”„ ๋ฌธ์ œ์™€๋Š” ๋ณ„๊ฐœ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋…์„ ์ดํ•ดํ•œ ๋’ค ์‹œํ—˜ ์ค€๋น„๋กœ ๋„˜์–ด๊ฐ€๋ฉด ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.", icon=None)
with st.container(border=True):
done_chip = "<span class='cert-chip cert-chip-done'>โœ“ ์™„๋ฃŒ</span>" if lesson_done else ""
st.markdown(f"**์ด๋ก  ์นด๋“œ** {done_chip}", unsafe_allow_html=True)
st.caption(f"{len(lessons)}๊ฐœ ์นด๋“œ ยท ํ•ต์‹ฌ ๊ฐœ๋…์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋ฅด๋Š” ๊ฒŒ ์žˆ์œผ๋ฉด ๋‹ค์Œ ์นด๋“œ๋กœ ์ด์–ด๊ฐ‘๋‹ˆ๋‹ค.")
btn_label = "์ด์–ด์„œ ๋ณด๊ธฐ" if lesson_done else "์‹œ์ž‘ํ•˜๊ธฐ"
btn_type = "secondary" if lesson_done else "primary"
if st.button(btn_label, type=btn_type, use_container_width=True, key="concept_lesson_btn"):
go_to("์ด๋ก  ํ•™์Šต")
with st.container(border=True):
done_chip = "<span class='cert-chip cert-chip-done'>โœ“ ์™„๋ฃŒ</span>" if quiz_done else ""
st.markdown(f"**ํ™•์ธ ํ€ด์ฆˆ** {done_chip}", unsafe_allow_html=True)
st.caption(f"{len(quizzes_list)}๋ฌธ์ œ ยท ๋ฐฉ๊ธˆ ๋ณธ ๊ฐœ๋…์„ ์งง์€ ํ€ด์ฆˆ๋กœ ์ ๊ฒ€ํ•ฉ๋‹ˆ๋‹ค.")
btn_label = "๋‹ค์‹œ ํ’€๊ธฐ" if quiz_done else "ํ€ด์ฆˆ ํ’€๊ธฐ"
btn_type = "secondary" if quiz_done else "primary"
if st.button(btn_label, type=btn_type, use_container_width=True, key="concept_quiz_btn"):
go_to("ํ™•์ธ ํ€ด์ฆˆ")
if lesson_done and quiz_done:
st.success("์˜ค๋Š˜ ๊ฐœ๋… ๊ณต๋ถ€๋ฅผ ๋งˆ์ณค์Šต๋‹ˆ๋‹ค. ์‹ค์Šต์ด๋‚˜ ์‹œํ—˜ ์ค€๋น„๋กœ ์ด์–ด๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
def render_practice_mode_home():
track_id = selected_lab_track()
certification = certification_for_track(track_id)
practices = [t for t in PRACTICE_TASKS if t.track == track_id and t.status == "approved"]
completed = st.session_state.lab_completed_practices
done_ids = completed & {t.id for t in practices}
session_done = completed_steps(track_id)
apply_done = "apply" in session_done
st.subheader("๐Ÿ–ฅ ์‹ค์Šต")
st.caption(f"{certification.get('name', '')} ๋Œ€๋น„ ยท ๋ช…๋ น์–ด๋‚˜ ๋„๊ตฌ๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•ด ๋ด…๋‹ˆ๋‹ค")
if not practices:
st.info(
"์ด Track์€ ์•„์ง ์‹ค์Šต ๊ณผ์ œ๋ฅผ ์ค€๋น„ ์ค‘์ž…๋‹ˆ๋‹ค. "
"Linux Track์„ ์„ ํƒํ•˜๋ฉด LFCS ๋ช…๋ น์–ด ์‹ค์Šต์„ ๋ฐ”๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
)
return
progress_pct = len(done_ids) / len(practices) if practices else 0
st.progress(progress_pct, text=f"์ „์ฒด ์ง„๋„ {len(done_ids)}/{len(practices)} ์™„๋ฃŒ")
if apply_done:
st.success("์˜ค๋Š˜ ์‹ค์Šต์„ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค.")
btn_label = "์‹ค์Šต ์ด์–ด๊ฐ€๊ธฐ" if len(done_ids) > 0 else "์‹ค์Šต ์‹œ์ž‘ํ•˜๊ธฐ"
btn_type = "secondary" if apply_done else "primary"
if st.button(btn_label, type=btn_type, use_container_width=True):
go_to("์‹ค์Šตํ•˜๊ธฐ")
with st.expander("์‹ค์Šต ๊ฐ€์ด๋“œ", expanded=False):
st.write(
"๋ช…๋ น์–ด๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•˜๊ณ  ์ฑ„์ ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. "
"ํžŒํŠธ๋ฅผ ๋ณด๋ฉด ํ•™์Šต ํšจ๊ณผ๊ฐ€ ์ค„์–ด๋“œ๋‹ˆ ์ตœ๋Œ€ํ•œ ํ˜ผ์ž ๋จผ์ € ์‹œ๋„ํ•ด ๋ณด์„ธ์š”. "
"ํ‹€๋ ค๋„ ๋ฐ”๋กœ ๋‹ค์Œ ๋ฌธ์ œ๋กœ ๋„˜์–ด๊ฐ€์ง€ ๋ง๊ณ  ์ •๋‹ต ๋ช…๋ น์–ด๋ฅผ ํ•œ ๋ฒˆ ์ง์ ‘ ์ณ๋ณด๋Š” ๊ฑธ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค."
)
def render_exam_prep_home(exams):
st.subheader("๐Ÿ“‹ ์‹œํ—˜ ์ค€๋น„")
st.caption("๋คํ”„ ๋ฌธ์ œ๋กœ ์‹ค์ „ ๊ฐ๊ฐ์„ ์ตํžˆ๊ณ , ํ‹€๋ฆฐ ๋ฌธ์ œ๋Š” ๊ฐ„๊ฒฉ ๋ฐ˜๋ณต์œผ๋กœ ์™„์ „ํžˆ ๋‚ด ๊ฒƒ์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.")
st.info("๐Ÿ’ก ์—ฌ๊ธฐ๋Š” **์‹ค์ œ ์‹œํ—˜ ํ˜•์‹ ๋ฌธ์ œ ํ’€์ด** ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค. ๊ฐœ๋…์ด ์•„์ง ์ต์ˆ™ํ•˜์ง€ ์•Š๋‹ค๋ฉด '๊ฐœ๋… ๊ณต๋ถ€'๋ฅผ ๋จผ์ € ํ•˜์„ธ์š”.", icon=None)
due_count = spaced_review_count()
if due_count > 0:
with st.container(border=True):
st.markdown(f"**๐Ÿ” ๊ฐ„๊ฒฉ ๋ณต์Šต ยท {due_count}๋ฌธ์ œ ๋Œ€๊ธฐ**")
st.caption("ํ‹€๋ ธ๋˜ ๋ฌธ์ œ๊ฐ€ ์˜ค๋Š˜ ๋ณต์Šต ๊ธฐํ•œ์ด ๋์Šต๋‹ˆ๋‹ค. ๋ณต์Šต๋ถ€ํ„ฐ ํ•˜๋ฉด ์žฅ๊ธฐ ๊ธฐ์–ต ํšจ์œจ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.")
if st.button(f"๋ณต์Šต ์‹œ์ž‘ ({due_count}๋ฌธ์ œ)", type="primary", use_container_width=True):
due_ids = spaced_review_due_today(limit=1)
if due_ids:
st.session_state.question_id = due_ids[0]
st.session_state.exam_source = None
st.session_state.selected = None
st.session_state.last_result = None
go_to("์ž๊ฒฉ์ฆ ๋ฌธ์ œ")
st.markdown('<div class="cert-section-title">์‹œํ—˜๋ณ„ ๋ฌธ์ œ ํ’€์ด</div>', unsafe_allow_html=True)
az_exam = next((e for e in exams if e.get("source") == "AZ-104"), None)
with st.container(border=True):
tc, bc = st.columns([4, 1])
tc.markdown("**AZ-104** ยท Microsoft Azure Administrator")
if az_exam:
tc.caption(f"๋ฌธ์ œ์€ํ–‰ {az_exam['count']}๋ฌธํ•ญ ยท ์ค€๋น„๋จ")
else:
tc.caption("์ค€๋น„๋จ ยท ๋ฌธ์ œ ์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘")
if bc.button("์‹œ์ž‘", key="exam_az104", use_container_width=True):
st.session_state.exam_source = "AZ-104"
go_to("์ž๊ฒฉ์ฆ ๋ฌธ์ œ")
with st.container(border=True):
tc, bc = st.columns([4, 1])
tc.markdown("**LFCS** ยท Linux Foundation Certified Sysadmin")
tc.caption("๋ฌธ์ œ์€ํ–‰ ์ค€๋น„ ์ค‘ ยท ํ˜„์žฌ๋Š” ์‹ค์Šต์œผ๋กœ ๋Œ€๋น„")
if bc.button("์‹ค์Šต์œผ๋กœ ์ด๋™", key="exam_lfcs", use_container_width=True):
go_to("์‹ค์Šต")
st.markdown('<div class="cert-section-title">๊ฐœ๋… ๋ชจ์˜์‹œํ—˜</div>', unsafe_allow_html=True)
with st.container(border=True):
tc, bc = st.columns([4, 1])
tc.markdown("**๐Ÿงช ๊ฐœ๋… ๋ชจ์˜์‹œํ—˜** ยท ์ง์ ‘ ์ œ์ž‘ ํ€ด์ฆˆ ๊ธฐ๋ฐ˜")
tc.caption("์ด๋ก ยทCLI ํ€ด์ฆˆ๋ฅผ ๋žœ๋ค ์ถœ์ œ, ํƒ€์ด๋จธ ์žˆ์Œ ยท ๋คํ”„ ๋ฌธ์ œ์™€ ๋ณ„๊ฐœ")
if bc.button("์‹œ์ž‘", key="exam_concept_mock", use_container_width=True):
go_to("์‹œํ—˜ ๋ชจ๋“œ")
st.markdown('<div class="cert-section-title">๋ณต์Šต</div>', unsafe_allow_html=True)
col1, col2 = st.columns(2)
if col1.button("์˜ค๋‹ต๋…ธํŠธ", use_container_width=True):
go_to("์˜ค๋‹ต๋…ธํŠธ")
if col2.button("์ทจ์•ฝ ๊ฐœ๋… ํ•™์Šต", use_container_width=True):
go_to("์ทจ์•ฝ ๊ฐœ๋… ํ•™์Šต")
def render_exam_overview(exams, selected_exam):
st.subheader("์‹œํ—˜ ํ˜„ํ™ฉ")
total_questions = sum(exam["count"] for exam in exams)
col1, col2, col3 = st.columns(3)
col1.metric("์‹œํ—˜", f"{len(exams)}๊ฐœ")
col2.metric("์ „์ฒด ๋ฌธํ•ญ", f"{total_questions}๊ฐœ")
col3.metric("์„ ํƒ ๋ฌธํ•ญ", f"{selected_exam['count']}๊ฐœ" if selected_exam else f"{total_questions}๊ฐœ")
if not exams:
st.info("์•„์ง ๋“ฑ๋ก๋œ ์‹œํ—˜ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์—…๋กœ๋“œ์—์„œ ์‹œํ—˜๋ช…์„ ์ง€์ •ํ•˜๊ณ  PDF๋ฅผ ์ ์žฌํ•ด ์ฃผ์„ธ์š”.")
return
st.dataframe(
[
{
"์‹œํ—˜": exam["name"],
"๋ฌธํ•ญ ์ˆ˜": exam["count"],
"์ฒซ ๋ฌธํ•ญ": exam["first_question_id"],
"๋งˆ์ง€๋ง‰ ๋ฌธํ•ญ": exam["last_question_id"],
}
for exam in exams
],
hide_index=True,
use_container_width=True,
)