2026-04-13 19:17:49 +09:00
|
|
|
import Select from '../../../components/Select'
|
2026-04-15 20:50:37 +09:00
|
|
|
import { DIFFICULTIES, formatMeso } from '../admin/constants'
|
|
|
|
|
|
|
|
|
|
const LABEL_EN = { easy: 'EASY', normal: 'NORMAL', hard: 'HARD', chaos: 'CHAOS', extreme: 'EXTREME' }
|
2026-04-13 19:17:49 +09:00
|
|
|
|
|
|
|
|
export default function BossSelector({ characterName, bosses, selections, onChange, maxReached, selectedCount, maxPerCharacter }) {
|
|
|
|
|
if (!characterName) {
|
|
|
|
|
return (
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="rounded-2xl border border-dashed p-16 text-center text-sm"
|
|
|
|
|
style={{
|
|
|
|
|
borderColor: 'var(--dashed-border)',
|
|
|
|
|
background: 'var(--skeleton-bg)',
|
|
|
|
|
color: 'var(--text-dim)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-13 19:17:49 +09:00
|
|
|
좌측에서 캐릭터를 선택해주세요
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bosses.length === 0) {
|
|
|
|
|
return (
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="rounded-2xl border border-dashed p-16 text-center text-sm"
|
|
|
|
|
style={{
|
|
|
|
|
borderColor: 'var(--dashed-border)',
|
|
|
|
|
background: 'var(--skeleton-bg)',
|
|
|
|
|
color: 'var(--text-dim)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-13 19:17:49 +09:00
|
|
|
등록된 보스가 없습니다
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="rounded-xl border overflow-hidden flex flex-col h-full"
|
|
|
|
|
style={{
|
|
|
|
|
background: 'var(--panel-bg)',
|
|
|
|
|
borderColor: 'var(--panel-border)',
|
|
|
|
|
boxShadow: 'var(--panel-shadow)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-13 19:17:49 +09:00
|
|
|
{/* 헤더 (고정) */}
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="flex items-center gap-3 px-3 py-3 border-b text-base font-medium shrink-0"
|
|
|
|
|
style={{
|
|
|
|
|
background: 'var(--surface-2)',
|
|
|
|
|
borderColor: 'var(--panel-border)',
|
|
|
|
|
color: 'var(--text-emphasis)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-04-13 19:17:49 +09:00
|
|
|
<div className="w-52 shrink-0">보스</div>
|
|
|
|
|
<div className="flex-1">난이도</div>
|
|
|
|
|
<div className="w-20 shrink-0 text-center">파티원 수</div>
|
|
|
|
|
<div className="w-32 shrink-0 text-right">가격</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/* 목록 (스크롤) */}
|
|
|
|
|
<div className="flex-1 overflow-y-auto min-h-0">
|
2026-04-18 12:15:04 +09:00
|
|
|
<div className="divide-y" style={{ '--tw-divide-opacity': 1 }}>
|
2026-04-13 19:17:49 +09:00
|
|
|
{bosses.map((boss) => {
|
|
|
|
|
const availableDiffs = DIFFICULTIES.filter((d) =>
|
|
|
|
|
boss.difficulties.some((bd) => bd.difficulty === d.key)
|
|
|
|
|
)
|
|
|
|
|
const sel = selections[boss.id]
|
|
|
|
|
const bdInfo = sel ? boss.difficulties.find((bd) => bd.difficulty === sel.difficulty) : null
|
|
|
|
|
const partyN = sel?.party || 1
|
|
|
|
|
const revenue = bdInfo ? Math.floor(bdInfo.crystal_price / partyN) : 0
|
|
|
|
|
|
|
|
|
|
const partyOptions = Array.from({ length: boss.max_party_size }, (_, i) => i + 1).map((n) => ({
|
|
|
|
|
value: n,
|
|
|
|
|
label: `${n}인`,
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
// 한도 도달 + 이 보스가 선택 안 됐으면 비활성화
|
|
|
|
|
const disabled = maxReached && !sel
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={boss.id}
|
2026-04-18 12:15:04 +09:00
|
|
|
className={`flex items-center gap-3 px-3 py-3 border-t first:border-t-0 ${
|
|
|
|
|
disabled ? 'pointer-events-none' : ''
|
2026-04-13 19:17:49 +09:00
|
|
|
}`}
|
2026-04-18 12:15:04 +09:00
|
|
|
style={{
|
|
|
|
|
borderColor: 'var(--panel-border)',
|
|
|
|
|
opacity: disabled ? 'var(--disabled-opacity)' : 1,
|
|
|
|
|
}}
|
2026-04-13 19:17:49 +09:00
|
|
|
>
|
|
|
|
|
{/* 보스 이미지 + 이름 */}
|
|
|
|
|
<div className="flex items-center gap-2.5 w-52 shrink-0">
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="shrink-0 w-11 h-11 rounded-lg overflow-hidden"
|
|
|
|
|
style={{ background: 'var(--surface-nested)' }}
|
|
|
|
|
>
|
2026-04-13 19:17:49 +09:00
|
|
|
<img src={boss.image_url || '/default.png'} alt={boss.name} className="w-full h-full object-cover" />
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-base font-medium leading-tight whitespace-nowrap overflow-hidden text-ellipsis">{boss.name}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 난이도 - 한 줄 고정 */}
|
|
|
|
|
<div className="flex-1 flex items-center gap-2 flex-nowrap min-w-0">
|
|
|
|
|
{availableDiffs.map((d) => {
|
|
|
|
|
const active = sel?.difficulty === d.key
|
2026-04-18 16:10:09 +09:00
|
|
|
const hasVisibleBorder = d.colors.border !== d.colors.bg
|
|
|
|
|
const borderColor = hasVisibleBorder ? d.colors.border : 'rgba(0, 0, 0, 0.55)'
|
2026-04-15 20:50:37 +09:00
|
|
|
const style = {
|
|
|
|
|
background: d.colors.bg,
|
2026-04-18 16:10:09 +09:00
|
|
|
borderColor,
|
|
|
|
|
borderWidth: '1.5px',
|
2026-04-15 20:50:37 +09:00
|
|
|
color: d.colors.text,
|
2026-04-18 12:15:04 +09:00
|
|
|
filter: active ? 'none' : 'var(--inactive-filter)',
|
2026-04-15 20:50:37 +09:00
|
|
|
}
|
2026-04-13 19:17:49 +09:00
|
|
|
return (
|
2026-04-15 20:50:37 +09:00
|
|
|
<button
|
|
|
|
|
key={d.key}
|
|
|
|
|
type="button"
|
|
|
|
|
tabIndex={-1}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.currentTarget.blur()
|
|
|
|
|
if (active) onChange(boss.id, null)
|
|
|
|
|
else onChange(boss.id, { difficulty: d.key, party: partyN })
|
|
|
|
|
}}
|
|
|
|
|
style={style}
|
2026-04-18 16:10:09 +09:00
|
|
|
className="shrink-0 rounded-full border-solid px-4 h-7 text-xs font-bold tracking-wider transition focus:outline-none"
|
2026-04-15 20:50:37 +09:00
|
|
|
>
|
|
|
|
|
{LABEL_EN[d.key] || d.key.toUpperCase()}
|
|
|
|
|
</button>
|
2026-04-13 19:17:49 +09:00
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 파티 인원 - 커스텀 Select */}
|
|
|
|
|
<div className="w-20 shrink-0">
|
|
|
|
|
{sel ? (
|
|
|
|
|
<Select
|
|
|
|
|
value={partyN}
|
|
|
|
|
onChange={(val) => onChange(boss.id, { ...sel, party: val })}
|
|
|
|
|
options={partyOptions}
|
|
|
|
|
align="right"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="text-xs text-center"
|
|
|
|
|
style={{ color: 'var(--text-dim)' }}
|
|
|
|
|
>
|
|
|
|
|
-
|
|
|
|
|
</div>
|
2026-04-13 19:17:49 +09:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 수익 */}
|
2026-04-18 12:15:04 +09:00
|
|
|
<div
|
|
|
|
|
className="w-32 shrink-0 text-right text-sm font-medium tabular-nums"
|
|
|
|
|
style={{ color: sel ? 'var(--accent-bright)' : 'var(--text-dim)' }}
|
|
|
|
|
>
|
2026-04-13 19:17:49 +09:00
|
|
|
{sel ? formatMeso(revenue) : '-'}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|