From f0a04c51ff545ad2c46ddbc1530e9829742be666 Mon Sep 17 00:00:00 2001 From: caadiq Date: Sat, 18 Apr 2026 12:27:23 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=AC=EB=B3=BC=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CharacterCard/SymbolCard/검색 패널/탭/요약 카드 전체 이관 - 입력/버튼/진행바/메소·체납·MAX 텍스트 모두 semantic 토큰 - equipped 아닌 심볼 카드 opacity 0.6 유지 Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/features/symbol/Symbol.jsx | 304 +++++++++++++++--------- 1 file changed, 187 insertions(+), 117 deletions(-) diff --git a/frontend/src/features/symbol/Symbol.jsx b/frontend/src/features/symbol/Symbol.jsx index c3c0f2c..109538b 100644 --- a/frontend/src/features/symbol/Symbol.jsx +++ b/frontend/src/features/symbol/Symbol.jsx @@ -63,24 +63,22 @@ function CharacterCard({ char, active, onSelect, onRemove }) { if (e.target.closest('button')) return onSelect() }} - className={`group relative shrink-0 w-36 rounded-xl border cursor-pointer select-none transition ${ - active - ? 'border-emerald-500/40 bg-emerald-500/[0.08]' - : 'border-white/5 hover:border-white/15 bg-gray-950/40 hover:bg-gray-950/60' - }`} + className="group relative shrink-0 w-36 rounded-xl border cursor-pointer select-none" + style={{ + borderColor: active ? 'var(--selected-border)' : 'var(--panel-border)', + background: active ? 'var(--selected-bg)' : 'var(--surface-3)', + }} > - {/* 삭제 (우상단) */} - {/* 내용 */}
{char.character_image ? ( @@ -92,13 +90,19 @@ function CharacterCard({ char, active, onSelect, onRemove }) { draggable={false} /> ) : ( - ? + ? )}
-
+
{char.character_name}
-
+
Lv.{char.character_level} · {char.job_name}
@@ -177,14 +181,23 @@ function SymbolCard({ symbol, equipped, charId }) { }) }, [equipped, isMax, remainingSymbols, daily, weeklyCount, symbol.weekly_default, extra, dailyDone]) + const inputClass = "w-full h-10 rounded-md border px-3 text-base text-right tabular-nums outline-none focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)] disabled:opacity-50" + return ( -
+
-
+
{symbol.image_url && (
-
{symbol.region}
-
- Lv.{level} - / {symbol.max_level} +
{symbol.region}
+
+ Lv.{level} + / {symbol.max_level}
{equipped && !isMax && !effectivelyMax && ( @@ -206,11 +222,16 @@ function SymbolCard({ symbol, equipped, charId }) { type="button" onClick={() => patch({ dailyDone: !dailyDone })} title="오늘 일퀘 완료 여부" - className={`shrink-0 rounded-md h-8 px-3 text-xs font-semibold border transition disabled:opacity-40 disabled:cursor-not-allowed ${ - dailyDone - ? 'bg-emerald-500/20 border-emerald-500/50 text-emerald-300' - : 'bg-red-500/10 border-red-500/40 text-red-300 hover:bg-red-500/20' - }`} + className="shrink-0 rounded-md h-8 px-3 text-xs font-semibold border disabled:opacity-40 disabled:cursor-not-allowed" + style={dailyDone ? { + background: 'var(--selected-bg)', + borderColor: 'var(--selected-border)', + color: 'var(--accent-bright)', + } : { + background: 'var(--danger-bg-hover)', + borderColor: 'var(--icon-danger-border)', + color: 'var(--danger-text)', + }} > {dailyDone ? '금일 일퀘 완료' : '금일 일퀘 미완료'} @@ -221,36 +242,42 @@ function SymbolCard({ symbol, equipped, charId }) {
{isMax ? ( - - 성장치 MAX + + 성장치 MAX ) : effectivelyMax ? ( - - 성장치 {growth} (MAX) / {requireGrowth} + + 성장치 {growth} (MAX) / {requireGrowth} ) : reachableLevel > level ? ( - + 성장치 {growth} / {requireGrowth} ) : ( - + 성장치 {growth} / {requireGrowth} )} {!isMax && !effectivelyMax && ( - + {requireGrowth ? Math.min(Math.floor((growth / requireGrowth) * 100), 100) : 0}% )}
-
+
@@ -261,19 +288,24 @@ function SymbolCard({ symbol, equipped, charId }) { style={{ gridTemplateColumns: symbol.weekly_default > 0 ? '0.7fr 1.3fr 1fr' : '1fr 1fr' }} >
- + patch({ daily: Number(e.target.value.replace(/[^\d]/g, '')) || 0 })} disabled={!interactable} - className="w-full h-10 rounded-md border border-white/10 bg-gray-950 px-3 text-base text-right tabular-nums outline-none focus:border-emerald-500/50 hover:border-white/20 disabled:opacity-50 transition" + className={inputClass} + style={{ + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', + }} />
{symbol.weekly_default > 0 && (
- + patch({ extra: Number(e.target.value.replace(/[^\d]/g, '')) || 0 })} disabled={!interactable} - className="w-full h-10 rounded-md border border-white/10 bg-gray-950 px-3 text-base text-right tabular-nums outline-none focus:border-emerald-500/50 hover:border-white/20 disabled:opacity-50 transition" + className={inputClass} + style={{ + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', + }} />
{/* 정보 */} -
-
- 남은 심볼 - - {equipped && !isMax && !effectivelyMax ? `${remainingSymbols.toLocaleString()}개` : '-'} - -
-
- 필요 메소 - {equipped && !isMax ? ( - - - {remainingMeso.toLocaleString()} +
+ {[ + { label: '남은 심볼', value: equipped && !isMax && !effectivelyMax ? `${remainingSymbols.toLocaleString()}개` : '-', color: 'var(--text-emphasis)' }, + { label: '필요 메소', value: equipped && !isMax ? remainingMeso.toLocaleString() : '-', color: 'var(--warning-text-bright)', tooltip: equipped && !isMax ? formatMesoKorean(remainingMeso) : null }, + { label: '체납 메소', value: equipped && !isMax ? arrearMeso.toLocaleString() : '-', color: 'var(--danger-text)', tooltip: equipped && !isMax ? formatMesoKorean(arrearMeso) : null }, + { label: '남은 일수', value: equipped && !isMax && !effectivelyMax && daysLeft != null ? `${daysLeft.toLocaleString()}일` : '-', color: 'var(--text-emphasis)' }, + { label: '예상 완료일', value: equipped && !isMax && !effectivelyMax && completeDate ? formatKoreanDate(completeDate) : '-', color: equipped && !isMax && !effectivelyMax && completeDate ? 'var(--accent-bright)' : 'var(--text-dim)', strong: true }, + ].map((row, i) => ( +
+ {row.label} + {row.tooltip ? ( + + + {row.value} + + + ) : ( + + {row.value} - - ) : ( - - - )} -
-
- 체납 메소 - {equipped && !isMax ? ( - - - {arrearMeso.toLocaleString()} - - - ) : ( - - - )} -
-
- 남은 일수 - - {equipped && !isMax && !effectivelyMax && daysLeft != null ? `${daysLeft.toLocaleString()}일` : '-'} - -
-
- 예상 완료일 - - {equipped && !isMax && !effectivelyMax && completeDate ? formatKoreanDate(completeDate) : '-'} - -
+ )} +
+ ))}
) @@ -383,7 +404,7 @@ export default function Symbol() { const tab = storedTab || tabs[0]?.key || null const setTab = (t) => { if (selectedCharId) setTabStore(selectedCharId, t) } - + // 각 캐릭터 기본정보(코디 이미지) 새로고침 const basicQueries = useQueries({ queries: characters.map((c) => ({ @@ -526,10 +547,20 @@ export default function Symbol() { return (
{/* 캐릭터 조회 */} -
+
- + @@ -540,18 +571,30 @@ export default function Symbol() { value={addName} onChange={(e) => { setAddName(e.target.value); if (addError) setAddError('') }} placeholder="캐릭터 닉네임으로 장착 심볼 불러오기" - className="w-full h-12 box-border rounded-lg border border-white/10 bg-gray-950 pl-10 pr-4 text-base outline-none focus:border-emerald-500/60 hover:border-white/20 transition" + className="w-full h-12 box-border rounded-lg border pl-10 pr-4 text-base outline-none focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)]" + style={{ + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', + }} />
- {addError &&

{addError}

} + {addError && ( +

{addError}

+ )} {/* 캐릭터 목록 */} {characters.length > 0 && ( @@ -571,25 +614,34 @@ export default function Symbol() { {/* 심볼 타입 탭 */}
- {tabs.map((t) => ( - - ))} + {tabs.map((t) => { + const active = tab === t.key + return ( + + ) + })}
{/* 심볼 카드 그리드 */} @@ -600,27 +652,45 @@ export default function Symbol() {
{/* 전체 요약 */} -
+
-
{tabInfo?.label} 전체 만렙 완료 예상일
-
+
+ {tabInfo?.label} 전체 만렙 완료 예상일 +
+
{overallDate ? formatKoreanDate(overallDate) : '-'}
-
누적 체납 메소
+
누적 체납 메소
-
+
{totalArrearMeso.toLocaleString()}
-
+
-
남은 필요 메소
+
남은 필요 메소
-
+
{totalRequiredMeso.toLocaleString()}