검증게시판 기능 추가
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 검증 이슈 등록·수정 모달
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { VerificationIssueDto } from '../../utils/verificationBoardApi';
|
||||
import {
|
||||
VERIFICATION_ISSUE_STAGES,
|
||||
type VerificationIssueStage,
|
||||
} from '../../utils/verificationBoardStages';
|
||||
import ReproductionPathInput from './ReproductionPathInput';
|
||||
import VerificationIssueAttachmentsSection from './VerificationIssueAttachmentsSection';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
initial: VerificationIssueDto | null;
|
||||
saving: boolean;
|
||||
onSave: (dto: VerificationIssueDto, pendingImages: File[]) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const EMPTY: VerificationIssueDto = {
|
||||
title: '',
|
||||
content: '',
|
||||
reproductionPath: '',
|
||||
stage: 'REGISTERED',
|
||||
};
|
||||
|
||||
const VerificationIssueFormModal: React.FC<Props> = ({
|
||||
open, initial, saving, onSave, onCancel,
|
||||
}) => {
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
const [reproductionPath, setReproductionPath] = useState('');
|
||||
const [stage, setStage] = useState<VerificationIssueStage>('REGISTERED');
|
||||
const [draftImages, setDraftImages] = useState<File[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setTitle(initial?.title ?? '');
|
||||
setContent(initial?.content ?? '');
|
||||
setReproductionPath(initial?.reproductionPath ?? '');
|
||||
setStage(initial?.stage ?? 'REGISTERED');
|
||||
setDraftImages([]);
|
||||
}, [open, initial]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const trimmed = title.trim();
|
||||
if (!trimmed) {
|
||||
window.alert('제목을 입력하세요.');
|
||||
return;
|
||||
}
|
||||
const pathTrimmed = reproductionPath.trim();
|
||||
onSave({
|
||||
...(initial?.id != null ? { id: initial.id } : {}),
|
||||
title: trimmed,
|
||||
content: content.trim(),
|
||||
reproductionPath: pathTrimmed || undefined,
|
||||
stage,
|
||||
}, initial?.id == null ? draftImages : []);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vbd-modal-overlay" onMouseDown={e => { if (e.target === e.currentTarget) onCancel(); }}>
|
||||
<div className="vbd-modal vbd-modal--wide" role="dialog" aria-modal="true" aria-labelledby="vbd-modal-title">
|
||||
<div className="vbd-modal-header">
|
||||
<h3 id="vbd-modal-title">{initial?.id != null ? '검증 이슈 수정' : '검증 이슈 등록'}</h3>
|
||||
<button type="button" className="vbd-modal-close" onClick={onCancel} aria-label="닫기">✕</button>
|
||||
</div>
|
||||
<form className="vbd-modal-form" onSubmit={handleSubmit}>
|
||||
<label className="vbd-field">
|
||||
<span className="vbd-field-label">단계</span>
|
||||
<select
|
||||
value={stage}
|
||||
onChange={e => setStage(e.target.value as VerificationIssueStage)}
|
||||
>
|
||||
{VERIFICATION_ISSUE_STAGES.map(s => (
|
||||
<option key={s.value} value={s.value}>{s.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="vbd-field">
|
||||
<span className="vbd-field-label">제목</span>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={e => setTitle(e.target.value)}
|
||||
maxLength={300}
|
||||
autoFocus
|
||||
placeholder="검증 이슈 제목"
|
||||
/>
|
||||
</label>
|
||||
<label className="vbd-field">
|
||||
<span className="vbd-field-label">재현경로</span>
|
||||
<ReproductionPathInput
|
||||
value={reproductionPath}
|
||||
onChange={setReproductionPath}
|
||||
disabled={saving}
|
||||
/>
|
||||
<span className="vbd-field-hint">
|
||||
직접 입력하거나, 입력 시 관련 메뉴 경로를 선택해 자동 입력할 수 있습니다.
|
||||
</span>
|
||||
</label>
|
||||
<label className="vbd-field vbd-field--grow">
|
||||
<span className="vbd-field-label">내용</span>
|
||||
<textarea
|
||||
value={content}
|
||||
onChange={e => setContent(e.target.value)}
|
||||
rows={10}
|
||||
placeholder="문제점·재현 방법·기대 결과 등"
|
||||
/>
|
||||
</label>
|
||||
<VerificationIssueAttachmentsSection
|
||||
issueId={initial?.id ?? null}
|
||||
draftFiles={draftImages}
|
||||
onDraftFilesChange={setDraftImages}
|
||||
disabled={saving}
|
||||
compact
|
||||
/>
|
||||
<div className="vbd-modal-actions">
|
||||
<button type="button" className="vbd-btn" onClick={onCancel} disabled={saving}>취소</button>
|
||||
<button type="submit" className="vbd-btn vbd-btn--primary" disabled={saving}>
|
||||
{saving ? '저장 중…' : '저장'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificationIssueFormModal;
|
||||
Reference in New Issue
Block a user