feat:템플릿 편집 모달 확장 (PDF 관리 + 서식 필드 관리)

백엔드:
- uploadTemplatePdf: 템플릿 PDF 업로드/교체 API
- removeTemplatePdf: 템플릿 PDF 제거 API
- destroyTemplateItem: 개별 필드 아이템 삭제 API (signer_count 자동 재계산)
- updateTemplate 응답에 items 관계 포함

프론트엔드:
- 모달 폭 420px → 680px 확장
- 3개 탭 구성: 기본 정보 / PDF 파일 / 서식 필드
- PDF 탭: 현재 파일 정보, 다운로드, 교체, 제거 기능
- 서식 필드 탭: 필드 목록 테이블 (유형/라벨/서명자/페이지/위치/필수), 개별 삭제
- 편집 시 상세 데이터(items 포함) 로드

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-13 06:50:13 +09:00
parent 36654d9992
commit a464cf40de
3 changed files with 333 additions and 45 deletions

View File

@@ -732,7 +732,89 @@ public function updateTemplate(Request $request, int $id): JsonResponse
return response()->json([
'success' => true,
'message' => '템플릿이 수정되었습니다.',
'data' => $template->fresh()->load('creator:id,name'),
'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
]);
}
/**
* 템플릿 PDF 교체
*/
public function uploadTemplatePdf(Request $request, int $id): JsonResponse
{
$request->validate([
'file' => 'required|file|mimes:pdf|max:20480',
]);
$tenantId = session('selected_tenant_id', 1);
$template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);
// 기존 파일 삭제
if ($template->file_path && Storage::disk('local')->exists($template->file_path)) {
Storage::disk('local')->delete($template->file_path);
}
$file = $request->file('file');
$filePath = $file->store("esign/{$tenantId}/templates", 'local');
$template->update([
'file_path' => $filePath,
'file_name' => $file->getClientOriginalName(),
'file_hash' => hash_file('sha256', $file->getRealPath()),
'file_size' => $file->getSize(),
]);
return response()->json([
'success' => true,
'message' => 'PDF가 교체되었습니다.',
'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
]);
}
/**
* 템플릿 PDF 제거
*/
public function removeTemplatePdf(int $id): JsonResponse
{
$tenantId = session('selected_tenant_id', 1);
$template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);
if ($template->file_path && Storage::disk('local')->exists($template->file_path)) {
Storage::disk('local')->delete($template->file_path);
}
$template->update([
'file_path' => null,
'file_name' => null,
'file_hash' => null,
'file_size' => null,
]);
return response()->json([
'success' => true,
'message' => 'PDF가 제거되었습니다.',
'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
]);
}
/**
* 템플릿 필드 아이템 삭제
*/
public function destroyTemplateItem(int $templateId, int $itemId): JsonResponse
{
$tenantId = session('selected_tenant_id', 1);
$template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($templateId);
$item = EsignFieldTemplateItem::where('template_id', $template->id)->findOrFail($itemId);
$item->delete();
// signer_count 재계산
$maxOrder = EsignFieldTemplateItem::where('template_id', $template->id)->max('signer_order');
$template->update(['signer_count' => $maxOrder ?: 0]);
return response()->json([
'success' => true,
'message' => '필드가 삭제되었습니다.',
'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
]);
}