- Document 관련 모델 4개 생성 (Document, DocumentApproval, DocumentData, DocumentAttachment) - DocumentController 생성 (목록/생성/상세/수정 페이지) - DocumentApiController 생성 (AJAX CRUD 처리) - 문서 관리 뷰 3개 생성 (index, edit, show) - 웹/API 라우트 등록 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
211 lines
10 KiB
PHP
211 lines
10 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', $isCreate ? '새 문서 작성' : '문서 수정')
|
|
|
|
@section('content')
|
|
<div class="p-6">
|
|
{{-- 헤더 --}}
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">{{ $isCreate ? '새 문서 작성' : '문서 수정' }}</h1>
|
|
<p class="text-sm text-gray-500 mt-1">
|
|
@if($document)
|
|
{{ $document->document_no }} - {{ $document->title }}
|
|
@else
|
|
템플릿을 선택하여 문서를 작성합니다.
|
|
@endif
|
|
</p>
|
|
</div>
|
|
<a href="{{ route('documents.index') }}"
|
|
class="inline-flex items-center px-4 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-200 transition-colors">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
</svg>
|
|
목록으로
|
|
</a>
|
|
</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)
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
{{ $field->label }}
|
|
@if($field->is_required)
|
|
<span class="text-red-500">*</span>
|
|
@endif
|
|
</label>
|
|
@if($field->type === 'textarea')
|
|
<textarea name="data[{{ $field->field_key }}]"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
rows="3"
|
|
{{ $field->is_required ? 'required' : '' }}
|
|
placeholder="{{ $field->placeholder ?? '' }}">{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}</textarea>
|
|
@elseif($field->type === 'select' && $field->options)
|
|
<select name="data[{{ $field->field_key }}]"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
{{ $field->is_required ? 'required' : '' }}>
|
|
<option value="">선택하세요</option>
|
|
@foreach($field->options as $option)
|
|
<option value="{{ $option }}" {{ ($document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '') === $option ? 'selected' : '' }}>
|
|
{{ $option }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
@elseif($field->type === 'date')
|
|
<input type="date" name="data[{{ $field->field_key }}]"
|
|
value="{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
{{ $field->is_required ? 'required' : '' }}>
|
|
@elseif($field->type === 'number')
|
|
<input type="number" name="data[{{ $field->field_key }}]"
|
|
value="{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
{{ $field->is_required ? 'required' : '' }}
|
|
placeholder="{{ $field->placeholder ?? '' }}">
|
|
@else
|
|
<input type="text" name="data[{{ $field->field_key }}]"
|
|
value="{{ $document?->data->where('field_key', $field->field_key)->first()?->field_value ?? '' }}"
|
|
class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500"
|
|
{{ $field->is_required ? 'required' : '' }}
|
|
placeholder="{{ $field->placeholder ?? '' }}">
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- 섹션 (테이블 형태) --}}
|
|
@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->name }}</h2>
|
|
<p class="text-sm text-gray-500 mb-4">테이블 형태의 데이터는 추후 구현 예정입니다.</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) {
|
|
alert(isCreate ? '문서가 저장되었습니다.' : '문서가 수정되었습니다.');
|
|
window.location.href = '/documents';
|
|
} else {
|
|
alert(result.message || '오류가 발생했습니다.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('오류가 발생했습니다.');
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
@endpush |