import { UITypes, ViewTypes } from 'nocodb-sdk' import type { AuditType, ColumnType, TableType } from 'nocodb-sdk' import type { Ref } from 'vue' import dayjs from 'dayjs' import { NOCO, computed, extractPkFromRow, extractSdkResponseErrorMsg, message, populateInsertObject, ref, storeToRefs, useApi, useI18n, useInjectionState, useKanbanViewStoreOrThrow, useMetas, useNuxtApp, useProject, useProvideSmartsheetRowStore, useSharedView, useUndoRedo, } from '#imports' import type { Row, UndoRedoAction } from '~/lib' const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, row: Ref<Row>) => { const { $e, $state, $api } = useNuxtApp() const { api, isLoading: isCommentsLoading, error: commentsError } = useApi() const { t } = useI18n() const commentsOnly = ref(true) const commentsAndLogs = ref<any[]>([]) const comment = ref('') const commentsDrawer = ref(true) const saveRowAndStay = ref(0) const changedColumns = ref(new Set<string>()) const { project } = storeToRefs(useProject()) const rowStore = useProvideSmartsheetRowStore(meta, row) const activeView = inject(ActiveViewInj, ref()) const { sharedView } = useSharedView() const { addUndo, clone, defineViewScope } = useUndoRedo() const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) // getters const displayValue = computed(() => { if (row?.value?.row) { const col = meta?.value?.columns?.find((c) => c.pv) if (!col) { return } const value = row.value.row?.[col.title as string] const uidt = col.uidt if (uidt === UITypes.Date) { return (/^\d+$/.test(value) ? dayjs(+value) : dayjs(value)).format('YYYY-MM-DD') } else if (uidt === UITypes.DateTime) { return (/^\d+$/.test(value) ? dayjs(+value) : dayjs(value)).format('YYYY-MM-DD HH:mm') } else if (uidt === UITypes.Time) { let dateTime = dayjs(value) if (!dateTime.isValid()) { dateTime = dayjs(value, 'HH:mm:ss') } if (!dateTime.isValid()) { dateTime = dayjs(`1999-01-01 ${value}`) } if (!dateTime.isValid()) { return value } return dateTime.format('HH:mm:ss') } return value } }) const primaryKey = computed(() => { return extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) }) // actions const loadCommentsAndLogs = async () => { if (!row.value) return const rowId = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) if (!rowId) return commentsAndLogs.value = ( await api.utils.commentList({ row_id: rowId, fk_model_id: meta.value.id as string, comments_only: commentsOnly.value, }) ).list?.reverse?.() || [] } const isYou = (email: string) => { return $state.user?.value?.email === email } const saveComment = async () => { try { if (!row.value || !comment.value) return const rowId = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) if (!rowId) return await api.utils.commentRow({ fk_model_id: meta.value?.id as string, row_id: rowId, description: `The following comment has been created: ${comment.value}`, }) comment.value = '' await loadCommentsAndLogs() } catch (e: any) { message.error(e.message) } $e('a:row-expand:comment') } const save = async (ltarState: Record<string, any> = {}, undo = false) => { let data try { const isNewRow = row.value.rowMeta?.new ?? false if (isNewRow) { const { getMeta } = useMetas() const { missingRequiredColumns, insertObj } = await populateInsertObject({ meta: meta.value, ltarState, getMeta, row: row.value.row, throwError: true, }) if (missingRequiredColumns.size) return data = await $api.dbTableRow.create('noco', project.value.title as string, meta.value.title, insertObj) Object.assign(row.value, { row: data, rowMeta: {}, oldRow: { ...data }, }) if (!undo) { const id = extractPkFromRow(data, meta.value?.columns as ColumnType[]) const pkData = rowPkData(row.value.row, meta.value?.columns as ColumnType[]) // TODO remove linked record addUndo({ redo: { fn: async function redo(this: UndoRedoAction, rowData: any) { await $api.dbTableRow.create('noco', project.value.title as string, meta.value.title, { ...pkData, ...rowData }) if (activeView.value?.type === ViewTypes.KANBAN) { const { loadKanbanData } = useKanbanViewStoreOrThrow() await loadKanbanData() } reloadTrigger?.trigger() }, args: [clone(insertObj)], }, undo: { fn: async function undo(this: UndoRedoAction, id: string) { const res: any = await $api.dbViewRow.delete( 'noco', project.value.id as string, meta.value?.id as string, activeView.value?.id as string, id, ) if (res.message) { throw new Error(res.message) } if (activeView.value?.type === ViewTypes.KANBAN) { const { loadKanbanData } = useKanbanViewStoreOrThrow() await loadKanbanData() } reloadTrigger?.trigger() }, args: [id], }, scope: defineViewScope({ view: activeView.value }), }) } } else { const updateOrInsertObj = [...changedColumns.value].reduce((obj, col) => { obj[col] = row.value.row[col] return obj }, {} as Record<string, any>) if (Object.keys(updateOrInsertObj).length) { const id = extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]) if (!id) { return message.info("Update not allowed for table which doesn't have primary Key") } await $api.dbTableRow.update(NOCO, project.value.title as string, meta.value.title, id, updateOrInsertObj) if (!undo) { const undoObject = [...changedColumns.value].reduce((obj, col) => { obj[col] = row.value.oldRow[col] return obj }, {} as Record<string, any>) addUndo({ redo: { fn: async (id: string, data: Record<string, any>) => { await $api.dbTableRow.update(NOCO, project.value.title as string, meta.value.title, id, data) if (activeView.value?.type === ViewTypes.KANBAN) { const { loadKanbanData } = useKanbanViewStoreOrThrow() await loadKanbanData() } reloadTrigger?.trigger() }, args: [id, clone(updateOrInsertObj)], }, undo: { fn: async (id: string, data: Record<string, any>) => { await $api.dbTableRow.update(NOCO, project.value.title as string, meta.value.title, id, data) if (activeView.value?.type === ViewTypes.KANBAN) { const { loadKanbanData } = useKanbanViewStoreOrThrow() await loadKanbanData() } reloadTrigger?.trigger() }, args: [id, clone(undoObject)], }, scope: defineViewScope({ view: activeView.value }), }) } if (commentsDrawer.value) { await loadCommentsAndLogs() } } else { // No columns to update message.info(t('msg.info.noColumnsToUpdate')) return } } if (activeView.value?.type === ViewTypes.KANBAN) { const { addOrEditStackRow } = useKanbanViewStoreOrThrow() addOrEditStackRow(row.value, isNewRow) } message.success(`${displayValue.value || 'Row'} updated successfully.`) changedColumns.value = new Set() } catch (e: any) { message.error(`${t('msg.error.rowUpdateFailed')}: ${await extractSdkResponseErrorMsg(e)}`) } $e('a:row-expand:add') return data } const loadRow = async (rowId?: string) => { const record = await $api.dbTableRow.read( NOCO, // todo: project_id missing on view type (project?.value?.id || (sharedView.value?.view as any)?.project_id) as string, meta.value.title, rowId ?? extractPkFromRow(row.value.row, meta.value.columns as ColumnType[]), ) Object.assign(row.value, { row: record, oldRow: { ...record }, rowMeta: {}, }) } const updateComment = async (auditId: string, audit: Partial<AuditType>) => { return await $api.utils.commentUpdate(auditId, audit) } return { ...rowStore, commentsOnly, loadCommentsAndLogs, commentsAndLogs, isCommentsLoading, commentsError, saveComment, comment, isYou, commentsDrawer, row, displayValue, save, changedColumns, loadRow, primaryKey, saveRowAndStay, updateComment, } }, 'expanded-form-store') export { useProvideExpandedFormStore } export function useExpandedFormStoreOrThrow() { const expandedFormStore = useExpandedFormStore() if (expandedFormStore == null) throw new Error('Please call `useExpandedFormStore` on the appropriate parent component') return expandedFormStore }