diff --git a/app/Http/Controllers/ESign/EsignApiController.php b/app/Http/Controllers/ESign/EsignApiController.php index 9996b7c9..b39441bb 100644 --- a/app/Http/Controllers/ESign/EsignApiController.php +++ b/app/Http/Controllers/ESign/EsignApiController.php @@ -818,6 +818,62 @@ public function destroyTemplateItem(int $templateId, int $itemId): JsonResponse ]); } + /** + * 템플릿 필드 아이템 일괄 저장 (에디터에서 사용) + */ + public function updateTemplateItems(Request $request, int $templateId): JsonResponse + { + $request->validate([ + 'items' => 'present|array', + 'items.*.signer_order' => 'required|integer|min:1', + 'items.*.page_number' => 'required|integer|min:1', + 'items.*.position_x' => 'required|numeric', + 'items.*.position_y' => 'required|numeric', + 'items.*.width' => 'required|numeric', + 'items.*.height' => 'required|numeric', + 'items.*.field_type' => 'required|in:signature,stamp,text,date,checkbox', + 'items.*.field_label' => 'nullable|string|max:100', + 'items.*.is_required' => 'nullable|boolean', + ]); + + $tenantId = session('selected_tenant_id', 1); + $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($templateId); + + $items = $request->input('items', []); + + DB::transaction(function () use ($template, $items) { + // 기존 아이템 삭제 + EsignFieldTemplateItem::where('template_id', $template->id)->delete(); + + // 새 아이템 생성 + foreach ($items as $i => $itemData) { + EsignFieldTemplateItem::create([ + 'template_id' => $template->id, + 'signer_order' => $itemData['signer_order'], + 'page_number' => $itemData['page_number'], + 'position_x' => round($itemData['position_x'], 2), + 'position_y' => round($itemData['position_y'], 2), + 'width' => round($itemData['width'], 2), + 'height' => round($itemData['height'], 2), + 'field_type' => $itemData['field_type'], + 'field_label' => $itemData['field_label'] ?? '', + 'is_required' => $itemData['is_required'] ?? true, + 'sort_order' => $i, + ]); + } + + // signer_count 업데이트 + $maxOrder = collect($items)->max('signer_order') ?: 0; + $template->update(['signer_count' => $maxOrder]); + }); + + return response()->json([ + 'success' => true, + 'message' => '템플릿 필드가 저장되었습니다.', + 'data' => $template->fresh()->load('items'), + ]); + } + /** * 템플릿 복제 */ diff --git a/app/Http/Controllers/ESign/EsignController.php b/app/Http/Controllers/ESign/EsignController.php index 5dcc5e6c..95ed5179 100644 --- a/app/Http/Controllers/ESign/EsignController.php +++ b/app/Http/Controllers/ESign/EsignController.php @@ -54,6 +54,15 @@ public function send(Request $request, int $id): View|Response return view('esign.send', ['contractId' => $id]); } + public function templateFields(Request $request, int $templateId): View|Response + { + if ($request->header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('esign.template-fields', $templateId)); + } + + return view('esign.template-fields', ['templateId' => $templateId]); + } + public function templates(Request $request): View|Response { if ($request->header('HX-Request')) { diff --git a/resources/views/esign/template-fields.blade.php b/resources/views/esign/template-fields.blade.php new file mode 100644 index 00000000..7520d46e --- /dev/null +++ b/resources/views/esign/template-fields.blade.php @@ -0,0 +1,790 @@ +@extends('layouts.app') + +@section('title', 'SAM E-Sign - 템플릿 필드 편집') + +@section('content') + +
+@endsection + +@push('scripts') +@include('partials.react-cdn') + + + +@verbatim + +@endverbatim +@endpush diff --git a/resources/views/esign/templates.blade.php b/resources/views/esign/templates.blade.php index 408b45a8..7869e8b6 100644 --- a/resources/views/esign/templates.blade.php +++ b/resources/views/esign/templates.blade.php @@ -236,10 +236,24 @@ className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 {/* 서식 필드 탭 */} {tab === 'fields' && (
+ {/* 비주얼 편집 버튼 */} + {tpl.file_path && ( +
+
+

PDF에서 시각적으로 필드를 편집할 수 있습니다

+

드래그 앤 드롭으로 서명 위치를 설정하세요

+
+ e.stopPropagation()}> + 비주얼 편집 + +
+ )} {(!tpl.items || tpl.items.length === 0) ? (

서식 필드가 없습니다.

-

계약의 필드 에디터에서 "템플릿으로 저장" 시 필드가 포함됩니다.

+

{tpl.file_path ? '위의 "비주얼 편집" 버튼으로 PDF에서 필드를 추가하세요.' : '계약의 필드 에디터에서 "템플릿으로 저장" 시 필드가 포함됩니다.'}

) : (
@@ -339,6 +353,10 @@ className="w-7 h-7 flex items-center justify-center rounded-md text-gray-400 hov
+ {template.file_path && ( + 필드 편집 + )}
@@ -384,6 +402,15 @@ className="w-full text-left px-3 py-1.5 text-sm text-red-600 hover:bg-red-50"> 필드 {template.items_count ?? template.items?.length ?? 0}개
+ {/* 필드 편집 버튼 (PDF가 있을 때) */} + {template.file_path && ( + + 필드 편집 + + )} + {/* 하단: 생성자 + 시간 */}
diff --git a/routes/web.php b/routes/web.php index f76f1120..b71fee9c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1399,6 +1399,7 @@ Route::get('/create', [EsignController::class, 'create'])->name('create'); Route::get('/docs', [EsignController::class, 'docs'])->name('docs'); Route::get('/templates', [EsignController::class, 'templates'])->name('templates'); + Route::get('/templates/{templateId}/fields', [EsignController::class, 'templateFields'])->whereNumber('templateId')->name('template-fields'); Route::get('/{id}', [EsignController::class, 'detail'])->whereNumber('id')->name('detail'); Route::get('/{id}/fields', [EsignController::class, 'fields'])->whereNumber('id')->name('fields'); Route::get('/{id}/send', [EsignController::class, 'send'])->whereNumber('id')->name('send'); @@ -1430,6 +1431,7 @@ Route::post('/templates/{templateId}/upload-pdf', [EsignApiController::class, 'uploadTemplatePdf'])->whereNumber('templateId')->name('templates.upload-pdf'); Route::delete('/templates/{templateId}/remove-pdf', [EsignApiController::class, 'removeTemplatePdf'])->whereNumber('templateId')->name('templates.remove-pdf'); Route::delete('/templates/{templateId}/items/{itemId}', [EsignApiController::class, 'destroyTemplateItem'])->whereNumber('templateId')->whereNumber('itemId')->name('templates.items.destroy'); + Route::put('/templates/{templateId}/items', [EsignApiController::class, 'updateTemplateItems'])->whereNumber('templateId')->name('templates.items.update'); Route::get('/templates/{templateId}/download', [EsignApiController::class, 'downloadTemplatePdf'])->whereNumber('templateId')->name('templates.download'); // 템플릿 적용 / 필드 복사