mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
12 KiB
397 lines
12 KiB
<script setup lang="ts"> |
|
import type { ColumnType } from 'nocodb-sdk' |
|
import ItemChip from './components/ItemChip.vue' |
|
import { ColumnInj, ValueInj } from '~/context' |
|
import { useHasMany } from '#imports' |
|
|
|
const column = inject(ColumnInj) |
|
const value = inject(ValueInj) |
|
const row = inject(RowInj) |
|
|
|
const listItemsDlg = ref(false) |
|
const childListDlg = ref(false) |
|
|
|
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore( |
|
column as Required<ColumnType>, |
|
row, |
|
) |
|
await loadRelatedTableMeta() |
|
|
|
/* // import ApiFactory from '@/components/project/spreadsheet/apis/apiFactory' |
|
import { RelationTypes, UITypes, isSystemColumn } from 'nocodb-sdk' |
|
import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel' |
|
import Pagination from '~/components/project/spreadsheet/components/Pagination' |
|
import ListItems from '~/components/project/spreadsheet/components/virtualCell/components/ListItems' |
|
import ListChildItems from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItems' |
|
import listChildItemsModal from '~/components/project/spreadsheet/components/virtualCell/components/ListChildItemsModal' |
|
import { parseIfInteger } from '@/helpers' |
|
import ItemChip from '~/components/project/spreadsheet/components/virtualCell/components/ItemChip' |
|
|
|
// todo: handling add new record for new row |
|
|
|
export default { |
|
name: 'HasManyCell', |
|
components: { |
|
ListChildItems, |
|
ItemChip, |
|
ListItems, |
|
Pagination, |
|
DlgLabelSubmitCancel, |
|
ListChildItemsModal: listChildItemsModal, |
|
}, |
|
props: { |
|
isLocked: Boolean, |
|
breadcrumbs: { |
|
type: Array, |
|
default() { |
|
return [] |
|
}, |
|
}, |
|
value: [Object, Array], |
|
meta: [Object], |
|
nodes: [Object], |
|
row: [Object], |
|
active: Boolean, |
|
isNew: Boolean, |
|
isForm: Boolean, |
|
required: Boolean, |
|
isPublic: Boolean, |
|
metas: Object, |
|
password: String, |
|
column: Object, |
|
}, |
|
data: () => ({ |
|
newRecordModal: false, |
|
childListModal: false, |
|
// childMeta: null, |
|
dialogShow: false, |
|
confirmAction: null, |
|
confirmMessage: '', |
|
selectedChild: null, |
|
expandFormModal: false, |
|
isNewChild: false, |
|
localState: [], |
|
}), |
|
computed: { |
|
childMeta() { |
|
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 |
|
childApi() {}, |
|
childPrimaryCol() { |
|
return this.childMeta && (this.childMeta.columns.find((c) => c.pv) || {}).title |
|
}, |
|
primaryCol() { |
|
return this.meta && (this.meta.columns.find((c) => c.pv) || {}).title |
|
}, |
|
childPrimaryKey() { |
|
return this.childMeta && (this.childMeta.columns.find((c) => c.pk) || {}).title |
|
}, |
|
childForeignKey() { |
|
return ( |
|
this.childMeta && (this.childMeta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id) || {}).title |
|
) |
|
}, |
|
childForeignKeyVal() { |
|
return this.meta && this.meta.columns |
|
? this.meta.columns |
|
.filter((c) => c.title === this.childForeignKey) |
|
.map((c) => this.row[c.title] || '') |
|
.join('___') |
|
: '' |
|
}, |
|
isVirtualRelation() { |
|
return this.column && this.column.colOptions.virtual // (this.childMeta && (!!this.childMeta.columns.find(c => c.column_name === this.hm.column_name && this.hm.type === 'virtual'))) || false |
|
}, |
|
isByPass() { |
|
if (this.isVirtualRelation) { |
|
return false |
|
} |
|
// if child fk references a column in parent which is not pk, |
|
// then this column has to be filled |
|
// if (((this.meta && this.meta.columns.find(c => !c.pk && c.id === this.hm.rcn)) || false)) { |
|
// return this.childForeignKeyVal === '' |
|
// } |
|
if ((this.meta && this.meta.columns.find((c) => !c.pk && c.id === this.column.fk_parent_column_id)) || false) { |
|
return this.childForeignKeyVal === '' |
|
} |
|
return false |
|
}, |
|
disabledChildColumns() { |
|
return { [this.childForeignKey]: true } |
|
}, |
|
// todo: |
|
form() { |
|
return this.selectedChild && !this.isPublic |
|
? () => import('~/components/project/spreadsheet/components/ExpandedForm') |
|
: 'span' |
|
}, |
|
childAvailableColumns() { |
|
if (!this.childMeta) { |
|
return [] |
|
} |
|
|
|
const columns = [] |
|
if (this.childMeta.columns) { |
|
columns.push(...this.childMeta.columns.filter((c) => !isSystemColumn(c))) |
|
} |
|
return columns |
|
}, |
|
childQueryParams() { |
|
if (!this.childMeta) { |
|
return {} |
|
} |
|
// todo: use reduce |
|
return { |
|
hm: |
|
(this.childMeta && |
|
this.childMeta.v && |
|
this.childMeta.v |
|
.filter((v) => v.hm) |
|
.map(({ hm }) => hm.table_name) |
|
.join()) || |
|
'', |
|
bt: |
|
(this.childMeta && |
|
this.childMeta.v && |
|
this.childMeta.v |
|
.filter((v) => v.bt) |
|
.map(({ bt }) => bt.rtn) |
|
.join()) || |
|
'', |
|
mm: |
|
(this.childMeta && |
|
this.childMeta.v && |
|
this.childMeta.v |
|
.filter((v) => v.mm) |
|
.map(({ mm }) => mm.rtn) |
|
.join()) || |
|
'', |
|
} |
|
}, |
|
parentId() { |
|
return ( |
|
(this.meta && |
|
this.meta.columns && |
|
(this.meta.columns |
|
.filter((c) => c.title === this.childForeignKey) |
|
.map((c) => this.row[c.title] || '') |
|
.join('___') || |
|
this.meta.columns |
|
.filter((c) => c.pk) |
|
.map((c) => this.row[c.title]) |
|
.join('___'))) || |
|
'' |
|
) |
|
}, |
|
}, |
|
watch: { |
|
isNew(n, o) { |
|
if (!n && o) { |
|
this.saveLocalState() |
|
} |
|
}, |
|
}, |
|
async mounted() { |
|
await this.loadChildMeta() |
|
|
|
if (this.isNew && this.value) { |
|
this.localState = [...this.value] |
|
} |
|
}, |
|
created() { |
|
this.loadChildMeta() |
|
}, |
|
methods: { |
|
onChildSave() { |
|
if (this.isNew) { |
|
this.addChildToParent(this.selectedChild) |
|
} else { |
|
this.$emit('loadTableData') |
|
} |
|
}, |
|
async showChildListModal() { |
|
await this.loadChildMeta() |
|
this.childListModal = true |
|
}, |
|
async deleteChild(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.childMeta.columns |
|
.filter((c) => c.pk) |
|
.map((c) => child[c.title]) |
|
.join('___') |
|
try { |
|
await this.$api.data.delete(this.childMeta.id, id) |
|
this.dialogShow = false |
|
this.$emit('loadTableData') |
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
|
this.$refs.childList.loadData() |
|
} |
|
} catch (e) { |
|
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000) |
|
} |
|
} |
|
} |
|
}, |
|
async unlinkChild(child) { |
|
if (this.isNew) { |
|
this.localState.splice(this.localState.indexOf(child), 1) |
|
this.$emit('update:localState', [...this.localState]) |
|
return |
|
} |
|
|
|
await this.loadChildMeta() |
|
const column = this.childMeta.columns.find((c) => c.id === this.column.colOptions.fk_child_column_id) |
|
|
|
if (column.rqd) { |
|
this.$toast.info('Unlink is not possible, instead add to another record.').goAway(3000) |
|
return |
|
} |
|
const id = this.childMeta.columns |
|
.filter((c) => c.pk) |
|
.map((c) => child[c.title]) |
|
.join('___') |
|
await this.$api.dbTableRow.nestedRemove( |
|
NOCO, |
|
this.projectName, |
|
this.meta.title, |
|
this.parentId, |
|
RelationTypes.HAS_MANY, |
|
this.column.title, |
|
id, |
|
) |
|
|
|
this.$emit('loadTableData') |
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
|
this.$refs.childList.loadData() |
|
} |
|
// } |
|
// } |
|
}, |
|
async loadChildMeta() { |
|
// todo: optimize |
|
if (!this.childMeta) { |
|
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.loadChildMeta() |
|
this.newRecordModal = true |
|
}, |
|
async addChildToParent(child) { |
|
if (this.isNew && this.localState.every((it) => it[this.childForeignKey] !== child[this.childPrimaryKey])) { |
|
this.localState.push(child) |
|
this.$emit('update:localState', [...this.localState]) |
|
this.$emit('saveRow') |
|
this.newRecordModal = false |
|
return |
|
} |
|
|
|
const id = this.childMeta.columns |
|
.filter((c) => c.pk) |
|
.map((c) => child[c.title]) |
|
.join('___') |
|
this.newRecordModal = false |
|
await this.$api.dbTableRow.nestedAdd(NOCO, this.projectName, this.meta.title, this.parentId, 'hm', this.column.title, id) |
|
|
|
this.$emit('loadTableData') |
|
if ((this.childListModal || this.isForm) && this.$refs.childList) { |
|
await this.$refs.childList.loadData() |
|
} |
|
}, |
|
async editChild(child) { |
|
await this.loadChildMeta() |
|
this.isNewChild = false |
|
this.expandFormModal = true |
|
this.selectedChild = child |
|
setTimeout(() => { |
|
this.$refs.expandedForm && this.$refs.expandedForm.reload() |
|
}, 500) |
|
}, |
|
async insertAndAddNewChildRecord() { |
|
this.newRecordModal = false |
|
await this.loadChildMeta() |
|
this.isNewChild = true |
|
this.selectedChild = { |
|
[this.childForeignKey]: parseIfInteger(this.parentId), |
|
[( |
|
this.childMeta.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.BELONGS_TO, |
|
) || {} |
|
).title]: this.row, |
|
} |
|
this.expandFormModal = true |
|
if (!this.isNew) { |
|
setTimeout(() => { |
|
this.$refs.expandedForm && |
|
this.$refs.expandedForm.$set(this.$refs.expandedForm.changedColumns, this.childForeignKey, true) |
|
}, 500) |
|
} |
|
}, |
|
getCellValue(cellObj) { |
|
if (cellObj) { |
|
if (this.childMeta && this.childPrimaryCol) { |
|
return cellObj[this.childPrimaryCol] |
|
} |
|
return Object.values(cellObj)[1] |
|
} |
|
}, |
|
async saveLocalState(row) { |
|
let child |
|
// eslint-disable-next-line no-cond-assign |
|
while ((child = this.localState.pop())) { |
|
if (row) { |
|
const pid = this.meta.columns |
|
.filter((c) => c.pk) |
|
.map((c) => row[c.title]) |
|
.join('___') |
|
const id = this.childMeta.columns |
|
.filter((c) => c.pk) |
|
.map((c) => child[c.title]) |
|
.join('___') |
|
|
|
await this.$api.dbTableRow.nestedAdd(NOCO, this.projectName, this.meta.title, pid, 'hm', this.column.title, id) |
|
} else { |
|
await this.addChildToParent(child) |
|
} |
|
} |
|
this.$emit('newRecordsSaved') |
|
}, |
|
}, |
|
} */ |
|
</script> |
|
|
|
<template> |
|
<div class="flex align-center gap-1 w-full chips-wrapper group"> |
|
<!-- <template v-if="!isForm"> --> |
|
<div class="chips flex align-center img-container flex-grow hm-items flex-nowrap min-w-0 overflow-hidden"> |
|
<template v-if="value"> |
|
<ItemChip v-for="(ch, i) in value" :key="i" :value="ch[relatedTablePrimaryValueProp]" @unlink="unlink(ch)" /> |
|
<span v-if="value?.length === 10" class="caption pointer ml-1 grey--text" @click="childListDlg = true">more... </span> |
|
</template> |
|
</div> |
|
|
|
<MdiExpandIcon class="hidden group-hover:inline w-[20px] text-gray-500/50 hover:text-gray-500" @click="childListDlg = true" /> |
|
<MdiPlusIcon class="hidden group-hover:inline w-[20px] text-gray-500/50 hover:text-gray-500" @click="listItemsDlg = true" /> |
|
<ListItems v-model="listItemsDlg" /> |
|
<ListChildItems v-model="childListDlg" /> |
|
|
|
</div> |
|
</template>
|
|
|