371 lines
24 KiB
React
371 lines
24 KiB
React
/* eslint-disable */
|
||
// ─────────────────────────────────────────────────────────────
|
||
// searchgg · 클랜홈 섹션 컴포넌트 (브랜드 컬러 고정)
|
||
// 의존: window.Panel, Icon, RankBadge, Emblem, SA
|
||
// ─────────────────────────────────────────────────────────────
|
||
const CLPT = '#48515f';
|
||
const CLCH = "'Chakra Petch',sans-serif";
|
||
|
||
function RoleBadge({ role, theme }) {
|
||
const map = {
|
||
'클랜장': { bg: theme, color: '#fff' },
|
||
'운영진': { bg: `${theme}22`, color: theme },
|
||
'정예': { bg: '#eef0f2', color: '#5b626c' },
|
||
'일반': { bg: 'transparent', color: '#949aa3' },
|
||
};
|
||
const s = map[role] || map['일반'];
|
||
return <span style={{ fontSize: 10, fontWeight: 700, padding: '1px 7px', borderRadius: 4, background: s.bg, color: s.color, flexShrink: 0 }}>{role}</span>;
|
||
}
|
||
|
||
// ── 멤버 ──
|
||
function ClanMembers({ clan, onPick }) {
|
||
const groups = ['클랜장', '운영진', '정예', '일반'];
|
||
const byRole = (r) => clan.members.filter((m) => m.role === r);
|
||
return (
|
||
<Panel title="클랜원" en="MEMBERS" right={<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>{clan.totalMembers} / {clan.memberMax}명</span>} bodyPad="6px 0 6px">
|
||
{groups.map((g) => {
|
||
const list = byRole(g);
|
||
if (!list.length) return null;
|
||
return (
|
||
<div key={g}>
|
||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', padding: '8px 16px 4px' }}>
|
||
<span style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--t3)', letterSpacing: '.3px' }}>{g} · {list.length}</span>
|
||
<span style={{ fontSize: 9.5, fontWeight: 600, color: 'var(--t3)' }}>기여도</span>
|
||
</div>
|
||
{list.map((m, i) => (
|
||
<button key={m.name + i} onClick={() => onPick && onPick(m.name)} style={{ display: 'flex', alignItems: 'center', gap: 9, width: '100%', border: 'none', background: 'transparent', padding: '8px 16px', cursor: 'pointer', textAlign: 'left', fontFamily: "'Pretendard',sans-serif" }}
|
||
onMouseEnter={(e) => e.currentTarget.style.background = '#f6f7f8'} onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
|
||
{m.rank && <RankBadge rank={m.rank} size={26} />}
|
||
<span style={{ flex: 1, minWidth: 0, fontSize: 13, fontWeight: 600, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.name}</span>
|
||
<span style={{ display: 'inline-flex', alignItems: 'baseline', gap: 3, flexShrink: 0 }}>
|
||
<span style={{ fontFamily: CLCH, fontSize: 12.5, color: CLPT, fontWeight: 700 }}>{window.SA.fmt(m.contrib)}</span>
|
||
<span style={{ fontSize: 9.5, color: 'var(--t3)', fontWeight: 600 }}>P</span>
|
||
</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
);
|
||
})}
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
// ── 정기전 일정 ──
|
||
function ClanSchedule({ clan }) {
|
||
const t = CLPT;
|
||
const [items, setItems] = React.useState(clan.schedule);
|
||
const [open, setOpen] = React.useState(false);
|
||
const [day, setDay] = React.useState('월');
|
||
const [time, setTime] = React.useState('21:00');
|
||
const [title, setTitle] = React.useState('');
|
||
React.useEffect(() => { setItems(clan.schedule); setOpen(false); }, [clan.name]);
|
||
|
||
const register = () => {
|
||
const name = title.trim();
|
||
if (!name) return;
|
||
setItems(items.map((s) => s.day === day ? { ...s, on: true, time, title: name } : s));
|
||
setTitle('');
|
||
setOpen(false);
|
||
};
|
||
const remove = (d) => setItems(items.map((s) => s.day === d ? { ...s, on: false, time: '—', title: '휴식' } : s));
|
||
|
||
const inputStyle = { border: '1px solid var(--line2)', borderRadius: 6, padding: '9px 11px', fontSize: 13, fontFamily: "'Pretendard',sans-serif", fontWeight: 600, color: 'var(--ink)', outline: 'none', boxSizing: 'border-box' };
|
||
|
||
return (
|
||
<Panel title="클랜 일정" en="SCHEDULE" right={clan.isMine ? (
|
||
<button onClick={() => setOpen(true)} style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 12, color: t, padding: 0 }}>+ 일정 등록</button>
|
||
) : null} bodyPad="12px 14px 14px">
|
||
<div style={{ display: 'flex', gap: 5 }}>
|
||
{items.map((s) => (
|
||
<div key={s.day} style={{ flex: 1, textAlign: 'center', padding: '9px 2px', borderRadius: 6, background: s.on ? t : '#f4f5f7', color: s.on ? '#fff' : 'var(--t3)' }}>
|
||
<div style={{ fontSize: 12, fontWeight: 800 }}>{s.day}</div>
|
||
<div style={{ fontFamily: CLCH, fontSize: 9.5, marginTop: 2, opacity: .9 }}>{s.time}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||
{items.filter((s) => s.on).map((s) => (
|
||
<div key={s.day} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5 }}>
|
||
<span style={{ fontWeight: 800, color: t, width: 16 }}>{s.day}</span>
|
||
<span style={{ fontFamily: CLCH, color: 'var(--t2)', width: 44 }}>{s.time}</span>
|
||
<span style={{ color: 'var(--ink)', fontWeight: 600, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.title}</span>
|
||
{clan.isMine && (
|
||
<button onClick={() => remove(s.day)} title="일정 삭제" style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--t3)', fontSize: 14, lineHeight: 1, padding: '0 2px', flexShrink: 0 }}>×</button>
|
||
)}
|
||
</div>
|
||
))}
|
||
{items.every((s) => !s.on) && <div style={{ fontSize: 12, color: 'var(--t3)', textAlign: 'center', padding: '6px 0' }}>등록된 일정이 없어요.</div>}
|
||
</div>
|
||
|
||
{/* 일정 등록 모달 */}
|
||
{open && (
|
||
<div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 260, background: 'rgba(20,22,26,.55)', backdropFilter: 'blur(2px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, animation: 'sgFade .18s ease' }}>
|
||
<div onClick={(e) => e.stopPropagation()} style={{ width: 380, maxWidth: '100%', background: '#fff', borderRadius: 12, overflow: 'hidden', boxShadow: '0 24px 70px rgba(0,0,0,.3)', fontFamily: "'Pretendard',sans-serif" }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 18px', borderBottom: '1px solid var(--line)' }}>
|
||
<span style={{ fontSize: 15, fontWeight: 800, color: 'var(--ink)' }}>일정 등록</span>
|
||
<button onClick={() => setOpen(false)} style={{ width: 28, height: 28, borderRadius: 6, border: 'none', background: '#eef0f2', color: '#5b626c', cursor: 'pointer', fontSize: 15 }}>×</button>
|
||
</div>
|
||
<div style={{ padding: '16px 18px' }}>
|
||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--t2)', marginBottom: 7 }}>요일</div>
|
||
<div style={{ display: 'flex', gap: 5, marginBottom: 14 }}>
|
||
{items.map((s) => (
|
||
<button key={s.day} onClick={() => setDay(s.day)} style={{ flex: 1, padding: '8px 0', borderRadius: 6, cursor: 'pointer', fontFamily: "'Pretendard',sans-serif", fontWeight: 800, fontSize: 12.5,
|
||
border: `1px solid ${day === s.day ? t : 'var(--line2)'}`, background: day === s.day ? t : '#fff', color: day === s.day ? '#fff' : (s.on ? t : 'var(--t2)') }}>{s.day}</button>
|
||
))}
|
||
</div>
|
||
{items.find((s) => s.day === day && s.on) && (
|
||
<div style={{ fontSize: 11.5, color: 'var(--t3)', marginTop: -8, marginBottom: 12 }}>이미 일정이 있는 요일이에요. 등록하면 덮어써요.</div>
|
||
)}
|
||
<div style={{ display: 'flex', gap: 10, marginBottom: 14 }}>
|
||
<div style={{ width: 120 }}>
|
||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--t2)', marginBottom: 7 }}>시간</div>
|
||
<input type="time" value={time} onChange={(e) => setTime(e.target.value)} style={{ ...inputStyle, width: '100%' }} />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--t2)', marginBottom: 7 }}>일정명</div>
|
||
<input value={title} onChange={(e) => setTitle(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') register(); }} placeholder="예: 정기전, 내전 데이" style={{ ...inputStyle, width: '100%' }} />
|
||
</div>
|
||
</div>
|
||
<div style={{ display: 'flex', gap: 8 }}>
|
||
<button onClick={() => setOpen(false)} style={{ flex: 1, padding: '11px 0', borderRadius: 7, border: '1px solid var(--line2)', background: '#fff', color: '#3a4049', fontWeight: 700, fontSize: 13.5, cursor: 'pointer', fontFamily: "'Pretendard',sans-serif" }}>취소</button>
|
||
<button onClick={register} disabled={!title.trim()} style={{ flex: 1.4, padding: '11px 0', borderRadius: 7, border: 'none', background: title.trim() ? t : '#c4c8ce', color: '#fff', fontWeight: 700, fontSize: 13.5, cursor: title.trim() ? 'pointer' : 'not-allowed', fontFamily: "'Pretendard',sans-serif" }}>등록하기</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
// ── 주간 활동 (접속률) ──
|
||
function ClanActivity({ clan }) {
|
||
const days = ['월', '화', '수', '목', '금', '토', '일'];
|
||
const max = Math.max(...clan.attendance);
|
||
return (
|
||
<Panel title="주간 활동" en="ACTIVITY" right={<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>접속률</span>} bodyPad="14px 14px 12px">
|
||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 7, height: 86 }}>
|
||
{clan.attendance.map((v, i) => (
|
||
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}>
|
||
<span style={{ fontFamily: CLCH, fontSize: 10, color: 'var(--t3)', fontWeight: 600 }}>{v}%</span>
|
||
<div style={{ width: '100%', height: `${(v / max) * 58}px`, background: CLPT, borderRadius: '3px 3px 0 0', opacity: 0.55 + 0.45 * (v / max) }} />
|
||
<span style={{ fontSize: 10.5, color: 'var(--t3)' }}>{days[i]}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
// ── 주간 매치 (모드별) ──
|
||
function ClanWeeklyMatches({ clan }) {
|
||
const days = ['월', '화', '수', '목', '금', '토', '일'];
|
||
const acts = clan.activity;
|
||
const cats = [
|
||
['clanWar', '클랜전', CLPT],
|
||
['clanRanked', '클랜 랭크전', '#6b7585'],
|
||
['soloRanked', '솔로 랭크전', '#99a1ae'],
|
||
['partyRanked', '파티 랭크전', '#cdd2d9'],
|
||
];
|
||
const sum = (a) => cats.reduce((s, [k]) => s + a[k], 0);
|
||
const max = Math.max(...acts.map(sum));
|
||
const totals = cats.map(([k, label, color]) => [label, acts.reduce((s, a) => s + a[k], 0), color]);
|
||
return (
|
||
<Panel title="주간 매치" en="MATCHES" right={<span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>모드별 판수</span>} bodyPad="14px 14px 12px">
|
||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 7, height: 96 }}>
|
||
{acts.map((a, i) => (
|
||
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5 }}>
|
||
<span style={{ fontFamily: CLCH, fontSize: 10, color: 'var(--t3)', fontWeight: 600 }}>{sum(a)}</span>
|
||
<div style={{ width: '100%', height: `${(sum(a) / max) * 62}px`, display: 'flex', flexDirection: 'column-reverse', borderRadius: '3px 3px 0 0', overflow: 'hidden' }}>
|
||
{cats.map(([k, label, color]) => (
|
||
<div key={k} title={`${label} ${a[k]}판`} style={{ height: `${(a[k] / sum(a)) * 100}%`, background: color }} />
|
||
))}
|
||
</div>
|
||
<span style={{ fontSize: 10.5, color: 'var(--t3)' }}>{days[i]}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div style={{ marginTop: 11, paddingTop: 10, borderTop: '1px solid var(--line)', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '7px 10px' }}>
|
||
{totals.map(([label, n, color]) => (
|
||
<span key={label} style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11.5, color: 'var(--t2)', fontWeight: 600 }}>
|
||
<span style={{ width: 8, height: 8, borderRadius: 2, background: color, flexShrink: 0 }} />
|
||
{label} <b style={{ fontFamily: CLCH, color: 'var(--ink)', marginLeft: 'auto' }}>{n}판</b>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
// ── 미니 페이저 ──
|
||
function ClanPager({ page, total, onPage }) {
|
||
if (total <= 1) return null;
|
||
const nav = (dis) => ({ width: 26, height: 26, borderRadius: 5, border: '1px solid var(--line2)', background: '#fff', cursor: dis ? 'default' : 'pointer', opacity: dis ? .4 : 1, display: 'grid', placeItems: 'center' });
|
||
return (
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, padding: '11px 0 5px' }}>
|
||
<button onClick={() => onPage(page - 1)} disabled={page === 1} style={nav(page === 1)}><Icon name="chevL" size={13} stroke="var(--t2)" /></button>
|
||
{Array.from({ length: total }, (_, i) => i + 1).map((n) => (
|
||
<button key={n} onClick={() => onPage(n)} style={{ minWidth: 26, height: 26, padding: '0 5px', borderRadius: 5, cursor: 'pointer', fontFamily: CLCH, fontWeight: 700, fontSize: 11.5, border: n === page ? 'none' : '1px solid var(--line2)', background: n === page ? CLPT : '#fff', color: n === page ? '#fff' : 'var(--t2)' }}>{n}</button>
|
||
))}
|
||
<button onClick={() => onPage(page + 1)} disabled={page === total} style={nav(page === total)}><Icon name="chevR" size={13} stroke="var(--t2)" /></button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ── 공지 ──
|
||
function ClanNotices({ clan }) {
|
||
const PER = 4;
|
||
const [page, setPage] = React.useState(1);
|
||
React.useEffect(() => setPage(1), [clan.name]);
|
||
const total = Math.max(1, Math.ceil(clan.notices.length / PER));
|
||
const cur = Math.min(page, total);
|
||
const rows = clan.notices.slice((cur - 1) * PER, cur * PER);
|
||
return (
|
||
<Panel title="클랜 공지" en="NOTICE" bodyPad="2px 14px 6px">
|
||
{rows.map((n, i) => (
|
||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 2px', borderBottom: i < rows.length - 1 ? '1px solid #eaecef' : 'none', cursor: 'pointer' }}>
|
||
{n.pin && <span style={{ fontSize: 10, fontWeight: 800, color: CLPT, flexShrink: 0 }}>고정</span>}
|
||
<span style={{ flex: 1, fontSize: 13.5, color: '#33373d', fontWeight: n.pin ? 700 : 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{n.title}</span>
|
||
<span style={{ fontSize: 11, color: 'var(--t3)', flexShrink: 0 }}>{n.author} · {n.time}</span>
|
||
</div>
|
||
))}
|
||
<ClanPager page={cur} total={total} onPage={(n) => setPage(Math.max(1, Math.min(total, n)))} />
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
// ── 클랜 피드 ──
|
||
function ClanFeed({ clan }) {
|
||
const PER = 3;
|
||
const [items, setItems] = React.useState(clan.feed);
|
||
const [draft, setDraft] = React.useState('');
|
||
const [page, setPage] = React.useState(1);
|
||
React.useEffect(() => { setItems(clan.feed); setDraft(''); setPage(1); }, [clan.name]);
|
||
const total = Math.max(1, Math.ceil(items.length / PER));
|
||
const cur = Math.min(page, total);
|
||
const rows = items.slice((cur - 1) * PER, cur * PER);
|
||
const post = () => {
|
||
const text = draft.trim();
|
||
if (!text) return;
|
||
setItems([{ author: window.SA.me.name, role: '일반', time: '방금 전', text, likes: 0 }, ...items]);
|
||
setDraft('');
|
||
setPage(1);
|
||
};
|
||
return (
|
||
<Panel title="클랜 피드" en="FEED" bodyPad="6px 14px 12px">
|
||
{clan.isMine && (
|
||
<div style={{ display: 'flex', gap: 8, padding: '10px 0 12px', borderBottom: '1px solid var(--line)' }}>
|
||
<div style={{ width: 34, height: 34, borderRadius: '50%', background: `${CLPT}1c`, color: CLPT, display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 14, flexShrink: 0 }}>{window.SA.me.name.slice(0, 1)}</div>
|
||
<input value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') post(); }}
|
||
placeholder="클랜원들에게 소식을 남겨보세요"
|
||
style={{ flex: 1, minWidth: 0, border: '1px solid var(--line2)', borderRadius: 6, padding: '9px 12px', fontSize: 13, fontFamily: "'Pretendard',sans-serif", outline: 'none' }} />
|
||
<button onClick={post} disabled={!draft.trim()} style={{ padding: '0 16px', borderRadius: 6, border: 'none', cursor: draft.trim() ? 'pointer' : 'not-allowed', background: draft.trim() ? CLPT : '#c4c8ce', color: '#fff', fontWeight: 700, fontSize: 12.5, fontFamily: "'Pretendard',sans-serif", flexShrink: 0 }}>작성</button>
|
||
</div>
|
||
)}
|
||
{rows.map((f, i) => (
|
||
<div key={(cur - 1) * PER + i} style={{ display: 'flex', gap: 11, padding: '11px 0', borderBottom: i < rows.length - 1 ? '1px solid #f1f2f4' : 'none' }}>
|
||
<div style={{ width: 34, height: 34, borderRadius: '50%', background: `${CLPT}1c`, color: CLPT, display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 14, flexShrink: 0 }}>{f.author.slice(0, 1)}</div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
|
||
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>{f.author}</span>
|
||
<span style={{ marginLeft: 'auto', fontSize: 11, color: 'var(--t3)' }}>{f.time}</span>
|
||
</div>
|
||
<div style={{ fontSize: 13.5, color: '#3a4049', lineHeight: 1.5 }}>{f.text}</div>
|
||
<div style={{ marginTop: 6, display: 'flex', alignItems: 'center', gap: 5, color: 'var(--t3)' }}>
|
||
<Icon name="heart" size={13} stroke="var(--t3)" /><span style={{ fontSize: 11.5, fontWeight: 600 }}>{f.likes}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
<ClanPager page={cur} total={total} onPage={(n) => setPage(Math.max(1, Math.min(total, n)))} />
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
// ── 방명록 ──
|
||
function ClanGuestbook({ clan }) {
|
||
const PER = 4;
|
||
const [items, setItems] = React.useState(clan.guestbook);
|
||
const [draft, setDraft] = React.useState('');
|
||
const [page, setPage] = React.useState(1);
|
||
React.useEffect(() => { setItems(clan.guestbook); setDraft(''); setPage(1); }, [clan.name]);
|
||
const total = Math.max(1, Math.ceil(items.length / PER));
|
||
const cur = Math.min(page, total);
|
||
const rows = items.slice((cur - 1) * PER, cur * PER);
|
||
const post = () => {
|
||
const text = draft.trim();
|
||
if (!text) return;
|
||
setItems([{ author: window.SA.me.name, text, time: '방금 전' }, ...items]);
|
||
setDraft('');
|
||
setPage(1);
|
||
};
|
||
const [replyAt, setReplyAt] = React.useState(-1);
|
||
const [replyDraft, setReplyDraft] = React.useState('');
|
||
React.useEffect(() => { setReplyAt(-1); setReplyDraft(''); }, [clan.name]);
|
||
const postReply = (gi) => {
|
||
const text = replyDraft.trim();
|
||
if (!text) return;
|
||
setItems(items.map((g, idx) => idx === gi ? { ...g, replies: [...(g.replies || []), { author: window.SA.me.name, text, time: '방금 전' }] } : g));
|
||
setReplyDraft('');
|
||
setReplyAt(-1);
|
||
};
|
||
return (
|
||
<Panel title="방명록" en="GUESTBOOK" right={clan.isMine ? <span style={{ fontSize: 11, color: 'var(--t3)', fontWeight: 600 }}>클랜원은 답글만 달 수 있어요</span> : null} bodyPad="10px 14px 14px">
|
||
{!clan.isMine && (
|
||
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
||
<input value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') post(); }} placeholder="따뜻한 한마디를 남겨보세요" style={{ flex: 1, minWidth: 0, border: '1px solid var(--line2)', borderRadius: 6, padding: '9px 12px', fontSize: 13, fontFamily: "'Pretendard',sans-serif", outline: 'none' }} />
|
||
<button onClick={post} disabled={!draft.trim()} style={{ padding: '0 16px', borderRadius: 6, border: 'none', background: draft.trim() ? CLPT : '#c4c8ce', color: '#fff', fontWeight: 700, fontSize: 13, cursor: draft.trim() ? 'pointer' : 'not-allowed', fontFamily: "'Pretendard',sans-serif", flexShrink: 0 }}>등록</button>
|
||
</div>
|
||
)}
|
||
{rows.map((g, i) => {
|
||
const gi = (cur - 1) * PER + i;
|
||
const replies = g.replies || [];
|
||
return (
|
||
<div key={gi} style={{ display: 'flex', gap: 9, padding: '9px 0', borderTop: '1px solid #f1f2f4' }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: '50%', background: '#eef0f2', color: '#7a8089', display: 'grid', placeItems: 'center', fontWeight: 700, fontSize: 12, flexShrink: 0 }}>{g.author.slice(0, 1)}</div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 7 }}>
|
||
<span style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)' }}>{g.author}</span>
|
||
<span style={{ fontSize: 11, color: 'var(--t3)' }}>{g.time}</span>
|
||
{clan.isMine && (
|
||
<button onClick={() => { setReplyAt(replyAt === gi ? -1 : gi); setReplyDraft(''); }} style={{ marginLeft: 'auto', border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: "'Pretendard',sans-serif", fontWeight: 700, fontSize: 11, color: CLPT, padding: 0, flexShrink: 0 }}>
|
||
{replyAt === gi ? '취소' : '답글'}
|
||
</button>
|
||
)}
|
||
</div>
|
||
<div style={{ fontSize: 13, color: '#3a4049', marginTop: 2 }}>{g.text}</div>
|
||
|
||
{/* 답글 목록 */}
|
||
{replies.map((rp, j) => (
|
||
<div key={j} style={{ display: 'flex', gap: 9, marginTop: 13, paddingLeft: 10, borderLeft: `2px solid ${CLPT}33` }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: '50%', background: `${CLPT}1c`, color: CLPT, display: 'grid', placeItems: 'center', fontWeight: 800, fontSize: 12, flexShrink: 0 }}>{rp.author.slice(0, 1)}</div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ display: 'flex', alignItems: 'baseline', gap: 7 }}>
|
||
<span style={{ fontSize: 12.5, fontWeight: 700, color: 'var(--ink)' }}>{rp.author}</span>
|
||
<span style={{ fontSize: 9.5, fontWeight: 700, color: CLPT, border: `1px solid ${CLPT}55`, borderRadius: 3, padding: '0 4px' }}>클랜원</span>
|
||
<span style={{ fontSize: 11, color: 'var(--t3)' }}>{rp.time}</span>
|
||
</div>
|
||
<div style={{ fontSize: 13, color: '#3a4049', marginTop: 4 }}>{rp.text}</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
|
||
{/* 답글 입력 */}
|
||
{replyAt === gi && (
|
||
<div style={{ display: 'flex', gap: 7, marginTop: 13, paddingLeft: 10, borderLeft: `2px solid ${CLPT}33` }}>
|
||
<input autoFocus value={replyDraft} onChange={(e) => setReplyDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') postReply(gi); }} placeholder="답글을 남겨보세요"
|
||
style={{ flex: 1, minWidth: 0, border: '1px solid var(--line2)', borderRadius: 6, padding: '7px 10px', fontSize: 12.5, fontFamily: "'Pretendard',sans-serif", outline: 'none' }} />
|
||
<button onClick={() => postReply(gi)} disabled={!replyDraft.trim()} style={{ padding: '0 13px', borderRadius: 6, border: 'none', background: replyDraft.trim() ? CLPT : '#c4c8ce', color: '#fff', fontWeight: 700, fontSize: 11.5, cursor: replyDraft.trim() ? 'pointer' : 'not-allowed', fontFamily: "'Pretendard',sans-serif", flexShrink: 0 }}>등록</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
<ClanPager page={cur} total={total} onPage={(n) => setPage(Math.max(1, Math.min(total, n)))} />
|
||
</Panel>
|
||
);
|
||
}
|
||
|
||
Object.assign(window, { ClanMembers, ClanSchedule, ClanActivity, ClanWeeklyMatches, ClanNotices, ClanFeed, ClanGuestbook });
|