Support per-map contact membership with scoped graph views, relation editing on edges, layout caching, and automatic detection of full JSON backups so contacts and relations import together. Co-authored-by: Cursor <cursoragent@cursor.com>
73 lines
1.8 KiB
Vue
73 lines
1.8 KiB
Vue
<template>
|
|
<div v-if="open" class="modal-overlay" @click.self="$emit('close')">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Добавить на карту</h3>
|
|
<button class="btn btn-secondary btn-sm" type="button" @click="$emit('close')">✕</button>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Контакт</label>
|
|
<SearchableSelect
|
|
v-model="selectedContactId"
|
|
:options="contactOptions"
|
|
placeholder="Выберите контакт..."
|
|
/>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @click="$emit('close')">Отмена</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary"
|
|
:disabled="!selectedContactId"
|
|
@click="onAdd"
|
|
>
|
|
Добавить
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from 'vue'
|
|
import SearchableSelect from './SearchableSelect.vue'
|
|
|
|
const props = defineProps({
|
|
open: { type: Boolean, default: false },
|
|
contacts: { type: Array, default: () => [] },
|
|
memberContactIds: { type: Array, default: () => [] },
|
|
})
|
|
const emit = defineEmits(['close', 'add'])
|
|
|
|
const selectedContactId = ref('')
|
|
|
|
const memberSet = computed(() => new Set(props.memberContactIds.map(String)))
|
|
|
|
const contactOptions = computed(() =>
|
|
props.contacts
|
|
.filter((c) => !memberSet.value.has(String(c.id)))
|
|
.map((c) => ({ value: String(c.id), label: c.name }))
|
|
)
|
|
|
|
watch(
|
|
() => props.open,
|
|
(isOpen) => {
|
|
if (isOpen) selectedContactId.value = ''
|
|
}
|
|
)
|
|
|
|
function onAdd() {
|
|
if (!selectedContactId.value) return
|
|
emit('add', selectedContactId.value)
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|