615 lines
36 KiB
Vue
615 lines
36 KiB
Vue
<script setup lang="ts">
|
||
import { Form, Head, Link } from '@inertiajs/vue3';
|
||
import { ref, watch } from 'vue';
|
||
import MedicalReportController from '@/actions/App/Http/Controllers/MedicalReportController';
|
||
import Heading from '@/components/Heading.vue';
|
||
import InputError from '@/components/InputError.vue';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from '@/components/ui/select';
|
||
import type {
|
||
MedicalReport,
|
||
MedicalReportBuilderDepartment,
|
||
MedicalReportBuilderSection,
|
||
MedicalReportBuilderTemplate,
|
||
MedicalReportStructuredEntry,
|
||
} from '@/types';
|
||
|
||
type Props = {
|
||
report: MedicalReport;
|
||
selectedDepartment?: string | null;
|
||
departments: MedicalReportBuilderDepartment[];
|
||
builder?: {
|
||
department: {
|
||
key: string;
|
||
name: string;
|
||
profile_name?: string | null;
|
||
report_input_type?: string | null;
|
||
report_input_type_label?: string | null;
|
||
sources?: Array<{
|
||
name: string;
|
||
path: string;
|
||
extension: string;
|
||
}>;
|
||
};
|
||
template: MedicalReportBuilderTemplate;
|
||
uses_database_template: boolean;
|
||
starter_source: string;
|
||
} | null;
|
||
};
|
||
|
||
const props = defineProps<Props>();
|
||
|
||
const template = ref<MedicalReportBuilderTemplate | null>(null);
|
||
|
||
watch(
|
||
() => props.builder?.template,
|
||
(value) => {
|
||
template.value = value
|
||
? JSON.parse(JSON.stringify(value))
|
||
: null;
|
||
},
|
||
{ immediate: true },
|
||
);
|
||
|
||
const addSection = (): void => {
|
||
if (!template.value) {
|
||
return;
|
||
}
|
||
|
||
template.value.schema.sections.push({
|
||
key: `section_${template.value.schema.sections.length + 1}`,
|
||
title: `Секция ${template.value.schema.sections.length + 1}`,
|
||
economist_label: `${template.value.name} / Секция ${template.value.schema.sections.length + 1}`,
|
||
fields: [
|
||
{ key: 'name', label: 'Показатель', type: 'text' },
|
||
{ key: 'value', label: 'Значение', type: 'number' },
|
||
],
|
||
export_metrics: [{ key: 'value_total', label: 'Значение', source_field: 'value', aggregation: 'sum' }],
|
||
default_entries: [{ name: '', value: '0' }],
|
||
});
|
||
};
|
||
|
||
const removeSection = (index: number): void => {
|
||
template.value?.schema.sections.splice(index, 1);
|
||
};
|
||
|
||
const addField = (section: MedicalReportBuilderSection): void => {
|
||
const key = `field_${section.fields.length + 1}`;
|
||
section.fields.push({
|
||
key,
|
||
label: `Поле ${section.fields.length + 1}`,
|
||
type: 'text',
|
||
});
|
||
section.default_entries = section.default_entries.map((entry) => ({
|
||
...entry,
|
||
[key]: '',
|
||
}));
|
||
};
|
||
|
||
const removeField = (section: MedicalReportBuilderSection, fieldIndex: number): void => {
|
||
const [field] = section.fields.splice(fieldIndex, 1);
|
||
|
||
if (!field) {
|
||
return;
|
||
}
|
||
|
||
section.default_entries = section.default_entries.map((entry) => {
|
||
const nextEntry = { ...entry };
|
||
delete nextEntry[field.key];
|
||
|
||
return nextEntry;
|
||
});
|
||
section.export_metrics = section.export_metrics.filter((metric) => metric.source_field !== field.key);
|
||
};
|
||
|
||
const addDefaultEntry = (section: MedicalReportBuilderSection): void => {
|
||
const entry = section.fields.reduce(
|
||
(carry, field) => {
|
||
carry[field.key] = field.type === 'number' ? '0' : '';
|
||
|
||
return carry;
|
||
},
|
||
{} as MedicalReportStructuredEntry,
|
||
);
|
||
|
||
section.default_entries.push(entry);
|
||
};
|
||
|
||
const removeDefaultEntry = (section: MedicalReportBuilderSection, index: number): void => {
|
||
section.default_entries.splice(index, 1);
|
||
};
|
||
|
||
const addExportMetric = (section: MedicalReportBuilderSection): void => {
|
||
const numericField = section.fields.find((field) => field.type === 'number');
|
||
const hasDepartmentSelect = section.fields.some((field) => field.type === 'department-select');
|
||
|
||
section.export_metrics.push({
|
||
key: `metric_${section.export_metrics.length + 1}`,
|
||
label: `Метрика ${section.export_metrics.length + 1}`,
|
||
source_field: numericField?.key ?? '',
|
||
aggregation: 'sum',
|
||
analysis_column: '',
|
||
row_mode: hasDepartmentSelect ? 'entry_department' : 'fixed_unit',
|
||
target_unit_slug: '',
|
||
});
|
||
};
|
||
|
||
const removeExportMetric = (section: MedicalReportBuilderSection, index: number): void => {
|
||
section.export_metrics.splice(index, 1);
|
||
};
|
||
|
||
const numericFields = (section: MedicalReportBuilderSection) => {
|
||
return section.fields.filter((field) => field.type === 'number');
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<Head :title="`${report.name} - Конструктор форм`" />
|
||
|
||
<div class="flex flex-col gap-6 p-4 md:p-6">
|
||
<section class="rounded-3xl border border-slate-200 bg-linear-to-br from-white via-slate-50 to-sky-50 p-6 shadow-sm">
|
||
<div class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
||
<div class="space-y-3">
|
||
<div class="inline-flex w-fit rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold tracking-[0.18em] text-sky-700 uppercase">
|
||
Конструктор форм
|
||
</div>
|
||
<Heading
|
||
title="Уникальные шаблоны отчетов отделений"
|
||
description="Здесь настраивается структура формы: секции, поля и строки по умолчанию. Именно эта схема будет использоваться отделением при вводе отчета."
|
||
/>
|
||
</div>
|
||
|
||
<div class="flex flex-wrap gap-2">
|
||
<Link
|
||
:href="MedicalReportController.show({ medicalReport: report.id })"
|
||
class="rounded-full border border-slate-200 bg-white px-4 py-2 text-sm text-slate-700 transition-colors hover:border-slate-300"
|
||
>
|
||
Ввод отделений
|
||
</Link>
|
||
<Link
|
||
:href="MedicalReportController.economists({ medicalReport: report.id })"
|
||
class="rounded-full border border-slate-200 bg-white px-4 py-2 text-sm text-slate-700 transition-colors hover:border-slate-300"
|
||
>
|
||
Свод экономистов
|
||
</Link>
|
||
<div class="rounded-full border border-slate-950 bg-slate-950 px-4 py-2 text-sm text-white">
|
||
Конструктор
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="grid gap-6 xl:grid-cols-[320px_1fr]">
|
||
<aside class="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm">
|
||
<div class="mb-4 text-sm font-semibold text-slate-900">
|
||
Отделения
|
||
</div>
|
||
|
||
<div class="space-y-2">
|
||
<Link
|
||
v-for="department in departments"
|
||
:key="department.key"
|
||
:href="
|
||
MedicalReportController.builder(
|
||
{ medicalReport: report.id },
|
||
{ query: { department: department.key } },
|
||
)
|
||
"
|
||
class="block rounded-2xl border px-4 py-3 transition-colors"
|
||
:class="
|
||
selectedDepartment === department.key
|
||
? 'border-sky-300 bg-sky-50 text-sky-900'
|
||
: 'border-slate-200 bg-slate-50 text-slate-700 hover:border-slate-300 hover:bg-white'
|
||
"
|
||
>
|
||
<div class="text-[11px] uppercase tracking-[0.16em] text-slate-400">
|
||
{{ department.profile_name ?? 'Без профиля' }}
|
||
</div>
|
||
<div class="font-medium">{{ department.name }}</div>
|
||
<div class="mt-1 text-xs text-slate-500">
|
||
{{ department.report_input_type_label ?? 'Тип ввода не задан' }}
|
||
</div>
|
||
<div class="mt-1 text-xs">
|
||
<span :class="department.has_builder_template ? 'text-sky-600' : 'text-amber-600'">
|
||
{{ department.has_builder_template ? 'Настроено в конструкторе' : 'Используется автосхема' }}
|
||
</span>
|
||
</div>
|
||
</Link>
|
||
</div>
|
||
</aside>
|
||
|
||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm">
|
||
<div v-if="!builder || !template" class="rounded-2xl bg-slate-50 p-6 text-sm text-slate-500">
|
||
Отделение не выбрано.
|
||
</div>
|
||
|
||
<Form
|
||
v-else
|
||
v-bind="MedicalReportController.upsertBuilder.form({ medicalReport: report.id })"
|
||
class="space-y-6"
|
||
v-slot="{ errors, processing, recentlySuccessful }"
|
||
>
|
||
<input type="hidden" name="department" :value="builder.department.key" />
|
||
|
||
<div class="flex flex-col gap-4 border-b border-slate-100 pb-5 md:flex-row md:items-start md:justify-between">
|
||
<div>
|
||
<div class="text-xs uppercase tracking-[0.16em] text-slate-500">
|
||
Отделение
|
||
</div>
|
||
<h1 class="mt-2 text-2xl font-semibold text-slate-950">
|
||
{{ builder.department.name }}
|
||
</h1>
|
||
<p class="mt-1 text-sm text-slate-500">
|
||
{{
|
||
builder.uses_database_template
|
||
? 'Форма уже сохраняется из конструктора.'
|
||
: builder.starter_source === 'blueprint'
|
||
? 'Сейчас показан стартовый шаблон по типу ввода этого подразделения.'
|
||
: 'Сейчас показан стартовый шаблон, собранный из существующей схемы.'
|
||
}}
|
||
</p>
|
||
</div>
|
||
|
||
<div class="flex flex-wrap gap-2">
|
||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
||
Источников: {{ builder.department.sources?.length ?? 0 }}
|
||
</div>
|
||
<div
|
||
v-if="builder.department.report_input_type_label"
|
||
class="rounded-2xl border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-700"
|
||
>
|
||
{{ builder.department.report_input_type_label }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid gap-4 md:grid-cols-2">
|
||
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||
<Label for="builder-name">Название шаблона</Label>
|
||
<Input id="builder-name" v-model="template.name" name="name" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors.name" />
|
||
</div>
|
||
|
||
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||
<Label for="schema-title">Заголовок формы</Label>
|
||
<Input id="schema-title" v-model="template.schema.title" name="schema[title]" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors['schema.title']" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||
<Label for="builder-description">Описание</Label>
|
||
<textarea
|
||
id="builder-description"
|
||
v-model="template.description"
|
||
name="description"
|
||
class="mt-3 min-h-24 w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none ring-0"
|
||
/>
|
||
<input type="hidden" name="schema[description]" :value="template.description" />
|
||
<InputError class="mt-2" :message="errors.description || errors['schema.description']" />
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<div
|
||
v-for="(section, sectionIndex) in template.schema.sections"
|
||
:key="`${section.key}-${sectionIndex}`"
|
||
class="rounded-3xl border border-slate-200 bg-slate-50/80 p-4"
|
||
>
|
||
<div class="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||
<div class="text-lg font-semibold text-slate-900">
|
||
Секция {{ sectionIndex + 1 }}
|
||
</div>
|
||
<Button type="button" variant="outline" size="sm" :disabled="template.schema.sections.length === 1" @click="removeSection(sectionIndex)">
|
||
Удалить секцию
|
||
</Button>
|
||
</div>
|
||
|
||
<div class="grid gap-4 md:grid-cols-3">
|
||
<div class="rounded-2xl border border-white bg-white p-4">
|
||
<Label :for="`section-key-${sectionIndex}`">Ключ</Label>
|
||
<Input :id="`section-key-${sectionIndex}`" v-model="section.key" :name="`schema[sections][${sectionIndex}][key]`" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.key`]" />
|
||
</div>
|
||
<div class="rounded-2xl border border-white bg-white p-4">
|
||
<Label :for="`section-title-${sectionIndex}`">Название секции</Label>
|
||
<Input :id="`section-title-${sectionIndex}`" v-model="section.title" :name="`schema[sections][${sectionIndex}][title]`" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.title`]" />
|
||
</div>
|
||
<div class="rounded-2xl border border-white bg-white p-4">
|
||
<Label :for="`section-economist-${sectionIndex}`">Заголовок для экономистов</Label>
|
||
<Input :id="`section-economist-${sectionIndex}`" v-model="section.economist_label" :name="`schema[sections][${sectionIndex}][economist_label]`" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.economist_label`]" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-4 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-sm font-semibold text-slate-900">Поля секции</div>
|
||
<Button type="button" variant="outline" size="sm" @click="addField(section)">
|
||
Добавить поле
|
||
</Button>
|
||
</div>
|
||
|
||
<div
|
||
v-for="(field, fieldIndex) in section.fields"
|
||
:key="`${section.key}-${field.key}-${fieldIndex}`"
|
||
class="grid gap-4 rounded-2xl border border-slate-200 bg-white p-4 md:grid-cols-[1fr_1fr_220px_auto]"
|
||
>
|
||
<div>
|
||
<Label :for="`field-key-${sectionIndex}-${fieldIndex}`">Ключ поля</Label>
|
||
<Input :id="`field-key-${sectionIndex}-${fieldIndex}`" v-model="field.key" :name="`schema[sections][${sectionIndex}][fields][${fieldIndex}][key]`" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.fields.${fieldIndex}.key`]" />
|
||
</div>
|
||
<div>
|
||
<Label :for="`field-label-${sectionIndex}-${fieldIndex}`">Подпись</Label>
|
||
<Input :id="`field-label-${sectionIndex}-${fieldIndex}`" v-model="field.label" :name="`schema[sections][${sectionIndex}][fields][${fieldIndex}][label]`" class="mt-3" />
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.fields.${fieldIndex}.label`]" />
|
||
</div>
|
||
<div>
|
||
<Label>Тип</Label>
|
||
<input type="hidden" :name="`schema[sections][${sectionIndex}][fields][${fieldIndex}][type]`" :value="field.type" />
|
||
<Select v-model="field.type">
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue placeholder="Тип поля" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="text">Текст</SelectItem>
|
||
<SelectItem value="number">Число</SelectItem>
|
||
<SelectItem value="department-select">Выбор отделения</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.fields.${fieldIndex}.type`]" />
|
||
</div>
|
||
<div class="flex items-end">
|
||
<Button type="button" variant="outline" size="sm" :disabled="section.fields.length === 1" @click="removeField(section, fieldIndex)">
|
||
Удалить
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-sm font-semibold text-slate-900">Экспорт для экономистов</div>
|
||
<Button type="button" variant="outline" size="sm" @click="addExportMetric(section)">
|
||
Добавить метрику
|
||
</Button>
|
||
</div>
|
||
|
||
<div
|
||
v-for="(metric, metricIndex) in section.export_metrics"
|
||
:key="`${section.key}-metric-${metricIndex}`"
|
||
class="grid gap-4 rounded-2xl border border-slate-200 bg-white p-4 md:grid-cols-2 xl:grid-cols-4"
|
||
>
|
||
<div>
|
||
<Label :for="`metric-key-${sectionIndex}-${metricIndex}`">Ключ</Label>
|
||
<Input
|
||
:id="`metric-key-${sectionIndex}-${metricIndex}`"
|
||
v-model="metric.key"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][key]`"
|
||
class="mt-3"
|
||
/>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.key`]" />
|
||
</div>
|
||
<div>
|
||
<Label :for="`metric-label-${sectionIndex}-${metricIndex}`">Подпись</Label>
|
||
<Input
|
||
:id="`metric-label-${sectionIndex}-${metricIndex}`"
|
||
v-model="metric.label"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][label]`"
|
||
class="mt-3"
|
||
/>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.label`]" />
|
||
</div>
|
||
<div>
|
||
<Label>Числовое поле</Label>
|
||
<input
|
||
type="hidden"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][source_field]`"
|
||
:value="metric.source_field"
|
||
/>
|
||
<Select v-model="metric.source_field">
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue placeholder="Поле для агрегации" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem
|
||
v-for="field in numericFields(section)"
|
||
:key="`${section.key}-metric-field-${field.key}`"
|
||
:value="field.key"
|
||
>
|
||
{{ field.label }}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.source_field`]" />
|
||
</div>
|
||
<div>
|
||
<Label>Агрегация</Label>
|
||
<input
|
||
type="hidden"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][aggregation]`"
|
||
:value="metric.aggregation"
|
||
/>
|
||
<Select v-model="metric.aggregation">
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue placeholder="Агрегация" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="sum">Сумма</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.aggregation`]" />
|
||
</div>
|
||
<div>
|
||
<Label>Колонка анализа</Label>
|
||
<input
|
||
type="hidden"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][analysis_column]`"
|
||
:value="metric.analysis_column ?? ''"
|
||
/>
|
||
<Select v-model="metric.analysis_column">
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue placeholder="Колонка для экономистов" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem
|
||
v-for="column in template.analysis_columns ?? []"
|
||
:key="`${section.key}-analysis-${column.key}`"
|
||
:value="column.key"
|
||
>
|
||
{{ column.coordinate }} · {{ column.label }}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.analysis_column`]" />
|
||
</div>
|
||
<div>
|
||
<Label>Строка анализа</Label>
|
||
<input
|
||
type="hidden"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][row_mode]`"
|
||
:value="metric.row_mode ?? ''"
|
||
/>
|
||
<Select v-model="metric.row_mode">
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue placeholder="Куда писать" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="entry_department">Из выбранного отделения строки</SelectItem>
|
||
<SelectItem value="fixed_unit">В фиксированное подразделение</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.row_mode`]" />
|
||
</div>
|
||
<div v-if="metric.row_mode === 'fixed_unit'">
|
||
<Label>Фиксированное подразделение</Label>
|
||
<input
|
||
type="hidden"
|
||
:name="`schema[sections][${sectionIndex}][export_metrics][${metricIndex}][target_unit_slug]`"
|
||
:value="metric.target_unit_slug ?? ''"
|
||
/>
|
||
<Select v-model="metric.target_unit_slug">
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue placeholder="Подразделение финансистов" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem
|
||
v-for="unit in template.analysis_units ?? []"
|
||
:key="`${section.key}-unit-${unit.slug}`"
|
||
:value="unit.slug"
|
||
>
|
||
{{ unit.name }}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<InputError class="mt-2" :message="errors[`schema.sections.${sectionIndex}.export_metrics.${metricIndex}.target_unit_slug`]" />
|
||
</div>
|
||
<div class="flex items-end">
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
size="sm"
|
||
:disabled="section.export_metrics.length === 1"
|
||
@click="removeExportMetric(section, metricIndex)"
|
||
>
|
||
Удалить
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-sm font-semibold text-slate-900">Значения по умолчанию</div>
|
||
<Button type="button" variant="outline" size="sm" @click="addDefaultEntry(section)">
|
||
Добавить строку
|
||
</Button>
|
||
</div>
|
||
|
||
<div
|
||
v-for="(entry, entryIndex) in section.default_entries"
|
||
:key="`${section.key}-default-${entryIndex}`"
|
||
class="rounded-2xl border border-slate-200 bg-white p-4"
|
||
>
|
||
<div class="mb-4 flex items-center justify-between gap-3">
|
||
<div class="text-sm font-semibold text-slate-900">
|
||
Строка по умолчанию {{ entryIndex + 1 }}
|
||
</div>
|
||
<Button type="button" variant="outline" size="sm" :disabled="section.default_entries.length === 1" @click="removeDefaultEntry(section, entryIndex)">
|
||
Удалить
|
||
</Button>
|
||
</div>
|
||
|
||
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||
<div
|
||
v-for="field in section.fields"
|
||
:key="`${section.key}-${field.key}-default-${entryIndex}`"
|
||
class="rounded-2xl border border-slate-100 bg-slate-50 p-4"
|
||
>
|
||
<Label :for="`${section.key}-${field.key}-${entryIndex}`">
|
||
{{ field.label }}
|
||
</Label>
|
||
|
||
<input
|
||
v-if="field.type === 'department-select'"
|
||
type="hidden"
|
||
:name="`schema[sections][${sectionIndex}][default_entries][${entryIndex}][${field.key}]`"
|
||
:value="entry[field.key] ?? ''"
|
||
/>
|
||
<Select
|
||
v-if="field.type === 'department-select'"
|
||
v-model="entry[field.key]"
|
||
>
|
||
<SelectTrigger class="mt-3 w-full">
|
||
<SelectValue :placeholder="`Выберите: ${field.label}`" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem
|
||
v-for="option in template.department_options ?? []"
|
||
:key="option.id"
|
||
:value="String(option.id)"
|
||
>
|
||
{{ option.name }}
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
<Input
|
||
v-else
|
||
:id="`${section.key}-${field.key}-${entryIndex}`"
|
||
v-model="entry[field.key]"
|
||
:name="`schema[sections][${sectionIndex}][default_entries][${entryIndex}][${field.key}]`"
|
||
class="mt-3"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex flex-wrap items-center gap-4">
|
||
<Button type="button" variant="outline" @click="addSection">
|
||
Добавить секцию
|
||
</Button>
|
||
<Button :disabled="processing">
|
||
Сохранить шаблон
|
||
</Button>
|
||
<p v-if="recentlySuccessful" class="text-sm text-emerald-600">
|
||
Шаблон сохранен.
|
||
</p>
|
||
</div>
|
||
</Form>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</template>
|