135 lines
4.7 KiB
TypeScript
135 lines
4.7 KiB
TypeScript
/**
|
|
* 검증 이슈 등록·수정 모달
|
|
*/
|
|
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;
|