feat:새 계약 생성 - 계약제목 드롭박스 + 번개 버튼 랜덤 상대방
- 계약 제목을 드롭박스로 변경 (영업파트너 계약서/비밀유지 서약서/고객 서비스이용 계약서/직접입력) - 직접입력 선택 시 텍스트 입력 필드 표시 - 번개마크 2개 → 1개로 축소 - 번개마크 클릭 시 영업파트너 목록에서 랜덤으로 상대방 정보 자동 채우기 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,12 @@
|
||||
{ id: 2, title: '템플릿 & 미리보기' },
|
||||
{ id: 3, title: '확인 & 생성' },
|
||||
];
|
||||
const TITLE_PRESETS = [
|
||||
{ value: '영업파트너 계약서', label: '영업파트너 계약서' },
|
||||
{ value: '비밀유지 서약서', label: '비밀유지 서약서' },
|
||||
{ value: '고객 서비스이용 계약서', label: '고객 서비스이용 계약서' },
|
||||
{ value: '__custom__', label: '직접입력' },
|
||||
];
|
||||
|
||||
// ─── Input ───
|
||||
const Input = ({ label, name, value, error, onChange, type = 'text', required = false, placeholder = '', style }) => (
|
||||
@@ -350,11 +356,12 @@ className={`w-full text-left px-3 py-2.5 rounded-lg mb-1 transition-colors ${i =
|
||||
const App = () => {
|
||||
const [step, setStep] = useState(1);
|
||||
const [form, setForm] = useState({
|
||||
title: '', description: '', sign_order_type: 'counterpart_first',
|
||||
title: '영업파트너 계약서', description: '', sign_order_type: 'counterpart_first',
|
||||
expires_at: (() => { const d = new Date(); d.setDate(d.getDate() + 7); return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0') + 'T23:59'; })(),
|
||||
creator_name: '(주) 코드브릿지엑스', creator_email: 'contact@codebridge-x.com', creator_phone: '02-6347-0005',
|
||||
counterpart_name: '', counterpart_email: '', counterpart_phone: '',
|
||||
});
|
||||
const [titleType, setTitleType] = useState('영업파트너 계약서');
|
||||
const [file, setFile] = useState(null);
|
||||
const [registeredStamp, setRegisteredStamp] = useState(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -415,33 +422,22 @@ className={`w-full text-left px-3 py-2.5 rounded-lg mb-1 transition-colors ${i =
|
||||
} catch (_) { setTemplateItems([]); }
|
||||
};
|
||||
|
||||
const fillTestData = () => {
|
||||
const year = new Date().getFullYear();
|
||||
setForm(f => ({
|
||||
...f,
|
||||
title: `${year}년 영업파트너 계약서`,
|
||||
description: '영업파트너 위촉 및 수수료 지급에 관한 계약',
|
||||
creator_name: '(주) 코드브릿지엑스',
|
||||
creator_email: 'contact@codebridge-x.com',
|
||||
creator_phone: '02-6347-0005',
|
||||
counterpart_name: '김지훈',
|
||||
counterpart_email: 'awesomemyword@gmail.com',
|
||||
counterpart_phone: '010-5123-8210',
|
||||
}));
|
||||
};
|
||||
|
||||
const fillTestDataNda = () => {
|
||||
setForm(f => ({
|
||||
...f,
|
||||
title: '비밀유지서약서',
|
||||
description: '영업파트너 등 비밀유지서약서',
|
||||
creator_name: '(주) 코드브릿지엑스',
|
||||
creator_email: 'contact@codebridge-x.com',
|
||||
creator_phone: '02-6347-0005',
|
||||
counterpart_name: '김지훈',
|
||||
counterpart_email: 'awesomemyword@gmail.com',
|
||||
counterpart_phone: '010-5123-8210',
|
||||
}));
|
||||
const fillRandomCounterpart = async () => {
|
||||
try {
|
||||
const res = await fetch(`/esign/contracts/search-partners?q=`, {
|
||||
headers: { 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success && json.data.length > 0) {
|
||||
const partner = json.data[Math.floor(Math.random() * json.data.length)];
|
||||
setForm(f => ({
|
||||
...f,
|
||||
counterpart_name: partner.name || '',
|
||||
counterpart_email: partner.email || '',
|
||||
counterpart_phone: partner.phone || '',
|
||||
}));
|
||||
}
|
||||
} catch (_) {}
|
||||
};
|
||||
|
||||
// 파트너 선택 시 자동 채우기
|
||||
@@ -567,7 +563,24 @@ className={`w-full text-left px-3 py-2.5 rounded-lg mb-1 transition-colors ${i =
|
||||
<div className="bg-white rounded-lg border p-4">
|
||||
<h2 className="text-sm font-semibold text-gray-900 mb-3">계약 정보</h2>
|
||||
<div className="space-y-3">
|
||||
<Input label="계약 제목" name="title" value={form.title} error={errors.title} onChange={handleChange} required placeholder="예: 2026년 공급 계약서" />
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">계약 제목 <span className="text-red-500">*</span></label>
|
||||
<select value={titleType} onChange={e => {
|
||||
const val = e.target.value;
|
||||
setTitleType(val);
|
||||
if (val !== '__custom__') handleChange('title', val);
|
||||
else handleChange('title', '');
|
||||
}}
|
||||
className="w-full border border-gray-300 rounded-md px-2.5 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 outline-none mb-1">
|
||||
{TITLE_PRESETS.map(p => <option key={p.value} value={p.value}>{p.label}</option>)}
|
||||
</select>
|
||||
{titleType === '__custom__' && (
|
||||
<input type="text" value={form.title} onChange={e => handleChange('title', e.target.value)}
|
||||
placeholder="계약 제목을 직접 입력하세요"
|
||||
className="w-full border border-gray-300 rounded-md px-2.5 py-1.5 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors mt-1" />
|
||||
)}
|
||||
{errors.title && <p className="text-red-500 text-xs mt-1">{errors.title}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">설명</label>
|
||||
<textarea value={form.description} onChange={e => handleChange('description', e.target.value)}
|
||||
@@ -875,20 +888,14 @@ className="px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<a href="/esign" className="text-gray-400 hover:text-gray-600 text-lg" hx-boost="false">←</a>
|
||||
<h1 className="text-xl font-bold text-gray-900">새 계약 생성</h1>
|
||||
{IS_ADMIN && (<>
|
||||
<button type="button" onClick={fillTestData} title="영업파트너 계약서 테스트 데이터"
|
||||
{IS_ADMIN && (
|
||||
<button type="button" onClick={fillRandomCounterpart} title="랜덤 상대방 정보 채우기"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-lg text-amber-500 hover:bg-amber-50 hover:text-amber-600 transition-colors">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onClick={fillTestDataNda} title="비밀유지서약서 테스트 데이터"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-lg text-purple-500 hover:bg-purple-50 hover:text-purple-600 transition-colors">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</>)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 스텝 인디케이터 (템플릿 있을 때만) */}
|
||||
|
||||
Reference in New Issue
Block a user