feat: [approval] 이사회의사록 양식 추가

- 이사회의사록 전용 폼(_board-minutes-form.blade.php) 생성
- 이사회의사록 읽기전용 뷰(_board-minutes-show.blade.php) 생성
- Alpine.js 의안/서명란 동적 추가/삭제 기능
- 테넌트 정보에서 회사명/대표자 자동 채움
- create/edit/show 페이지 통합
- 미리보기/인쇄 기능 포함
- 인사/근태 카테고리에 배치
This commit is contained in:
김보곤
2026-03-06 23:00:22 +09:00
parent f149a987c9
commit 05321c8839
5 changed files with 746 additions and 5 deletions

View File

@@ -140,6 +140,11 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-
'tenantInfo' => $tenantInfo ?? [],
])
{{-- 이사회의사록 전용 --}}
@include('approvals.partials._board-minutes-form', [
'tenantInfo' => $tenantInfo ?? [],
])
{{-- 지출결의서 전용 --}}
@include('approvals.partials._expense-form', [
'initialData' => [],
@@ -324,6 +329,11 @@ class="p-1 text-gray-400 hover:text-gray-600 transition">
color: 'border-amber-200 bg-amber-50', titleColor: 'text-amber-800', textColor: 'text-amber-600',
text: '법인의 업무를 대리인에게 위임하는 문서입니다. 위임인(회사), 수임인(대리인), 위임사항, 위임기간을 기재하며, 승인 후 위임장으로 사용할 수 있습니다.',
},
board_minutes: {
title: '이사회의사록', icon: '📋',
color: 'border-slate-300 bg-slate-50', titleColor: 'text-slate-800', textColor: 'text-slate-600',
text: '이사회 개최 내용을 기록하는 공식 문서입니다. 일시, 장소, 출석현황, 의안, 의사경과, 기명날인 등을 기재하며, 법적 효력을 갖는 회의록입니다.',
},
pr_expense: {
title: '지출품의서', icon: '📋',
color: 'border-orange-200 bg-orange-50', titleColor: 'text-orange-800', textColor: 'text-orange-700',
@@ -354,7 +364,7 @@ class="p-1 text-gray-400 hover:text-gray-600 transition">
// 2단계 분류 정의 (코드 → 카테고리)
const formCategoryMap = {
BUSINESS_DRAFT: '일반',
leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', delegation: '인사/근태',
leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', delegation: '인사/근태', board_minutes: '인사/근태',
employment_cert: '증명서', career_cert: '증명서', appointment_cert: '증명서', seal_usage: '증명서',
pr_expense: '품의', pr_contract: '품의', pr_purchase: '품의', pr_trip: '품의', pr_settlement: '품의',
expense: '재무',
@@ -443,6 +453,7 @@ function updateFormDescription(formId) {
let isResignationForm = false;
let isSealUsageForm = false;
let isDelegationForm = false;
let isBoardMinutesForm = false;
// 양식코드별 표시할 유형 목록
const leaveTypesByFormCode = {
@@ -693,6 +704,7 @@ function switchFormMode(formId) {
const resignationContainer = document.getElementById('resignation-form-container');
const sealUsageContainer = document.getElementById('seal-usage-form-container');
const delegationContainer = document.getElementById('delegation-form-container');
const boardMinutesContainer = document.getElementById('board-minutes-form-container');
const bodyArea = document.getElementById('body-area');
const expenseLoadBtn = document.getElementById('expense-load-btn');
@@ -708,6 +720,7 @@ function switchFormMode(formId) {
resignationContainer.style.display = 'none';
sealUsageContainer.style.display = 'none';
delegationContainer.style.display = 'none';
boardMinutesContainer.style.display = 'none';
expenseLoadBtn.style.display = 'none';
bodyArea.style.display = 'none';
isExpenseForm = false;
@@ -719,6 +732,7 @@ function switchFormMode(formId) {
isResignationForm = false;
isSealUsageForm = false;
isDelegationForm = false;
isBoardMinutesForm = false;
if (code === 'expense') {
isExpenseForm = true;
@@ -784,6 +798,9 @@ function switchFormMode(formId) {
} else if (code === 'delegation') {
isDelegationForm = true;
delegationContainer.style.display = '';
} else if (code === 'board_minutes') {
isBoardMinutesForm = true;
boardMinutesContainer.style.display = '';
} else {
bodyArea.style.display = '';
}
@@ -794,7 +811,7 @@ function applyBodyTemplate(formId) {
switchFormMode(formId);
// 전용 폼이면 제목을 양식명으로 설정하고 body template 적용 건너뜀
if (isExpenseForm || isPurchaseRequestForm || isLeaveForm || isCertForm || isCareerCertForm || isAppointmentCertForm || isResignationForm || isSealUsageForm || isDelegationForm) {
if (isExpenseForm || isPurchaseRequestForm || isLeaveForm || isCertForm || isCareerCertForm || isAppointmentCertForm || isResignationForm || isSealUsageForm || isDelegationForm || isBoardMinutesForm) {
const titleEl = document.getElementById('title');
const formSelect = document.getElementById('form_id');
titleEl.value = formSelect.options[formSelect.selectedIndex].text;
@@ -1011,6 +1028,52 @@ function applyBodyTemplate(formId) {
company_address: document.getElementById('su-company-address').value,
};
formBody = null;
} else if (isBoardMinutesForm) {
const bmDatetime = document.getElementById('bm-meeting-datetime').value;
if (!bmDatetime) {
showToast('일시를 입력해주세요.', 'warning');
return;
}
const bmPlace = document.getElementById('bm-meeting-place').value.trim();
if (!bmPlace) {
showToast('장소를 입력해주세요.', 'warning');
return;
}
const bmChairman = document.getElementById('bm-chairman-name').value.trim();
if (!bmChairman) {
showToast('의장(대표이사) 성명을 입력해주세요.', 'warning');
return;
}
const bmContainer = document.getElementById('board-minutes-form-container');
const bmAlpine = bmContainer._x_dataStack?.[0];
const bmAgendas = bmAlpine ? bmAlpine.agendas : [];
const bmSigners = bmAlpine ? bmAlpine.signers : [];
if (bmAgendas.length === 0 || !bmAgendas[0].title.trim()) {
showToast('의안을 1건 이상 입력해주세요.', 'warning');
return;
}
formContent = {
meeting_datetime: bmDatetime,
meeting_place: bmPlace,
total_directors: parseInt(document.getElementById('bm-total-directors').value) || 0,
present_directors: parseInt(document.getElementById('bm-present-directors').value) || 0,
total_auditors: parseInt(document.getElementById('bm-total-auditors').value) || 0,
present_auditors: parseInt(document.getElementById('bm-present-auditors').value) || 0,
chairman_name: bmChairman,
agendas: bmAgendas.filter(a => a.title.trim()).map(a => ({ no: a.no, title: a.title.trim(), result: a.result.trim() })),
proceedings: document.getElementById('bm-proceedings').value.trim(),
closing_time: document.getElementById('bm-closing-time').value,
signers: bmSigners.filter(s => s.name.trim()).map(s => ({ role: s.role.trim(), name: s.name.trim() })),
company_name: document.getElementById('bm-company-name').value,
business_num: document.getElementById('bm-business-num').value,
ceo_name: document.getElementById('bm-ceo-name').value,
company_address: document.getElementById('bm-company-address').value,
meeting_date: bmDatetime.split('T')[0],
};
formBody = null;
} else if (isDelegationForm) {
const dlAgentName = document.getElementById('dl-agent-name').value.trim();
if (!dlAgentName) {
@@ -2053,6 +2116,118 @@ function closeDelegationPreview() {
document.body.style.overflow = '';
}
// ── 이사회의사록 미리보기 ──
function buildBoardMinutesPreviewHtml(data) {
const dt = data.meeting_datetime || '';
let dateStr = '';
if (dt) {
const d = new Date(dt);
const year = d.getFullYear();
const month = d.getMonth() + 1;
const day = d.getDate();
const hour = d.getHours();
const min = String(d.getMinutes()).padStart(2, '0');
const ampm = hour < 12 ? '오전' : '오후';
const h12 = hour % 12 || 12;
dateStr = `${year}년 ${month}월 ${day}일 ${ampm} ${h12}시 ${min}분`;
}
const agendas = data.agendas || [];
let agendasHtml = agendas.map(a =>
`<div style="margin-bottom: 12px;">
<div style="font-weight: 600; margin-bottom: 4px;">[제${a.no}호 의안: ${a.title}]</div>
${a.result ? `<div style="padding-left: 16px;">* ${a.result}</div>` : ''}
</div>`
).join('');
const signers = data.signers || [];
let signersHtml = signers.map(s =>
`<div style="display: flex; gap: 40px; margin-bottom: 8px; justify-content: center;">
<span style="min-width: 120px; text-align: right;">${s.role}:</span>
<span style="min-width: 100px;">${s.name}</span>
<span>(인) &#12958;</span>
</div>`
).join('');
return `
<div style="text-align: center; margin-bottom: 36px;">
<h1 style="font-size: 26px; font-weight: 800; letter-spacing: 6px;">이 사 회 의 사 록</h1>
</div>
<div style="font-size: 13px; line-height: 2;">
<p><strong>1. 일 시:</strong> ${dateStr}</p>
<p><strong>2. 장 소:</strong> ${data.meeting_place || ''}</p>
<p><strong>3. 출석이사 및 감사:</strong></p>
<div style="padding-left: 24px;">
<p>* 이사 총수: ${data.total_directors || 0}명 (출석이사: ${data.present_directors || 0}명)</p>
<p>* 감사 총수: ${data.total_auditors || 0}명 (출석감사: ${data.present_auditors || 0}명)</p>
</div>
<p style="margin-top: 8px;"><strong>4. 의 안:</strong></p>
<div style="padding-left: 24px; margin-bottom: 8px;">
${agendas.map(a => `<p>${a.title}</p>`).join('')}
</div>
<p><strong>5. 의사 경과 및 결과:</strong></p>
<div style="padding-left: 24px; margin-bottom: 8px;">
${data.proceedings ? `<p>${data.proceedings}</p>` : `<p>의장(대표이사 ${data.chairman_name || ''}) 위와 같이 법정 수에 달하는 이사가 출석하였으므로 이사회가 적법하게 성립되었음을 선언하고, 다음의 의안을 상정하여 승인을 구하다.</p>`}
${agendasHtml}
</div>
<p><strong>6. 폐 회:</strong></p>
<div style="padding-left: 24px;">
<p>의장은 이상으로써 의안 전부의 심의를 종료하였으므로 폐회를 선언하다.</p>
${data.closing_time ? `<p>(폐회 시각: ${data.closing_time})</p>` : ''}
</div>
</div>
<div style="margin-top: 32px; font-size: 13px; line-height: 1.8; text-align: center;">
<p>위 의사의 경과와 결과를 명확히 하기 위하여 이 의사록을 작성하고,</p>
<p>의장과 출석한 이사 및 감사가 아래와 같이 기명날인한다.</p>
<p style="margin-top: 20px; font-size: 14px;">${data.meeting_date || ''}</p>
<p style="margin-top: 8px; font-size: 15px; font-weight: 700;">${data.company_name || ''}</p>
</div>
<div style="margin-top: 32px; font-size: 13px;">
${signersHtml}
</div>
`;
}
function openBoardMinutesPreview() {
const bmContainer = document.getElementById('board-minutes-form-container');
const bmAlpine = bmContainer._x_dataStack?.[0];
const data = {
meeting_datetime: document.getElementById('bm-meeting-datetime').value,
meeting_place: document.getElementById('bm-meeting-place').value,
total_directors: parseInt(document.getElementById('bm-total-directors').value) || 0,
present_directors: parseInt(document.getElementById('bm-present-directors').value) || 0,
total_auditors: parseInt(document.getElementById('bm-total-auditors').value) || 0,
present_auditors: parseInt(document.getElementById('bm-present-auditors').value) || 0,
chairman_name: document.getElementById('bm-chairman-name').value,
agendas: bmAlpine ? bmAlpine.agendas.filter(a => a.title.trim()) : [],
proceedings: document.getElementById('bm-proceedings').value,
closing_time: document.getElementById('bm-closing-time').value,
signers: bmAlpine ? bmAlpine.signers.filter(s => s.name.trim()) : [],
company_name: document.getElementById('bm-company-name').value,
meeting_date: (document.getElementById('bm-meeting-datetime').value || '').split('T')[0],
};
document.getElementById('board-minutes-preview-content').innerHTML = buildBoardMinutesPreviewHtml(data);
document.getElementById('board-minutes-preview-modal').style.display = '';
document.body.style.overflow = 'hidden';
}
function closeBoardMinutesPreview() {
document.getElementById('board-minutes-preview-modal').style.display = 'none';
document.body.style.overflow = '';
}
function printBoardMinutesPreview() {
const content = document.getElementById('board-minutes-preview-content').innerHTML;
const win = window.open('', '_blank');
win.document.write('<html><head><title>이사회의사록</title>');
win.document.write('<style>body{font-family:"Pretendard","Malgun Gothic",sans-serif;padding:48px 56px;margin:0;}@media print{body{padding:40px 48px;}}</style>');
win.document.write('</head><body>');
win.document.write(content);
win.document.write('</body></html>');
win.document.close();
win.print();
}
function printDelegationPreview() {
const content = document.getElementById('delegation-preview-content').innerHTML;
const win = window.open('', '_blank');

View File

@@ -158,6 +158,11 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-
'tenantInfo' => $tenantInfo ?? [],
])
{{-- 이사회의사록 전용 --}}
@include('approvals.partials._board-minutes-form', [
'tenantInfo' => $tenantInfo ?? [],
])
{{-- 지출결의서 전용 --}}
@php
$existingFiles = [];
@@ -296,6 +301,7 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
let isCertForm = false;
let isSealUsageForm = false;
let isDelegationForm = false;
let isBoardMinutesForm = false;
const formDescriptions = {
BUSINESS_DRAFT: {
@@ -353,6 +359,11 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
color: 'border-amber-200 bg-amber-50', titleColor: 'text-amber-800', textColor: 'text-amber-600',
text: '법인의 업무를 대리인에게 위임하는 문서입니다. 위임인(회사), 수임인(대리인), 위임사항, 위임기간을 기재하며, 승인 후 위임장으로 사용할 수 있습니다.',
},
board_minutes: {
title: '이사회의사록', icon: '📋',
color: 'border-slate-300 bg-slate-50', titleColor: 'text-slate-800', textColor: 'text-slate-600',
text: '이사회 개최 내용을 기록하는 공식 문서입니다. 일시, 장소, 출석현황, 의안, 의사경과, 기명날인 등을 기재하며, 법적 효력을 갖는 회의록입니다.',
},
pr_expense: {
title: '지출품의서', icon: '📋',
color: 'border-orange-200 bg-orange-50', titleColor: 'text-orange-800', textColor: 'text-orange-700',
@@ -383,7 +394,7 @@ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm fon
// 2단계 분류 정의
const formCategoryMap = {
BUSINESS_DRAFT: '일반',
leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', delegation: '인사/근태',
leave: '인사/근태', attendance_request: '인사/근태', resignation: '인사/근태', reason_report: '인사/근태', delegation: '인사/근태', board_minutes: '인사/근태',
employment_cert: '증명서', career_cert: '증명서', appointment_cert: '증명서', seal_usage: '증명서',
pr_expense: '품의', pr_contract: '품의', pr_purchase: '품의', pr_trip: '품의', pr_settlement: '품의',
expense: '재무',
@@ -625,6 +636,7 @@ function switchFormMode(formId) {
const certContainer = document.getElementById('cert-form-container');
const sealUsageContainer = document.getElementById('seal-usage-form-container');
const delegationContainer = document.getElementById('delegation-form-container');
const boardMinutesContainer = document.getElementById('board-minutes-form-container');
const bodyArea = document.getElementById('body-area');
expenseContainer.style.display = 'none';
@@ -632,12 +644,14 @@ function switchFormMode(formId) {
certContainer.style.display = 'none';
sealUsageContainer.style.display = 'none';
delegationContainer.style.display = 'none';
boardMinutesContainer.style.display = 'none';
bodyArea.style.display = 'none';
isExpenseForm = false;
isPurchaseRequestForm = false;
isCertForm = false;
isSealUsageForm = false;
isDelegationForm = false;
isBoardMinutesForm = false;
if (code === 'expense') {
isExpenseForm = true;
@@ -660,6 +674,9 @@ function switchFormMode(formId) {
} else if (code === 'delegation') {
isDelegationForm = true;
delegationContainer.style.display = '';
} else if (code === 'board_minutes') {
isBoardMinutesForm = true;
boardMinutesContainer.style.display = '';
} else {
bodyArea.style.display = '';
}
@@ -670,7 +687,7 @@ function applyBodyTemplate(formId) {
switchFormMode(formId);
// 전용 폼이면 제목만 자동 설정하고 body template 적용 건너뜀
if (isExpenseForm || isPurchaseRequestForm || isCertForm || isSealUsageForm || isDelegationForm) {
if (isExpenseForm || isPurchaseRequestForm || isCertForm || isSealUsageForm || isDelegationForm || isBoardMinutesForm) {
const titleEl = document.getElementById('title');
if (!titleEl.value.trim()) {
const formSelect = document.getElementById('form_id');
@@ -792,8 +809,30 @@ function applyBodyTemplate(formId) {
}
}
// 이사회의사록 기존 데이터 복원
if (isBoardMinutesForm) {
const bmContent = @json($approval->content ?? []);
if (bmContent.meeting_datetime) {
document.getElementById('bm-meeting-datetime').value = bmContent.meeting_datetime || '';
document.getElementById('bm-meeting-place').value = bmContent.meeting_place || '';
document.getElementById('bm-total-directors').value = bmContent.total_directors || 0;
document.getElementById('bm-present-directors').value = bmContent.present_directors || 0;
document.getElementById('bm-total-auditors').value = bmContent.total_auditors || 0;
document.getElementById('bm-present-auditors').value = bmContent.present_auditors || 0;
document.getElementById('bm-chairman-name').value = bmContent.chairman_name || '';
document.getElementById('bm-proceedings').value = bmContent.proceedings || '';
document.getElementById('bm-closing-time').value = bmContent.closing_time || '';
const bmAlpine = document.getElementById('board-minutes-form-container')._x_dataStack?.[0];
if (bmAlpine) {
if (bmContent.agendas && bmContent.agendas.length > 0) bmAlpine.agendas = bmContent.agendas;
if (bmContent.signers && bmContent.signers.length > 0) bmAlpine.signers = bmContent.signers;
}
}
}
// 전용 폼이 아닌 경우에만 Quill 편집기 자동 활성화
if (!isExpenseForm && !isPurchaseRequestForm && !isCertForm && !isSealUsageForm && !isDelegationForm) {
if (!isExpenseForm && !isPurchaseRequestForm && !isCertForm && !isSealUsageForm && !isDelegationForm && !isBoardMinutesForm) {
const existingBody = document.getElementById('body').value;
if (/<[a-z][\s\S]*>/i.test(existingBody)) {
document.getElementById('useEditor').checked = true;
@@ -890,6 +929,38 @@ function applyBodyTemplate(formId) {
company_address: document.getElementById('su-company-address').value,
};
formBody = null;
} else if (isBoardMinutesForm) {
const bmDatetime = document.getElementById('bm-meeting-datetime').value;
if (!bmDatetime) { showToast('일시를 입력해주세요.', 'warning'); return; }
const bmPlace = document.getElementById('bm-meeting-place').value.trim();
if (!bmPlace) { showToast('장소를 입력해주세요.', 'warning'); return; }
const bmChairman = document.getElementById('bm-chairman-name').value.trim();
if (!bmChairman) { showToast('의장(대표이사) 성명을 입력해주세요.', 'warning'); return; }
const bmContainer = document.getElementById('board-minutes-form-container');
const bmAlpine = bmContainer._x_dataStack?.[0];
const bmAgendas = bmAlpine ? bmAlpine.agendas : [];
const bmSigners = bmAlpine ? bmAlpine.signers : [];
formContent = {
meeting_datetime: bmDatetime,
meeting_place: bmPlace,
total_directors: parseInt(document.getElementById('bm-total-directors').value) || 0,
present_directors: parseInt(document.getElementById('bm-present-directors').value) || 0,
total_auditors: parseInt(document.getElementById('bm-total-auditors').value) || 0,
present_auditors: parseInt(document.getElementById('bm-present-auditors').value) || 0,
chairman_name: bmChairman,
agendas: bmAgendas.filter(a => a.title.trim()).map(a => ({ no: a.no, title: a.title.trim(), result: a.result.trim() })),
proceedings: document.getElementById('bm-proceedings').value.trim(),
closing_time: document.getElementById('bm-closing-time').value,
signers: bmSigners.filter(s => s.name.trim()).map(s => ({ role: s.role.trim(), name: s.name.trim() })),
company_name: document.getElementById('bm-company-name').value,
business_num: document.getElementById('bm-business-num').value,
ceo_name: document.getElementById('bm-ceo-name').value,
company_address: document.getElementById('bm-company-address').value,
meeting_date: bmDatetime.split('T')[0],
};
formBody = null;
} else if (isDelegationForm) {
const dlAgentName = document.getElementById('dl-agent-name').value.trim();
if (!dlAgentName) {
@@ -1246,6 +1317,75 @@ function printSealUsagePreview() {
win.print();
}
// ── 이사회의사록 미리보기 ──
function buildBoardMinutesPreviewHtml(data) {
const dt = data.meeting_datetime || '';
let dateStr = '';
if (dt) {
const d = new Date(dt);
const year = d.getFullYear();
const month = d.getMonth() + 1;
const day = d.getDate();
const hour = d.getHours();
const min = String(d.getMinutes()).padStart(2, '0');
const ampm = hour < 12 ? '오전' : '오후';
const h12 = hour % 12 || 12;
dateStr = `${year}년 ${month}월 ${day}일 ${ampm} ${h12}시 ${min}분`;
}
const agendas = data.agendas || [];
let agendasHtml = agendas.map(a =>
`<div style="margin-bottom: 12px;"><div style="font-weight: 600; margin-bottom: 4px;">[제${a.no}호 의안: ${a.title}]</div>${a.result ? `<div style="padding-left: 16px;">* ${a.result}</div>` : ''}</div>`
).join('');
const signers = data.signers || [];
let signersHtml = signers.map(s =>
`<div style="display: flex; gap: 40px; margin-bottom: 8px; justify-content: center;"><span style="min-width: 120px; text-align: right;">${s.role}:</span><span style="min-width: 100px;">${s.name}</span><span>(인) &#12958;</span></div>`
).join('');
return `<div style="text-align: center; margin-bottom: 36px;"><h1 style="font-size: 26px; font-weight: 800; letter-spacing: 6px;">이 사 회 의 사 록</h1></div>
<div style="font-size: 13px; line-height: 2;"><p><strong>1. 일 시:</strong> ${dateStr}</p><p><strong>2. 장 소:</strong> ${data.meeting_place || ''}</p>
<p><strong>3. 출석이사 및 감사:</strong></p><div style="padding-left: 24px;"><p>* 이사 총수: ${data.total_directors || 0}명 (출석이사: ${data.present_directors || 0}명)</p><p>* 감사 총수: ${data.total_auditors || 0}명 (출석감사: ${data.present_auditors || 0}명)</p></div>
<p style="margin-top: 8px;"><strong>4. 의 안:</strong></p><div style="padding-left: 24px; margin-bottom: 8px;">${agendas.map(a => `<p>${a.title}</p>`).join('')}</div>
<p><strong>5. 의사 경과 및 결과:</strong></p><div style="padding-left: 24px; margin-bottom: 8px;">${data.proceedings ? `<p>${data.proceedings}</p>` : `<p>의장(대표이사 ${data.chairman_name || ''}) 위와 같이 법정 수에 달하는 이사가 출석하였으므로 이사회가 적법하게 성립되었음을 선언하고, 다음의 의안을 상정하여 승인을 구하다.</p>`}${agendasHtml}</div>
<p><strong>6. 폐 회:</strong></p><div style="padding-left: 24px;"><p>의장은 이상으로써 의안 전부의 심의를 종료하였으므로 폐회를 선언하다.</p>${data.closing_time ? `<p>(폐회 시각: ${data.closing_time})</p>` : ''}</div></div>
<div style="margin-top: 32px; font-size: 13px; line-height: 1.8; text-align: center;"><p>위 의사의 경과와 결과를 명확히 하기 위하여 이 의사록을 작성하고,</p><p>의장과 출석한 이사 및 감사가 아래와 같이 기명날인한다.</p><p style="margin-top: 20px; font-size: 14px;">${data.meeting_date || ''}</p><p style="margin-top: 8px; font-size: 15px; font-weight: 700;">${data.company_name || ''}</p></div>
<div style="margin-top: 32px; font-size: 13px;">${signersHtml}</div>`;
}
function openBoardMinutesPreview() {
const bmContainer = document.getElementById('board-minutes-form-container');
const bmAlpine = bmContainer._x_dataStack?.[0];
const data = {
meeting_datetime: document.getElementById('bm-meeting-datetime').value,
meeting_place: document.getElementById('bm-meeting-place').value,
total_directors: parseInt(document.getElementById('bm-total-directors').value) || 0,
present_directors: parseInt(document.getElementById('bm-present-directors').value) || 0,
total_auditors: parseInt(document.getElementById('bm-total-auditors').value) || 0,
present_auditors: parseInt(document.getElementById('bm-present-auditors').value) || 0,
chairman_name: document.getElementById('bm-chairman-name').value,
agendas: bmAlpine ? bmAlpine.agendas.filter(a => a.title.trim()) : [],
proceedings: document.getElementById('bm-proceedings').value,
closing_time: document.getElementById('bm-closing-time').value,
signers: bmAlpine ? bmAlpine.signers.filter(s => s.name.trim()) : [],
company_name: document.getElementById('bm-company-name').value,
meeting_date: (document.getElementById('bm-meeting-datetime').value || '').split('T')[0],
};
document.getElementById('board-minutes-preview-content').innerHTML = buildBoardMinutesPreviewHtml(data);
document.getElementById('board-minutes-preview-modal').style.display = '';
document.body.style.overflow = 'hidden';
}
function closeBoardMinutesPreview() {
document.getElementById('board-minutes-preview-modal').style.display = 'none';
document.body.style.overflow = '';
}
function printBoardMinutesPreview() {
const content = document.getElementById('board-minutes-preview-content').innerHTML;
const win = window.open('', '_blank');
win.document.write('<html><head><title>이사회의사록</title><style>body{font-family:"Pretendard","Malgun Gothic",sans-serif;padding:48px 56px;margin:0;}@media print{body{padding:40px 48px;}}</style></head><body>' + content + '</body></html>');
win.document.close();
win.print();
}
// ── 위임장 미리보기 ──
function buildDelegationPreviewHtml(data) {
return `

View File

@@ -0,0 +1,235 @@
{{--
이사회의사록 전용
Props:
$tenantInfo (array) - 테넌트(회사) 정보
--}}
@php
$tenantInfo = $tenantInfo ?? [];
@endphp
<div id="board-minutes-form-container" style="display: none;" class="mb-4"
x-data="{
agendas: [{ no: 1, title: '', result: '출석이사 전원 일치로 찬성하여 원안대로 승인 가결' }],
signers: [
{ role: '의장 대표이사', name: '{{ $tenantInfo['ceo_name'] ?? '' }}' },
{ role: '사내이사', name: '' },
{ role: '감사', name: '' },
],
addAgenda() {
this.agendas.push({ no: this.agendas.length + 1, title: '', result: '출석이사 전원 일치로 찬성하여 원안대로 승인 가결' });
},
removeAgenda(idx) {
if (this.agendas.length <= 1) return;
this.agendas.splice(idx, 1);
this.agendas.forEach((a, i) => a.no = i + 1);
},
addSigner() {
this.signers.push({ role: '', name: '' });
},
removeSigner(idx) {
if (this.signers.length <= 1) return;
this.signers.splice(idx, 1);
},
}">
<input type="hidden" id="bm-company-name" value="{{ $tenantInfo['company_name'] ?? '' }}">
<input type="hidden" id="bm-business-num" value="{{ $tenantInfo['business_num'] ?? '' }}">
<input type="hidden" id="bm-ceo-name" value="{{ $tenantInfo['ceo_name'] ?? '' }}">
<input type="hidden" id="bm-company-address" value="{{ $tenantInfo['address'] ?? '' }}">
<div class="space-y-4">
{{-- 1. 일시 장소 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">1. 일시 장소</h3>
</div>
<div class="p-4 space-y-3">
<div class="flex gap-4 flex-wrap">
<div style="flex: 1 1 220px; max-width: 280px;">
<label class="block text-xs font-medium text-gray-500 mb-1">일시 <span class="text-red-500">*</span></label>
<input type="datetime-local" id="bm-meeting-datetime"
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 style="flex: 1 1 300px;">
<label class="block text-xs font-medium text-gray-500 mb-1">장소 <span class="text-red-500">*</span></label>
<input type="text" id="bm-meeting-place" value="본점 회의실" 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>
</div>
</div>
{{-- 2. 출석이사 감사 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">2. 출석이사 감사</h3>
</div>
<div class="p-4">
<div class="flex gap-4 flex-wrap">
<div style="flex: 1 1 150px; max-width: 180px;">
<label class="block text-xs font-medium text-gray-500 mb-1">이사 총수 <span class="text-red-500">*</span></label>
<input type="number" id="bm-total-directors" min="1" value="3"
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 style="flex: 1 1 150px; max-width: 180px;">
<label class="block text-xs font-medium text-gray-500 mb-1">출석이사 <span class="text-red-500">*</span></label>
<input type="number" id="bm-present-directors" min="1" value="3"
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 style="flex: 1 1 150px; max-width: 180px;">
<label class="block text-xs font-medium text-gray-500 mb-1">감사 총수</label>
<input type="number" id="bm-total-auditors" min="0" value="1"
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 style="flex: 1 1 150px; max-width: 180px;">
<label class="block text-xs font-medium text-gray-500 mb-1">출석감사</label>
<input type="number" id="bm-present-auditors" min="0" value="1"
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>
</div>
</div>
{{-- 3. 의안 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200 flex items-center justify-between">
<h3 class="text-sm font-semibold text-gray-700">3. 의안</h3>
<button type="button" @click="addAgenda()"
class="px-2 py-1 bg-blue-50 text-blue-600 hover:bg-blue-100 border border-blue-200 rounded text-xs font-medium transition">+ 의안 추가</button>
</div>
<div class="p-4 space-y-3">
<template x-for="(agenda, idx) in agendas" :key="idx">
<div class="flex gap-3 items-start border border-gray-100 rounded-lg p-3 bg-gray-50">
<div class="shrink-0 pt-6">
<span class="inline-flex items-center justify-center rounded-full bg-blue-100 text-blue-700 text-xs font-bold" style="width: 28px; height: 28px;" x-text="'#' + agenda.no"></span>
</div>
<div class="flex-1 space-y-2">
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">의안 제목 <span class="text-red-500">*</span></label>
<input type="text" x-model="agenda.title" 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>
<label class="block text-xs font-medium text-gray-500 mb-1">결과</label>
<input type="text" x-model="agenda.result" 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>
<button type="button" @click="removeAgenda(idx)" x-show="agendas.length > 1"
class="shrink-0 mt-6 p-1 text-red-400 hover:text-red-600 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</template>
</div>
</div>
{{-- 4. 의사 경과 결과 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">4. 의사 경과 결과</h3>
</div>
<div class="p-4 space-y-3">
<div style="max-width: 250px;">
<label class="block text-xs font-medium text-gray-500 mb-1">의장(대표이사) <span class="text-red-500">*</span></label>
<input type="text" id="bm-chairman-name" value="{{ $tenantInfo['ceo_name'] ?? '' }}" 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>
<label class="block text-xs font-medium text-gray-500 mb-1">의사 경과 내용</label>
<textarea id="bm-proceedings" rows="4"
placeholder="의장(대표이사 OOO)은 위와 같이 법정 수에 달하는 이사가 출석하였으므로 본 이사회가 적법하게 성립되었음을 선언하고, 다음의 의안을 상정하여 승인을 구하다."
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"></textarea>
</div>
</div>
</div>
{{-- 5. 폐회 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">5. 폐회</h3>
</div>
<div class="p-4">
<div style="max-width: 180px;">
<label class="block text-xs font-medium text-gray-500 mb-1">폐회 시각</label>
<input type="time" id="bm-closing-time"
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>
</div>
{{-- 6. 기명날인 (서명란) --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200 flex items-center justify-between">
<h3 class="text-sm font-semibold text-gray-700">6. 기명날인</h3>
<button type="button" @click="addSigner()"
class="px-2 py-1 bg-blue-50 text-blue-600 hover:bg-blue-100 border border-blue-200 rounded text-xs font-medium transition">+ 서명자 추가</button>
</div>
<div class="p-4 space-y-2">
<template x-for="(signer, idx) in signers" :key="idx">
<div class="flex gap-3 items-center">
<div style="flex: 0 0 160px;">
<input type="text" x-model="signer.role" 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 style="flex: 1 1 200px; max-width: 250px;">
<input type="text" x-model="signer.name" 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>
<span class="text-xs text-gray-400 shrink-0">()</span>
<button type="button" @click="removeSigner(idx)" x-show="signers.length > 1"
class="shrink-0 p-1 text-red-400 hover:text-red-600 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</template>
</div>
</div>
{{-- 미리보기 버튼 --}}
<div class="flex justify-end">
<button type="button" onclick="openBoardMinutesPreview()"
class="px-3 py-2 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 border border-indigo-200 rounded-lg text-sm font-medium transition inline-flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
미리보기
</button>
</div>
</div>
</div>
{{-- 이사회의사록 미리보기 모달 --}}
<div id="board-minutes-preview-modal" style="display: none;" class="fixed inset-0 z-50">
<div class="absolute inset-0 bg-black/50" onclick="closeBoardMinutesPreview()"></div>
<div class="relative flex items-center justify-center min-h-full p-4">
<div class="bg-white rounded-xl shadow-2xl w-full overflow-hidden relative" style="max-width: 700px;">
<div class="flex items-center justify-between px-5 py-3 border-b border-gray-200 bg-gray-50">
<h3 class="text-base font-semibold text-gray-800">이사회의사록 미리보기</h3>
<div class="flex items-center gap-2">
<button type="button" onclick="printBoardMinutesPreview()"
class="px-3 py-1.5 bg-white border border-gray-300 hover:bg-gray-50 rounded-lg text-xs font-medium transition inline-flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
</svg>
인쇄
</button>
<button type="button" onclick="closeBoardMinutesPreview()"
class="p-1 text-gray-400 hover:text-gray-600 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<div class="overflow-y-auto" style="max-height: 80vh;">
<div id="board-minutes-preview-content" style="padding: 48px 56px; font-family: 'Pretendard', 'Malgun Gothic', sans-serif;">
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,189 @@
{{--
이사회의사록 읽기전용 렌더링
Props:
$content (array) - approvals.content JSON
--}}
<div class="space-y-4">
{{-- 미리보기 버튼 --}}
<div class="flex justify-end gap-2">
<button type="button" onclick="openBoardMinutesShowPreview()"
class="px-3 py-1.5 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 border border-indigo-200 rounded-lg text-sm font-medium transition inline-flex items-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
이사회의사록 미리보기
</button>
</div>
{{-- 1. 일시 장소 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">1. 일시 장소</h3>
</div>
<div class="p-4">
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));">
<div>
<span class="text-xs text-gray-500">일시</span>
<div class="text-sm font-medium mt-0.5">{{ $content['meeting_datetime'] ?? '-' }}</div>
</div>
<div>
<span class="text-xs text-gray-500">장소</span>
<div class="text-sm font-medium mt-0.5">{{ $content['meeting_place'] ?? '-' }}</div>
</div>
</div>
</div>
</div>
{{-- 2. 출석이사 감사 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">2. 출석이사 감사</h3>
</div>
<div class="p-4">
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));">
<div>
<span class="text-xs text-gray-500">이사 총수</span>
<div class="text-sm font-medium mt-0.5">{{ $content['total_directors'] ?? '-' }}</div>
</div>
<div>
<span class="text-xs text-gray-500">출석이사</span>
<div class="text-sm font-medium mt-0.5">{{ $content['present_directors'] ?? '-' }}</div>
</div>
<div>
<span class="text-xs text-gray-500">감사 총수</span>
<div class="text-sm font-medium mt-0.5">{{ $content['total_auditors'] ?? '-' }}</div>
</div>
<div>
<span class="text-xs text-gray-500">출석감사</span>
<div class="text-sm font-medium mt-0.5">{{ $content['present_auditors'] ?? '-' }}</div>
</div>
</div>
</div>
</div>
{{-- 3. 의안 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">3. 의안</h3>
</div>
<div class="p-4 space-y-3">
@foreach(($content['agendas'] ?? []) as $agenda)
<div class="flex gap-3 items-start">
<span class="inline-flex items-center justify-center rounded-full bg-blue-100 text-blue-700 text-xs font-bold shrink-0" style="width: 28px; height: 28px;">
#{{ $agenda['no'] ?? $loop->iteration }}
</span>
<div>
<div class="text-sm font-medium">{{ $agenda['title'] ?? '-' }}</div>
@if(!empty($agenda['result']))
<div class="text-xs text-gray-500 mt-1">{{ $agenda['result'] }}</div>
@endif
</div>
</div>
@endforeach
</div>
</div>
{{-- 4. 의사 경과 결과 --}}
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">4. 의사 경과 결과</h3>
</div>
<div class="p-4">
<div class="mb-2">
<span class="text-xs text-gray-500">의장(대표이사)</span>
<div class="text-sm font-medium mt-0.5">{{ $content['chairman_name'] ?? '-' }}</div>
</div>
@if(!empty($content['proceedings']))
<div class="text-sm text-gray-700 whitespace-pre-wrap mt-2">{{ $content['proceedings'] }}</div>
@endif
</div>
</div>
{{-- 5. 폐회 --}}
@if(!empty($content['closing_time']))
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">5. 폐회</h3>
</div>
<div class="p-4">
<span class="text-xs text-gray-500">폐회 시각</span>
<div class="text-sm font-medium mt-0.5">{{ $content['closing_time'] }}</div>
</div>
</div>
@endif
{{-- 6. 기명날인 --}}
@if(!empty($content['signers']))
<div class="border border-gray-200 rounded-lg overflow-hidden">
<div class="bg-gray-50 px-4 py-2 border-b border-gray-200">
<h3 class="text-sm font-semibold text-gray-700">6. 기명날인</h3>
</div>
<div class="p-4">
<div class="space-y-2">
@foreach($content['signers'] as $signer)
<div class="flex items-center gap-3">
<span class="text-sm text-gray-500" style="min-width: 120px;">{{ $signer['role'] ?? '' }}</span>
<span class="text-sm font-medium">{{ $signer['name'] ?? '' }}</span>
<span class="text-xs text-gray-400">()</span>
</div>
@endforeach
</div>
</div>
</div>
@endif
</div>
{{-- 미리보기 모달 --}}
<div id="board-minutes-show-preview-modal" style="display: none;" class="fixed inset-0 z-50">
<div class="absolute inset-0 bg-black/50" onclick="closeBoardMinutesShowPreview()"></div>
<div class="relative flex items-center justify-center min-h-full p-4">
<div class="bg-white rounded-xl shadow-2xl w-full overflow-hidden relative" style="max-width: 700px;">
<div class="flex items-center justify-between px-5 py-3 border-b border-gray-200 bg-gray-50">
<h3 class="text-base font-semibold text-gray-800">이사회의사록 미리보기</h3>
<div class="flex items-center gap-2">
<button type="button" onclick="printBoardMinutesShowPreview()"
class="px-3 py-1.5 bg-white border border-gray-300 hover:bg-gray-50 rounded-lg text-xs font-medium transition inline-flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
</svg>
인쇄
</button>
<button type="button" onclick="closeBoardMinutesShowPreview()"
class="p-1 text-gray-400 hover:text-gray-600 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<div class="overflow-y-auto" style="max-height: 80vh;">
<div id="board-minutes-show-preview-content" style="padding: 48px 56px; font-family: 'Pretendard', 'Malgun Gothic', sans-serif;">
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
function buildBoardMinutesShowPreviewHtml() {
const data = @json($content);
return buildBoardMinutesPreviewHtml(data);
}
function openBoardMinutesShowPreview() {
document.getElementById('board-minutes-show-preview-content').innerHTML = buildBoardMinutesShowPreviewHtml();
document.getElementById('board-minutes-show-preview-modal').style.display = '';
}
function closeBoardMinutesShowPreview() {
document.getElementById('board-minutes-show-preview-modal').style.display = 'none';
}
function printBoardMinutesShowPreview() {
const content = document.getElementById('board-minutes-show-preview-content').innerHTML;
const win = window.open('', '_blank');
win.document.write('<html><head><title>이사회의사록</title><style>body{font-family:"Pretendard","Malgun Gothic",sans-serif;padding:48px 56px;}@media print{body{padding:20px 30px;}}</style></head><body>' + content + '</body></html>');
win.document.close();
win.print();
}
</script>
@endpush

View File

@@ -117,6 +117,8 @@ class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition
@include('approvals.partials._seal-usage-show', ['content' => $approval->content])
@elseif(!empty($approval->content) && $approval->form?->code === 'delegation')
@include('approvals.partials._delegation-show', ['content' => $approval->content])
@elseif(!empty($approval->content) && $approval->form?->code === 'board_minutes')
@include('approvals.partials._board-minutes-show', ['content' => $approval->content])
@elseif($approval->body && preg_match('/<[a-z][\s\S]*>/i', $approval->body))
<div class="prose prose-sm max-w-none text-gray-700">
{!! strip_tags($approval->body, '<p><br><strong><b><em><i><u><s><del><h1><h2><h3><h4><h5><h6><ul><ol><li><blockquote><pre><code><a><span><div><table><thead><tbody><tr><th><td>') !!}