- Phase 1.3: EGI/SUS 수입검사 시드 데이터 생성 (IncomingInspectionTemplateSeeder) - Phase 1.5: 양식 복제 기능 (duplicate API, 테이블 버튼, JS) - Phase 2.1: 문서 생성 보완 - 문서번호 카테고리별 prefix (IQC/PRD/SLS/PUR-YYMMDD-순번) - 결재라인 초기화 (template.approvalLines → document_approvals) - 기본필드 뷰 속성 수정 (field_type, Str::slug field_key) - store()에 DB 트랜잭션 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
8.8 KiB
PHP
200 lines
8.8 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', $isCreate ? '새 문서 작성' : '문서 수정')
|
|
|
|
@section('content')
|
|
<div class="max-w-7xl mx-auto">
|
|
<!-- 헤더 -->
|
|
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">{{ $isCreate ? '새 문서 작성' : '문서 수정' }}</h1>
|
|
<p class="text-sm text-gray-500 mt-1 hidden sm:block">
|
|
@if($document)
|
|
{{ $document->document_no }} - {{ $document->title }}
|
|
@else
|
|
템플릿을 선택하여 문서를 작성합니다.
|
|
@endif
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<a href="{{ route('documents.index') }}"
|
|
class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transition">
|
|
목록
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 템플릿 선택 (생성 시) --}}
|
|
@if($isCreate && !$template)
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
|
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">템플릿 선택</h2>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
@forelse($templates as $tpl)
|
|
<a href="{{ route('documents.create', ['template_id' => $tpl->id]) }}"
|
|
class="block p-4 border border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition-colors">
|
|
<h3 class="font-medium text-gray-900">{{ $tpl->name }}</h3>
|
|
<p class="text-sm text-gray-500 mt-1">{{ $tpl->category }}</p>
|
|
</a>
|
|
@empty
|
|
<p class="text-gray-500 col-span-3">사용 가능한 템플릿이 없습니다.</p>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- 문서 폼 --}}
|
|
@if($template)
|
|
<form id="documentForm" class="space-y-6">
|
|
@csrf
|
|
<input type="hidden" name="template_id" value="{{ $template->id }}">
|
|
|
|
{{-- 기본 정보 --}}
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">기본 정보</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">템플릿</label>
|
|
<input type="text" value="{{ $template->name }}" disabled
|
|
class="w-full rounded-lg border-gray-300 bg-gray-50 text-sm">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
제목 <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" name="title" value="{{ $document->title ?? '' }}" required
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
placeholder="문서 제목을 입력하세요">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 기본 필드 --}}
|
|
@if($template->basicFields && $template->basicFields->count() > 0)
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $template->title ?? '문서 정보' }}</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
@foreach($template->basicFields as $field)
|
|
@php
|
|
$fieldKey = \Illuminate\Support\Str::slug($field->label, '_');
|
|
$savedValue = $document?->data->where('field_key', $fieldKey)->first()?->field_value ?? $field->default_value ?? '';
|
|
@endphp
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
{{ $field->label }}
|
|
</label>
|
|
@if($field->field_type === 'textarea')
|
|
<textarea name="data[{{ $fieldKey }}]"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
rows="3">{{ $savedValue }}</textarea>
|
|
@elseif($field->field_type === 'date')
|
|
<input type="date" name="data[{{ $fieldKey }}]"
|
|
value="{{ $savedValue }}"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">
|
|
@elseif($field->field_type === 'number')
|
|
<input type="number" name="data[{{ $fieldKey }}]"
|
|
value="{{ $savedValue }}"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">
|
|
@else
|
|
<input type="text" name="data[{{ $fieldKey }}]"
|
|
value="{{ $savedValue }}"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- 섹션 (테이블 형태 - Phase 2.2에서 구현) --}}
|
|
@if($template->sections && $template->sections->count() > 0)
|
|
@foreach($template->sections as $section)
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $section->title }}</h2>
|
|
@if($section->image_path)
|
|
<img src="{{ asset('storage/' . $section->image_path) }}" alt="{{ $section->title }}" class="max-w-full h-auto mb-4 rounded">
|
|
@endif
|
|
<p class="text-sm text-gray-400 italic">검사 데이터 테이블은 Phase 2.2에서 구현됩니다.</p>
|
|
</div>
|
|
@endforeach
|
|
@endif
|
|
|
|
{{-- 버튼 --}}
|
|
<div class="flex justify-end gap-3">
|
|
<a href="{{ route('documents.index') }}"
|
|
class="px-6 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-200 transition-colors">
|
|
취소
|
|
</a>
|
|
<button type="submit"
|
|
class="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors">
|
|
{{ $isCreate ? '저장' : '수정' }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
@endif
|
|
</div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const form = document.getElementById('documentForm');
|
|
if (!form) return;
|
|
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(form);
|
|
const data = {
|
|
template_id: formData.get('template_id'),
|
|
title: formData.get('title'),
|
|
data: []
|
|
};
|
|
|
|
// data 필드 수집
|
|
for (const [key, value] of formData.entries()) {
|
|
if (key.startsWith('data[') && key.endsWith(']')) {
|
|
const fieldKey = key.slice(5, -1);
|
|
data.data.push({
|
|
field_key: fieldKey,
|
|
field_value: value
|
|
});
|
|
}
|
|
}
|
|
|
|
const isCreate = {{ $isCreate ? 'true' : 'false' }};
|
|
const url = isCreate ? '/api/admin/documents' : '/api/admin/documents/{{ $document?->id }}';
|
|
const method = isCreate ? 'POST' : 'PATCH';
|
|
|
|
fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
showToast(isCreate ? '문서가 저장되었습니다.' : '문서가 수정되었습니다.', 'success');
|
|
if (isCreate && result.data?.id) {
|
|
window.location.href = '/documents/' + result.data.id + '/edit';
|
|
} else {
|
|
window.location.href = '/documents';
|
|
}
|
|
} else {
|
|
showToast(result.message || '오류가 발생했습니다.', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('오류가 발생했습니다.', 'error');
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
@endpush |