Files

191 lines
7.1 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, h, computed } from 'vue'
import { router, Link } from '@inertiajs/vue3'
import {
NButton, NText, NDataTable, NEmpty, NDropdown, NIcon, NScrollbar,
NTag, useDialog, useMessage,
NEl,
} from 'naive-ui'
import { TbReportMedical, TbPlus, TbDotsVertical, TbLayoutGrid } from 'vue-icons-plus/tb'
import AppLayout from '../../Layouts/AppLayout.vue'
import AppContainer from '../../Components/AppContainer.vue'
import SectionCard from '../../Components/SectionCard.vue'
import PageBanner from '../../Components/PageBanner.vue'
import PresetCard from './Components/PresetCard.vue'
import TemplatePickerModal from './Components/TemplatePickerModal.vue'
const props = defineProps({
documents: { type: Array, default: () => [] },
presets: { type: Array, default: () => [] },
categories: { type: Array, default: () => [] },
canManage: { type: Boolean, default: false },
})
const dialog = useDialog()
const message = useMessage()
const showPicker = ref(false)
// «Отчёт с нуля» + первые шаблоны в ленте создания.
const blankPreset = computed(() => props.presets.find((p) => p.key === 'blank'))
const featured = computed(() => props.presets.filter((p) => p.key !== 'blank').slice(0, 5))
const openPreset = (preset) => {
router.get(preset.key === 'blank' ? '/reports/new' : `/reports/new?preset=${preset.key}`)
}
const openDocument = (id) => router.get(`/reports/${id}`)
const rowProps = (row) => ({ style: 'cursor: pointer;', onClick: () => openDocument(row.id) })
const duplicate = (row) => router.post(`/reports/${row.id}/duplicate`)
const remove = (row) => {
dialog.warning({
title: 'Удалить отчёт',
content: `Удалить «${row.name}»? Действие необратимо.`,
positiveText: 'Удалить',
negativeText: 'Отмена',
onPositiveClick: () => router.delete(`/reports/${row.id}`, {
preserveScroll: true,
onSuccess: () => message.success('Отчёт удалён'),
}),
})
}
const rowMenu = (row) => [
{ key: 'open', label: 'Открыть' },
{ key: 'duplicate', label: 'Дублировать' },
{ key: 'delete', label: 'Удалить' },
]
const onMenuSelect = (key, row) => {
if (key === 'open') openDocument(row.id)
if (key === 'duplicate') duplicate(row)
if (key === 'delete') remove(row)
}
const columns = computed(() => [
{
title: 'Название',
key: 'name',
render: (row) => h('div', {}, [
h('div', { style: 'font-weight: 600;' }, row.name),
row.description ? h(NText, { depth: 3, style: 'font-size: 12px;' }, () => row.description) : null,
]),
},
{ title: 'Источник', key: 'datasetLabel', width: 200, render: (row) => h(NTag, { size: 'small', bordered: false, round: true }, () => row.datasetLabel) },
{ title: 'Автор', key: 'author', width: 180, render: (row) => row.author ?? '—' },
{ title: 'Изменено', key: 'updatedAt', width: 130, render: (row) => row.updatedAt ?? '—' },
{
title: '',
key: 'actions',
width: 56,
render: (row) => props.canManage
? h(NDropdown, {
trigger: 'click',
options: rowMenu(row),
onSelect: (key) => onMenuSelect(key, row),
}, () => h(NButton, {
round: true,
size: 'small',
onClick: (e) => e.stopPropagation(),
}, () => h(NIcon, null, () => h(TbDotsVertical)))) : null,
},
])
</script>
<template>
<AppLayout>
<AppContainer>
<PageBanner
title="Отчёты"
:icon="TbReportMedical"
:breadcrumbs="[{ label: 'Главная', href: '/', tag: Link }]"
>
<template #meta>
<NText depth="3" style="font-size: 13px;">Создавайте отчёты с нуля или по готовым шаблонам</NText>
</template>
</PageBanner>
<SectionCard v-if="canManage" title="Создание отчёта">
<template #header-extra>
<NButton text type="primary" @click="showPicker = true">
<template #icon><TbLayoutGrid /></template>
Открыть все шаблоны
</NButton>
</template>
<NScrollbar x-scrollable class="pb-3">
<div class="create-row">
<NEl class="create-card" @click="openPreset({ key: 'blank' })">
<div class="blank-tile"><NIcon :size="28"><TbPlus /></NIcon></div>
<NText class="blank-title">Отчёт с нуля</NText>
</NEl>
<div v-for="preset in featured" :key="preset.key" class="create-card" @click="openPreset(preset)">
<PresetCard :preset="preset" />
</div>
</div>
</NScrollbar>
</SectionCard>
<SectionCard title="Отчёты" no-padding>
<NEmpty v-if="!documents.length" description="Пока нет сохранённых отчётов" style="padding: 32px;" />
<NDataTable
v-else
min-height="calc(100vh - 570px)"
:columns="columns"
:data="documents"
:row-props="rowProps"
:bordered="false"
size="small"
/>
</SectionCard>
<TemplatePickerModal
v-model:show="showPicker"
:presets="presets"
:categories="categories"
@select="openPreset"
/>
</AppContainer>
</AppLayout>
</template>
<style scoped>
:deep(.n-data-table-th) { background: transparent !important; }
:deep(.n-data-table) { background: transparent; }
:deep(.n-data-table-wrapper) { border-radius: 0; }
:deep(.n-data-table-th .n-data-table-th__title) { font-size: 12px; }
:deep(.n-data-table-td) { font-size: 13px; }
.create-row {
display: flex;
gap: 12px;
padding-top: 2px;
}
.create-card {
width: 220px;
flex-shrink: 0;
}
.blank-tile {
height: 84px;
display: flex;
align-items: center;
justify-content: center;
color: var(--primary-color);
border-radius: 8px;
background: color-mix(in srgb, var(--primary-color) 8%, transparent);
margin-bottom: 10px;
}
.create-card .blank-tile { border: 1px dashed color-mix(in srgb, var(--primary-color) 40%, transparent); }
.create-card:first-child {
border: 1px solid var(--n-border-color, rgba(255,255,255,.1));
border-radius: 12px;
padding: 14px;
cursor: pointer;
transition: border-color .15s, transform .15s;
background: var(--n-card-color);
}
.create-card:first-child:hover { border-color: var(--primary-color); transform: translateY(-2px); }
.blank-title { display: block; font-weight: 600; font-size: 14px; }
</style>