feat: [document] 양식 디자이너(Block Builder) Phase 2 - 블록 런타임 렌더러
- BlockRendererService: view/edit/print 3모드 렌더링 지원
- edit 모드: 폼 필드(input/select/textarea/checkbox) 생성
- view 모드: 읽기 전용 데이터 표시
- print 모드: 인쇄 최적화 레이아웃
- 데이터 바인딩: block.binding → document_data.field_key 매핑
- 체크박스 그룹: 콤마 구분 값으로 저장/복원
- 테이블 셀 편집: tbl_{blockId}_r{row}_c{col} 키로 EAV 저장
- edit.blade.php: 블록 빌더 서식 분기 (blockFormContainer)
- show.blade.php: 블록 빌더 조회 모드 분기
- DocumentController: renderBlockHtml() 메서드 추가
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
use App\Models\Documents\DocumentData;
|
||||
use App\Models\DocumentTemplate;
|
||||
use App\Models\Items\Item;
|
||||
use App\Services\BlockRendererService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -80,6 +81,7 @@ public function create(Request $request): View|Response
|
||||
'templates' => $templates,
|
||||
'isCreate' => true,
|
||||
'linkedItemSpecs' => $template ? $this->getLinkedItemSpecs($template) : [],
|
||||
'blockHtml' => $template ? $this->renderBlockHtml($template, null) : '',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -123,6 +125,7 @@ public function edit(int $id): View|Response
|
||||
'templates' => $templates,
|
||||
'isCreate' => false,
|
||||
'linkedItemSpecs' => $this->getLinkedItemSpecs($document->template),
|
||||
'blockHtml' => $this->renderBlockHtml($document->template, $document),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -165,6 +168,7 @@ public function print(int $id): View
|
||||
return view('documents.print', [
|
||||
'document' => $document,
|
||||
'workOrderItems' => $workOrderItems,
|
||||
'blockHtml' => $this->renderBlockHtml($document->template, $document, 'print'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -274,6 +278,7 @@ public function show(int $id): View
|
||||
'salesOrder' => $salesOrder,
|
||||
'materialInputLots' => $materialInputLots,
|
||||
'itemLotMap' => $itemLotMap ?? collect(),
|
||||
'blockHtml' => $this->renderBlockHtml($document->template, $document, 'view'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -416,6 +421,30 @@ private function buildInspectionResolveMap(object $workOrder, $workOrderItems, ?
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 블록 빌더 서식의 HTML 렌더링
|
||||
*/
|
||||
private function renderBlockHtml(DocumentTemplate $template, ?Document $document, string $mode = 'edit'): string
|
||||
{
|
||||
if (! $template->isBlockBuilder() || empty($template->schema)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$schema = $template->schema;
|
||||
|
||||
// document_data에서 field_key => field_value 맵 생성
|
||||
$data = [];
|
||||
if ($document && $document->data) {
|
||||
foreach ($document->data as $d) {
|
||||
$data[$d->field_key] = $d->field_value;
|
||||
}
|
||||
}
|
||||
|
||||
$renderer = new BlockRendererService;
|
||||
|
||||
return $renderer->render($schema, $mode, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿에 연결된 품목들의 규격 정보 (thickness, width, length) 조회
|
||||
*/
|
||||
|
||||
@@ -2,13 +2,28 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
/**
|
||||
* 블록 스키마를 HTML로 렌더링하는 서비스
|
||||
*
|
||||
* 3가지 모드 지원:
|
||||
* - view: 읽기 전용 (문서 상세 조회)
|
||||
* - edit: 입력 폼 (문서 생성/수정)
|
||||
* - print: 인쇄/미리보기 전용
|
||||
*/
|
||||
class BlockRendererService
|
||||
{
|
||||
protected string $mode = 'view'; // view | edit | print
|
||||
|
||||
/**
|
||||
* JSON 블록 트리를 HTML로 렌더링
|
||||
* JSON 블록 스키마를 HTML로 렌더링
|
||||
*
|
||||
* @param array $schema 블록 스키마 (blocks, page)
|
||||
* @param string $mode 렌더링 모드 (view|edit|print)
|
||||
* @param array $data 바인딩 데이터 (field_key => value)
|
||||
*/
|
||||
public function render(array $schema, array $data = []): string
|
||||
public function render(array $schema, string $mode = 'view', array $data = []): string
|
||||
{
|
||||
$this->mode = $mode;
|
||||
$blocks = $schema['blocks'] ?? [];
|
||||
$page = $schema['page'] ?? [];
|
||||
|
||||
@@ -30,27 +45,76 @@ public function renderBlock(array $block, array $data = []): string
|
||||
{
|
||||
$type = $block['type'] ?? '';
|
||||
$props = $block['props'] ?? [];
|
||||
$blockId = $block['id'] ?? '';
|
||||
|
||||
return match ($type) {
|
||||
'heading' => $this->renderHeading($props, $data),
|
||||
'paragraph' => $this->renderParagraph($props, $data),
|
||||
'table' => $this->renderTable($props, $data),
|
||||
'table' => $this->renderTable($props, $blockId, $data),
|
||||
'columns' => $this->renderColumns($props, $data),
|
||||
'divider' => $this->renderDivider($props),
|
||||
'spacer' => $this->renderSpacer($props),
|
||||
'text_field' => $this->renderTextField($props, $data),
|
||||
'number_field' => $this->renderNumberField($props, $data),
|
||||
'date_field' => $this->renderDateField($props, $data),
|
||||
'select_field' => $this->renderSelectField($props, $data),
|
||||
'checkbox_field' => $this->renderCheckboxField($props, $data),
|
||||
'textarea_field' => $this->renderTextareaField($props, $data),
|
||||
'signature_field' => $this->renderSignatureField($props, $data),
|
||||
'text_field' => $this->renderTextField($props, $blockId, $data),
|
||||
'number_field' => $this->renderNumberField($props, $blockId, $data),
|
||||
'date_field' => $this->renderDateField($props, $blockId, $data),
|
||||
'select_field' => $this->renderSelectField($props, $blockId, $data),
|
||||
'checkbox_field' => $this->renderCheckboxField($props, $blockId, $data),
|
||||
'textarea_field' => $this->renderTextareaField($props, $blockId, $data),
|
||||
'signature_field' => $this->renderSignatureField($props, $blockId, $data),
|
||||
default => "<!-- Unknown block type: {$type} -->",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 바인딩 치환
|
||||
* 블록 스키마에서 폼 필드의 binding 목록 추출
|
||||
* edit.blade.php에서 JavaScript 데이터 수집 시 사용
|
||||
*/
|
||||
public function extractBindings(array $schema): array
|
||||
{
|
||||
$bindings = [];
|
||||
$formTypes = ['text_field', 'number_field', 'date_field', 'select_field', 'checkbox_field', 'textarea_field', 'signature_field'];
|
||||
|
||||
foreach ($schema['blocks'] ?? [] as $block) {
|
||||
$type = $block['type'] ?? '';
|
||||
if (in_array($type, $formTypes)) {
|
||||
$binding = $block['props']['binding'] ?? '';
|
||||
$blockId = $block['id'] ?? '';
|
||||
$fieldKey = $binding ?: 'block_'.$blockId;
|
||||
$bindings[] = [
|
||||
'block_id' => $blockId,
|
||||
'type' => $type,
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $block['props']['label'] ?? '',
|
||||
'required' => ! empty($block['props']['required']),
|
||||
];
|
||||
}
|
||||
// columns 내부 블록도 탐색
|
||||
if ($type === 'columns') {
|
||||
foreach ($block['props']['children'] ?? [] as $children) {
|
||||
foreach ($children as $child) {
|
||||
$childType = $child['type'] ?? '';
|
||||
if (in_array($childType, $formTypes)) {
|
||||
$binding = $child['props']['binding'] ?? '';
|
||||
$childId = $child['id'] ?? '';
|
||||
$fieldKey = $binding ?: 'block_'.$childId;
|
||||
$bindings[] = [
|
||||
'block_id' => $childId,
|
||||
'type' => $childType,
|
||||
'field_key' => $fieldKey,
|
||||
'label' => $child['props']['label'] ?? '',
|
||||
'required' => ! empty($child['props']['required']),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 바인딩 치환 (변수 해석)
|
||||
*/
|
||||
protected function resolveBinding(string $text, array $data): string
|
||||
{
|
||||
@@ -60,19 +124,56 @@ protected function resolveBinding(string $text, array $data): string
|
||||
if ($key === 'today') {
|
||||
return now()->format('Y-m-d');
|
||||
}
|
||||
if ($key === 'now') {
|
||||
return now()->format('Y-m-d H:i');
|
||||
}
|
||||
|
||||
return data_get($data, $key, $matches[0]);
|
||||
}, $text);
|
||||
}
|
||||
|
||||
// ===== 렌더링 메서드 =====
|
||||
/**
|
||||
* 폼 필드의 field_key 결정: binding이 있으면 binding, 없으면 block_{id}
|
||||
*/
|
||||
protected function getFieldKey(array $props, string $blockId): string
|
||||
{
|
||||
$binding = $props['binding'] ?? '';
|
||||
|
||||
return $binding !== '' ? $binding : 'block_'.$blockId;
|
||||
}
|
||||
|
||||
/**
|
||||
* EAV 데이터에서 값 조회
|
||||
*/
|
||||
protected function getFieldValue(string $fieldKey, array $data, string $default = ''): string
|
||||
{
|
||||
return (string) ($data[$fieldKey] ?? $default);
|
||||
}
|
||||
|
||||
protected function isEditMode(): bool
|
||||
{
|
||||
return $this->mode === 'edit';
|
||||
}
|
||||
|
||||
protected function isViewMode(): bool
|
||||
{
|
||||
return $this->mode === 'view';
|
||||
}
|
||||
|
||||
protected function isPrintMode(): bool
|
||||
{
|
||||
return $this->mode === 'print';
|
||||
}
|
||||
|
||||
// ===== 레이아웃 블록 렌더링 =====
|
||||
|
||||
protected function renderPageOpen(array $page): string
|
||||
{
|
||||
$size = $page['size'] ?? 'A4';
|
||||
$orientation = $page['orientation'] ?? 'portrait';
|
||||
$printStyle = $this->isPrintMode() ? 'max-width: 210mm; margin: 0 auto; padding: 20mm 15mm;' : '';
|
||||
|
||||
return '<div class="block-document" data-page-size="'.e($size).'" data-orientation="'.e($orientation).'" style="font-family: \'Noto Sans KR\', sans-serif; max-width: 210mm; margin: 0 auto; padding: 20mm 15mm;">';
|
||||
return '<div class="block-document" data-page-size="'.e($size).'" data-orientation="'.e($orientation).'" style="font-family: \'Noto Sans KR\', sans-serif; '.$printStyle.'">';
|
||||
}
|
||||
|
||||
protected function renderPageClose(): string
|
||||
@@ -101,7 +202,7 @@ protected function renderParagraph(array $props, array $data): string
|
||||
return "<p style=\"text-align:{$align}; margin:0.3em 0; font-size:0.9em; line-height:1.6;\">{$text}</p>";
|
||||
}
|
||||
|
||||
protected function renderTable(array $props, array $data): string
|
||||
protected function renderTable(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$headers = $props['headers'] ?? [];
|
||||
$rows = $props['rows'] ?? [];
|
||||
@@ -118,11 +219,23 @@ protected function renderTable(array $props, array $data): string
|
||||
}
|
||||
|
||||
$html .= '<tbody>';
|
||||
foreach ($rows as $row) {
|
||||
foreach ($rows as $rIdx => $row) {
|
||||
$html .= '<tr>';
|
||||
foreach ($row as $cell) {
|
||||
$value = $this->resolveBinding($cell ?? '', $data);
|
||||
$html .= '<td style="border:1px solid #333; padding:4px 8px;">'.e($value).'</td>';
|
||||
foreach ($row as $cIdx => $cell) {
|
||||
$cellKey = "tbl_{$blockId}_r{$rIdx}_c{$cIdx}";
|
||||
$savedValue = $this->getFieldValue($cellKey, $data, '');
|
||||
$cellText = $cell ?? '';
|
||||
|
||||
if ($this->isEditMode()) {
|
||||
$displayValue = $savedValue !== '' ? $savedValue : $cellText;
|
||||
$html .= '<td style="border:1px solid #333; padding:2px 4px;">';
|
||||
$html .= '<input type="text" value="'.e($displayValue).'" data-field-key="'.e($cellKey).'" class="w-full border-0 bg-transparent text-sm p-0 focus:ring-0" style="outline:none;">';
|
||||
$html .= '</td>';
|
||||
} else {
|
||||
$displayValue = $savedValue !== '' ? $savedValue : $cellText;
|
||||
$value = $this->resolveBinding($displayValue, $data);
|
||||
$html .= '<td style="border:1px solid #333; padding:4px 8px;">'.e($value).'</td>';
|
||||
}
|
||||
}
|
||||
$html .= '</tr>';
|
||||
}
|
||||
@@ -135,7 +248,6 @@ protected function renderColumns(array $props, array $data): string
|
||||
{
|
||||
$count = intval($props['count'] ?? 2);
|
||||
$children = $props['children'] ?? [];
|
||||
$width = round(100 / $count, 2);
|
||||
|
||||
$html = '<div style="display:flex; gap:10px; margin:0.5em 0;">';
|
||||
|
||||
@@ -167,69 +279,188 @@ protected function renderSpacer(array $props): string
|
||||
return "<div style=\"height:{$height}px;\"></div>";
|
||||
}
|
||||
|
||||
protected function renderTextField(array $props, array $data): string
|
||||
// ===== 폼 필드 블록 렌더링 =====
|
||||
|
||||
protected function renderTextField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '');
|
||||
$binding = $props['binding'] ?? '';
|
||||
$value = $binding ? e($this->resolveBinding($binding, $data)) : '';
|
||||
$required = ! empty($props['required']) ? ' <span style="color:red;">*</span>' : '';
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$value = $this->getFieldValue($fieldKey, $data);
|
||||
$required = ! empty($props['required']);
|
||||
$requiredMark = $required ? ' <span style="color:red;">*</span>' : '';
|
||||
$placeholder = e($props['placeholder'] ?? '');
|
||||
|
||||
return "<div style=\"margin:0.3em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}{$required}</label><div style=\"border:1px solid #ccc; padding:4px 8px; min-height:24px; font-size:0.9em;\">{$value}</div></div>";
|
||||
if ($this->isEditMode()) {
|
||||
$requiredAttr = $required ? 'required' : '';
|
||||
|
||||
return '<div style="margin:0.4em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.$requiredMark.'</label>'
|
||||
.'<input type="text" value="'.e($value).'" placeholder="'.($placeholder ?: $label).'" data-field-key="'.e($fieldKey).'" '.$requiredAttr
|
||||
.' class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">'
|
||||
.'</div>';
|
||||
}
|
||||
|
||||
// view / print
|
||||
$displayValue = $value !== '' ? e($value) : '<span style="color:#999;">-</span>';
|
||||
|
||||
return '<div style="margin:0.3em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.$requiredMark.'</label><div style="border-bottom:1px solid #ddd; padding:4px 0; min-height:24px; font-size:0.9em;">'.$displayValue.'</div></div>';
|
||||
}
|
||||
|
||||
protected function renderNumberField(array $props, array $data): string
|
||||
protected function renderNumberField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '');
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$value = $this->getFieldValue($fieldKey, $data);
|
||||
$unit = e($props['unit'] ?? '');
|
||||
$unitDisplay = $unit ? " ({$unit})" : '';
|
||||
$min = $props['min'] ?? null;
|
||||
$max = $props['max'] ?? null;
|
||||
$decimal = intval($props['decimal'] ?? 0);
|
||||
|
||||
return "<div style=\"margin:0.3em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}{$unitDisplay}</label><div style=\"border:1px solid #ccc; padding:4px 8px; min-height:24px; font-size:0.9em;\"></div></div>";
|
||||
if ($this->isEditMode()) {
|
||||
$attrs = '';
|
||||
if ($min !== null) {
|
||||
$attrs .= ' min="'.e($min).'"';
|
||||
}
|
||||
if ($max !== null) {
|
||||
$attrs .= ' max="'.e($max).'"';
|
||||
}
|
||||
if ($decimal > 0) {
|
||||
$attrs .= ' step="'.e(1 / pow(10, $decimal)).'"';
|
||||
}
|
||||
|
||||
return '<div style="margin:0.4em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.$unitDisplay.'</label>'
|
||||
.'<input type="number" value="'.e($value).'" data-field-key="'.e($fieldKey).'"'.$attrs
|
||||
.' class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">'
|
||||
.'</div>';
|
||||
}
|
||||
|
||||
$displayValue = $value !== '' ? e($value).$unitDisplay : '<span style="color:#999;">-</span>';
|
||||
|
||||
return '<div style="margin:0.3em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.$unitDisplay.'</label><div style="border-bottom:1px solid #ddd; padding:4px 0; min-height:24px; font-size:0.9em;">'.$displayValue.'</div></div>';
|
||||
}
|
||||
|
||||
protected function renderDateField(array $props, array $data): string
|
||||
protected function renderDateField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '');
|
||||
$binding = $props['binding'] ?? '';
|
||||
$value = $binding ? e($this->resolveBinding($binding, $data)) : '';
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$value = $this->getFieldValue($fieldKey, $data);
|
||||
|
||||
return "<div style=\"margin:0.3em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}</label><div style=\"border:1px solid #ccc; padding:4px 8px; min-height:24px; font-size:0.9em;\">{$value}</div></div>";
|
||||
if ($this->isEditMode()) {
|
||||
$defaultValue = $value !== '' ? $value : ($this->resolveBinding($props['default'] ?? '', $data) ?: '');
|
||||
|
||||
return '<div style="margin:0.4em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.'</label>'
|
||||
.'<input type="date" value="'.e($defaultValue).'" data-field-key="'.e($fieldKey).'"'
|
||||
.' class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">'
|
||||
.'</div>';
|
||||
}
|
||||
|
||||
$displayValue = $value !== '' ? e($value) : '<span style="color:#999;">-</span>';
|
||||
|
||||
return '<div style="margin:0.3em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.'</label><div style="border-bottom:1px solid #ddd; padding:4px 0; min-height:24px; font-size:0.9em;">'.$displayValue.'</div></div>';
|
||||
}
|
||||
|
||||
protected function renderSelectField(array $props, array $data): string
|
||||
protected function renderSelectField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '');
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$value = $this->getFieldValue($fieldKey, $data);
|
||||
$options = $props['options'] ?? [];
|
||||
|
||||
return "<div style=\"margin:0.3em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}</label><div style=\"border:1px solid #ccc; padding:4px 8px; min-height:24px; font-size:0.9em;\">".e(implode(' / ', $options)).'</div></div>';
|
||||
if ($this->isEditMode()) {
|
||||
$html = '<div style="margin:0.4em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.'</label>'
|
||||
.'<select data-field-key="'.e($fieldKey).'" class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">'
|
||||
.'<option value="">선택하세요</option>';
|
||||
foreach ($options as $opt) {
|
||||
$selected = $value === $opt ? ' selected' : '';
|
||||
$html .= '<option value="'.e($opt).'"'.$selected.'>'.e($opt).'</option>';
|
||||
}
|
||||
$html .= '</select></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
$displayValue = $value !== '' ? e($value) : '<span style="color:#999;">-</span>';
|
||||
|
||||
return '<div style="margin:0.3em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.'</label><div style="border-bottom:1px solid #ddd; padding:4px 0; min-height:24px; font-size:0.9em;">'.$displayValue.'</div></div>';
|
||||
}
|
||||
|
||||
protected function renderCheckboxField(array $props, array $data): string
|
||||
protected function renderCheckboxField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '');
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$savedValues = array_filter(explode(',', $this->getFieldValue($fieldKey, $data)));
|
||||
$options = $props['options'] ?? [];
|
||||
|
||||
$html = "<div style=\"margin:0.3em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}</label><div style=\"display:flex; gap:12px; flex-wrap:wrap;\">";
|
||||
if ($this->isEditMode()) {
|
||||
$html = '<div style="margin:0.4em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.'</label>'
|
||||
.'<div class="flex flex-wrap gap-3 mt-1">';
|
||||
foreach ($options as $idx => $opt) {
|
||||
$checked = in_array($opt, $savedValues) ? ' checked' : '';
|
||||
$html .= '<label class="flex items-center gap-1.5 text-sm text-gray-700">'
|
||||
.'<input type="checkbox" value="'.e($opt).'" data-field-key="'.e($fieldKey).'" data-checkbox-group="'.e($fieldKey).'"'.$checked
|
||||
.' class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">'
|
||||
.e($opt).'</label>';
|
||||
}
|
||||
$html .= '</div></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// view / print
|
||||
$html = '<div style="margin:0.3em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.'</label><div style="display:flex; gap:12px; flex-wrap:wrap;">';
|
||||
foreach ($options as $opt) {
|
||||
$html .= '<label style="font-size:0.85em; display:flex; align-items:center; gap:4px;"><input type="checkbox" disabled> '.e($opt).'</label>';
|
||||
$checked = in_array($opt, $savedValues) ? '☑' : '☐';
|
||||
$html .= '<span style="font-size:0.85em;">'.$checked.' '.e($opt).'</span>';
|
||||
}
|
||||
$html .= '</div></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function renderTextareaField(array $props, array $data): string
|
||||
protected function renderTextareaField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '');
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$value = $this->getFieldValue($fieldKey, $data);
|
||||
$rows = max(intval($props['rows'] ?? 3), 1);
|
||||
|
||||
if ($this->isEditMode()) {
|
||||
return '<div style="margin:0.4em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.'</label>'
|
||||
.'<textarea data-field-key="'.e($fieldKey).'" rows="'.$rows.'"'
|
||||
.' class="w-full rounded-lg border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500">'.e($value).'</textarea>'
|
||||
.'</div>';
|
||||
}
|
||||
|
||||
$displayValue = $value !== '' ? nl2br(e($value)) : '<span style="color:#999;">-</span>';
|
||||
$height = $rows * 24;
|
||||
|
||||
return "<div style=\"margin:0.3em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}</label><div style=\"border:1px solid #ccc; padding:4px 8px; min-height:{$height}px; font-size:0.9em;\"></div></div>";
|
||||
return '<div style="margin:0.3em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.'</label><div style="border:1px solid #eee; padding:4px 8px; min-height:'.$height.'px; font-size:0.9em; white-space:pre-wrap;">'.$displayValue.'</div></div>';
|
||||
}
|
||||
|
||||
protected function renderSignatureField(array $props, array $data): string
|
||||
protected function renderSignatureField(array $props, string $blockId, array $data): string
|
||||
{
|
||||
$label = e($props['label'] ?? '서명');
|
||||
$fieldKey = $this->getFieldKey($props, $blockId);
|
||||
$value = $this->getFieldValue($fieldKey, $data);
|
||||
|
||||
return "<div style=\"margin:0.5em 0;\"><label style=\"font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;\">{$label}</label><div style=\"border:2px dashed #ccc; height:60px; display:flex; align-items:center; justify-content:center; font-size:0.8em; color:#999;\">서명 영역</div></div>";
|
||||
if ($this->isEditMode()) {
|
||||
return '<div style="margin:0.5em 0;">'
|
||||
.'<label class="block text-sm font-medium text-gray-700 mb-1">'.$label.'</label>'
|
||||
.'<div style="border:2px dashed #d1d5db; height:80px; display:flex; align-items:center; justify-content:center; border-radius:8px; cursor:pointer;" data-field-key="'.e($fieldKey).'" data-block-type="signature">'
|
||||
.'<span class="text-sm text-gray-400">클릭하여 서명</span>'
|
||||
.'</div></div>';
|
||||
}
|
||||
|
||||
if ($value) {
|
||||
return '<div style="margin:0.5em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.'</label><img src="'.e($value).'" style="max-height:60px;" alt="서명"></div>';
|
||||
}
|
||||
|
||||
return '<div style="margin:0.5em 0;"><label style="font-size:0.8em; font-weight:bold; display:block; margin-bottom:2px;">'.$label.'</label><div style="border:2px dashed #ccc; height:60px; display:flex; align-items:center; justify-content:center; font-size:0.8em; color:#999;">서명 영역</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user