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 = "โœ“ ์™„๋ฃŒ" 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 = "โœ“ ์™„๋ฃŒ" 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('
์‹œํ—˜๋ณ„ ๋ฌธ์ œ ํ’€์ด
', 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('
๊ฐœ๋… ๋ชจ์˜์‹œํ—˜
', 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('
๋ณต์Šต
', 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, )