import type { ComputedRef, Ref } from 'vue' import type { Api, ColumnType, KanbanType, TableType, ViewType } from 'nocodb-sdk' import { useI18n } from 'vue-i18n' import { message } from 'ant-design-vue' import type { Row } from '~/composables/useViewData' import { enumColor } from '~/utils' import { useNuxtApp } from '#app' export function useKanbanViewData( meta: Ref | ComputedRef | undefined, viewMeta: Ref | ComputedRef | undefined, where?: ComputedRef, ) { const { t } = useI18n() const isPublic = inject(IsPublicInj, ref(false)) const { api } = useApi() const { project } = useProject() const { $api, $e } = useNuxtApp() const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() const { isUIAllowed } = useUIPermission() const groupingFieldColOptions = useState[]>('KanbanGroupingFieldColOptions', () => []) const kanbanMetaData = useState('KanbanMetaData', () => ({})) // formattedData structure // { // [val1] : [ // {row: {...}, oldRow: {...}, rowMeta: {...}}, // {row: {...}, oldRow: {...}, rowMeta: {...}}, // ... // ], // [val2] : [ // {row: {...}, oldRow: {...}, rowMeta: {...}}, // {row: {...}, oldRow: {...}, rowMeta: {...}}, // ... // ], // } const formattedData = useState>('KanbanFormattedData', () => ({})) const groupingField = useState('KanbanGroupingField', () => '') const formatData = (list: Record[]) => list.map((row) => ({ row: { ...row }, oldRow: { ...row }, rowMeta: {}, })) async function loadKanbanData(params: Parameters['dbViewRow']['list']>[4] = {}) { // each stack only loads 25 records -> scroll to load more (to be integrated with infinite scroll) // TODO: integrate with infinite scroll // TODO: handle share view case if ((!project?.value?.id || !meta?.value?.id || !viewMeta?.value?.id) && !isPublic.value) return // reset formattedData to avoid storing previous data after changing grouping field formattedData.value = {} const { grp_column_id, stack_meta } = kanbanMetaData.value const stackMetaObj = JSON.parse(stack_meta as string) || {} if (stack_meta && grp_column_id && stackMetaObj[grp_column_id]) { // the target stack meta exists, use it directly groupingFieldColOptions.value = stackMetaObj[grp_column_id] } else { // retrieve the grouping field column const groupingFieldColumn = meta?.value?.columns?.filter((f) => f.title === groupingField.value)[0] as Record // build stack meta groupingFieldColOptions.value = [ ...(groupingFieldColumn?.colOptions?.options ?? []), // enrich uncategorized stack { id: 'uncategorized', title: 'Uncategorized', order: 0, color: enumColor.light[2] }, ].sort((a: Record, b: Record) => a.order - b.order) // if grouping column id is present, add the grouping field column options to stackMetaObj if (grp_column_id) { stackMetaObj[grp_column_id] = groupingFieldColOptions.value await updateKanbanMeta({ stack_meta: stackMetaObj, }) } } await Promise.all( groupingFieldColOptions.value.map(async (option) => { let where = `(${groupingField.value},eq,${option.title})` if (option.title === 'Uncategorized') { where = `(${groupingField.value},is,null)` } const response = await api.dbViewRow.list('noco', project.value.id!, meta!.value.id!, viewMeta!.value.id, { ...params, ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), where, }) formattedData.value[option.title] = formatData(response.list) }), ) } async function loadKanbanMeta() { if (!viewMeta?.value?.id) return kanbanMetaData.value = await $api.dbView.kanbanRead(viewMeta.value.id) // set groupingField const groupingFieldCol = meta?.value?.columns?.filter((f) => f.id === kanbanMetaData?.value?.grp_column_id)[0] groupingField.value = groupingFieldCol?.title as string } async function updateKanbanMeta(updateObj: Partial) { if (!viewMeta?.value?.id) return await $api.dbView.kanbanUpdate(viewMeta.value.id, { ...kanbanMetaData.value, ...updateObj, }) await loadKanbanMeta() } async function insertRow(row: Record, rowIndex = formattedData.value.uncatgorized?.length) { try { const insertObj = meta?.value?.columns?.reduce((o: any, col) => { if (!col.ai && row?.[col.title as string] !== null) { o[col.title as string] = row?.[col.title as string] } return o }, {}) const insertedData = await $api.dbViewRow.create( NOCO, project?.value.id as string, meta?.value.id as string, viewMeta?.value?.id as string, insertObj, ) formattedData.value.uncatgorized?.splice(rowIndex ?? 0, 1, { row: insertedData, rowMeta: {}, oldRow: { ...insertedData }, }) return insertedData } catch (error: any) { message.error(await extractSdkResponseErrorMsg(error)) } } async function updateRowProperty(toUpdate: Row, property: string) { try { const id = extractPkFromRow(toUpdate.row, meta?.value.columns as ColumnType[]) const updatedRowData = await $api.dbViewRow.update( NOCO, project?.value.id as string, meta?.value.id as string, viewMeta?.value?.id as string, id, { [property]: toUpdate.row[property], }, // todo: // { // query: { ignoreWebhook: !saved } // } ) // audit $api.utils .auditRowUpdate(id, { fk_model_id: meta?.value.id as string, column_name: property, row_id: id, value: getHTMLEncodedText(toUpdate.row[property]), prev_value: getHTMLEncodedText(toUpdate.oldRow[property]), }) .then(() => {}) /** update row data(to sync formula and other related columns) */ Object.assign(toUpdate.row, updatedRowData) Object.assign(toUpdate.oldRow, updatedRowData) } catch (e: any) { message.error(`${t('msg.error.rowUpdateFailed')} ${await extractSdkResponseErrorMsg(e)}`) } } async function updateOrSaveRow(row: Row) { if (row.rowMeta.new) { await insertRow(row.row, formattedData.value[row.row.title].indexOf(row)) } else { await updateRowProperty(row, groupingField.value) } } function addEmptyRow(addAfter = formattedData.value.Uncategorized?.length) { formattedData.value.Uncategorized.splice(addAfter, 0, { row: {}, oldRow: {}, rowMeta: { new: true }, }) return formattedData.value.Uncategorized[addAfter] } watch(groupingField, async (v) => await loadKanbanData()) return { loadKanbanData, loadKanbanMeta, updateKanbanMeta, kanbanMetaData, formattedData, groupingField, groupingFieldColOptions, updateOrSaveRow, addEmptyRow, } }