Files
onboard/resources/js/Pages/Analytics/Components/PickerPanel.vue
2026-06-22 17:02:36 +09:00

115 lines
4.8 KiB
Vue

<script setup>
import { ref, computed } from 'vue'
import { NInput, NButton, NCheckbox, NText, NScrollbar, NIcon, NEmpty } from 'naive-ui'
import { VueDraggableNext as draggable } from 'vue-draggable-next'
import { TbArrowLeft, TbSearch, TbGripVertical, TbX, TbPlus } from 'vue-icons-plus/tb'
const props = defineProps({
title: { type: String, required: true },
description: { type: String, default: '' },
items: { type: Array, default: () => [] }, // [{ key, label, unit? }]
modelValue: { type: Array, default: () => [] }, // выбранные ключи (в порядке)
searchPlaceholder: { type: String, default: 'Поиск' },
allowCreate: { type: Boolean, default: false },
})
const emit = defineEmits(['update:modelValue', 'back', 'create'])
const search = ref('')
const itemMap = computed(() => Object.fromEntries(props.items.map((i) => [i.key, i])))
// Список «Выбрано» — объекты в порядке modelValue (для draggable).
const selectedItems = computed({
get: () => props.modelValue.map((key) => itemMap.value[key]).filter(Boolean),
set: (list) => emit('update:modelValue', list.map((i) => i.key)),
})
const availableItems = computed(() => props.items.filter((i) =>
!search.value || i.label.toLowerCase().includes(search.value.toLowerCase())
))
const isSelected = (key) => props.modelValue.includes(key)
const toggle = (key) => {
const next = isSelected(key)
? props.modelValue.filter((k) => k !== key)
: [...props.modelValue, key]
emit('update:modelValue', next)
}
const remove = (key) => emit('update:modelValue', props.modelValue.filter((k) => k !== key))
const reset = () => emit('update:modelValue', [])
</script>
<template>
<div class="picker-panel">
<div class="panel-head">
<NButton text size="small" @click="emit('back')">
<template #icon><TbArrowLeft /></template>
Назад
</NButton>
<NButton v-if="allowCreate" text size="small" type="primary" @click="emit('create')">
<template #icon><TbPlus /></template>
Создать
</NButton>
</div>
<div class="panel-title">{{ title }}</div>
<NText v-if="description" depth="3" style="font-size: 12px; display: block; margin-bottom: 10px;">{{ description }}</NText>
<NInput v-model:value="search" :placeholder="searchPlaceholder" clearable size="small" style="margin-bottom: 12px;">
<template #prefix><TbSearch :size="14" /></template>
</NInput>
<NScrollbar style="max-height: calc(100vh - 320px);">
<div class="block-head">
<NText depth="2" style="font-size: 12px; font-weight: 600;">Выбрано</NText>
<NButton v-if="modelValue.length" text size="tiny" @click="reset">Сбросить</NButton>
</div>
<NEmpty v-if="!selectedItems.length" description="Ничего не выбрано" size="small" style="padding: 8px 0;" />
<draggable v-else v-model="selectedItems" handle=".drag-handle" item-key="key" class="selected-list">
<div v-for="item in selectedItems" :key="item.key" class="selected-row">
<NIcon class="drag-handle" :size="16"><TbGripVertical /></NIcon>
<span class="row-label">{{ item.label }}</span>
<NButton text size="tiny" @click="remove(item.key)"><NIcon><TbX /></NIcon></NButton>
</div>
</draggable>
<div class="block-head" style="margin-top: 14px;">
<NText depth="2" style="font-size: 12px; font-weight: 600;">Доступно</NText>
</div>
<div class="available-list">
<NCheckbox
v-for="item in availableItems"
:key="item.key"
:checked="isSelected(item.key)"
style="display: flex; padding: 5px 0;"
@update:checked="toggle(item.key)"
>
{{ item.label }}
</NCheckbox>
</div>
</NScrollbar>
</div>
</template>
<style scoped>
.picker-panel { padding: 4px; }
.panel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.panel-title { font-size: 18px; font-weight: 600; margin-bottom: 2px; }
.block-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.selected-list { display: flex; flex-direction: column; gap: 4px; }
.selected-row {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
border-radius: 8px;
background: color-mix(in srgb, var(--primary-color) 7%, transparent);
}
.row-label { flex: 1; font-size: 13px; }
.drag-handle { cursor: grab; color: var(--n-text-color-3, #999); }
</style>