|
|
|
<script setup lang="ts">
|
|
|
|
import type { ColumnType } from 'nocodb-sdk'
|
|
|
|
import ItemChip from './components/ItemChip.vue'
|
|
|
|
import { ColumnInj } from '~/components'
|
|
|
|
import useBelongsTo from '~/composables/useBelongsTo'
|
|
|
|
|
|
|
|
const column = inject(ColumnInj)
|
|
|
|
const value = inject('value')
|
|
|
|
const active = false
|
|
|
|
|
|
|
|
const { parentMeta, loadParentMeta, primaryValueProp } = useBelongsTo(column as ColumnType)
|
|
|
|
await loadParentMeta()
|
|
|
|
// import ApiFactory from '@/components/project/spreadsheet/apis/apiFactory'
|
|
|
|
/* import { RelationTypes, UITypes, isSystemColumn } from 'nocodb-sdk'
|
|
|
|
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems'
|
|
|
|
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems'
|
|
|
|
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip'
|
|
|
|
import { parseIfInteger } from '@/helpers'
|
|
|
|
|
|
|
|
export default {
|
|
|
|
name: 'BelongsToCell',
|
|
|
|
components: { ListChildItems, ItemChip, ListItems },
|
|
|
|
props: {
|
|
|
|
isLocked: Boolean,
|
|
|
|
breadcrumbs: {
|
|
|
|
type: Array,
|
|
|
|
default() {
|
|
|
|
return []
|
|
|
|
},
|
|
|
|
},
|
|
|
|
isForm: Boolean,
|
|
|
|
value: [Array, Object],
|
|
|
|
meta: [Object],
|
|
|
|
nodes: [Object],
|
|
|
|
row: [Object],
|
|
|
|
api: [Object, Function],
|
|
|
|
sqlUi: [Object, Function],
|
|
|
|
active: Boolean,
|
|
|
|
isNew: Boolean,
|
|
|
|
disabledColumns: Object,
|
|
|
|
isPublic: Boolean,
|
|
|
|
metas: Object,
|
|
|
|
password: String,
|
|
|
|
column: Object,
|
|
|
|
},
|
|
|
|
data: () => ({
|
|
|
|
newRecordModal: false,
|
|
|
|
parentListModal: false,
|
|
|
|
// parentMeta: null,
|
|
|
|
list: null,
|
|
|
|
childList: null,
|
|
|
|
dialogShow: false,
|
|
|
|
confirmAction: null,
|
|
|
|
confirmMessage: '',
|
|
|
|
selectedParent: null,
|
|
|
|
isNewParent: false,
|
|
|
|
expandFormModal: false,
|
|
|
|
localState: null,
|
|
|
|
pid: null,
|
|
|
|
}),
|
|
|
|
computed: {
|
|
|
|
parentMeta() {
|
|
|
|
return this.metas
|
|
|
|
? this.metas[this.column.colOptions.fk_related_model_id]
|
|
|
|
: this.$store.state.meta.metas[this.column.colOptions.fk_related_model_id]
|
|
|
|
},
|
|
|
|
// todo : optimize
|
|
|
|
parentApi() {},
|
|
|
|
parentId() {
|
|
|
|
return (
|
|
|
|
this.pid ??
|
|
|
|
(this.value &&
|
|
|
|
this.parentMeta &&
|
|
|
|
this.parentMeta.columns
|
|
|
|
.filter((c) => c.pk)
|
|
|
|
.map((c) => this.value[c.title])
|
|
|
|
.join('___'))
|
|
|
|
)
|
|
|
|
},
|
|
|
|
rowId() {
|
|
|
|
return (
|
|
|
|
this.row &&
|
|
|
|
this.meta &&
|
|
|
|
this.meta.columns
|
|
|
|
.filter((c) => c.pk)
|
|
|
|
.map((c) => this.row[c.title])
|
|
|
|
.join('___')
|
|
|
|
)
|
|
|
|
},
|
|
|
|
parentPrimaryCol() {
|
|
|
|
return this.parentMeta && (this.parentMeta.columns.find((c) => c.pv) || {}).title
|
|
|
|
},
|
|
|
|
parentPrimaryKey() {
|
|
|
|
return this.parentMeta && (this.parentMeta.columns.find((c) => c.pk) || {}).title
|
|
|
|
},
|
|
|
|
parentReferenceKey() {
|
|
|
|
return (
|
|
|
|
this.parentMeta && (this.parentMeta.columns.find((c) => c.id === this.column.colOptions.fk_parent_column_id) || {}).title
|
|
|
|
)
|
|
|
|
},
|
|
|
|
btWhereClause() {
|
|
|
|
// if parent reference key is pk, then filter out the selected value
|
|
|
|
// else, filter out the selected value + empty values (as we can't set an empty value)
|
|
|
|
const prk = this.parentReferenceKey
|
|
|
|
const selectedValue =
|
|
|
|
this.meta && this.meta.columns
|
|
|
|
? this.meta.columns
|
|
|
|
.filter((c) => c.id === this.column.colOptions.fk_child_column_id)
|
|
|
|
.map((c) => this.row[c.title] || '')
|
|
|
|
.join('___')
|
|
|
|
: ''
|
|
|
|
return `(${prk},not,${selectedValue})~or(${prk},is,null)`
|
|
|
|
},
|
|
|
|
parentQueryParams() {
|
|
|
|
if (!this.parentMeta) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
// todo: use reduce
|
|
|
|
return {}
|
|
|
|
},
|
|
|
|
parentAvailableColumns() {
|
|
|
|
if (!this.parentMeta) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
const columns = []
|
|
|
|
if (this.parentMeta.columns) {
|
|
|
|
columns.push(...this.parentMeta.columns.filter((c) => !isSystemColumn(c)))
|
|
|
|
}
|
|
|
|
return columns
|
|
|
|
},
|
|
|
|
// todo:
|
|
|
|
form() {
|
|
|
|
return this.selectedParent && !this.isPublic
|
|
|
|
? () => import('~/components/project/spreadsheet/components/ExpandedForm')
|
|
|
|
: 'span'
|
|
|
|
},
|
|
|
|
cellValue() {
|
|
|
|
if (this.value || this.localState) {
|
|
|
|
if (this.parentMeta && this.parentPrimaryCol) {
|
|
|
|
return (this.value || this.localState)[this.parentPrimaryCol]
|
|
|
|
}
|
|
|
|
return Object.values(this.value || this.localState)[1]
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
},
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
isNew(n, o) {
|
|
|
|
if (!n && o) {
|
|
|
|
this.localState = null
|
|
|
|
this.$emit('update:localState', this.localState)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
async mounted() {
|
|
|
|
if (this.isNew && this.value) {
|
|
|
|
this.localState = this.value
|
|
|
|
}
|
|
|
|
if (this.isForm) {
|
|
|
|
await this.loadParentMeta()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
created() {
|
|
|
|
this.loadParentMeta()
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
async onParentSave(parent) {
|
|
|
|
if (this.isNewParent) {
|
|
|
|
await this.addChildToParent(parent)
|
|
|
|
} else {
|
|
|
|
this.$emit('loadTableData')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async insertAndMapNewParentRecord() {
|
|
|
|
await this.loadParentMeta()
|
|
|
|
this.newRecordModal = false
|
|
|
|
this.isNewParent = true
|
|
|
|
this.selectedParent = {
|
|
|
|
[(
|
|
|
|
this.parentMeta.columns.find(
|
|
|
|
(c) =>
|
|
|
|
c.uidt === UITypes.LinkToAnotherRecord &&
|
|
|
|
c.colOptions &&
|
|
|
|
this.column.colOptions &&
|
|
|
|
c.colOptions.fk_child_column_id === this.column.colOptions.fk_child_column_id &&
|
|
|
|
c.colOptions.fk_parent_column_id === this.column.colOptions.fk_parent_column_id &&
|
|
|
|
c.colOptions.type === RelationTypes.HAS_MANY,
|
|
|
|
) || {}
|
|
|
|
).title]: [this.row],
|
|
|
|
}
|
|
|
|
this.expandFormModal = true
|
|
|
|
},
|
|
|
|
|
|
|
|
async unlink(parent) {
|
|
|
|
const column = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id)
|
|
|
|
const _cn = column.title
|
|
|
|
if (this.isNew) {
|
|
|
|
this.$emit('updateCol', this.row, _cn, null)
|
|
|
|
this.localState = null
|
|
|
|
this.$emit('update:localState', this.localState)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (column.rqd) {
|
|
|
|
this.$toast.info('Unlink is not possible, instead map to another parent.').goAway(3000)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const id = this.meta.columns
|
|
|
|
.filter((c) => c.pk)
|
|
|
|
.map((c) => this.row[c.title])
|
|
|
|
.join('___')
|
|
|
|
|
|
|
|
// todo: audit
|
|
|
|
await this.$api.dbTableRow.nestedRemove(
|
|
|
|
NOCO,
|
|
|
|
this.projectName,
|
|
|
|
this.meta.title,
|
|
|
|
id,
|
|
|
|
'bt',
|
|
|
|
this.column.title,
|
|
|
|
parent[this.parentPrimaryKey],
|
|
|
|
)
|
|
|
|
|
|
|
|
this.$emit('loadTableData')
|
|
|
|
if (this.isForm && this.$refs.childList) {
|
|
|
|
this.$refs.childList.loadData()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async showParentListModal() {
|
|
|
|
this.parentListModal = true
|
|
|
|
await this.loadParentMeta()
|
|
|
|
const pid = this.meta.columns
|
|
|
|
.filter((c) => c.pk)
|
|
|
|
.map((c) => this.row[c.title])
|
|
|
|
.join('___')
|
|
|
|
const _cn = this.parentMeta.columns.find((c) => c.column_name === this.hm.column_name).title
|
|
|
|
this.childList = await this.parentApi.paginatedList({
|
|
|
|
where: `(${_cn},eq,${pid})`,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
async removeChild(child) {
|
|
|
|
this.dialogShow = true
|
|
|
|
this.confirmMessage = 'Do you want to delete the record?'
|
|
|
|
this.confirmAction = async (act) => {
|
|
|
|
if (act === 'hideDialog') {
|
|
|
|
this.dialogShow = false
|
|
|
|
} else {
|
|
|
|
const id = this.parentMeta.columns
|
|
|
|
.filter((c) => c.pk)
|
|
|
|
.map((c) => child[c.title])
|
|
|
|
.join('___')
|
|
|
|
await this.parentApi.delete(id)
|
|
|
|
this.pid = null
|
|
|
|
this.dialogShow = false
|
|
|
|
this.$emit('loadTableData')
|
|
|
|
if (this.isForm && this.$refs.childList) {
|
|
|
|
this.$refs.childList.loadData()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async loadParentMeta() {
|
|
|
|
// todo: optimize
|
|
|
|
if (!this.parentMeta) {
|
|
|
|
await this.$store.dispatch('meta/ActLoadMeta', {
|
|
|
|
env: this.nodes.env,
|
|
|
|
dbAlias: this.nodes.dbAlias,
|
|
|
|
id: this.column.colOptions.fk_related_model_id,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async showNewRecordModal() {
|
|
|
|
await this.loadParentMeta()
|
|
|
|
this.newRecordModal = true
|
|
|
|
},
|
|
|
|
async addChildToParent(parent) {
|
|
|
|
const pid = this._extractRowId(parent, this.parentMeta)
|
|
|
|
const id = this._extractRowId(this.row, this.meta)
|
|
|
|
const _cn = this.meta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id).title
|
|
|
|
|
|
|
|
if (this.isNew) {
|
|
|
|
const _rcn = this.parentMeta.columns.find((c) => c.id === this.column.colOptions.fk_parent_column_id).title
|
|
|
|
this.localState = parent
|
|
|
|
this.$emit('update:localState', this.localState)
|
|
|
|
this.$emit('updateCol', this.row, _cn, parent[_rcn])
|
|
|
|
this.newRecordModal = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
await this.$api.dbTableRow.nestedAdd(NOCO, this.projectName, this.meta.title, id, 'bt', this.column.title, pid)
|
|
|
|
|
|
|
|
this.pid = pid
|
|
|
|
|
|
|
|
this.newRecordModal = false
|
|
|
|
|
|
|
|
this.$emit('loadTableData')
|
|
|
|
if (this.isForm && this.$refs.childList) {
|
|
|
|
this.$refs.childList.loadData()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async editParent(parent) {
|
|
|
|
await this.loadParentMeta()
|
|
|
|
this.isNewParent = false
|
|
|
|
this.selectedParent = parent
|
|
|
|
this.expandFormModal = true
|
|
|
|
setTimeout(() => {
|
|
|
|
this.$refs.expandedForm && this.$refs.expandedForm.reload()
|
|
|
|
}, 500)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} */
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div class="d-flex d-100 chips-wrapper" :class="{ active }">
|
|
|
|
<!-- <template v-if="!isForm"> -->
|
|
|
|
<div class="chips d-flex align-center img-container flex-grow-1 hm-items">
|
|
|
|
<template v-if="value || localState">
|
|
|
|
<ItemChip :active="active" :item="value" :value="value[primaryValueProp]" />
|
|
|
|
<!-- :readonly="isLocked || (isPublic && !isForm)"
|
|
|
|
@edit="editParent"
|
|
|
|
@unlink="unlink" -->
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
<!-- <div
|
|
|
|
v-if="!isLocked && _isUIAllowed('xcDatatableEditable') && (isForm || !isPublic)"
|
|
|
|
class="action align-center justify-center px-1 flex-shrink-1"
|
|
|
|
:class="{ 'd-none': !active, 'd-flex': active }"
|
|
|
|
>
|
|
|
|
<x-icon small :color="['primary', 'grey']" @click="showNewRecordModal">
|
|
|
|
{{ value ? 'mdi-arrow-expand' : 'mdi-plus' }}
|
|
|
|
</x-icon>
|
|
|
|
</div> -->
|
|
|
|
<!-- </template> -->
|
|
|
|
<!-- <ListItems
|
|
|
|
v-if="newRecordModal"
|
|
|
|
:key="parentId"
|
|
|
|
v-model="newRecordModal"
|
|
|
|
:size="10"
|
|
|
|
:meta="parentMeta"
|
|
|
|
:column="column"
|
|
|
|
:primary-col="parentPrimaryCol"
|
|
|
|
:primary-key="parentPrimaryKey"
|
|
|
|
:parent-meta="meta"
|
|
|
|
:api="parentApi"
|
|
|
|
:query-params="{
|
|
|
|
...parentQueryParams,
|
|
|
|
where: isNew ? null : `${btWhereClause}`,
|
|
|
|
}"
|
|
|
|
:is-public="isPublic"
|
|
|
|
:tn="bt && bt.rtn"
|
|
|
|
:password="password"
|
|
|
|
:row-id="rowId"
|
|
|
|
@add-new-record="insertAndMapNewParentRecord"
|
|
|
|
@add="addChildToParent"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<ListChildItems
|
|
|
|
v-if="parentMeta && isForm"
|
|
|
|
ref="childList"
|
|
|
|
:is-form="isForm"
|
|
|
|
:local-state="localState ? [localState] : []"
|
|
|
|
:is-new="isNew"
|
|
|
|
:size="10"
|
|
|
|
:parent-meta="parentMeta"
|
|
|
|
:meta="parentMeta"
|
|
|
|
:primary-col="parentPrimaryCol"
|
|
|
|
:primary-key="parentPrimaryKey"
|
|
|
|
:api="parentApi"
|
|
|
|
:query-params="{
|
|
|
|
...parentQueryParams,
|
|
|
|
where: `(${parentPrimaryKey},eq,${parentId})`,
|
|
|
|
}"
|
|
|
|
:bt="value"
|
|
|
|
:is-public="isPublic"
|
|
|
|
:row-id="parentId"
|
|
|
|
@new-record="showNewRecordModal"
|
|
|
|
@edit="editParent"
|
|
|
|
@unlink="unlink"
|
|
|
|
/>
|
|
|
|
|
|
|
|
<v-dialog
|
|
|
|
v-if="!isPublic && selectedParent"
|
|
|
|
v-model="expandFormModal"
|
|
|
|
:overlay-opacity="0.8"
|
|
|
|
width="1000px"
|
|
|
|
max-width="100%"
|
|
|
|
class="mx-auto"
|
|
|
|
>
|
|
|
|
<component
|
|
|
|
:is="form"
|
|
|
|
v-if="selectedParent"
|
|
|
|
ref="expandedForm"
|
|
|
|
v-model="selectedParent"
|
|
|
|
v-model:is-new="isNewParent"
|
|
|
|
:db-alias="nodes.dbAlias"
|
|
|
|
:has-many="parentMeta.hasMany"
|
|
|
|
:belongs-to="parentMeta.belongsTo"
|
|
|
|
:table="parentMeta.table_name"
|
|
|
|
:old-row="{ ...selectedParent }"
|
|
|
|
:meta="parentMeta"
|
|
|
|
:sql-ui="sqlUi"
|
|
|
|
:primary-value-column="parentPrimaryCol"
|
|
|
|
:api="parentApi"
|
|
|
|
:available-columns="parentAvailableColumns"
|
|
|
|
:nodes="nodes"
|
|
|
|
:query-params="parentQueryParams"
|
|
|
|
icon-color="warning"
|
|
|
|
:breadcrumbs="breadcrumbs"
|
|
|
|
@cancel="
|
|
|
|
selectedParent = null
|
|
|
|
expandFormModal = false
|
|
|
|
"
|
|
|
|
@input="onParentSave"
|
|
|
|
/>
|
|
|
|
</v-dialog> -->
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
.items-container {
|
|
|
|
overflow-x: visible;
|
|
|
|
max-height: min(500px, 60vh);
|
|
|
|
overflow-y: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.primary-value {
|
|
|
|
.primary-key {
|
|
|
|
display: none;
|
|
|
|
margin-left: 0.5em;
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover .primary-key {
|
|
|
|
display: inline;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.child-card {
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
box-shadow: 0 0 0.2em var(--v-textColor-lighten5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.hm-items {
|
|
|
|
flex-wrap: wrap;
|
|
|
|
row-gap: 3px;
|
|
|
|
gap: 3px;
|
|
|
|
margin: 3px auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.chips-wrapper {
|
|
|
|
.chips {
|
|
|
|
max-width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
.chips {
|
|
|
|
max-width: calc(100% - 22px);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<!--
|
|
|
|
/**
|
|
|
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
|
|
|
|
*
|
|
|
|
* @author Naveen MR <oof1lab@gmail.com>
|
|
|
|
* @author Pranav C Balan <pranavxc@gmail.com>
|
|
|
|
* @author Md Ishtiaque Zafar <ishtiaque.zafar92@gmail.com>
|
|
|
|
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
|
|
|
|
*
|
|
|
|
* @license GNU AGPL version 3 or any later version
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
-->
|