Skip to content

Latest commit

ย 

History

History
550 lines (438 loc) ยท 32.2 KB

File metadata and controls

550 lines (438 loc) ยท 32.2 KB

Frontend Code Map

์‹ ๊ทœ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋„๋ก, ์‹ค์ œ ๊ตฌํ˜„ ํŒŒ์ผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ฆฌํ•œ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค.

๋ฌธ์„œ ๋ฉ”ํƒ€

ํ•ญ๋ชฉ ๋‚ด์šฉ
๋Œ€์ƒ ๋…์ž ์‹ ๊ทœ FE ๊ฐœ๋ฐœ์ž
๊ธฐ์ค€ ์ฝ”๋“œ frontend/src/*
๊ด€๋ จ ๋ฌธ์„œ frontend-architecture.md, frontend-api-reference.md, analytics-tracking.md, team-checklist.md
๋ฐฑ์—”๋“œ API ์›๋ฌธ ../../backend/docs/backend_api.md

1. ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๊ณผ ์•ฑ ์…ธ

ํŒŒ์ผ ์—ญํ• 
src/main.jsx React ์•ฑ ์—”ํŠธ๋ฆฌํฌ์ธํŠธ. #root์— <App /> ๋งˆ์šดํŠธ
src/App.jsx ์ „์—ญ Provider(ThemeProvider, NetworkStatusProvider, PwaInstallProvider, AuthProvider) + ์ตœ์ƒ์œ„ ๋ผ์šฐํŒ… ๊ตฌ์„ฑ
src/layout/AppLayout.jsx ๊ณตํ†ต ๋ ˆ์ด์•„์›ƒ(์ ‘๊ทผ์„ฑ skip-link, Header, main, Footer, OfflineGate)
src/components/Header/Header.jsx ์ „์—ญ ๋‚ด๋น„๊ฒŒ์ด์…˜/ํ…Œ๋งˆ ํ† ๊ธ€/์ธ์ฆ ์ƒํƒœ UI. ๊ณต์ง€ ์˜์—ญ์˜ ํ•™๊ต ์†Œ๊ฐœ ์ง„์ž…์ , ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ณด๋“œ feature flag, ํ•™๊ต ์ƒํ™œ ์ •๋ณด ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋งํฌ(QR/์ƒค๋‹ˆ๋งˆ์Šค ์นด๋“œ/์Šคํฌ์ธ ๋ฆฌ๊ทธ ๋“ฑ) ํฌํ•จ
src/components/Footer/Footer.jsx ์™ธ๋ถ€ ๋งํฌ/๋ฒ•์  ๋ฌธ์„œ ๋งํฌ/์šด์˜ ์ •๋ณด
src/components/pwa/OfflineGate.jsx ์˜คํ”„๋ผ์ธ ์‹œ ์ „์ฒด ํ™”๋ฉด ์˜ค๋ฒ„๋ ˆ์ด์™€ ์žฌ์‹œ๋„ ํ๋ฆ„ ์ œ๊ณต
src/pages/CommunityPage.jsx ์ปค๋ฎค๋‹ˆํ‹ฐ ํ—ˆ๋ธŒ ํŽ˜์ด์ง€. ๋ชจ๋“  ๋ณด๋“œ ์นด๋“œ๋ฅผ ๊ทธ๋ฆฌ๋“œ๋กœ ๋‚˜์—ดํ•˜๋ฉฐ, ํ˜„์žฌ ํ™œ์„ฑ ๋ณด๋“œ๋ฅผ ํ•˜์ด๋ผ์ดํŠธ

2. ์ฃผ์š” ๋””๋ ‰ํ„ฐ๋ฆฌ ๋งต

๋””๋ ‰ํ„ฐ๋ฆฌ ์—ญํ• 
src/pages ๋ผ์šฐํŠธ ๋‹จ์œ„ ํ™”๋ฉด ์ปดํฌ๋„ŒํŠธ
src/components ์žฌ์‚ฌ์šฉ UI ์ปดํฌ๋„ŒํŠธ
src/api ๋ฐฑ์—”๋“œ ์—ฐ๋™ ๋ชจ๋“ˆ + FastAPI/๊ธ‰์‹ ์•Œ๋ฆผ ํด๋ผ์ด์–ธํŠธ
src/features ๊ธฐ๋Šฅ ๋‹จ์œ„ data/hook/utils ๋ฌถ์Œ
src/context ์ „์—ญ ์ƒํƒœ ์ปจํ…์ŠคํŠธ(Theme/NetworkStatus/PWA/Auth)
src/pwa ์˜คํ”„๋ผ์ธ/์„ค์น˜ ์ƒํƒœ, Firebase Web Push, ์„ค์น˜ ๊ธฐ๊ธฐ ์‹๋ณ„ ์œ ํ‹ธ
src/security URL/HTML/CSV/์„ค๋ฌธ ์Šคํ‚ค๋งˆ sanitize ์ •์ฑ…
src/analytics Zaraz ์ด๋ฒคํŠธ ์ „์†ก ๋ž˜ํผ
src/config ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํŒŒ์‹ฑ ๋ฐ ์ƒ์ˆ˜ ๋…ธ์ถœ
src/styles ์ „์—ญ ์Šคํƒ€์ผ ํ† ํฐ/๋ ˆ์ด์•„์›ƒ CSS

3. ๋ผ์šฐํŠธ ํŠธ๋ฆฌ (์‹ค์ œ ์ฝ”๋“œ ๊ธฐ์ค€)

3.1 ์ตœ์ƒ์œ„ ๋ผ์šฐํŠธ (src/App.jsx)

๊ฒฝ๋กœ ์š”์†Œ
/ MainPage
/login LoginPage
/signup SignUpPage
/notices/* NoticesPage (src/pages/Notices/index.jsx)
/community/* CommunityRouter
/school-info/* SchoolInfoRouter (src/pages/SchoolLifeInfo/index.jsx)
/privacy PrivacyPolicyPage
/terms TermsOfServicePage
* NotFoundPage

/privacy, /terms๋Š” ์ •์  ๋ฒ•์  ๋ฌธ์„œ ํŽ˜์ด์ง€์ด๋ฉฐ, ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ fetch ์—†์ด anchor ๊ธฐ๋ฐ˜ ๋ชฉ์ฐจ์™€ ๋งจ ์œ„๋กœ ์Šคํฌ๋กค ํ—ฌํผ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

3.2 ๊ณต์ง€ ๋ผ์šฐํŠธ (src/pages/Notices/index.jsx)

๊ฒฝ๋กœ ์š”์†Œ
/notices Navigate -> /notices/school
/notices/budget/* BudgetBoardPage
/notices/lost-found LostFoundListView
/notices/lost-found/new LostFoundComposeView
/notices/lost-found/:id LostFoundDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/notices/school-info/* SchoolInfoRouter
/notices/* (budget, lost-found, school-info ์™ธ ๊ฒฝ๋กœ) NoticeCenterPage

3.2.1 ํ•™๊ต/ํ•™์ƒํšŒ ๊ณต์ง€ ์„œ๋ธŒ๋ผ์šฐํŠธ (src/pages/Notices/NoticeCenter/NoticeCenterPage.jsx)

๊ฒฝ๋กœ ์š”์†Œ
/notices/school ListView
/notices/council ListView
/notices/:category/new ComposeView(mode=create) (school, council๋งŒ ํ—ˆ์šฉ)
/notices/:category/:id DetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/notices/:category/:id/edit ComposeView(mode=edit) (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/notices/* (invalid path) NotFoundPage

3.2.2 ์˜ˆ์‚ฐ ๊ณต๊ฐœ ์„œ๋ธŒ๋ผ์šฐํŠธ (src/pages/Notices/BudgetBoard/BudgetBoardPage.jsx)

๊ฒฝ๋กœ ์š”์†Œ
/notices/budget Navigate -> /notices/budget/:budgetYear/:budgetMonth (GET /api/notices/budget/settings์˜ ๊ธฐ๋ณธ ์—ฐ/์›” ์‚ฌ์šฉ)
/notices/budget/:budgetYear/:budgetMonth BudgetListView
/notices/budget/:budgetYear/:budgetMonth/new BudgetComposeView(mode=create)
/notices/budget/:budgetYear/:budgetMonth/:id BudgetDetailView (budgetYear, id ์ˆซ์ž ๊ฒฝ๋กœ, budgetMonth ํ—ˆ์šฉ๊ฐ’ ๊ฐ€๋“œ)
/notices/budget/:budgetYear/:budgetMonth/:id/edit BudgetComposeView(mode=edit) (budgetYear, id ์ˆซ์ž ๊ฒฝ๋กœ, budgetMonth ํ—ˆ์šฉ๊ฐ’ ๊ฐ€๋“œ)
/notices/budget/* (invalid path) NotFoundPage

์˜ˆ์‚ฐ ๊ณต๊ฐœ ๋ผ์šฐํŠธ ๋ฉ”๋ชจ:

  • ํšŒ๊ณ„ ์‚ฌ์ดํด์€ 03~12, 01, 02 ๊ณ ์ • ์ˆœ์„œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • URL์˜ budgetYear๋Š” ๋‹ฌ๋ ฅ ์—ฐ๋„๊ฐ€ ์•„๋‹ˆ๋ผ ํšŒ๊ณ„์—ฐ๋„ ์‹œ์ž‘ ์—ฐ๋„์ž…๋‹ˆ๋‹ค.
  • BudgetBoardPage๋Š” settings ์‘๋‹ต์˜ ์—ฐ๋„ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ๊ฒฝ๋กœ๋ฅผ ์ฆ‰์‹œ 404 ํ™”๋ฉด์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

3.2.3 ํ•™๊ต ์†Œ๊ฐœ ๋ผ์šฐํŠธ (src/pages/Notices/SchoolInfo/index.jsx)

๊ฒฝ๋กœ ์š”์†Œ
/notices/school-info Navigate -> /notices/school-info/bshs-info
/notices/school-info/bshs-info BeomseoInfoPage
/notices/school-info/cshs-info CSHSInfoPage
/notices/school-info/* (invalid path) NotFoundPage

ํ•™๊ต ์†Œ๊ฐœ ๋ผ์šฐํŠธ ๋ฉ”๋ชจ:

  • SchoolInfoTabs๊ฐ€ ๋ฒ”์„œ๊ณ ยท์ฒœ์ƒ๊ณ  ์†Œ๊ฐœ ํƒญ์˜ ๋ผ๋ฒจ, ๊ฒฝ๋กœ, ํ™œ์„ฑ ์ƒํƒœ ํŒ์ •์„ ํ•œ๊ณณ์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • BeomseoInfoPage๋Š” ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์˜ ๊ต์œก ๋ฐฉํ–ฅ, ํ•™๊ต์ƒ์ง•, ํ•™๊ตํ˜„ํ™ฉ, ์—ฐํ˜, ์˜ค์‹œ๋Š” ๊ธธ์„ ์ •์  ์ฝ˜ํ…์ธ ๋กœ ๋ณด์—ฌ์ฃผ๊ณ  ๊ฐ ์›๋ฌธ ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋‘ ํ•™๊ต ์†Œ๊ฐœ ํŽ˜์ด์ง€๋Š” ๋ฐฑ์—”๋“œ API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ •์  SEO/prerender ์ •๋ณด๋Š” src/seo/policy.js์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

3.3 ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ผ์šฐํŠธ (src/pages/Community/index.jsx)

์ธ์„ฑ ๊ฐ€์น˜ PICK!, ๋™์•„๋ฆฌ ๋ชจ์ง‘, ์„ ํƒ๊ณผ๋ชฉ ๋ณ€๊ฒฝ, BOSPI, ์Šคํ„ฐ๋”” ์œ— ๋ฒ”์„œ, ์ˆ˜ํ•™์—ฌํ–‰ ๋ผ์šฐํŠธ๋Š” ๊ฐ ๋ณด๋“œ feature flag๊ฐ€ ์ผœ์ง„ ๋ฐฐํฌ์—์„œ๋งŒ ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค.

graph TD
    CR["CommunityRouter"] -->|index| DEF["Navigate โ†’ /community/free"]
    CR --> F["free/*"]
    CR --> VP["value-pick/*"]
    CR --> CLB["club-recruit/*"]
    CR --> SUB["subjects/*"]
    CR --> PET["petition/*"]
    CR --> SUR["survey/*"]
    CR --> VOT["vote/*"]
    CR --> BOS["bospi"]
    CR --> SWB["study-with-beomseo"]
    CR --> FT["field-trip"]
    CR --> LF["lost-found/*"]
    CR --> GM["gomsol-market/*"]
    CR -->|"*"| FALL["NotFoundPage"]

    F --> F1["FreeBoardListView"]
    F --> F2["FreeBoardComposeView(create)"]
    F --> F3["FreeBoardDetailView"]
    F --> F4["FreeBoardComposeView(edit)"]

    VP --> VP1["ValuePickListView"]
    VP --> VP2["ValuePickComposeView(create)"]
    VP --> VP3["ValuePickDetailView"]
    VP --> VP4["ValuePickComposeView(edit)"]

    CLB --> CLB1["ClubRecruitListPage"]
    CLB --> CLB2["ClubRecruitComposePage"]
    CLB --> CLB3["ClubRecruitDetailPage"]

    SUB --> SUB1["SubjectsListPage"]
    SUB --> SUB2["SubjectComposePage"]
    SUB --> SUB3["SubjectDetailPage"]

    PET --> PET1["PetitionListView"]
    PET --> PET2["PetitionComposeView"]
    PET --> PET3["PetitionDetailView"]

    SUR --> SUR1["SurveyExchangeListView"]
    SUR --> SUR2["SurveyExchangeComposePage"]
    SUR --> SUR3["SurveyExchangeDetailView"]
    SUR --> SUR4["SurveyExchangeComposePage(edit)"]
    SUR --> SUR5["SurveyResultsView"]

    VOT --> VOT1["VoteListView"]
    VOT --> VOT2["VoteComposeView"]
    VOT --> VOT3["VoteDetailView"]

    BOS --> BOS1["BospiPage"]

    SWB --> SWB1["StudyWithBeomseoPage"]

    FT --> FT1["FieldTripPage (Hub)"]
    FT --> FT2["FieldTripClassBoardPage"]
    FT --> FT3["FieldTripPostDetailPage"]

    LF --> LF1["LostFoundListView"]
    LF --> LF2["LostFoundComposeView"]
    LF --> LF3["LostFoundDetailView"]

    GM --> GM1["GomsolMarketListView"]
    GM --> GM2["GomsolMarketComposeView"]
    GM --> GM3["GomsolMarketDetailView"]
Loading
๊ฒฝ๋กœ ์š”์†Œ
/community Navigate โ†’ /community/free
/community/free FreeBoardListView
/community/free/new FreeBoardComposeView(mode=create)
/community/free/:id FreeBoardDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/free/:id/edit FreeBoardComposeView(mode=edit) (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/value-pick ValuePickListView
/community/value-pick/new ValuePickComposeView(mode=create)
/community/value-pick/:id ValuePickDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/value-pick/:id/edit ValuePickComposeView(mode=edit) (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/club-recruit ClubRecruitListPage
/community/club-recruit/new ClubRecruitComposePage
/community/club-recruit/:id ClubRecruitDetailPage (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/subjects SubjectsListPage
/community/subjects/new SubjectComposePage
/community/subjects/:id SubjectDetailPage (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/petition PetitionListView
/community/petition/new PetitionComposeView
/community/petition/:id PetitionDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/survey SurveyExchangeListView
/community/survey/new SurveyExchangeComposePage
/community/survey/:id SurveyExchangeDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/survey/:id/edit SurveyExchangeComposePage (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/survey/:id/results SurveyResultsView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/vote VoteListView
/community/vote/new VoteComposeView
/community/vote/:id VoteDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/bospi BospiPage
/community/study-with-beomseo StudyWithBeomseoPage
/community/field-trip FieldTripPage (FieldTripHubPage re-export)
/community/field-trip/classes/:classId FieldTripClassBoardPage
/community/field-trip/classes/:classId/new FieldTripClassBoardPage
/community/field-trip/classes/:classId/posts/:postId FieldTripPostDetailPage
/community/field-trip/classes/:classId/posts/:postId/edit FieldTripClassBoardPage
/community/lost-found LostFoundListView
/community/lost-found/new LostFoundComposeView
/community/lost-found/:id LostFoundDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/gomsol-market GomsolMarketListView
/community/gomsol-market/new GomsolMarketComposeView
/community/gomsol-market/:id GomsolMarketDetailView (id ์ˆซ์ž ๊ฒฝ๋กœ๋งŒ ํ—ˆ์šฉ)
/community/* (invalid path) NotFoundPage

3.4 ํ•™๊ต ์ƒํ™œ ์ •๋ณด ๋ผ์šฐํŠธ (src/pages/SchoolLifeInfo/index.jsx)

๊ฒฝ๋กœ ์š”์†Œ
/school-info SchoolInfoHub
/school-info/qr-generator QrCodeGeneratorPage
/school-info/shany-card-generator ShanyCardGeneratorPage
/school-info/timetable TimetableDownloadPage
/school-info/evaluation-plans EvaluationPlansPage
/school-info/meal MealPage
/school-info/calendar AcademicCalendarPage
/school-info/sports-league Navigate โ†’ /school-info/sports-league/2026-spring-grade2-boys-soccer
/school-info/sports-league/:categoryId SportsLeagueCategoryPage
/school-info/* (invalid path) NotFoundPage

4. ๊ธฐ๋Šฅ๋ณ„ ์ˆ˜์ง ์Šฌ๋ผ์ด์Šค ํƒ์ƒ‰

๊ธฐ๋Šฅ ํŽ˜์ด์ง€ ๋ ˆ์ด์–ด ์ปดํฌ๋„ŒํŠธ ๋ ˆ์ด์–ด API ๋ ˆ์ด์–ด
๊ณต์ง€/์˜ˆ์‚ฐ ๊ณต๊ฐœ src/pages/Notices/* src/components/notices/* src/api/notices.js
๊ณต์ง€(ํ•™๊ต ์†Œ๊ฐœ) src/pages/Notices/SchoolInfo/* SchoolInfoTabs, ํŽ˜์ด์ง€๋ณ„ CSS Module ์—†์Œ (์ •์  ํ•™๊ต ์†Œ๊ฐœ ์ฝ˜ํ…์ธ  + ๊ณต์‹ ์ถœ์ฒ˜ ๋งํฌ)
์ž์œ ๊ฒŒ์‹œํŒ src/pages/Community/FreeBoard/* src/components/freeboard/* src/api/community.js
์ธ์„ฑ ๊ฐ€์น˜ PICK! src/pages/Community/ValuePick/* src/components/Community/ValuePick/* src/api/valuePick.js
๋™์•„๋ฆฌ ๋ชจ์ง‘ src/pages/Community/ClubRecruit/* src/components/clubRecruit/* src/api/clubRecruit.js
์„ ํƒ๊ณผ๋ชฉ ๋ณ€๊ฒฝ src/pages/Community/Subjects/* src/components/subjects/* src/api/subjectChanges.js
์ฒญ์› src/pages/Community/Petition/* src/components/petition/* src/api/petition.js
์„ค๋ฌธ ํ’ˆ์•—์ด src/pages/Community/SurveyExchange/* src/components/survey/* src/api/survey.js
ํˆฌํ‘œ src/pages/Community/Vote/* src/components/vote/* src/api/vote.js
BOSPI src/pages/Community/Bospi/BospiPage.jsx BospiPage.module.css์˜ ํ˜„์žฌ ์ง€์ˆ˜/์˜ˆ์ธก/๋žญํ‚น/์ง€์ˆ˜๋ณด๋“œ ์„น์…˜ src/api/bospi.js
์Šคํ„ฐ๋”” ์œ— ๋ฒ”์„œ src/pages/Community/StudyWithBeomseo/StudyWithBeomseoPage.jsx StudyWithBeomseoPage.module.css์˜ ์ ์ˆ˜ํŒ/์˜ˆ์•ฝ ๊ณต๊ฐœ ์„น์…˜ src/api/studyWithBeomseo.js
์ˆ˜ํ•™์—ฌํ–‰ ์ด๋ฒคํŠธ src/pages/Community/FieldTrip/* (FieldTripHubPage, FieldTripClassBoardPage, FieldTripPostDetailPage) src/components/fieldTrip/*, src/features/fieldTrip/* src/api/fieldTrip.js
๋ถ„์‹ค๋ฌผ src/pages/Notices/LostFound/* src/components/lostfound/* src/api/lostFound.js
๊ณฐ์†”๋งˆ์ผ“ src/pages/Community/GomsolMarket/* src/components/gomsolmarket/* src/api/gomsolMarket.js
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(QR ์ฝ”๋“œ ์ƒ์„ฑ๊ธฐ) src/pages/SchoolLifeInfo/QrCodeGenerator/QrCodeGeneratorPage.jsx QrCodeGeneratorPage.module.css, react-qrcode-logo ์—†์Œ (๋ธŒ๋ผ์šฐ์ € ์บ”๋ฒ„์Šค ๊ธฐ๋ฐ˜ ์ƒ์„ฑ/๋‹ค์šด๋กœ๋“œ)
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(์ƒค๋‹ˆ๋งˆ์Šค ์นด๋“œ ์ƒ์„ฑ๊ธฐ) src/pages/SchoolLifeInfo/ShanyCardGenerator/ShanyCardGeneratorPage.jsx ShanyCardGeneratorPage.module.css, react-shany-card-generator ์—†์Œ (๋ธŒ๋ผ์šฐ์ € Blob ๊ธฐ๋ฐ˜ ์ƒ์„ฑ/๋‹ค์šด๋กœ๋“œ)
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(์‹œ๊ฐ„ํ‘œ) src/pages/SchoolLifeInfo/TimetableDownload/* src/components/timetable/* ์—†์Œ (src/components/timetable/timetableTemplates.json ์ •์  ํ…œํ”Œ๋ฆฟ ์‚ฌ์šฉ)
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(ํ‰๊ฐ€๊ณ„ํš์„œ) src/pages/SchoolLifeInfo/EvaluationPlans/* ์—†์Œ ์—†์Œ (public/evaluation-plans/* ์ •์  HWP ์‚ฌ์šฉ)
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(์˜ค๋Š˜์˜ ๊ธ‰์‹) src/pages/SchoolLifeInfo/Meal/MealPage.jsx src/components/MealCard/*, src/features/meals/* src/api/meals.js, src/api/mealNotifications.js
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(ํ•™์‚ฌ ์บ˜๋ฆฐ๋”) src/pages/SchoolLifeInfo/AcademicCalendar/AcademicCalendarPage.jsx src/components/AcademicUpcomingCard/*, src/features/academicCalendar/* ์—†์Œ (src/features/academicCalendar/data.js ์ •์  ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ)
ํ•™๊ต ์ƒํ™œ ์ •๋ณด(์Šคํฌ์ธ ๋ฆฌ๊ทธ ๋ฌธ์ž์ค‘๊ณ„/๋ผ์ธ์—…/๊ฐœ์ธ ์ˆœ์œ„) src/pages/SchoolLifeInfo/SportsLeagueCategory/SportsLeagueCategoryPage.jsx src/features/sportsLeague/* (useSportsLeagueLive, usePlayersStore, TeamLineupPanel, PlayerRankingPanel) src/api/sportsLeague.js

4.1 ๊ณต์ง€/์˜ˆ์‚ฐ ๊ณต๊ฐœ ํŒŒ์ผ ๊ตฌ์„ฑ

ํŒŒ์ผ ์—ญํ• 
src/pages/Notices/index.jsx /notices/*๋ฅผ NoticeCenterPage์™€ BudgetBoardPage๋กœ ๋ถ„๊ธฐ
src/pages/Notices/NoticeCenter/NoticeCenterPage.jsx ํ•™๊ต/ํ•™์ƒํšŒ ๊ณต์ง€ ์ „์šฉ ์…ธ๊ณผ ์ค‘์ฒฉ ๋ผ์šฐํŠธ
src/pages/Notices/BudgetBoard/BudgetBoardPage.jsx ์˜ˆ์‚ฐ ๊ณต๊ฐœ ์„ค์ • ๋กœ๋“œ, ์—ฐ๋„ ์ „ํ™˜, ์›” ํƒญ, ์˜ˆ์‚ฐ ๊ณต๊ฐœ ์ค‘์ฒฉ ๋ผ์šฐํŠธ
src/pages/Notices/BudgetBoard/BudgetListView.jsx category='budget', budgetYear, budgetMonth ํ•„ํ„ฐ ๊ธฐ๋ฐ˜ ์›”๋ณ„ ๋ฆฌ์ŠคํŠธ
src/pages/Notices/BudgetBoard/BudgetDetailView.jsx ์˜ˆ์‚ฐ ๊ณต๊ฐœ ์ƒ์„ธ, ๋Œ“๊ธ€/๋ฐ˜์‘/์ฒจ๋ถ€ ์žฌ์‚ฌ์šฉ
src/pages/Notices/BudgetBoard/BudgetComposeView.jsx ๊ฒฝ๋กœ ๊ธฐ๋ฐ˜ ํšŒ๊ณ„์—ฐ๋„/์›”์„ ๊ณ ์ •ํ•œ ์ž‘์„ฑยท์ˆ˜์ • ํ™”๋ฉด
src/pages/Notices/BudgetBoard/budgetUtils.js 03~02 ํšŒ๊ณ„ ์‚ฌ์ดํด ๊ณ„์‚ฐ ๋ฐ ๋ผ๋ฒจ/๊ฒฝ๋กœ ์œ ํ‹ธ
src/pages/Notices/SchoolInfo/index.jsx /notices/school-info/* ํ•˜์œ„ ํ•™๊ต ์†Œ๊ฐœ ๋ผ์šฐํ„ฐ์™€ ์ž˜๋ชป๋œ ํ•™๊ต ์†Œ๊ฐœ ๊ฒฝ๋กœ 404 ์ฒ˜๋ฆฌ
src/pages/Notices/SchoolInfo/SchoolInfoTabs.jsx ๋ฒ”์„œ๊ณ ยท์ฒœ์ƒ๊ณ  ์†Œ๊ฐœ ํƒญ๊ณผ ํ™œ์„ฑ ๊ฒฝ๋กœ ํŒ์ •
src/pages/Notices/SchoolInfo/BeomseoInfoPage.jsx ๋ฒ”์„œ๊ณ  ๊ต์œก ๋ฐฉํ–ฅ, ์ƒ์ง•, ํ˜„ํ™ฉ, ์—ฐํ˜, ์œ„์น˜, ๊ณต์‹ ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ์ •์  ๋ Œ๋”๋ง
src/pages/Notices/SchoolInfo/CSHSInfoPage.jsx ์ฒœ์ƒ๊ณ  ์†Œ๊ฐœ ์นด๋“œ์™€ ๋‹ค์Œ ํ•™๊ต ํ–‰์‚ฌ D-Day ๋ Œ๋”๋ง
src/components/notices/NoticeToolbar.jsx showAttributeFilters, sortOptions, searchPlaceholder๋กœ budget ๋ณด๋“œ ํ™•์žฅ
src/components/notices/NoticeList.jsx emptyStateProps, cardProps๋กœ budget ์ „์šฉ ๋น„์–ด ์žˆ์Œ/์นด๋“œ ๋ Œ๋”๋ง ์žฌ์‚ฌ์šฉ
src/components/notices/NoticeCard.jsx hideBadges, hideTags๋กœ ์˜ˆ์‚ฐ ๊ณต๊ฐœ ์นด๋“œ ํ‘œํ˜„ ๋‹จ์ˆœํ™”
src/components/notices/EmptyState.jsx createPath, title, description override ์ง€์›

4.2 ์Šคํฌ์ธ ๋ฆฌ๊ทธ feature ์Šฌ๋ผ์ด์Šค (src/features/sportsLeague/*)

ํŒŒ์ผ ์—ญํ• 
data.js ๊ธฐ๋ณธ/์ด์ „ ์นดํ…Œ๊ณ ๋ฆฌ ID, API ๋กœ๋”ฉ ์ „ ๋Œ€์ฒด ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก, ์ด๋ฒคํŠธ ํ…œํ”Œ๋ฆฟ, ์šด์˜์ง„ ์—ญํ•  ์ƒ์ˆ˜
useSportsLeagueLive.js category ๋ณ€๊ฒฝ ์‹œ ๊ธฐ์กด snapshot ์ดˆ๊ธฐํ™” + snapshot ์กฐํšŒ + SSE ๊ตฌ๋… + ์ด๋ฒคํŠธ CRUD orchestration
usePlayersStore.js ์„ ์ˆ˜ ๋ผ์ธ์—…/๊ฐœ์ธ ์ˆœ์œ„ ์ „์šฉ ์ƒํƒœ ํ›… (getPlayers, add/remove/stat)
TeamLineupPanel.jsx ํŒ€๋ณ„ ๋ผ์ธ์—… ํƒญ UI, ํŒ€ ์„ ํƒ/์„ ์ˆ˜ ์ถ”๊ฐ€ยท์‚ญ์ œ
PlayerRankingPanel.jsx ๊ฐœ์ธ๋ณ„ ์ˆœ์œ„ ํƒญ UI, ๋“์ /์–ด์‹œ์ŠคํŠธ ์ •๋ ฌ ๋ฐ inline +/- ์กฐ์ •
utils.js ๊ฒฝ๊ธฐ ์ •๋ ฌ, ํ˜„์žฌ/๋‹ค์Œ ๊ฒฝ๊ธฐ ํŒ๋ณ„, ์ˆœ์œ„ ๊ณ„์‚ฐ, tone/label ํ—ฌํผ

5. ์ปจํ…์ŠคํŠธ ์ฑ…์ž„

src/context/AuthContext.jsx

  • ์„ธ์…˜ ์ดˆ๊ธฐํ™”: authApi.getMe()
  • ์ธ์ฆ ์•ก์…˜: login, register, logout
  • ๋งŒ๋ฃŒ ์ด๋ฒคํŠธ ๊ตฌ๋…: AUTH_EXPIRED_EVENT ์ˆ˜์‹  ์‹œ ์‚ฌ์šฉ์ž ์ƒํƒœ ์ดˆ๊ธฐํ™”
  • ๋ถ„์„ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ: ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต/์‹คํŒจ ํŠธ๋ž˜ํ‚น ํ˜ธ์ถœ

src/context/ThemeContext.jsx

  • ํ…Œ๋งˆ ์ƒํƒœ(light/dark) ์œ ์ง€
  • localStorage + ์‹œ์Šคํ…œ ํ…Œ๋งˆ ๊ฐ์ง€
  • document.documentElement[data-theme] ๋™๊ธฐํ™”

src/context/NetworkStatusContext.jsx

  • ๋ธŒ๋ผ์šฐ์ € online/offline ์ด๋ฒคํŠธ์™€ app:network-request-failed ์ปค์Šคํ…€ ์ด๋ฒคํŠธ๋ฅผ ํ•จ๊ป˜ ๊ตฌ๋…
  • /api/health ์žฌํ™•์ธ ๊ฒฐ๊ณผ๋กœ ์‹ค์ œ API ๋„๋‹ฌ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํŒ์ •
  • OfflineGate๊ฐ€ ์‚ฌ์šฉํ•  isOffline, lastSource, recheckConnection() ์ œ๊ณต

src/context/PwaInstallContext.jsx

  • beforeinstallprompt, appinstalled, display-mode: standalone ์ƒํƒœ๋ฅผ ํ†ตํ•ฉ ๊ด€๋ฆฌ
  • iOS Safari ์ˆ˜๋™ ์„ค์น˜ ๊ฒฝ๋กœ(isIosManualInstall)์™€ ์ผ๋ฐ˜ ์„ค์น˜ ํ”„๋กฌํ”„ํŠธ ๊ฒฝ๋กœ๋ฅผ ๋ถ„๋ฆฌ
  • ์„ค์น˜ CTA๊ฐ€ ์‚ฌ์šฉํ•  canInstall, promptInstall(), helpOpen ์ƒํƒœ ์ œ๊ณต

6. API ๊ณ„์ธต ๊ตฌ์กฐ

๊ตฌ๋ถ„ ์†Œ์Šค ์˜ค๋ธŒ ํŠธ๋ฃจ์Šค ํŒŒ์ผ ์„ค๋ช…
๊ณตํ†ต HTTP ํด๋ผ์ด์–ธํŠธ src/api/auth.js Axios ์ธ์Šคํ„ด์Šค, CSRF ํ—ค๋”, 401 refresh ์žฌ์‹œ๋„, transport ์‹คํŒจ ์‹œ ์˜คํ”„๋ผ์ธ ์ด๋ฒคํŠธ ๋ฐœํ–‰
FastAPI HTTP ํด๋ผ์ด์–ธํŠธ src/api/fastapiClient.js FastAPI origin ์ „์šฉ Axios ์ธ์Šคํ„ด์Šค, CSRF ํ—ค๋”, transport ์‹คํŒจ ์ „ํŒŒ
๊ธฐ๋Šฅ API src/api/*.js ๊ธฐ๋Šฅ๋ณ„ endpoint ๋ž˜ํ•‘ ๋ฐ ์‘๋‹ต ์ •๊ทœํ™”
์‘๋‹ต ์ •๊ทœํ™” src/api/normalizers.js ํŽ˜์ด์ง€๋„ค์ด์…˜/์—…๋กœ๋“œ URL ๋ณด์ •

์ƒ์„ธ ๋ฉ”์„œ๋“œ/์—”๋“œํฌ์ธํŠธ๋Š” frontend-api-reference.md๋ฅผ ์ฐธ๊ณ ํ•ฉ๋‹ˆ๋‹ค.

7. ๋ณด์•ˆ ๊ฒฝ๊ณ„ ํŒŒ์ผ ์ง‘ํ•ฉ (src/security/*)

ํŒŒ์ผ ์ฑ…์ž„
src/security/urlPolicy.js ์™ธ๋ถ€ ๋งํฌ/์˜คํ”ˆ์ฑ„ํŒ…/์—์…‹ URL ์•ˆ์ „์„ฑ ๊ฒ€์ฆ
src/security/htmlSanitizer.js DOMPurify ๊ธฐ๋ฐ˜ ๋ฆฌ์น˜ HTML sanitize
src/security/surveySchemaSanitizer.js ์„ค๋ฌธ ์Šคํ‚ค๋งˆ์˜ link/src ํ•„๋“œ sanitize
src/security/csvSanitizer.js CSV formula injection ๋ฐฉ์–ด
src/components/security/SafeHtml.jsx sanitize ์ดํ›„ dangerouslySetInnerHTML ๋ Œ๋”๋ง ๊ฒฝ๊ณ„

8. ๋ ˆ๊ฑฐ์‹œ/ํ˜ธํ™˜ ๋ ˆ์ด์–ด

ํŒŒ์ผ ๋ชฉ์ 
src/pages/MainPage/index.js MainPage.jsx ์ง„์ž…์  ์žฌ-export

์ƒˆ ์ฝ”๋“œ์—์„œ๋Š” ํด๋” ๊ธฐ๋ฐ˜ ์—”ํŠธ๋ฆฌ(src/pages/Notices/index.jsx, src/pages/SchoolLifeInfo/index.jsx)๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

9. ์˜จ๋ณด๋”ฉ ๊ถŒ์žฅ ์ฝ๊ธฐ ์ˆœ์„œ

  1. src/main.jsx
  2. src/App.jsx
  3. src/layout/AppLayout.jsx
  4. src/context/AuthContext.jsx
  5. src/api/auth.js
  6. src/security/urlPolicy.js + src/security/htmlSanitizer.js
  7. src/pages/Community/index.jsx
  8. ์ž„์˜์˜ ๊ธฐ๋Šฅ 1๊ฐœ ์ˆ˜์ง ์Šฌ๋ผ์ด์Šค(page + component + api) ๋๊นŒ์ง€ ์ถ”์ 

10. ๋ณ€๊ฒฝ ์‹œ ๋™๊ธฐํ™” ๊ทœ์น™

  • ๋ผ์šฐํŠธ ๋ณ€๊ฒฝ: ๋ณธ ๋ฌธ์„œ + frontend-architecture.md ๋™์‹œ ๊ฐฑ์‹ 
  • ๋ผ์šฐํŠธ ๋ณ€๊ฒฝ: ์šด์˜ Nginx SPA allowlist๋„ ํ•จ๊ป˜ ๊ฐฑ์‹ 
  • API ๋ณ€๊ฒฝ: frontend-api-reference.md ๋™์‹œ ๊ฐฑ์‹ 
  • ํŠธ๋ž˜ํ‚น ๋ณ€๊ฒฝ: analytics-tracking.md ๋™์‹œ ๊ฐฑ์‹ 
  • ์—ญํ•  ํ‘œ์‹œ ๋กœ์ง(src/utils/roleDisplay.js) ๋ณ€๊ฒฝ: ๋ณธ ๋ฌธ์„œ ๊ฐฑ์‹ 
  • ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ถ”๊ฐ€/๋ณ€๊ฒฝ: README.md ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ‘œ + src/config/env.js ํ•จ๊ป˜ ๊ฐฑ์‹ 
  • PR ์ „ ์ตœ์ข… ์ ๊ฒ€: team-checklist.md

11. ๊ณต์œ  UI ์ปดํฌ๋„ŒํŠธ

src/components/ ์ค‘ ๊ธฐ๋Šฅ ๋ณด๋“œ ์™ธ์˜ ๊ณต์œ  ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

๋””๋ ‰ํ„ฐ๋ฆฌ ์—ญํ•  ์‚ฌ์šฉ ์œ„์น˜
AnnouncementCard/ ๋ฉ”์ธ ํŽ˜์ด์ง€ ๊ณต์ง€ ์นด๋“œ MainPage
CountdownWidget/ D-Day ์นด์šดํŠธ๋‹ค์šด ์œ„์ ฏ MainPage
MealCard/ ๊ธ‰์‹ ์ •๋ณด ์นด๋“œ MainPage
QuickLinkCard/ ๋ฐ”๋กœ๊ฐ€๊ธฐ ์นด๋“œ MainPage
RoleName/ ์—ญํ•  ๊ธฐ๋ฐ˜ ๋‹‰๋„ค์ž„ ๋ Œ๋”๋ง ๊ฒŒ์‹œํŒ ์ƒ์„ธ/๋ชฉ๋ก ์ „์—ญ
security/SafeHtml.jsx sanitize ํ›„ dangerouslySetInnerHTML ๋ Œ๋”๋ง ๊ฒฝ๊ณ„ ๋ฆฌ์น˜ HTML ์ถœ๋ ฅ ์ „์—ญ

11.1 ์‹œ๊ฐ„ํ‘œ ๋‹ค์šด๋กœ๋“œ ๋ชจ๋“ˆ

ํ•™๊ต ์ƒํ™œ ์ •๋ณด > ์‹œ๊ฐ„ํ‘œ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์€ ์ •์  ํ…œํ”Œ๋ฆฟ + SVG ๋ Œ๋”๋ง ์กฐํ•ฉ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—ญํ• 
src/pages/SchoolLifeInfo/TimetableDownload/TimetableDownloadPage.jsx ํ•™๋…„/๋ฐ˜ ์„ ํƒ, ์ž…๋ ฅ ์ƒํƒœ, ๋‹ค์šด๋กœ๋“œ ์•ก์…˜ orchestration
src/components/timetable/TimetableControls.jsx ํ•™๋…„/๋ฐ˜ ๋“œ๋กญ๋‹ค์šด๊ณผ ์„ ํƒ๊ณผ๋ชฉ ์ž…๋ ฅ ํผ
src/components/timetable/TimetablePreview.jsx ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์นด๋“œ์™€ SVG ํ”„๋ฆฌ๋ทฐ ๋ž˜ํผ
src/components/timetable/TimetableSvg.jsx ์‹œ๊ฐ„ํ‘œ SVG ๋ Œ๋”๋ง
src/components/timetable/exportTimetablePng.js SVG โ†’ PNG ๋‹ค์šด๋กœ๋“œ
src/components/timetable/timetableUtils.js ํ…œํ”Œ๋ฆฟ ์กฐํšŒ, ๊ธ€์ž ํฌ๊ธฐ ๊ณ„์‚ฐ, ํฐํŠธ ๋กœ๋”ฉ ์œ ํ‹ธ
src/components/timetable/timetableTemplates.json ๋ฐ˜๋ณ„ ์‹œ๊ฐ„ํ‘œ ํ…œํ”Œ๋ฆฟ ๋ฐ์ดํ„ฐ

11.2 ํ‰๊ฐ€๊ณ„ํš์„œ ๋‹ค์šด๋กœ๋“œ ๋ชจ๋“ˆ

ํ•™๊ต ์ƒํ™œ ์ •๋ณด > ํ‰๊ฐ€๊ณ„ํš์„œ ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ์€ ๋ฐฑ์—”๋“œ API ์—†์ด frontend/public/evaluation-plans/์˜ HWP ์›๋ณธ ํŒŒ์ผ์„ ์ง์ ‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—ญํ• 
src/pages/SchoolLifeInfo/EvaluationPlans/EvaluationPlansPage.jsx ํ•™๋…„๋ณ„ ๋‹ค์šด๋กœ๋“œ ์นด๋“œ์™€ ๊ณต๊ณต๋ˆ„๋ฆฌ ์ œ3์œ ํ˜• ๊ณ ์ง€ ๋ Œ๋”๋ง
src/pages/SchoolLifeInfo/EvaluationPlans/EvaluationPlansPage.module.css ๋‹ค์šด๋กœ๋“œ ์นด๋“œ, ๊ณต๊ณต๋ˆ„๋ฆฌ ๊ณ ์ง€, ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ
public/evaluation-plans/*.hwp ํ•™๊ต์•Œ๋ฆฌ๋ฏธ ์›๋ณธ ํ‰๊ฐ€๊ณ„ํš์„œ ํŒŒ์ผ
public/kogl/img_opentype03.jpg ๊ณต๊ณต๋ˆ„๋ฆฌ ์ œ3์œ ํ˜• ํ‘œ์‹œ ๋งˆํฌ

ํ‰๊ฐ€๊ณ„ํš์„œ HWP ํŒŒ์ผ๊ณผ ๊ณต๊ณต๋ˆ„๋ฆฌ ๋งˆํฌ๋Š” GPL-3.0 ์ฝ”๋“œ ๋ผ์ด์„ ์Šค๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฃจํŠธ์˜ THIRD_PARTY_NOTICES.md์— ๋ช…์‹œํ•œ ๊ณต๊ณต๋ˆ„๋ฆฌ ์ œ3์œ ํ˜• ์กฐ๊ฑด์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

11.3 QR ์ฝ”๋“œ ์ƒ์„ฑ๊ธฐ ๋ชจ๋“ˆ

ํ•™๊ต ์ƒํ™œ ์ •๋ณด > QR ์ฝ”๋“œ ์ƒ์„ฑ๊ธฐ ๊ธฐ๋Šฅ์€ ๋ฐฑ์—”๋“œ API ์—†์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ QR ์ƒ์„ฑ๊ณผ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ๋๋‚ด๋Š” ํด๋ผ์ด์–ธํŠธ ์ „์šฉ ์œ ํ‹ธ๋ฆฌํ‹ฐ์ž…๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—ญํ• 
src/pages/SchoolLifeInfo/QrCodeGenerator/QrCodeGeneratorPage.jsx QR ๋‚ด์šฉ, ์˜ค๋ฅ˜ ๋ณด์ • ๋‹จ๊ณ„, ์ƒ‰์ƒ, ๋กœ๊ณ , ํŒจํ„ด, ๋ˆˆ ์Šคํƒ€์ผ, ๋‹ค์šด๋กœ๋“œ ํ˜•์‹ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  QRCode ์ปดํฌ๋„ŒํŠธ์— props๋กœ ์ „๋‹ฌ
src/pages/SchoolLifeInfo/QrCodeGenerator/QrCodeGeneratorPage.module.css ์ขŒ์ธก ์„ค์ • ํŒจ๋„, ์šฐ์ธก sticky ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ์ปฌ๋Ÿฌ ์ž…๋ ฅ, ํ† ๊ธ€/๋ผ๋””์˜ค, ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๋ฐ˜์‘ํ˜• ์Šคํƒ€์ผ
src/pages/SchoolLifeInfo/index.jsx /school-info/qr-generator ์ง€์—ฐ ๋กœ๋”ฉ ๋ผ์šฐํŠธ ๋“ฑ๋ก
src/pages/SchoolLifeInfo/SchoolInfoHub/SchoolInfoHub.jsx ํ•™๊ต ์ƒํ™œ ์ •๋ณด ํ—ˆ๋ธŒ ์นด๋“œ ๋“ฑ๋ก
src/components/Header/Header.jsx ์ „์—ญ ํ•™๊ต ์ƒํ™œ ์ •๋ณด ๋ฉ”๋‰ด ๋งํฌ ๋“ฑ๋ก
src/seo/policy.js QR ์ƒ์„ฑ๊ธฐ ์ •์  SEO/sitemap/prerender ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋“ฑ๋ก

๋™์ž‘ ๋ฉ”๋ชจ:

  • react-qrcode-logo๊ฐ€ QR ์บ”๋ฒ„์Šค๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ  ์ฐธ์กฐ(ref)์˜ download() ๋ฉ”์„œ๋“œ๋กœ PNG/JPG/WebP ์ €์žฅ์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค์šด๋กœ๋“œ API๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์บ”๋ฒ„์Šค toDataURL() ๊ธฐ๋ฐ˜ ๋Œ€์ฒด ๊ฒฝ๋กœ๋กœ ๊ฐ™์€ ํŒŒ์ผ ํ˜•์‹์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ๋กœ๊ณ  ๋˜๋Š” ์—…๋กœ๋“œ ๋กœ๊ณ ๊ฐ€ ์„ ํƒ๋˜๋ฉด ์ค‘์•™ ๋ชจ๋“ˆ ์†์‹ค์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ์˜ค๋ฅ˜ ๋ณด์ • ๋‹จ๊ณ„๊ฐ€ 4๋‹จ๊ณ„๋กœ ๊ณ ์ •๋ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ์—…๋กœ๋“œ ๋กœ๊ณ ๋Š” FileReader.readAsDataURL()๋กœ ๋ณ€ํ™˜ํ•ด ์„œ๋ฒ„ ์—…๋กœ๋“œ ์—†์ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ ๋‹ค์šด๋กœ๋“œ์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

11.4 ์ƒค๋‹ˆ๋งˆ์Šค ์นด๋“œ ์ƒ์„ฑ๊ธฐ ๋ชจ๋“ˆ

ํ•™๊ต ์ƒํ™œ ์ •๋ณด > ์ƒค๋‹ˆ๋งˆ์Šค ์นด๋“œ ์ƒ์„ฑ๊ธฐ ๊ธฐ๋Šฅ์€ ๋ฐฑ์—”๋“œ API ์—†์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ƒค๋‹ˆ๋งˆ์Šค ์Šคํƒ€์ผ ์ด๋ฆ„ ๋ ˆ์ด์–ด์™€ ์นด๋“œ ํ•ฉ์„ฑ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—ญํ• 
src/pages/SchoolLifeInfo/ShanyCardGenerator/ShanyCardGeneratorPage.jsx ์ƒ์„ฑ ๋ชจ๋“œ, ๋ ˆ์–ด๋ฆฌํ‹ฐ, ํ…์ŠคํŠธ, ์—…๋กœ๋“œ ์ด๋ฏธ์ง€, ์ด๋ฆ„ ์œ„์น˜, ํŒŒ์ผ ํ˜•์‹, ๋ฐฐ์œจ, ๋ฐฐ๊ฒฝ์ƒ‰ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  react-shany-card-generator ๋ Œ๋”๋ง/๋‹ค์šด๋กœ๋“œ API๋ฅผ ํ˜ธ์ถœ
src/pages/SchoolLifeInfo/ShanyCardGenerator/ShanyCardGeneratorPage.module.css ํ”„๋ฆฌ์…‹ ๋ฐ”, ์„ค์ • ํŒจ๋„, sticky ๋ฏธ๋ฆฌ๋ณด๊ธฐ, ์—…๋กœ๋“œ ์ž…๋ ฅ, ์Šฌ๋ผ์ด๋”, ์ƒํƒœ ๋ฉ”์‹œ์ง€ ๋ฐ˜์‘ํ˜• ์Šคํƒ€์ผ
src/pages/SchoolLifeInfo/index.jsx /school-info/shany-card-generator ์ง€์—ฐ ๋กœ๋”ฉ ๋ผ์šฐํŠธ ๋“ฑ๋ก
src/pages/SchoolLifeInfo/SchoolInfoHub/SchoolInfoHub.jsx ํ•™๊ต ์ƒํ™œ ์ •๋ณด ํ—ˆ๋ธŒ ์นด๋“œ ๋“ฑ๋ก
src/components/Header/Header.jsx ์ „์—ญ ํ•™๊ต ์ƒํ™œ ์ •๋ณด ๋ฉ”๋‰ด ๋งํฌ ๋“ฑ๋ก
src/seo/policy.js ์ƒค๋‹ˆ๋งˆ์Šค ์นด๋“œ ์ƒ์„ฑ๊ธฐ ์ •์  SEO/sitemap/prerender ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋“ฑ๋ก
package.json / package-lock.json react-shany-card-generator์™€ ์ „์ด ์˜์กด์„ฑ html-to-image ์ถ”๊ฐ€

๋™์ž‘ ๋ฉ”๋ชจ:

  • ์ด๋ฆ„ ๋ ˆ์ด์–ด ๋ชจ๋“œ๋Š” ShanyCardNameLayer ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ renderCardNameBlob() ๋‹ค์šด๋กœ๋“œ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์นด๋“œ ํ•ฉ์„ฑ ๋ชจ๋“œ๋Š” ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ShanyCardPreview์™€ renderShanyCardBlob()์— ์ „๋‹ฌํ•˜๋ฉฐ, ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์•ˆ์—์„œ X/Y ์œ„์น˜๋ฅผ ๋ณด์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ensureShanyCardFontElements()๋ฅผ ํŽ˜์ด์ง€ ๋งˆ์šดํŠธ ์‹œ ํ˜ธ์ถœํ•ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ export ์ด๋ฏธ์ง€์˜ ํฐํŠธ ํ‘œํ˜„์„ ๋งž์ถฅ๋‹ˆ๋‹ค.
  • ํŒŒ์ผ๋ช…์€ ์นด๋“œ ์ด๋ฆ„์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค๋˜ ํŒŒ์ผ ์‹œ์Šคํ…œ์—์„œ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๋ฌธ์ž๋ฅผ -๋กœ ์น˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ถœ๋ ฅ ๋ฐฐ์œจ, ์บก์ฒ˜ ๋ฐฐ์œจ, ์ด๋ฆ„ ๋ฐฐ์œจ์€ UI์—์„œ๋Š” 0~500% ๋ฒ”์œ„๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ ์‹ค์ œ ๋ Œ๋”๋ง์—๋Š” 0 ํฌ๊ธฐ ์ด๋ฏธ์ง€๊ฐ€ ์ƒ๊ธฐ์ง€ ์•Š๋„๋ก ์ตœ์†Œ ์–‘์ˆ˜ ๋ฐฐ์œจ๋กœ ๋ณด์ •ํ•ฉ๋‹ˆ๋‹ค.

11.5 ๊ณต์ง€ ์˜์—ญ ํ•™๊ต ์†Œ๊ฐœ ๋ชจ๋“ˆ

๊ณต์ง€์‚ฌํ•ญ > ํ•™๊ต ์†Œ๊ฐœ๋Š” ๊ณต์ง€ CRUD์™€ ๋ณ„๊ฐœ๋กœ ์šด์˜๋˜๋Š” ์ •์  ์†Œ๊ฐœ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. ๊ณต์ง€ ๋“œ๋กญ๋‹ค์šด์—์„œ๋Š” /notices/school-info๋กœ ์ง„์ž…ํ•˜๊ณ , ๋ผ์šฐํ„ฐ๋Š” ๊ธฐ๋ณธ๊ฐ’์„ ๋ฒ”์„œ๊ณ  ์†Œ๊ฐœ(/notices/school-info/bshs-info)๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค.

ํŒŒ์ผ ์—ญํ• 
src/pages/Notices/SchoolInfo/index.jsx ๊ธฐ๋ณธ ๊ฒฝ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ, ๋ฒ”์„œ๊ณ /์ฒœ์ƒ๊ณ  ์†Œ๊ฐœ ๋ผ์šฐํŠธ, ํ•™๊ต ์†Œ๊ฐœ ์ „์šฉ 404 ๊ตฌ์„ฑ
src/pages/Notices/SchoolInfo/SchoolInfoTabs.jsx ๋ฒ”์„œ๊ณ ์™€ ์ฒœ์ƒ๊ณ  ํƒญ์˜ ๋‹จ์ผ ๊ฒฝ๋กœ ๋ชฉ๋ก๊ณผ ํ™œ์„ฑ ํŒ์ •
src/pages/Notices/SchoolInfo/BeomseoInfoPage.jsx ๋ฒ”์„œ๊ณ  ๊ณต์‹ ์ž๋ฃŒ๋ฅผ ํ•™์ƒ์šฉ ์„น์…˜์œผ๋กœ ์žฌ๊ตฌ์„ฑํ•˜๊ณ  ์›๋ฌธ ์ถœ์ฒ˜ ๋งํฌ ์ œ๊ณต
src/pages/Notices/SchoolInfo/CSHSInfoPage.jsx ์ฒœ์ƒ๊ณ  ์†Œ๊ฐœ ๋ฌธ๊ตฌ, ํ•ต์‹ฌ ๊ฐ€์น˜ ์นด๋“œ, ๋‹ค์Œ ํ•™๊ต ํ–‰์‚ฌ D-Day ์ œ๊ณต
src/seo/policy.js ๋‘ ์†Œ๊ฐœ ๊ฒฝ๋กœ์˜ title, description, breadcrumbs, sitemap/prerender ์„ค์ •๊ณผ ๋ฒ”์„œ๊ณ  HighSchool JSON-LD

๋™์ž‘ ๋ฉ”๋ชจ:

  • ์ด ๋ชจ๋“ˆ์€ ๋ฐฑ์—”๋“œ API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฐฐํฌ ์ „ ์ •์  ์ฝ˜ํ…์ธ ์™€ ๊ณต์‹ ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ์ฝ”๋“œ ๋ฆฌ๋ทฐ์—์„œ ํ•จ๊ป˜ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • main.jsx์˜ prerender preload ๋ชฉ๋ก์— ํ•™๊ต ์†Œ๊ฐœ ๋ผ์šฐํ„ฐ์™€ ๋‘ ํŽ˜์ด์ง€๊ฐ€ ํฌํ•จ๋˜์–ด Suspense ๊ฒฝ๊ณ„๋ฅผ ์ •์  HTML ์ƒ์„ฑ ์‹œ ํ•ด์†Œํ•ฉ๋‹ˆ๋‹ค.
  • ์šด์˜ Nginx allowlist์—๋Š” /notices/school-info, /notices/school-info/bshs-info, /notices/school-info/cshs-info๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ์ง์ ‘ URL ์ง„์ž…์ด 404๋กœ ์˜คํŒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

12. ์œ ํ‹ธ๋ฆฌํ‹ฐ & ์„ค์ • ํŒŒ์ผ

src/config/env.js

VITE_* ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ฝ์–ด ํƒ€์ž…์ด ๋ณด์žฅ๋œ ์ƒ์ˆ˜๋กœ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

Export ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํƒ€์ž… ๊ธฐ๋ณธ๊ฐ’
APP_NAME VITE_APP_NAME string beomseo.in
API_BASE_URL VITE_API_URL string http://localhost:5000
FASTAPI_BASE_URL VITE_SPORTS_LEAGUE_API_URL string API_BASE_URL fallback
VALUE_PICK_BOARD_ENABLED VITE_VALUE_PICK_BOARD_ENABLED boolean true
CLUB_RECRUIT_BOARD_ENABLED VITE_CLUB_RECRUIT_BOARD_ENABLED boolean true
SUBJECT_CHANGES_BOARD_ENABLED VITE_SUBJECT_CHANGES_BOARD_ENABLED boolean true
BOSPI_BOARD_ENABLED VITE_BOSPI_BOARD_ENABLED boolean true
STUDY_WITH_BEOMSEO_BOARD_ENABLED VITE_STUDY_WITH_BEOMSEO_BOARD_ENABLED boolean true
FIELD_TRIP_BOARD_ENABLED VITE_FIELD_TRIP_BOARD_ENABLED boolean true
COMMUNITY_BOARD_FEATURE_FLAGS (์œ„ ๋ณด๋“œ ํ”Œ๋ž˜๊ทธ ๋ฌถ์Œ) Readonly<Record<string, boolean>> ๋ชจ๋‘ true
UPLOAD_MAX_ATTACHMENTS VITE_UPLOAD_MAX_ATTACHMENTS number 5
UPLOAD_MAX_IMAGES VITE_UPLOAD_MAX_IMAGES number 5
UPLOAD_MAX_FILE_SIZE_MB VITE_UPLOAD_MAX_FILE_SIZE_MB number 10
UPLOAD_MAX_FILE_SIZE_BYTES (๊ณ„์‚ฐ๊ฐ’) number 10 * 1024 * 1024
FIELD_TRIP_VIDEO_MAX_SIZE_MB VITE_FIELD_TRIP_VIDEO_MAX_SIZE_MB number 500
PETITION_THRESHOLD_DEFAULT VITE_PETITION_THRESHOLD_DEFAULT number 50
ALLOWED_ASSET_HOSTS VITE_ALLOWED_ASSET_HOSTS string[] []

src/pwa/firebaseMessaging.js

Firebase Web Push์™€ foreground ์•Œ๋ฆผ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

Export ์—ญํ• 
isFirebaseMessagingConfigured() ํ•„์ˆ˜ VITE_FIREBASE_* ๊ฐ’ ์ถฉ์กฑ ์—ฌ๋ถ€
isFirebaseMessagingSupported() ํ˜„์žฌ ๋ธŒ๋ผ์šฐ์ €/์„œ๋น„์Šค์›Œ์ปค ํ™˜๊ฒฝ ์ง€์› ์—ฌ๋ถ€
getCurrentFirebaseMessagingToken() ๊ธฐ์กด FCM token ์กฐํšŒ
requestFirebaseMessagingPermissionAndToken() ๊ถŒํ•œ ์š”์ฒญ + token ๋ฐœ๊ธ‰
deleteCurrentFirebaseMessagingToken() ํ˜„์žฌ ๊ธฐ๊ธฐ์˜ token ํ•ด์ œ
startFirebaseForegroundMessageListener() foreground ๋ฉ”์‹œ์ง€๋ฅผ ๋ธŒ๋ผ์šฐ์ € ์•Œ๋ฆผ์œผ๋กœ ํ‘œ์‹œ

src/pwa/mealNotificationInstallationId.js

  • localStorage์— ์ €์žฅ๋˜๋Š” ๋ธŒ๋ผ์šฐ์ €/PWA ์ธ์Šคํ„ด์Šค ์‹๋ณ„์ž ๊ด€๋ฆฌ
  • ๊ธ‰์‹ ์•Œ๋ฆผ ๊ตฌ๋… API์˜ installationId ์ž…๋ ฅ๊ฐ’ ์†Œ์Šค

src/utils/roleDisplay.js

์—ญํ•  ๋ฌธ์ž์—ด์„ ์ •๊ทœํ™”ํ•˜๊ณ , ์ ‘๋‘์–ด/CSS ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ์ž…๋‹ˆ๋‹ค.

Export ์„ค๋ช…
getRoleDisplay({ role, nickname, showPrefix, prefixOverride }) ์ •๊ทœํ™”๋œ ์—ญํ• ์— ๋”ฐ๋ผ displayPrefix, ariaLabel, roleClassName, safeNickname ๋ฐ˜ํ™˜
default getRoleDisplay์™€ ๋™์ผ

์ง€์› ์—ญํ• : admin([๊ด€๋ฆฌ์ž]), student_council([ํ•™์ƒํšŒ]), teacher([๊ต์‚ฌ]), student(์ ‘๋‘์–ด ์—†์Œ)

๋ณ„์นญ ์ง€์›: council, studentcouncil, student-council, student council โ†’ student_council

13. ์Šคํƒ€์ผ ์‹œ์Šคํ…œ ํŒŒ์ผ

src/styles/ ๋””๋ ‰ํ„ฐ๋ฆฌ๋Š” ์ „์—ญ CSS ํ† ํฐ๊ณผ ๋ ˆ์ด์•„์›ƒ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

flowchart TD
    V["variables.css\n๋””์ž์ธ ํ† ํฐ (์ƒ‰์ƒ/ํƒ€์ดํฌ/๊ฐ„๊ฒฉ)"] --> P["primitives.css\n๊ธฐ๋ณธ ์š”์†Œ ๋ฆฌ์…‹/์Šคํƒ€์ผ"]
    P --> G["globals.css\n์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค/์ „์—ญ ์ปดํฌ๋„ŒํŠธ"]
    V --> L["layout.css\n๋ ˆ์ด์•„์›ƒ ๊ทธ๋ฆฌ๋“œ/์ปจํ…Œ์ด๋„ˆ"]
Loading
ํŒŒ์ผ ์—ญํ• 
variables.css CSS custom property (์ƒ‰์ƒ, ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ, ๊ฐ„๊ฒฉ, ๊ทธ๋ฆผ์ž ๋“ฑ) โ€” ๋ผ์ดํŠธ/๋‹คํฌ ํ…Œ๋งˆ ๋ชจ๋‘ ์ •์˜
primitives.css HTML ์š”์†Œ ๋ฆฌ์…‹ + ๊ธฐ๋ณธ ํƒ€์ดํฌ ์Šคํƒ€์ผ
globals.css ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค, ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ
layout.css ์ตœ์ƒ์œ„ ๋ ˆ์ด์•„์›ƒ ๊ทธ๋ฆฌ๋“œ ๊ทœ์น™