검증게시판 기능 추가
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 검증게시판 — 좌측 이슈 목록 (단계 필터 + 검색)
|
||||
*/
|
||||
import React, { useMemo } from 'react';
|
||||
import type { VerificationIssueDto } from '../../utils/verificationBoardApi';
|
||||
import {
|
||||
VERIFICATION_ISSUE_STAGES,
|
||||
stageColor,
|
||||
stageLabel,
|
||||
type VerificationIssueStage,
|
||||
} from '../../utils/verificationBoardStages';
|
||||
import VerificationStageIcon from './VerificationStageIcon';
|
||||
|
||||
interface Props {
|
||||
issues: VerificationIssueDto[];
|
||||
selectedId: number | null;
|
||||
stageFilter: VerificationIssueStage | '';
|
||||
searchQuery: string;
|
||||
onStageFilterChange: (stage: VerificationIssueStage | '') => void;
|
||||
onSearchChange: (q: string) => void;
|
||||
onSelect: (issue: VerificationIssueDto) => void;
|
||||
}
|
||||
|
||||
function formatDate(iso?: string): string {
|
||||
if (!iso) return '';
|
||||
try {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleString('ko-KR', {
|
||||
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
||||
});
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
const VerificationIssueListPanel: React.FC<Props> = ({
|
||||
issues,
|
||||
selectedId,
|
||||
stageFilter,
|
||||
searchQuery,
|
||||
onStageFilterChange,
|
||||
onSearchChange,
|
||||
onSelect,
|
||||
}) => {
|
||||
const filtered = useMemo(() => {
|
||||
const q = searchQuery.trim().toLowerCase();
|
||||
if (!q) return issues;
|
||||
return issues.filter(i =>
|
||||
i.title.toLowerCase().includes(q)
|
||||
|| i.content.toLowerCase().includes(q)
|
||||
|| (i.reproductionPath?.toLowerCase().includes(q) ?? false),
|
||||
);
|
||||
}, [issues, searchQuery]);
|
||||
|
||||
return (
|
||||
<div className="vbd-list-panel">
|
||||
<div className="vbd-list-filters">
|
||||
<select
|
||||
className="vbd-stage-select"
|
||||
value={stageFilter}
|
||||
onChange={e => onStageFilterChange(e.target.value as VerificationIssueStage | '')}
|
||||
aria-label="이슈 단계 필터"
|
||||
>
|
||||
<option value="">전체 단계</option>
|
||||
{VERIFICATION_ISSUE_STAGES.map(s => (
|
||||
<option key={s.value} value={s.value}>{s.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="search"
|
||||
className="vbd-search-input"
|
||||
placeholder="제목·내용·재현경로 검색…"
|
||||
value={searchQuery}
|
||||
onChange={e => onSearchChange(e.target.value)}
|
||||
aria-label="검색"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="vbd-list-count">
|
||||
{filtered.length}건
|
||||
{searchQuery.trim() && issues.length !== filtered.length && (
|
||||
<span className="vbd-list-count-sub"> / {issues.length}건 중</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul className="vbd-list">
|
||||
{filtered.length === 0 ? (
|
||||
<li className="vbd-list-empty">표시할 검증 이슈가 없습니다.</li>
|
||||
) : filtered.map(issue => {
|
||||
const active = issue.id === selectedId;
|
||||
return (
|
||||
<li key={issue.id}>
|
||||
<button
|
||||
type="button"
|
||||
className={`vbd-list-item${active ? ' vbd-list-item--active' : ''}`}
|
||||
onClick={() => onSelect(issue)}
|
||||
>
|
||||
<VerificationStageIcon stage={issue.stage} size={34} />
|
||||
<span className="vbd-list-item-body">
|
||||
<span className="vbd-list-item-top">
|
||||
<span
|
||||
className="vbd-stage-badge"
|
||||
style={{ backgroundColor: `${stageColor(issue.stage)}22`, color: stageColor(issue.stage) }}
|
||||
>
|
||||
{stageLabel(issue.stage)}
|
||||
</span>
|
||||
<span className="vbd-list-meta">{formatDate(issue.updatedAt ?? issue.createdAt)}</span>
|
||||
</span>
|
||||
<span className="vbd-list-title">{issue.title}</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificationIssueListPanel;
|
||||
Reference in New Issue
Block a user