224 lines
8.9 KiB
PHP
224 lines
8.9 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '기안 작성')
|
|
|
|
@section('content')
|
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6">
|
|
<h1 class="text-2xl font-bold text-gray-800">기안 작성</h1>
|
|
<a href="{{ route('approvals.drafts') }}" class="text-gray-600 hover:text-gray-800 text-sm">
|
|
← 기안함으로 돌아가기
|
|
</a>
|
|
</div>
|
|
|
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
{{-- 좌측: 양식 --}}
|
|
<div class="flex-1 min-w-0">
|
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">문서 내용</h2>
|
|
|
|
{{-- 양식 선택 --}}
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">양식 <span class="text-red-500">*</span></label>
|
|
<select id="form_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
@foreach($forms as $form)
|
|
<option value="{{ $form->id }}">{{ $form->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
{{-- 제목 --}}
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">제목 <span class="text-red-500">*</span></label>
|
|
<input type="text" id="title" maxlength="200" placeholder="결재 제목을 입력하세요"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
|
|
{{-- 긴급 여부 --}}
|
|
<div class="mb-4">
|
|
<label class="inline-flex items-center gap-2 cursor-pointer">
|
|
<input type="checkbox" id="is_urgent" class="rounded border-gray-300 text-red-600 focus:ring-red-500">
|
|
<span class="text-sm text-gray-700">긴급</span>
|
|
</label>
|
|
</div>
|
|
|
|
{{-- 본문 --}}
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
본문
|
|
<label class="inline-flex items-center gap-1 ml-3 cursor-pointer">
|
|
<input type="checkbox" id="useEditor" onchange="toggleEditor()"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
|
<span class="text-xs text-gray-500 font-normal">편집기</span>
|
|
</label>
|
|
</label>
|
|
<textarea id="body" rows="12" placeholder="기안 내용을 입력하세요..."
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
style="min-height: 300px;"></textarea>
|
|
<div id="quill-container" style="display: none; min-height: 300px;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- 우측: 결재선 --}}
|
|
<div class="shrink-0" style="width: 100%; max-width: 680px;">
|
|
@php
|
|
$defaultLine = collect($lines)->firstWhere('is_default', true);
|
|
@endphp
|
|
@include('approvals.partials._approval-line-editor', [
|
|
'lines' => $lines,
|
|
'initialSteps' => $defaultLine?->steps ?? [],
|
|
'selectedLineId' => $defaultLine?->id ?? '',
|
|
])
|
|
|
|
{{-- 액션 버튼 --}}
|
|
<div class="mt-4 space-y-2">
|
|
<button onclick="saveApproval('draft')"
|
|
class="w-full bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition text-sm font-medium">
|
|
임시저장
|
|
</button>
|
|
<button onclick="saveApproval('submit')"
|
|
class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition text-sm font-medium">
|
|
상신
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@push('styles')
|
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
|
|
<style>
|
|
#quill-container .ql-editor { min-height: 260px; font-size: 0.875rem; }
|
|
#quill-container .ql-toolbar { border-radius: 0.5rem 0.5rem 0 0; border-color: #d1d5db; }
|
|
#quill-container .ql-container { border-radius: 0 0 0.5rem 0.5rem; border-color: #d1d5db; }
|
|
</style>
|
|
@endpush
|
|
|
|
@push('scripts')
|
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
|
<script>
|
|
let quillInstance = null;
|
|
|
|
function toggleEditor() {
|
|
const useEditor = document.getElementById('useEditor').checked;
|
|
const textarea = document.getElementById('body');
|
|
const container = document.getElementById('quill-container');
|
|
|
|
if (useEditor) {
|
|
container.style.display = '';
|
|
textarea.style.display = 'none';
|
|
if (!quillInstance) {
|
|
quillInstance = new Quill('#quill-container', {
|
|
theme: 'snow',
|
|
modules: {
|
|
toolbar: [
|
|
[{ 'header': [1, 2, 3, false] }],
|
|
['bold', 'italic', 'underline', 'strike'],
|
|
[{ 'color': [] }, { 'background': [] }],
|
|
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
|
[{ 'align': [] }],
|
|
['blockquote', 'code-block'],
|
|
['link'],
|
|
['clean'],
|
|
],
|
|
},
|
|
placeholder: '기안 내용을 입력하세요...',
|
|
});
|
|
}
|
|
const text = textarea.value.trim();
|
|
if (text) {
|
|
if (/<[a-z][\s\S]*>/i.test(text)) {
|
|
quillInstance.root.innerHTML = text;
|
|
} else {
|
|
quillInstance.setText(text);
|
|
}
|
|
}
|
|
} else {
|
|
container.style.display = 'none';
|
|
textarea.style.display = '';
|
|
if (quillInstance) {
|
|
const html = quillInstance.root.innerHTML;
|
|
textarea.value = (html === '<p><br></p>') ? '' : html;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getBodyContent() {
|
|
const useEditor = document.getElementById('useEditor')?.checked;
|
|
if (useEditor && quillInstance) {
|
|
const html = quillInstance.root.innerHTML;
|
|
return (html === '<p><br></p>') ? '' : html;
|
|
}
|
|
return document.getElementById('body').value;
|
|
}
|
|
|
|
async function saveApproval(action) {
|
|
const title = document.getElementById('title').value.trim();
|
|
if (!title) {
|
|
showToast('제목을 입력해주세요.', 'warning');
|
|
return;
|
|
}
|
|
|
|
const editorEl = document.getElementById('approval-line-editor');
|
|
const steps = editorEl._x_dataStack[0].getStepsData();
|
|
|
|
if (action === 'submit' && steps.filter(s => s.step_type !== 'reference').length === 0) {
|
|
showToast('결재자를 1명 이상 추가해주세요.', 'warning');
|
|
return;
|
|
}
|
|
|
|
const payload = {
|
|
form_id: document.getElementById('form_id').value,
|
|
title: title,
|
|
body: getBodyContent(),
|
|
is_urgent: document.getElementById('is_urgent').checked,
|
|
steps: steps,
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/approvals', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Accept': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!data.success && !data.data) {
|
|
showToast(data.message || '저장에 실패했습니다.', 'error');
|
|
return;
|
|
}
|
|
|
|
if (action === 'submit') {
|
|
const submitResponse = await fetch(`/api/admin/approvals/${data.data.id}/submit`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Accept': 'application/json',
|
|
},
|
|
});
|
|
|
|
const submitData = await submitResponse.json();
|
|
|
|
if (submitData.success) {
|
|
showToast('결재가 상신되었습니다.', 'success');
|
|
setTimeout(() => location.href = '/approval-mgmt/drafts', 500);
|
|
} else {
|
|
showToast(submitData.message || '상신에 실패했습니다.', 'error');
|
|
}
|
|
} else {
|
|
showToast('임시저장되었습니다.', 'success');
|
|
setTimeout(() => location.href = `/approval-mgmt/${data.data.id}/edit`, 500);
|
|
}
|
|
} catch (e) {
|
|
showToast('서버 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
@endpush
|