import type { ColumnType, LinkToAnotherRecordType, PaginatedType, TableType } from 'nocodb-sdk' import type { ComputedRef, Ref } from 'vue' import { Modal, message } from 'ant-design-vue' import { useI18n } from 'vue-i18n' import { IsPublicInj, NOCO, computed, extractSdkResponseErrorMsg, reactive, ref, useInjectionState, useMetas, useNuxtApp, useProject, } from '#imports' import type { Row } from '~/composables' import { SharedViewPasswordInj } from '~/context' interface DataApiResponse { list: Record pageInfo: PaginatedType } /** Store for managing Link to another cells */ const [useProvideLTARStore, useLTARStore] = useInjectionState( (column: Ref>, row: Ref, isNewRow: ComputedRef | Ref, reloadData = () => {}) => { // state const { metas, getMeta } = useMetas() const { project } = useProject() const { $api } = useNuxtApp() const sharedViewPassword = inject(SharedViewPasswordInj, ref(null)) const childrenExcludedList = ref() const childrenList = ref() const childrenExcludedListPagination = reactive({ page: 1, query: '', size: 10, }) const childrenListPagination = reactive({ page: 1, query: '', size: 10, }) const { t } = useI18n() const isPublic: boolean = $(inject(IsPublicInj, ref(false))) const colOptions = $computed(() => column?.value.colOptions as LinkToAnotherRecordType) const { sharedView } = useSharedView() as Record const projectId = project?.value?.id || sharedView.value?.view?.project_id // getters const meta = computed(() => metas?.value?.[column?.value?.fk_model_id as string]) const relatedTableMeta = computed(() => { return metas.value?.[colOptions?.fk_related_model_id as string] }) const rowId = computed(() => meta.value.columns .filter((c: Required) => c.pk) .map((c: Required) => row?.value?.row?.[c.title]) .join('___'), ) // actions const getRelatedTableRowId = (row: Record) => { return relatedTableMeta.value?.columns ?.filter((c) => c.pk) .map((c) => row?.[c.title as string]) .join('___') } const loadRelatedTableMeta = async () => { await getMeta(colOptions?.fk_related_model_id as string) } const relatedTablePrimaryValueProp = computed(() => { return (relatedTableMeta?.value?.columns?.find((c) => c.pv) || relatedTableMeta?.value?.columns?.[0])?.title }) const relatedTablePrimaryKeyProps = computed(() => { return relatedTableMeta?.value?.columns?.filter((c) => c.pk)?.map((c) => c.title) ?? [] }) const primaryValueProp = computed(() => { return (meta?.value?.columns?.find((c: Required) => c.pv) || relatedTableMeta?.value?.columns?.[0])?.title }) const loadChildrenExcludedList = async () => { try { if (isPublic) { const route = useRoute() childrenExcludedList.value = await $api.public.dataRelationList( route.params.viewId as string, column?.value?.id, {}, { headers: { 'xc-password': sharedViewPassword.value, }, query: { limit: childrenExcludedListPagination.size, offset: childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1), where: childrenExcludedListPagination.query && `(${relatedTablePrimaryValueProp.value},like,${childrenExcludedListPagination.query})`, fields: [relatedTablePrimaryValueProp.value, ...relatedTablePrimaryKeyProps.value], } as any, }, ) /** if new row load all records */ } else if (isNewRow?.value) { childrenExcludedList.value = await $api.dbTableRow.list( NOCO, projectId, relatedTableMeta?.value?.id as string, { limit: childrenExcludedListPagination.size, offset: childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1), where: childrenExcludedListPagination.query && `(${relatedTablePrimaryValueProp.value},like,${childrenExcludedListPagination.query})`, fields: [relatedTablePrimaryValueProp.value, ...relatedTablePrimaryKeyProps.value], } as any, ) } else { childrenExcludedList.value = await $api.dbTableRow.nestedChildrenExcludedList( NOCO, projectId, meta.value.id, rowId.value, colOptions.type as 'mm' | 'hm', encodeURIComponent(column?.value?.title), { limit: String(childrenExcludedListPagination.size), offset: String(childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1)), // todo: where clause is missing from type where: childrenExcludedListPagination.query && `(${relatedTablePrimaryValueProp.value},like,${childrenExcludedListPagination.query})`, } as any, ) } } catch (e: any) { message.error(`${t('msg.error.failedToLoadList')}: ${await extractSdkResponseErrorMsg(e)}`) } } const loadChildrenList = async () => { try { if (colOptions.type === 'bt') return if (isPublic) { childrenList.value = await $api.public.dataNestedList( sharedView.value?.uuid as string, rowId.value, colOptions.type as 'mm' | 'hm', encodeURIComponent(column?.value?.id), { limit: String(childrenListPagination.size), offset: String(childrenListPagination.size * (childrenListPagination.page - 1)), where: childrenListPagination.query && `(${relatedTablePrimaryValueProp.value},like,${childrenListPagination.query})`, } as any, ) } else { childrenList.value = await $api.dbTableRow.nestedList( NOCO, (project?.value?.id || sharedView?.value?.view?.project_id) as string, meta.value.id, rowId.value, colOptions.type as 'mm' | 'hm', encodeURIComponent(column?.value?.title), { limit: String(childrenListPagination.size), offset: String(childrenListPagination.size * (childrenListPagination.page - 1)), where: childrenListPagination.query && `(${relatedTablePrimaryValueProp.value},like,${childrenListPagination.query})`, } as any, ) } } catch (e: any) { message.error(`${t('msg.error.failedToLoadChildrenList')}: ${await extractSdkResponseErrorMsg(e)}`) } } const deleteRelatedRow = async (row: Record, onSuccess?: (row: Record) => void) => { Modal.confirm({ title: 'Do you want to delete the record?', type: 'warning', onOk: async () => { const id = getRelatedTableRowId(row) try { $api.dbTableRow.delete(NOCO, projectId, relatedTableMeta.value.id as string, id as string) reloadData?.() /** reload child list if not a new row */ if (!isNewRow?.value) { await loadChildrenList() } onSuccess?.(row) } catch (e: any) { message.error(`${t('msg.error.deleteFailed')}: ${await extractSdkResponseErrorMsg(e)}`) } }, }) } const unlink = async (row: Record) => { // const column = meta.columns.find(c => c.id === this.column.colOptions.fk_child_column_id); // todo: handle if new record // if (this.isNew) { // this.$emit('updateCol', this.row, _cn, null); // this.localState = null; // this.$emit('update:localState', this.localState); // return; // } // todo: handle bt column if required // if (column.rqd) { // this.$toast.info('Unlink is not possible, instead map to another parent.').goAway(3000); // return; // } try { // todo: audit await $api.dbTableRow.nestedRemove( NOCO, project.value.title as string, meta.value.title, rowId.value, colOptions.type as 'mm' | 'hm', encodeURIComponent(column?.value?.title), getRelatedTableRowId(row) as string, ) } catch (e: any) { message.error(`${t('msg.error.unlinkFailed')}: ${await extractSdkResponseErrorMsg(e)}`) } reloadData?.() // todo: reload table data and children list // this.$emit('loadTableData'); // if (this.isForm && this.$refs.childList) { // this.$refs.childList.loadData(); // } } const link = async (row: Record) => { // todo: handle new record // 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; // } try { await $api.dbTableRow.nestedAdd( NOCO, project.value.title as string, meta.value.title as string, rowId.value, colOptions.type as 'mm' | 'hm', encodeURIComponent(column?.value?.title), getRelatedTableRowId(row) as string, ) await loadChildrenList() } catch (e: any) { message.error(`Linking failed: ${await extractSdkResponseErrorMsg(e)}`) } // todo: reload table data and child list // this.pid = pid; // // this.newRecordModal = false; // // this.$emit('loadTableData'); // if (this.isForm && this.$refs.childList) { // this.$refs.childList.loadData(); // } reloadData?.() } // watchers watch(childrenExcludedListPagination, async () => { await loadChildrenExcludedList() }) watch(childrenListPagination, async () => { await loadChildrenList() }) return { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, childrenExcludedList, childrenList, rowId, childrenExcludedListPagination, childrenListPagination, primaryValueProp, meta, unlink, link, loadChildrenExcludedList, loadChildrenList, row, deleteRelatedRow, getRelatedTableRowId, } }, 'ltar-store', ) export { useProvideLTARStore } export function useLTARStoreOrThrow() { const ltarStore = useLTARStore() if (ltarStore == null) throw new Error('Please call `useLTARStore` on the appropriate parent component') return ltarStore }