feat: [esign] 계약 수정 화면에 서명 필드 값 편집 기능 추가

- 수정 화면에서 text/date/checkbox 필드 값 인라인 편집
- 필드별 서명자 구분(작성자/상대방), 페이지 번호 표시
- update API에서 필드 값 일괄 업데이트 처리
This commit is contained in:
김보곤
2026-03-11 13:05:36 +09:00
parent edc69040ab
commit ea4e16bbd0
2 changed files with 98 additions and 1 deletions

View File

@@ -595,6 +595,9 @@ public function update(Request $request, int $id): JsonResponse
'signers.*.phone' => 'nullable|string|max:20',
'signers.*.role' => 'required|in:creator,counterpart',
'file' => 'nullable|file|mimes:pdf,doc,docx|max:20480',
'fields' => 'nullable|array',
'fields.*.id' => 'required|integer',
'fields.*.field_value' => 'nullable|string|max:500',
]);
$userId = auth()->id();
@@ -645,6 +648,16 @@ public function update(Request $request, int $id): JsonResponse
}
}
// 필드 값 업데이트
if ($request->has('fields')) {
foreach ($request->input('fields') as $fieldData) {
EsignSignField::withoutGlobalScopes()
->where('id', $fieldData['id'])
->where('contract_id', $contract->id)
->update(['field_value' => $fieldData['field_value'] ?? null]);
}
}
// 감사 로그
EsignAuditLog::create([
'tenant_id' => $tenantId,
@@ -659,7 +672,7 @@ public function update(Request $request, int $id): JsonResponse
return response()->json([
'success' => true,
'message' => '계약이 수정되었습니다.',
'data' => $contract->load('signers'),
'data' => $contract->load(['signers', 'signFields']),
]);
}

View File

@@ -460,6 +460,8 @@ className={`w-full text-left px-3 py-2.5 rounded-lg mb-1 transition-colors ${i =
const [tenantModalOpen, setTenantModalOpen] = useState(false);
const [editLoading, setEditLoading] = useState(IS_EDIT);
const [existingFileName, setExistingFileName] = useState('');
const [editFields, setEditFields] = useState([]);
const [editSigners, setEditSigners] = useState([]);
const fileRef = useRef(null);
const hasTemplates = templates.length > 0;
@@ -511,6 +513,9 @@ className={`w-full text-left px-3 py-2.5 rounded-lg mb-1 transition-colors ${i =
const preset = TITLE_PRESETS.find(p => p.value === c.title);
setTitleType(preset ? c.title : '__custom__');
if (c.original_file_name) setExistingFileName(c.original_file_name);
// 필드 및 서명자 정보 로드
if (c.sign_fields?.length) setEditFields(c.sign_fields);
if (c.signers?.length) setEditSigners(c.signers);
}
})
.catch(() => { alert('계약 정보를 불러오지 못했습니다.'); })
@@ -787,6 +792,14 @@ className={`w-full text-left px-3 py-2.5 rounded-lg mb-1 transition-colors ${i =
fd.append('signers[1][phone]', form.counterpart_phone || '');
fd.append('signers[1][role]', 'counterpart');
// 수정 모드: 필드 값 전송
if (IS_EDIT && editFields.length > 0) {
editFields.forEach((f, i) => {
fd.append(`fields[${i}][id]`, f.id);
fd.append(`fields[${i}][field_value]`, f.field_value || '');
});
}
let url, method;
if (IS_EDIT) {
url = `/esign/contracts/${EDIT_CONTRACT_ID}`;
@@ -920,6 +933,77 @@ className="w-full border border-gray-300 rounded-md px-2.5 py-1.5 text-sm focus:
</div>
</div>
{/* 수정 모드: 서명 필드 값 편집 */}
{IS_EDIT && editFields.length > 0 && (
<div className="bg-white rounded-lg border p-4">
<h2 className="text-sm font-semibold text-gray-900 mb-3">서명 필드 </h2>
<p className="text-xs text-gray-500 mb-3"> 필드의 값을 수정할 있습니다. 서명/도장 필드는 서명 입력됩니다.</p>
<div className="space-y-2">
{editFields.map((field, idx) => {
const signerInfo = editSigners.find(s => s.id === field.signer_id);
const signerLabel = signerInfo ? (signerInfo.role === 'creator' ? '작성자' : '상대방') : '';
const signerColor = signerInfo?.role === 'creator' ? '#3B82F6' : '#EF4444';
const isEditable = ['text', 'date'].includes(field.field_type);
return (
<div key={field.id} className="flex items-center gap-3 p-2 rounded-lg bg-gray-50">
<span className="shrink-0 w-5 h-5 rounded flex items-center justify-center text-white text-[10px] font-bold"
style={{ backgroundColor: signerColor }}>
{FIELD_TYPE_INFO[field.field_type]?.icon || '?'}
</span>
<div className="shrink-0" style={{ width: 90 }}>
<span className="text-[10px] px-1.5 py-0.5 rounded-full text-white font-medium" style={{ backgroundColor: signerColor }}>
{signerLabel}
</span>
</div>
<div className="shrink-0 text-xs text-gray-600" style={{ width: 100 }}>
{field.field_label || FIELD_TYPE_INFO[field.field_type]?.label || field.field_type}
</div>
<div style={{ flex: 1 }}>
{isEditable ? (
<input
type={field.field_type === 'date' ? 'date' : 'text'}
value={field.field_value || ''}
onChange={e => {
const updated = [...editFields];
updated[idx] = { ...updated[idx], field_value: e.target.value };
setEditFields(updated);
}}
placeholder={field.field_variable ? `{{${field.field_variable}}}` : '값 입력'}
className="w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-1 focus:ring-blue-500 outline-none"
/>
) : field.field_type === 'checkbox' ? (
<label className="flex items-center gap-1.5 cursor-pointer">
<input
type="checkbox"
checked={field.field_value === '1' || field.field_value === 'true'}
onChange={e => {
const updated = [...editFields];
updated[idx] = { ...updated[idx], field_value: e.target.checked ? '1' : '0' };
setEditFields(updated);
}}
className="rounded border-gray-300 text-blue-600"
/>
<span className="text-xs text-gray-500">{field.field_label || '체크'}</span>
</label>
) : (
<span className="text-xs text-gray-400 italic">서명 입력</span>
)}
</div>
<span className="shrink-0 text-[10px] text-gray-400">P{field.page_number}</span>
</div>
);
})}
</div>
</div>
)}
{IS_EDIT && editFields.length === 0 && (
<div className="bg-gray-50 rounded-lg border border-gray-200 p-4">
<p className="text-xs text-gray-500">설정된 서명 필드가 없습니다. 수정 완료 서명 위치 설정에서 필드를 추가하세요.</p>
</div>
)}
{/* 네비게이션 */}
<div className="flex justify-between">
<a href={IS_EDIT ? `/esign/${EDIT_CONTRACT_ID}` : '/esign'} className="px-4 py-1.5 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 text-sm transition-colors" hx-boost="false">취소</a>