Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 63 additions & 28 deletions frontend/src/pages/curriculum/CurriculumPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ function CrownIcon() {
function HonorOfFame({ isAdmin }) {
const [mvp, setMvp] = useState(null);
const [form, setForm] = useState(null);
const [isOpen, setIsOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [saving, setSaving] = useState(false);

const fetchMvp = async () => {
Expand All @@ -352,6 +354,16 @@ function HonorOfFame({ isAdmin }) {
];
const filledEntries = entries.filter(e => mvp[e.key] && mvp[e.key].trim());

const handleEditStart = () => {
setForm(mvp);
setIsEditing(true);
};

const handleCancel = () => {
setForm(mvp);
setIsEditing(false);
};

const handleSave = async () => {
setSaving(true);
try {
Expand All @@ -360,6 +372,7 @@ function HonorOfFame({ isAdmin }) {
body: JSON.stringify(form),
});
await fetchMvp();
setIsEditing(false);
} catch (e) {
} finally {
setSaving(false);
Expand All @@ -368,38 +381,60 @@ function HonorOfFame({ isAdmin }) {

return (
<div className={styles.honorSection}>
<div className={styles.honorTitleRow}>
<CrownIcon />
<span className={styles.honorTitle}>과제 MVP 명예의 전당</span>
<CrownIcon />
<div className={styles.honorHeader} onClick={() => setIsOpen(p => !p)}>
<div className={styles.honorTitleRow}>
<CrownIcon />
<span className={styles.honorTitle}>과제 MVP 명예의 전당</span>
<CrownIcon />
</div>
<img src={Toggle1} className={`${styles.toggleIcon} ${isOpen ? styles.toggleOpen : ''}`} alt="toggle" />
</div>
<hr className={styles.divider} />

{!isAdmin && filledEntries.length > 0 && (
<div className={styles.honorList}>
{filledEntries.map(e => (
<div key={e.key} className={styles.honorItem}>
{e.label}: <span className={styles.honorName}>{mvp[e.key]}</span>
</div>
))}
</div>
)}
{isOpen && (
<div className={styles.honorBody}>
{!isEditing && (
<>
{filledEntries.length > 0 ? (
<div className={styles.honorList}>
{filledEntries.map(e => (
<div key={e.key} className={styles.honorItem}>
{e.label}: <span className={styles.honorName}>{mvp[e.key]}</span>
</div>
))}
</div>
) : (
<div className={styles.honorEmpty}>아직 등록된 MVP가 없어요</div>
)}
{isAdmin && (
<button className={styles.honorEditBtn} onClick={handleEditStart}>수정</button>
)}
</>
)}

{isAdmin && (
<div className={styles.honorEditList}>
{entries.map(e => (
<div key={e.key} className={styles.honorEditRow}>
<label className={styles.honorEditLabel}>{e.label}</label>
<input
className={styles.honorEditInput}
value={form[e.key] || ''}
placeholder="이름을 입력하세요"
onChange={ev => setForm({ ...form, [e.key]: ev.target.value })}
/>
{isAdmin && isEditing && (
<div className={styles.honorEditList}>
{entries.map(e => (
<div key={e.key} className={styles.honorEditRow}>
<label className={styles.honorEditLabel}>{e.label}</label>
<input
className={styles.honorEditInput}
value={form[e.key] || ''}
placeholder="이름을 입력하세요"
onChange={ev => setForm({ ...form, [e.key]: ev.target.value })}
/>
</div>
))}
<div className={styles.honorEditBtns}>
<button className={styles.honorSaveBtn} onClick={handleSave} disabled={saving}>
{saving ? '저장 중...' : '저장'}
</button>
<button className={styles.honorCancelBtn} onClick={handleCancel} disabled={saving}>
취소
</button>
</div>
</div>
))}
<button className={styles.honorSaveBtn} onClick={handleSave} disabled={saving}>
{saving ? '저장 중...' : '저장하기'}
</button>
)}
</div>
)}
</div>
Expand Down
85 changes: 73 additions & 12 deletions frontend/src/pages/curriculum/CurriculumPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,30 @@
border: 1px solid #eee;
border-radius: 20px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
box-sizing: border-box;
}

.honorHeader {
display: grid;
grid-template-columns: 14px 1fr 14px;
align-items: center;
gap: 10px;
cursor: pointer;
}

.honorTitleRow {
grid-column: 2;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}

.honorHeader .toggleIcon {
grid-column: 3;
justify-self: end;
}

.crownIcon {
width: 22px;
height: 22px;
Expand All @@ -93,12 +103,18 @@
color: var(--black);
}

.honorBody {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}

.honorList {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
margin-top: 16px;
}

.honorItem {
Expand All @@ -113,12 +129,35 @@
color: var(--dark);
}

.honorEmpty {
font-family: var(--font-main);
font-size: 0.9rem;
color: #aaa;
}

.honorEditBtn {
margin-top: 14px;
padding: 6px 20px;
background: transparent;
border: 1.5px solid var(--dark);
border-radius: 10px;
color: var(--dark);
font-family: var(--font-main);
font-size: 0.85rem;
cursor: pointer;
}

.honorEditBtn:hover {
background: var(--dark);
color: var(--white);
transition: all ease-in-out 0.2s;
}

.honorEditList {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
margin-top: 20px;
width: 100%;
max-width: 360px;
}
Expand Down Expand Up @@ -151,23 +190,45 @@

.honorEditInput:focus { border-color: var(--dark); }

.honorEditBtns {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 10px;
}

.honorSaveBtn {
margin: 10px auto 0;
padding: 8px 30px;
padding: 6px 20px;
background: transparent;
border: 1.5px solid var(--dark);
border-radius: 10px;
color: var(--dark);
font-family: var(--font-main);
font-size: 0.9rem;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s;
}

.honorSaveBtn:hover { background: var(--dark); color: var(--white); }
.honorSaveBtn:hover {
background: var(--dark);
color: var(--white);
transition: all ease-in-out 0.2s;
}
.honorSaveBtn:disabled { opacity: 0.6; cursor: default; }

.honorCancelBtn {
padding: 6px 20px;
background: transparent;
border: none;
color: #aaa;
font-family: var(--font-main);
font-size: 0.85rem;
cursor: pointer;
}

.honorCancelBtn:hover { color: #777; }
.honorCancelBtn:disabled { opacity: 0.6; cursor: default; }

/* 카드 행 */
.cardsRow {
margin: 0 auto;
Expand Down
Loading