Browse Source

fix(nc-gui): use ref<Map<string | null, Row[]>> for formattedData & countByStack

pull/3563/head
Wing-Kam Wong 2 years ago
parent
commit
0d69bb3e30
  1. 42
      packages/nc-gui/components/smartsheet/Kanban.vue
  2. 109
      packages/nc-gui/composables/useKanbanViewStore.ts

42
packages/nc-gui/components/smartsheet/Kanban.vue

@ -185,11 +185,11 @@ async function onMoveStack(event: any) {
async function onMove(event: any, stackKey: string) {
if (event.added) {
const ele = event.added.element
ele.row[groupingField.value] = stackKey === 'uncategorized' ? null : stackKey
countByStack.value[stackKey] += 1
ele.row[groupingField.value] = stackKey
countByStack.value.set(stackKey, countByStack.value.get(stackKey)! + 1)
await updateOrSaveRow(ele)
} else if (event.removed) {
countByStack.value[stackKey] -= 1
countByStack.value.set(stackKey, countByStack.value.get(stackKey)! - 1)
}
}
@ -197,7 +197,7 @@ const kanbanListScrollHandler = async (e: any) => {
if (e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight) {
const stackTitle = e.target.getAttribute('data-stack-title')
const pageSize = appInfo.defaultLimit || 25
const page = Math.ceil(formattedData.value[stackTitle].length / pageSize)
const page = Math.ceil(formattedData.value.get(stackTitle)!.length / pageSize)
await loadMoreKanbanData(stackTitle, { offset: page * pageSize })
}
}
@ -234,7 +234,7 @@ openNewRecordFormHook?.on(async (stackTitle) => {
[groupingField.value]: stackTitle,
}
// increase total count by 1
countByStack.value.uncategorized += 1
countByStack.value.set(null, countByStack.value.get(null)! + 1)
// open the expanded form
expandForm(newRow)
})
@ -296,7 +296,7 @@ watch(view, async (nextView) => {
:key="stack.id"
class="mx-4 !bg-[#f0f2f5] flex flex-col w-[280px] h-full rounded-[12px]"
:class="{
'not-draggable': stack.id === 'uncategorized' || isLocked || isPublic || !hasEditPermission,
'not-draggable': stack.title === null || isLocked || isPublic || !hasEditPermission,
'!cursor-default': isLocked || !hasEditPermission,
}"
:head-style="{ paddingBottom: '0px' }"
@ -306,7 +306,7 @@ watch(view, async (nextView) => {
<div :style="`background-color: ${stack.color}`" class="nc-kanban-stack-head-color h-[10px]"></div>
<!-- Skeleton -->
<a-skeleton v-if="!formattedData[stack.title] || !countByStack" class="p-4" />
<a-skeleton v-if="!formattedData.get(stack.title) || !countByStack" class="p-4" />
<!-- Stack -->
<a-layout v-else class="!bg-[#f0f2f5]">
@ -315,9 +315,9 @@ watch(view, async (nextView) => {
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-kanban-stack-context-menu">
<div
class="flex items-center w-full"
:class="{ 'capitalize': stack.title === 'uncategorized', 'cursor-pointer': !isLocked }"
:class="{ 'capitalize': stack.title === null, 'cursor-pointer': !isLocked }"
>
<LazyGeneralTruncateText>{{ stack.title }}</LazyGeneralTruncateText>
<LazyGeneralTruncateText>{{ stack.title ?? 'uncategorized' }}</LazyGeneralTruncateText>
<span v-if="!isLocked" class="w-full flex w-[15px]">
<mdi-menu-down class="text-grey text-lg ml-auto" />
</span>
@ -327,7 +327,7 @@ watch(view, async (nextView) => {
<a-menu-item
v-if="hasEditPermission && !isPublic"
v-e="['c:kanban:add-new-record']"
@click="openNewRecordFormHook.trigger(stack.title === 'uncategorized' ? null : stack.title)"
@click="openNewRecordFormHook.trigger(stack.title === null ? null : stack.title)"
>
<div class="py-2 flex gap-2 items-center">
<mdi-plus class="text-gray-500" />
@ -341,7 +341,7 @@ watch(view, async (nextView) => {
</div>
</a-menu-item>
<a-menu-item
v-if="stack.title !== 'uncategorized' && !isPublic && hasEditPermission"
v-if="stack.title !== null && !isPublic && hasEditPermission"
v-e="['c:kanban:delete-stack']"
@click="handleDeleteStackClick(stack.title, stackIdx)"
>
@ -360,7 +360,7 @@ watch(view, async (nextView) => {
<div :ref="kanbanListRef" class="nc-kanban-list h-full overflow-y-auto" :data-stack-title="stack.title">
<!-- Draggable Record Card -->
<Draggable
v-model="formattedData[stack.title]"
:list="formattedData.get(stack.title)"
item-key="row.Id"
draggable=".nc-kanban-item"
group="kanban-card"
@ -430,7 +430,7 @@ watch(view, async (nextView) => {
</a-layout-content>
<a-layout-footer>
<div v-if="formattedData[stack.title] && countByStack[stack.title] >= 0" class="mt-5 text-center">
<div v-if="formattedData.get(stack.title) && countByStack.get(stack.title) >= 0" class="mt-5 text-center">
<!-- Stack Title -->
<mdi-plus
v-if="!isPublic"
@ -439,8 +439,8 @@ watch(view, async (nextView) => {
/>
<!-- Record Count -->
<div class="nc-kanban-data-count">
{{ formattedData[stack.title].length }} / {{ countByStack[stack.title] }}
{{ countByStack[stack.title] !== 1 ? $t('objects.records') : $t('objects.record') }}
{{ formattedData.get(stack.title).length }} / {{ countByStack.get(stack.title) }}
{{ countByStack.get(stack.title) !== 1 ? $t('objects.records') : $t('objects.record') }}
</div>
</div>
</a-layout-footer>
@ -454,13 +454,17 @@ watch(view, async (nextView) => {
:style="`background-color: ${stack.color} !important`"
class="nc-kanban-collapsed-stack mx-4 flex items-center w-[300px] h-[50px] rounded-[12px] cursor-pointer h-full !pr-[10px]"
:class="{
'not-draggable': stack.id === 'uncategorized' || isLocked || isPublic || !hasEditPermission,
'not-draggable': stack.title === null || isLocked || isPublic || !hasEditPermission,
}"
:body-style="{ padding: '0px', height: '100%', width: '100%', background: '#f0f2f5 !important' }"
>
<div class="items-center justify-between" @click="handleCollapseStack(stackIdx)">
<!-- Skeleton -->
<a-skeleton v-if="!formattedData[stack.title] || !countByStack" class="!w-[150px] pl-5" :paragraph="false" />
<a-skeleton
v-if="!formattedData.get(stack.title) || !countByStack"
class="!w-[150px] pl-5"
:paragraph="false"
/>
<div v-else class="nc-kanban-data-count mt-[12px] mx-[10px]">
<!-- Stack title -->
@ -469,8 +473,8 @@ watch(view, async (nextView) => {
<mdi-menu-down class="text-grey text-lg" />
</div>
<!-- Record Count -->
{{ formattedData[stack.title].length }} / {{ countByStack[stack.title] }}
{{ countByStack[stack.title] !== 1 ? $t('objects.records') : $t('objects.record') }}
{{ formattedData.get(stack.title).length }} / {{ countByStack.get(stack.title) }}
{{ countByStack.get(stack.title) !== 1 ? $t('objects.records') : $t('objects.record') }}
</div>
</div>
</a-card>

109
packages/nc-gui/composables/useKanbanViewStore.ts

@ -56,7 +56,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// ...
// ],
// }
const formattedData = ref<Record<string, Row[]>>({})
const formattedData = ref<Map<string | null, Row[]>>(new Map<string | null, Row[]>())
// countByStack structure
// {
@ -64,7 +64,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// [val1]: 10,
// [val2]: 20
// }
const countByStack = ref<Record<string, number>>({})
const countByStack = ref<Map<string | null, number>>(new Map<string | null, number>())
// grouping field title
const groupingField = ref<string>('')
@ -88,8 +88,8 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
if ((!project?.value?.id || !meta.value?.id || !viewMeta?.value?.id) && !isPublic.value) return
// reset formattedData & countByStack to avoid storing previous data after changing grouping field
formattedData.value = {}
countByStack.value = {}
formattedData.value = new Map<string | null, Row[]>()
countByStack.value = new Map<string | null, number>()
let res
@ -108,16 +108,16 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
}
for (const data of res) {
const key = data.key === null ? 'uncategorized' : data.key
formattedData.value[key] = formatData(data.value.list)
countByStack.value[key] = data.value.pageInfo.totalRows || 0
const key = data.key
formattedData.value.set(key, formatData(data.value.list))
countByStack.value.set(key, data.value.pageInfo.totalRows || 0)
}
}
async function loadMoreKanbanData(stackTitle: string, params: Parameters<Api<any>['dbViewRow']['list']>[4] = {}) {
if ((!project?.value?.id || !meta.value?.id || !viewMeta.value?.id) && !isPublic.value) return
let where = `(${groupingField.value},eq,${stackTitle})`
if (stackTitle === 'uncategorized') {
if (stackTitle === null) {
where = `(${groupingField.value},is,null)`
}
const response = !isPublic.value
@ -129,7 +129,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
})
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value })
formattedData.value[stackTitle] = [...formattedData.value[stackTitle], ...formatData(response.list)]
formattedData.value.set(stackTitle, [...formattedData.value.get(stackTitle)!, ...formatData(response.list)])
}
async function loadKanbanMeta() {
@ -164,8 +164,10 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
}
// rename the key in formattedData & countByStack
if (option.title !== rest.title) {
delete Object.assign(formattedData.value, { [option.title!]: formattedData.value[rest.title!] })[rest.title!]
delete Object.assign(countByStack.value, { [option.title!]: countByStack.value[rest.title!] })[rest.title!]
// option.title is new key
// rest.title is old key
formattedData.value.set(option.title!, formattedData.value.get(rest.title!)!)
countByStack.value.set(option.title!, countByStack.value.get(rest.title!)!)
// update grouping field value under the edited stack
await bulkUpdateGroupingFieldValue(option.title!)
}
@ -177,8 +179,8 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
...option,
collapsed: false,
})
formattedData.value[option.title!] = []
countByStack.value[option.title!] = 0
formattedData.value.set(option.title!, [])
countByStack.value.set(option.title!, 0)
isChanged = true
hasNewOptionsAdded = true
}
@ -196,21 +198,14 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// 2. delete option from grid view, then switch to kanban view
// for the second case, formattedData.value and countByStack.value would be empty at this moment
// however, the data will be correct after rendering
if (
Object.keys(formattedData.value).length &&
Object.keys(countByStack.value).length &&
col.title! in formattedData.value
) {
if (formattedData.value.size && countByStack.value.size && col.title! in formattedData.value) {
// for the first case, no reload is executed.
// hence, we set groupingField to null for all records under the target stack
await bulkUpdateGroupingFieldValue(col.title!, true)
// merge the to-be-deleted stack to uncategorized stack
formattedData.value.uncategorized = [
...(formattedData.value.uncategorized || []),
...formattedData.value[col.title!],
]
formattedData.value.set(null, [...(formattedData.value.get(null) || []), ...formattedData.value.get(col.title!)!])
// update the record count
countByStack.value.uncategorized += countByStack.value[col.title!]
countByStack.value.set(null, (countByStack.value.get(null) || 0) + (countByStack.value.get(col.title!) || 0))
}
isChanged = true
}
@ -228,7 +223,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
groupingFieldColOptions.value = [
...((groupingFieldColumn.value?.colOptions as SelectOptionsType & { collapsed: boolean })?.options ?? []),
// enrich uncategorized stack
{ id: 'uncategorized', title: 'uncategorized', order: 0, color: enumColor.light[2] },
{ id: 'uncategorized', title: null, order: 0, color: enumColor.light[2] },
]
// sort by initial order
.sort((a, b) => a.order! - b.order!)
@ -259,7 +254,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
})
}
async function insertRow(row: Record<string, any>, rowIndex = formattedData.value.uncatgorized?.length) {
async function insertRow(row: Record<string, any>, rowIndex = formattedData.value.get(null)!.length) {
try {
const insertObj = (meta?.value?.columns as ColumnType[]).reduce((o: Record<string, any>, col) => {
if (!col.ai && row?.[col.title as string] !== null) {
@ -276,7 +271,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
insertObj,
)
formattedData.value.uncatgorized?.splice(rowIndex ?? 0, 1, {
formattedData.value.get(null)?.splice(rowIndex ?? 0, 1, {
row: insertedData,
rowMeta: {},
oldRow: { ...insertedData },
@ -327,7 +322,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
async function updateOrSaveRow(row: Row) {
if (row.rowMeta.new) {
await insertRow(row.row, formattedData.value[row.row.title].indexOf(row))
await insertRow(row.row, formattedData.value.get(row.row.title!)!.indexOf(row))
} else {
await updateRowProperty(row, groupingField.value)
}
@ -351,7 +346,9 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
)
if (stackTitle in formattedData.value) {
// update to groupingField value to target value
formattedData.value[stackTitle] = formattedData.value[stackTitle].map((o) => ({
formattedData.value.set(
stackTitle,
formattedData.value.get(stackTitle)!.map((o) => ({
...o,
row: {
...o.row,
@ -361,7 +358,8 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
...o.oldRow,
[groupingField.value]: o.row[groupingField.value],
},
}))
})),
)
}
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
@ -374,11 +372,11 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// set groupingField to null for all records under the target stack
await bulkUpdateGroupingFieldValue(stackTitle, true)
// merge the to-be-deleted stack to uncategorized stack
formattedData.value.uncategorized = [...formattedData.value.uncategorized, ...formattedData.value[stackTitle]]
countByStack.value.uncategorized += countByStack.value[stackTitle]
formattedData.value.set(null, [...formattedData.value.get(null)!, ...formattedData.value.get(stackTitle)!])
countByStack.value.set(null, (countByStack.value.get(null) || 0) + (countByStack.value.get(stackTitle) || 0))
// clear state for the to-be-deleted stack
delete formattedData.value[stackTitle]
delete countByStack.value[stackTitle]
formattedData.value.delete(stackTitle)
countByStack.value.delete(stackTitle)
// delete the stack, i.e. grouping field value
const newOptions = (groupingFieldColumn.value.colOptions as SelectOptionsType).options.filter(
(o) => o.title !== stackTitle,
@ -401,26 +399,26 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
}
}
function addEmptyRow(addAfter = formattedData.value.uncategorized?.length) {
formattedData.value.uncategorized.splice(addAfter, 0, {
function addEmptyRow(addAfter = formattedData.value.get(null)!.length) {
formattedData.value.get(null)!.splice(addAfter, 0, {
row: {},
oldRow: {},
rowMeta: { new: true },
})
return formattedData.value.uncategorized[addAfter]
return formattedData.value.get(null)![addAfter]
}
function addOrEditStackRow(row: Row, isNewRow: boolean) {
const stackTitle = row.row[groupingField.value] ?? 'uncategorized'
const oldStackTitle = row.oldRow[groupingField.value] ?? 'uncategorized'
const stackTitle = row.row[groupingField.value]
const oldStackTitle = row.oldRow[groupingField.value]
if (isNewRow) {
// add a new record
if (stackTitle) {
// push the row to target stack
formattedData.value[stackTitle].push(row)
formattedData.value.get(stackTitle)!.push(row)
// increase the current count in the target stack by 1
countByStack.value[stackTitle] += 1
countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! + 1)
// clear the one under uncategorized since we don't reload the view
removeRowFromUncategorizedStack()
} else {
@ -430,20 +428,20 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
} else {
// update existing record
const targetPrimaryKey = extractPkFromRow(row.row, meta!.value!.columns as ColumnType[])
const idxToUpdate = formattedData.value[stackTitle].findIndex(
(ele) => extractPkFromRow(ele.row, meta!.value!.columns as ColumnType[]) === targetPrimaryKey,
)
const idxToUpdate = formattedData.value
.get(stackTitle)!
.findIndex((ele) => extractPkFromRow(ele.row, meta!.value!.columns as ColumnType[]) === targetPrimaryKey)
if (idxToUpdate !== -1) {
// update the row in formattedData
formattedData.value[stackTitle][idxToUpdate] = row
formattedData.value.get(stackTitle)![idxToUpdate] = row
}
if (stackTitle !== oldStackTitle) {
// remove old row from countByStack & formattedData
countByStack.value[oldStackTitle] -= 1
formattedData.value[oldStackTitle].pop()
countByStack.value.set(oldStackTitle, countByStack.value.get(oldStackTitle)! - 1)
formattedData.value.get(oldStackTitle)!.pop()
// add new row to countByStack & formattedData
countByStack.value[stackTitle] += 1
formattedData.value[stackTitle].push(row)
countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! + 1)
formattedData.value.get(stackTitle)!.push(row)
}
}
}
@ -452,20 +450,23 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// primary key of Row to be deleted
const targetPrimaryKey = extractPkFromRow(row.row, meta!.value!.columns as ColumnType[])
// stack title of Row to be deleted
const stackTitle = row.row[groupingField.value] ?? 'uncategorized'
const stackTitle = row.row[groupingField.value]
// remove target row from formattedData
formattedData.value[stackTitle] = formattedData.value[stackTitle].filter(
(ele) => extractPkFromRow(ele.row, meta!.value!.columns as ColumnType[]) !== targetPrimaryKey,
formattedData.value.set(
stackTitle,
formattedData.value
.get(stackTitle)!
.filter((ele) => extractPkFromRow(ele.row, meta!.value!.columns as ColumnType[]) !== targetPrimaryKey),
)
// decrease countByStack of target stack by 1
countByStack.value[stackTitle] -= 1
countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! - 1)
}
function removeRowFromUncategorizedStack() {
// remove the last record
formattedData.value.uncategorized.pop()
formattedData.value.get(null)!.pop()
// decrease total count by 1
countByStack.value.uncategorized -= 1
countByStack.value.set(null, countByStack.value.get(null)! - 1)
}
async function deleteRow(row: Row) {

Loading…
Cancel
Save