You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
5.0 KiB
TypeScript

import { useState, type CSSProperties } from 'react';
import type { Achievement } from '../network/api';
interface AchievementsPanelProps {
achievements: Achievement[];
}
const buttonStyle: CSSProperties = {
position: 'fixed',
top: 12,
right: 240,
zIndex: 50,
display: 'flex',
alignItems: 'center',
gap: 4,
padding: '5px 10px',
borderRadius: 8,
border: '1px solid rgba(255, 215, 0, 0.2)',
backgroundColor: 'rgba(0,0,0,0.55)',
color: '#daa520',
fontSize: 11,
fontWeight: 600,
cursor: 'pointer',
pointerEvents: 'auto',
userSelect: 'none',
};
const panelStyle: CSSProperties = {
position: 'fixed',
top: 12,
right: 240,
zIndex: 50,
width: 260,
maxHeight: 340,
borderRadius: 10,
border: '1px solid rgba(255, 215, 0, 0.15)',
backgroundColor: 'rgba(10, 10, 20, 0.88)',
backdropFilter: 'blur(6px)',
overflow: 'hidden',
pointerEvents: 'auto',
display: 'flex',
flexDirection: 'column',
};
const panelHeaderStyle: CSSProperties = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '7px 10px',
borderBottom: '1px solid rgba(255,255,255,0.08)',
fontSize: 12,
fontWeight: 700,
color: '#daa520',
flexShrink: 0,
};
const closeButtonStyle: CSSProperties = {
background: 'none',
border: 'none',
color: '#888',
fontSize: 14,
cursor: 'pointer',
padding: '0 4px',
lineHeight: 1,
};
const listStyle: CSSProperties = {
padding: '6px 10px 8px',
overflowY: 'auto',
flex: 1,
};
function achievementRowStyle(unlocked: boolean): CSSProperties {
return {
marginBottom: 8,
padding: '6px 8px',
borderRadius: 6,
border: unlocked
? '1px solid rgba(255, 215, 0, 0.5)'
: '1px solid rgba(255, 255, 255, 0.08)',
backgroundColor: unlocked
? 'rgba(255, 215, 0, 0.06)'
: 'rgba(255, 255, 255, 0.02)',
};
}
const titleRowStyle: CSSProperties = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 3,
};
const progressBarBgStyle: CSSProperties = {
width: '100%',
height: 5,
borderRadius: 3,
backgroundColor: 'rgba(255,255,255,0.08)',
overflow: 'hidden',
marginTop: 4,
};
function progressBarFillStyle(pct: number, unlocked: boolean): CSSProperties {
return {
width: `${Math.min(100, pct)}%`,
height: '100%',
borderRadius: 3,
backgroundColor: unlocked ? '#ffd700' : '#daa520',
transition: 'width 300ms ease',
};
}
function formatDate(iso: string): string {
try {
const d = new Date(iso);
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
} catch {
return '';
}
}
export function AchievementsPanel({ achievements }: AchievementsPanelProps) {
const [expanded, setExpanded] = useState(false);
const unlockedCount = achievements.filter((a) => a.unlocked).length;
if (!expanded) {
return (
<button style={buttonStyle} onClick={() => setExpanded(true)}>
<span style={{ fontSize: 13 }}>&#x1F3C6;</span>
<span>{unlockedCount}/{achievements.length}</span>
</button>
);
}
return (
<div style={panelStyle}>
<div style={panelHeaderStyle}>
<span>&#x1F3C6; Achievements</span>
<button style={closeButtonStyle} onClick={() => setExpanded(false)}>
&#x2715;
</button>
</div>
<div style={listStyle}>
{achievements.map((ach) => {
const pct = ach.conditionValue > 0
? (Math.min(ach.conditionValue, ach.conditionValue) / ach.conditionValue) * 100
: 0;
return (
<div key={ach.id} style={achievementRowStyle(ach.unlocked)}>
<div style={titleRowStyle}>
<span style={{
fontSize: 11,
fontWeight: 600,
color: ach.unlocked ? '#ffd700' : '#ccc',
opacity: ach.unlocked ? 1 : 0.85,
}}>
{ach.unlocked ? '\u2714 ' : ''}{ach.title}
</span>
{ach.unlocked && ach.unlockedAt && (
<span style={{ fontSize: 9, color: '#999' }}>
{formatDate(ach.unlockedAt)}
</span>
)}
</div>
<div style={{ fontSize: 10, color: '#aaa', marginBottom: 2 }}>
{ach.description}
</div>
{!ach.unlocked && (
<div style={progressBarBgStyle}>
<div style={progressBarFillStyle(pct, false)} />
</div>
)}
{ach.unlocked && (
<div style={{
fontSize: 9,
color: '#b8860b',
marginTop: 2,
fontWeight: 600,
}}>
+{ach.rewardAmount} {ach.rewardType}
</div>
)}
</div>
);
})}
{achievements.length === 0 && (
<div style={{ textAlign: 'center', opacity: 0.5, padding: 8, fontSize: 11, color: '#999' }}>
No achievements yet
</div>
)}
</div>
</div>
);
}