Browse Source

wip(gui-v2): filter, sort integration with grid view

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/2716/head
Pranav C 2 years ago
parent
commit
667f42d35a
  1. 2
      packages/nc-gui-v2/components/cell/DateTimePicker.vue
  2. 3
      packages/nc-gui-v2/components/index.ts
  3. 176
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  4. 12
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue
  5. 78
      packages/nc-gui-v2/components/smartsheet-toolbar/FieldListAutoCompleteDropdown.vue
  6. 6
      packages/nc-gui-v2/components/smartsheet-toolbar/FieldsMenu.vue
  7. 94
      packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue
  8. 23
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  9. 2
      packages/nc-gui-v2/components/smartsheet/Toolbar.vue
  10. 10
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  11. 7
      packages/nc-gui-v2/composables/useViewData.ts
  12. 89
      packages/nc-gui-v2/composables/useViewFilters.ts
  13. 26
      packages/nc-gui-v2/composables/useViewSorts.ts
  14. 1
      packages/nc-gui-v2/nuxt.config.ts

2
packages/nc-gui-v2/components/cell/DateTimePicker.vue

@ -4,7 +4,7 @@ import { computed, ref } from '#imports'
import useProject from '~/composables/useProject' import useProject from '~/composables/useProject'
interface Props { interface Props {
modelValue: string modelValue?: string
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()

3
packages/nc-gui-v2/components/index.ts

@ -1,5 +1,6 @@
import type { ColumnType, FormType, GalleryType, GridType, KanbanType, TableType } from 'nocodb-sdk' import type { ColumnType, FormType, GalleryType, GridType, KanbanType, TableType } from 'nocodb-sdk'
import type { InjectionKey, Ref } from 'vue' import type { InjectionKey, Ref } from 'vue'
import type { EventHook } from '@vueuse/core'
import type useViewData from '~/composables/useViewData' import type useViewData from '~/composables/useViewData'
export const ColumnInj: InjectionKey<ColumnType & { meta: any }> = Symbol('column-injection') export const ColumnInj: InjectionKey<ColumnType & { meta: any }> = Symbol('column-injection')
@ -15,4 +16,4 @@ export const ValueInj: InjectionKey<any> = Symbol('value-injection')
export const ActiveViewInj: InjectionKey<Ref<(GridType | GalleryType | FormType | KanbanType) & { id?: string }>> = export const ActiveViewInj: InjectionKey<Ref<(GridType | GalleryType | FormType | KanbanType) & { id?: string }>> =
Symbol('active-view-injection') Symbol('active-view-injection')
export const ReadonlyInj: InjectionKey<any> = Symbol('readonly-injection') export const ReadonlyInj: InjectionKey<any> = Symbol('readonly-injection')
export const ReloadViewDataInj: InjectionKey<any> = Symbol('reload-view-data-injection') export const ReloadViewDataHookInj: InjectionKey<EventHook<void>> = Symbol('reload-view-data-injection')

176
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -1,16 +1,74 @@
<script setup lang="ts"> <script setup lang="ts">
import { inject } from 'vue' import { UITypes } from 'nocodb-sdk'
import { ActiveViewInj, MetaInj } from '~/components' import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import { useNuxtApp } from '#app'
import { inject } from '#imports'
import { comparisonOp } from '~/utils/filterUtils'
import { ActiveViewInj, MetaInj, ReloadViewDataHookInj } from '~/components'
import useViewFilters from '~/composables/useViewFilters' import useViewFilters from '~/composables/useViewFilters'
import { comparisonOp } from '~/utils/comparisonOp' import MdiDeleteIcon from '~icons/mdi/close-box'
const { nested = false, parentId } = defineProps<{ nested?: boolean; parentId?: string }>()
const meta = inject(MetaInj) const meta = inject(MetaInj)
const activeView = inject(ActiveViewInj) const activeView = inject(ActiveViewInj)
const reloadDataHook = inject(ReloadViewDataHookInj)
const { $e } = useNuxtApp()
const { filters, deleteFilter, saveOrUpdate, loadFilters, addFilter } = useViewFilters(activeView, parentId, () => {
reloadDataHook?.trigger()
})
const filterUpdateCondition = (filter, i) => {
saveOrUpdate(filter, i)
$e('a:filter:update', {
logical: filter.logical_op,
comparison: filter.comparison_op,
})
}
const { filters, deleteFilter, saveOrUpdate } = useViewFilters(activeView) // todo
// const filterComparisonOp = (f) =>
// comparisonOp.filter((op) => {
// // if (
// // f &&
// // f.fk_column_id &&
// // this.columnsById[f.fk_column_id] &&
// // this.columnsById[f.fk_column_id].uidt === UITypes.LinkToAnotherRecord &&
// // this.columnsById[f.fk_column_id].uidt === UITypes.Lookup
// // ) {
// // return !['notempty', 'empty', 'notnull', 'null'].includes(op.value)
// // }
// return true
// })
const filterUpdateCondition = (filter, i) => {} const columns = computed(() => meta?.value?.columns)
const filterComparisonOp = (filter) => {} const types = computed(() => {
if (!meta?.value?.columns?.length) {
return {}
}
return meta?.value?.columns?.reduce((obj: any, col: any) => {
switch (col.uidt) {
case UITypes.Number:
case UITypes.Decimal:
obj[col.title] = obj[col.column_name] = 'number'
break
case UITypes.Checkbox:
obj[col.title] = obj[col.column_name] = 'boolean'
break
}
return obj
}, {})
})
watch(
() => activeView?.value?.id,
(n, o) => {
if (n !== o) loadFilters()
},
{ immediate: true },
)
/* import { UITypes, getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes' /* import { UITypes, getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes'
import FieldListAutoCompleteDropdown from '~/components/project/spreadsheet/components/FieldListAutoCompleteDropdown' import FieldListAutoCompleteDropdown from '~/components/project/spreadsheet/components/FieldListAutoCompleteDropdown'
@ -319,9 +377,9 @@ export default {
</script> </script>
<template> <template>
<div class="backgroundColor pa-2 menu-filter-dropdown" :style="{ width: nested ? '100%' : '530px' }"> <div class="backgroundColor pa-2 menu-filter-dropdown bg-background" :style="{ width: nested ? '100%' : '630px' }">
<div class="grid" @click.stop> <div v-if="filters && filters.length" class="grid" @click.stop>
<template v-for="(filter, i) in filters"> <template v-for="(filter, i) in filters" :key="filter.id || i">
<template v-if="filter.status !== 'delete'"> <template v-if="filter.status !== 'delete'">
<div v-if="filter.is_group" :key="i" style="grid-column: span 4; padding: 6px" class="elevation-4"> <div v-if="filter.is_group" :key="i" style="grid-column: span 4; padding: 6px" class="elevation-4">
<div class="d-flex" style="gap: 6px; padding: 0 6px"> <div class="d-flex" style="gap: 6px; padding: 0 6px">
@ -339,17 +397,16 @@ export default {
v-model="filter.logical_op" v-model="filter.logical_op"
class="flex-shrink-1 flex-grow-0 elevation-0 caption" class="flex-shrink-1 flex-grow-0 elevation-0 caption"
:items="['and', 'or']" :items="['and', 'or']"
solo density="compact"
flat variant="solo"
dense
hide-details hide-details
placeholder="Group op" placeholder="Group op"
@click.stop @click.stop
@change="saveOrUpdate(filter, i)" @change="saveOrUpdate(filter, i)"
> >
<template #item="{ item }"> <!-- <template #item="{ item }"> -->
<span class="caption font-weight-regular">{{ item }}</span> <!-- <span class="caption font-weight-regular">{{ item }}</span> -->
</template> <!-- </template> -->
</v-select> </v-select>
</div> </div>
<!-- <column-filter <!-- <column-filter
@ -368,41 +425,47 @@ export default {
/> --> /> -->
</div> </div>
<template v-else> <template v-else>
<v-icon <!-- <v-icon
v-if="!filter.readOnly"
:key="`${i}_3`"
small
class="nc-filter-item-remove-btn"
@click.stop="deleteFilter(filter, i)"
>
mdi-close-box
</v-icon> -->
<MdiDeleteIcon
v-if="!filter.readOnly" v-if="!filter.readOnly"
:key="`${i}_3`" class="nc-filter-item-remove-btn text-grey"
small
class="nc-filter-item-remove-btn"
@click.stop="deleteFilter(filter, i)" @click.stop="deleteFilter(filter, i)"
> ></MdiDeleteIcon>
mdi-close-box <span v-else />
</v-icon>
<span v-else :key="`${i}_1`" /> <span v-if="!i" :key="`${i}_2`" class="text-xs d-flex align-center">{{ $t('labels.where') }}</span>
<span v-if="!i" :key="`${i}_2`" class="caption d-flex align-center">{{ $t('labels.where') }}</span>
<v-select <v-select
v-else v-else
:key="`${i}_4`" :key="`${i}_4`"
v-model="filter.logical_op" v-model="filter.logical_op"
class="flex-shrink-1 flex-grow-0 elevation-0 caption" class="w-full elevation-0 caption"
:items="['and', 'or']" :items="['and', 'or']"
solo density="compact"
flat variant="solo"
dense
hide-details hide-details
:disabled="filter.readOnly" :disabled="filter.readOnly"
@click.stop @click.stop
@change="filterUpdateCondition(filter, i)" @change="filterUpdateCondition(filter, i)"
> />
<template #item="{ item }"> <!-- <template #item="{ item }">
<span class="caption font-weight-regular">{{ item }}</span> <span class="caption font-weight-regular">{{ item }}</span>
</template> </template>
</v-select> </v-select> -->
<FieldListAutoCompleteDropdown <FieldListAutoCompleteDropdown
:key="`${i}_6`" :key="`${i}_6`"
v-model="filter.fk_column_id" v-model="filter.fk_column_id"
class="caption nc-filter-field-select" class="caption text-sm nc-filter-field-select"
:columns="columns" :columns="columns"
:disabled="filter.readOnly" :disabled="filter.readOnly"
@click.stop @click.stop
@ -410,25 +473,26 @@ export default {
/> />
<v-select <v-select
:key="`k${i}`"
v-model="filter.comparison_op" v-model="filter.comparison_op"
class="flex-shrink-1 flex-grow-0 caption nc-filter-operation-select" class="caption nc-filter-operation-select text-sm"
:items="filterComparisonOp(filter)" :items="comparisonOp.map((it) => it.value)"
:placeholder="$t('labels.operation')" :placeholder="$t('labels.operation')"
solo density="compact"
flat variant="solo"
style="max-width: 120px"
dense
:disabled="filter.readOnly" :disabled="filter.readOnly"
hide-details hide-details
item-value="value"
@click.stop
@change="filterUpdateCondition(filter, i)" @change="filterUpdateCondition(filter, i)"
> /><!--
<template #item="{ item }"> todo: filter based on column type
<span class="caption font-weight-regular">{{ item.text }}</span>
</template> item-value="value"
</v-select> item-text="text"
:items="filterComparisonOp(filter)" -->
<!-- <template #item="{ item }"> -->
<!-- <span class="caption font-weight-regular">{{ item.text }}</span> -->
<!-- </template> -->
<!-- </v-select> -->
<span v-if="['null', 'notnull', 'empty', 'notempty'].includes(filter.comparison_op)" :key="`span${i}`" /> <span v-if="['null', 'notnull', 'empty', 'notempty'].includes(filter.comparison_op)" :key="`span${i}`" />
<v-checkbox <v-checkbox
v-else-if="types[filter.field] === 'boolean'" v-else-if="types[filter.field] === 'boolean'"
@ -442,11 +506,10 @@ export default {
v-else v-else
:key="`${i}_7`" :key="`${i}_7`"
v-model="filter.value" v-model="filter.value"
solo density="compact"
flat variant="solo"
hide-details hide-details
dense class="caption text-sm nc-filter-value-select"
class="caption nc-filter-value-select"
:disabled="filter.readOnly" :disabled="filter.readOnly"
@click.stop @click.stop
@input="saveOrUpdate(filter, i)" @input="saveOrUpdate(filter, i)"
@ -455,6 +518,13 @@ export default {
</template> </template>
</template> </template>
</div> </div>
<v-btn small class="elevation-0 text-sm text-capitalize my-3" @click.stop="addFilter">
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
<!-- Add Filter -->
{{ $t('activity.addFilter') }}
</v-btn>
<slot />
</div> </div>
<!-- <div class="backgroundColor pa-2 menu-filter-dropdown" :style="{ width: nested ? '100%' : '530px' }"> <!-- <div class="backgroundColor pa-2 menu-filter-dropdown" :style="{ width: nested ? '100%' : '530px' }">
@ -604,10 +674,10 @@ export default {
</template> </template>
<style scoped> <style scoped>
/*.grid { .grid {
display: grid; display: grid;
grid-template-columns: 22px 80px auto auto auto; grid-template-columns: 30px 130px auto auto auto;
column-gap: 6px; column-gap: 6px;
row-gap: 6px; row-gap: 6px;
}*/ }
</style> </style>

12
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilterMenu.vue

@ -3,6 +3,8 @@
import { useState } from '#app' import { useState } from '#app'
import { IsLockedInj } from '~/components' import { IsLockedInj } from '~/components'
import Smartsheet from '~/components/tabs/Smartsheet.vue' import Smartsheet from '~/components/tabs/Smartsheet.vue'
import MdiFilterIcon from '~icons/mdi/filter-outline'
import MdiMenuDownIcon from '~icons/mdi/menu-down'
const autoApplyFilter = useState('autoApplyFilter', () => false) const autoApplyFilter = useState('autoApplyFilter', () => false)
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)
@ -68,7 +70,7 @@ export default {
<template> <template>
<v-menu offset-y eager transition="slide-y-transition"> <v-menu offset-y eager transition="slide-y-transition">
<template #activator="props"> <template #activator="{ props }">
<v-badge :value="filters.length" color="primary" dot overlap> <v-badge :value="filters.length" color="primary" dot overlap>
<v-btn <v-btn
v-t="['c:filter']" v-t="['c:filter']"
@ -82,14 +84,14 @@ export default {
}" }"
v-bind="props" v-bind="props"
> >
<v-icon small class="mr-1" color="grey darken-3"> mdi-filter-outline</v-icon> <MdiFilterIcon class="mr-1 text-grey" />
<!-- Filter --> <!-- Filter -->
{{ $t('activity.filter') }} <span class="text-capitalize">{{ $t('activity.filter') }}</span>
<v-icon small color="#777"> mdi-menu-down</v-icon> <MdiMenuDownIcon class="text-grey" />
</v-btn> </v-btn>
</v-badge> </v-badge>
</template> </template>
<SmartsheetToolbarColumnFilter ref="filter"> <SmartsheetToolbarColumnFilter>
<!-- <!--
v-model="filters" v-model="filters"
:shared="shared" :shared="shared"

78
packages/nc-gui-v2/components/smartsheet-toolbar/FieldListAutoCompleteDropdown.vue

@ -0,0 +1,78 @@
<script setup lang="ts">
import { MetaInj } from '~/components'
interface Props {
modelValue?: string
}
const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const meta = inject(MetaInj)
const localValue = computed({
get: () => modelValue,
set: (val) => emit('update:modelValue', val),
})
/* export default {
name: 'FieldListAutoCompleteDropdown',
props: {
columns: Array,
value: String,
},
computed: {
localValue: {
set(v) {
this.$emit('input', v)
},
get() {
return this.value
},
},
},
mounted() {
const autocompleteInput = this.$refs.field.$refs.input
autocompleteInput.addEventListener('focus', this.onFocus, true)
},
methods: {
onFocus(e) {
this.$refs.field.isMenuActive = true // open item list
},
},
} */
</script>
<template>
<v-autocomplete
ref="field"
v-model="localValue"
class="caption"
:items="meta.columns"
item-value="id"
item-text="title"
:label="$t('objects.field')"
variant="solo"
hide-details
@click.stop
>
<!-- &lt;!&ndash; @change="$emit('change')" &ndash;&gt; -->
<!-- <template #selection="{ item }"> -->
<!-- <v-icon small class="mr-1"> -->
<!-- {{ item.icon }} -->
<!-- </v-icon> -->
<!-- {{ item.title }} -->
<!-- </template> -->
<!-- <template #item="{ item }"> -->
<!-- <span :class="`caption font-weight-regular nc-fld-${item.title}`"> -->
<!-- <v-icon color="grey" small class="mr-1"> -->
<!-- {{ item.icon }} -->
<!-- </v-icon> -->
<!-- {{ item.title }} -->
<!-- </span> -->
<!-- </template> -->
</v-autocomplete>
</template>
<style scoped></style>

6
packages/nc-gui-v2/components/smartsheet-toolbar/FieldsMenu.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject } from 'vue' import { computed, inject } from 'vue'
import { ActiveViewInj, MetaInj } from '~/components' import { ActiveViewInj, IsLockedInj, MetaInj, ReloadViewDataHookInj } from '~/components'
import useViewColumns from '~/composables/useViewColumns' import useViewColumns from '~/composables/useViewColumns'
import MdiMenuDownIcon from '~icons/mdi/menu-down' import MdiMenuDownIcon from '~icons/mdi/menu-down'
import MdiEyeIcon from '~icons/mdi/eye-off-outline' import MdiEyeIcon from '~icons/mdi/eye-off-outline'
@ -14,7 +14,8 @@ const { showSystemFields, fieldsOrder, coverImageField, modelValue } = definePro
const meta = inject(MetaInj) const meta = inject(MetaInj)
const activeView = inject(ActiveViewInj) const activeView = inject(ActiveViewInj)
const isLocked = false const reloadDataHook = inject(ReloadViewDataHookInj)
const isLocked = inject(IsLockedInj)
const isAnyFieldHidden = computed(() => { const isAnyFieldHidden = computed(() => {
return false return false
@ -307,7 +308,6 @@ export default {
:class="{ :class="{
'primary lighten-5 grey--text text--darken-3': isAnyFieldHidden, 'primary lighten-5 grey--text text--darken-3': isAnyFieldHidden,
}" }"
v-on="on"
> >
<!-- <v-icon small class="mr-1" color="#777"> mdi-eye-off-outline </v-icon> --> <!-- <v-icon small class="mr-1" color="#777"> mdi-eye-off-outline </v-icon> -->
<MdiEyeIcon class="mr-1 text-grey"></MdiEyeIcon> <MdiEyeIcon class="mr-1 text-grey"></MdiEyeIcon>

94
packages/nc-gui-v2/components/smartsheet-toolbar/SortListMenu.vue

@ -1,4 +1,26 @@
<script> <script setup lang="ts">
import { inject } from '@vue/runtime-core'
import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import { ActiveViewInj, IsLockedInj, ReloadViewDataHookInj } from '~/components'
import useViewSorts from '~/composables/useViewSorts'
import MdiMenuDownIcon from '~icons/mdi/menu-down'
import MdiSortIcon from '~icons/mdi/sort'
import MdiDeleteIcon from '~icons/mdi/close-box'
const view = inject(ActiveViewInj)
const isLocked = inject(IsLockedInj)
const reloadDataHook = inject(ReloadViewDataHookInj)
const { sorts, saveOrUpdate, loadSorts, addSort, deleteSort } = useViewSorts(view, () => reloadDataHook?.trigger())
watch(
() => view?.value?.id,
() => {
loadSorts()
},
{ immediate: true },
)
/* import { RelationTypes, UITypes } from 'nocodb-sdk' /* import { RelationTypes, UITypes } from 'nocodb-sdk'
import { getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes' import { getUIDTIcon } from '~/components/project/spreadsheet/helpers/uiTypes'
import FieldListAutoCompleteDropdown from '~/components/project/spreadsheet/components/FieldListAutoCompleteDropdown' import FieldListAutoCompleteDropdown from '~/components/project/spreadsheet/components/FieldListAutoCompleteDropdown'
@ -96,6 +118,70 @@ export default {
</script> </script>
<template> <template>
<v-menu offset-y transition="slide-y-transition">
<template #activator="{ props }">
<v-badge :value="sorts && sorts.length" color="primary" dot overlap>
<v-btn
v-t="['c:sort']"
class="nc-sort-menu-btn px-2 nc-remove-border"
:disabled="isLocked"
small
text
outlined
:class="{
'primary lighten-5 grey&#45;&#45;text text&#45;&#45;darken-3': sorts && sorts.length,
}"
v-bind="props"
>
<MdiSortIcon class="mr-1 text-grey" />
<!-- Sort -->
<span class="text-capitalize">{{ $t('activity.sort') }}</span>
<MdiMenuDownIcon class="text-grey" />
</v-btn>
</v-badge>
</template>
<div class="backgroundColor pa-2 menu-filter-dropdown bg-background min-w-[400px]">
<div class="sort-grid" @click.stop>
<template v-for="(sort, i) in sorts || []" :key="i">
<!-- <v-icon :key="`${i}icon`" class="nc-sort-item-remove-btn" small @click.stop="deleteSort(sort)"> mdi-close-box </v-icon> -->
<MdiDeleteIcon class="nc-sort-item-remove-btn text-grey" small @click.stop="deleteSort(sort, i)"></MdiDeleteIcon>
<FieldListAutoCompleteDropdown
v-model="sort.fk_column_id"
class="caption nc-sort-field-select"
:columns="columns"
@click.stop
@change="sync(sort, i)"
/>
<v-select
v-model="sort.direction"
class="flex-shrink-1 flex-grow-0 caption nc-sort-dir-select"
:items="[
// { text: 'A -> Z', value: 'asc' },
'asc',
// { text: 'Z -> A', value: 'desc' },
'desc',
]"
:label="$t('labels.operation')"
density="compact"
variant="solo"
hide-details
@click.stop
@change="saveOrUpdate(sort, i)"
>
<template #item="{ item }">
<span class="caption font-weight-regular">{{ item.text }}</span>
</template>
</v-select>
</template>
</div>
<v-btn small class="elevation-0 grey&#45;&#45;text my-3" @click.stop="addSort">
<!-- todo: <v-icon small color="grey"> mdi-plus </v-icon> -->
<!-- Add Sort Option -->
{{ $t('activity.addSort') }}
</v-btn>
</div>
</v-menu>
<!-- <v-menu offset-y transition="slide-y-transition"> <!-- <v-menu offset-y transition="slide-y-transition">
<template #activator="{ on }"> <template #activator="{ on }">
<v-badge :value="sortList && sortList.length" color="primary" dot overlap> <v-badge :value="sortList && sortList.length" color="primary" dot overlap>
@ -163,10 +249,10 @@ export default {
</template> </template>
<style scoped> <style scoped>
/*.sort-grid { .sort-grid {
display: grid; display: grid;
grid-template-columns: 22px auto 100px; grid-template-columns: 22px auto 150px;
column-gap: 6px; column-gap: 6px;
row-gap: 6px; row-gap: 6px;
}*/ }
</style> </style>

23
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -1,7 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from '@vue/reactivity'
import { isVirtualCol } from 'nocodb-sdk' import { isVirtualCol } from 'nocodb-sdk'
import { inject, onKeyStroke, onMounted, provide } from '#imports' import { inject, onKeyStroke, onMounted, provide } from '#imports'
import { ActiveViewInj, ChangePageInj, IsFormInj, IsGridInj, MetaInj, PaginationDataInj, ReloadViewDataInj } from '~/components' import {
ActiveViewInj,
ChangePageInj,
IsFormInj,
IsGridInj,
MetaInj,
PaginationDataInj,
ReloadViewDataHookInj,
} from '~/components'
import Smartsheet from '~/components/tabs/Smartsheet.vue' import Smartsheet from '~/components/tabs/Smartsheet.vue'
import useViewData from '~/composables/useViewData' import useViewData from '~/composables/useViewData'
@ -20,7 +29,11 @@ provide(IsFormInj, false)
provide(IsGridInj, true) provide(IsGridInj, true)
provide(PaginationDataInj, paginationData) provide(PaginationDataInj, paginationData)
provide(ChangePageInj, changePage) provide(ChangePageInj, changePage)
provide(ReloadViewDataInj, loadData)
const reloadViewDataHook = inject(ReloadViewDataHookInj)
reloadViewDataHook?.on(() => {
loadData()
})
const selectCell = (row: number, col: number) => { const selectCell = (row: number, col: number) => {
selected.row = row selected.row = row
@ -42,6 +55,10 @@ watch(
}, },
{ immediate: true }, { immediate: true },
) )
defineExpose({
loadData,
})
</script> </script>
<template> <template>
@ -178,7 +195,7 @@ watch(
overflow: auto; overflow: auto;
td, td,
tr { th {
min-height: 31px !important; min-height: 31px !important;
position: relative; position: relative;
padding: 0 5px !important; padding: 0 5px !important;

2
packages/nc-gui-v2/components/smartsheet/Toolbar.vue

@ -4,7 +4,7 @@
<v-toolbar dense class="nc-table-toolbar elevation-0 xc-toolbar xc-border-bottom" style="z-index: 7"> <v-toolbar dense class="nc-table-toolbar elevation-0 xc-toolbar xc-border-bottom" style="z-index: 7">
<SmartsheetToolbarFieldsMenu :show-system-fields="false" /> <SmartsheetToolbarFieldsMenu :show-system-fields="false" />
<SmartsheetToolbarColumnFilterMenu /> <SmartsheetToolbarColumnFilterMenu />
<!-- <SmartsheetToolbarSortListMenu /> --> <SmartsheetToolbarSortListMenu />
</v-toolbar> </v-toolbar>
</template> </template>

10
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useEventBus } from '@vueuse/core'
import type { FormType, GalleryType, GridType, KanbanType } from 'nocodb-sdk' import type { FormType, GalleryType, GridType, KanbanType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { computed, onMounted, provide, watch } from '#imports' import { computed, onMounted, provide, watch } from '#imports'
import { ActiveViewInj, MetaInj, TabMetaInj } from '~/components' import { ActiveViewInj, IsLockedInj, MetaInj, ReloadViewDataHookInj, TabMetaInj } from '~/components'
import useMetas from '~/composables/useMetas' import useMetas from '~/composables/useMetas'
const { tabMeta } = defineProps({ const { tabMeta } = defineProps({
@ -12,15 +13,20 @@ const { tabMeta } = defineProps({
const { getMeta, metas } = useMetas() const { getMeta, metas } = useMetas()
const activeView = ref<GridType | FormType | KanbanType | GalleryType>() const activeView = ref<GridType | FormType | KanbanType | GalleryType>()
const el = ref<any>()
const meta = computed(() => metas.value?.[tabMeta?.id]) const meta = computed(() => metas.value?.[tabMeta?.id])
onMounted(async () => { onMounted(async () => {
await getMeta(tabMeta?.id) await getMeta(tabMeta?.id)
}) })
const reloadEventHook = createEventHook<void>()
provide(MetaInj, meta) provide(MetaInj, meta)
provide(TabMetaInj, tabMeta) provide(TabMetaInj, tabMeta)
provide(ActiveViewInj, activeView) provide(ActiveViewInj, activeView)
provide(IsLockedInj, false)
provide(ReloadViewDataHookInj, reloadEventHook)
watch( watch(
() => tabMeta && tabMeta?.id, () => tabMeta && tabMeta?.id,
@ -36,7 +42,7 @@ watch(
<template v-if="meta"> <template v-if="meta">
<div class="d-flex"> <div class="d-flex">
<div v-if="activeView" class="flex-grow-1 min-w-0"> <div v-if="activeView" class="flex-grow-1 min-w-0">
<SmartsheetGrid v-if="activeView.type === ViewTypes.GRID" /> <SmartsheetGrid v-if="activeView.type === ViewTypes.GRID" :ref="el" />
<SmartsheetGallery v-else-if="activeView.type === ViewTypes.GALLERY" /> <SmartsheetGallery v-else-if="activeView.type === ViewTypes.GALLERY" />
<SmartsheetForm v-else-if="activeView.type === ViewTypes.FORM" /> <SmartsheetForm v-else-if="activeView.type === ViewTypes.FORM" />
</div> </div>

7
packages/nc-gui-v2/composables/useViewData.ts

@ -1,4 +1,4 @@
import type { Api, PaginatedType, TableType } from 'nocodb-sdk' import type { Api, FormType, GalleryType, GridType, PaginatedType, TableType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
import useProject from '~/composables/useProject' import useProject from '~/composables/useProject'
@ -13,7 +13,10 @@ const formatData = (list: Record<string, any>[]) =>
export default ( export default (
meta: Ref<TableType> | ComputedRef<TableType> | undefined, meta: Ref<TableType> | ComputedRef<TableType> | undefined,
viewMeta: Ref<TableType> | ComputedRef<TableType> | undefined, viewMeta:
| Ref<(GridType | GalleryType | FormType) & { id: string }>
| ComputedRef<(GridType | GalleryType | FormType) & { id: string }>
| undefined,
) => { ) => {
const data = ref<Record<string, any>[]>() const data = ref<Record<string, any>[]>()
const formattedData = ref<{ row: Record<string, any>; oldRow: Record<string, any>; rowMeta?: any }[]>() const formattedData = ref<{ row: Record<string, any>; oldRow: Record<string, any>; rowMeta?: any }[]>()

89
packages/nc-gui-v2/composables/useViewFilters.ts

@ -2,7 +2,11 @@ import type { FilterType, GalleryType, GridType, KanbanType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useNuxtApp } from '#imports' import { useNuxtApp } from '#imports'
export default function (view: Ref<(GridType | KanbanType | GalleryType) & { id?: string }> | undefined, parentId?: string) { export default function (
view: Ref<(GridType | KanbanType | GalleryType) & { id?: string }> | undefined,
parentId?: string,
reloadData?: () => void,
) {
const filters = ref<(FilterType & { status?: 'update' | 'delete' })[]>([]) const filters = ref<(FilterType & { status?: 'update' | 'delete' })[]>([])
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
@ -31,47 +35,70 @@ export default function (view: Ref<(GridType | KanbanType | GalleryType) & { id?
})) as any })) as any
} }
} }
reloadData?.()
} }
const deleteFilter = async (filter: FilterType, i: number) => { const deleteFilter = async (filter: FilterType, i: number) => {
// if (this.shared || !this._isUIAllowed('filterSync')) { // if (this.shared || !this._isUIAllowed('filterSync')) {
// this.filters.splice(i, 1) // this.filters.splice(i, 1)
// this.$emit('updated') // this.$emit('updated')
// } else if (filter.id) { // } else
// if (!this.autoApply) {
// this.$set(filter, 'status', 'delete') if (filter.id) {
// } else { // if (!this.autoApply) {
// await this.$api.dbTableFilter.delete(filter.id) // this.$set(filter, 'status', 'delete')
// await this.loadFilter() // } else {
// this.$emit('updated') await $api.dbTableFilter.delete(filter.id) /**/
// } // await this.loadFilter()
// } else { // this.$emit('updated')
// this.filters.splice(i, 1) // }
// this.$emit('updated') } else {
// } // this.$emit('updated')
}
const _filters = unref(filters.value)
_filters.splice(i, 1)
filters.value = _filters
// this.$e('a:filter:delete') // this.$e('a:filter:delete')
// // }, // // },
reloadData?.()
} }
const saveOrUpdate = async (filter: FilterType, i: number) => { const saveOrUpdate = async (filter: FilterType, i: number) => {
// if (this.shared || !this._isUIAllowed('filterSync')) { if (!view?.value) return
// this.filters.splice(i, 1)
// this.$emit('updated') // if (this.shared || !this._isUIAllowed('filterSync')) {
// } else if (filter.id) { // this.$emit('input', this.filters.filter(f => f.fk_column_id && f.comparison_op))
// if (!this.autoApply) { // this.$emit('updated')
// this.$set(filter, 'status', 'delete') // } else if (!this.autoApply) {
// } else { // filter.status = 'update'
// await this.$api.dbTableFilter.delete(filter.id) // } else
// await this.loadFilter() if (filter.id) {
// this.$emit('updated') await $api.dbTableFilter.update(filter.id, {
// } ...filter,
// } else { fk_parent_id: parentId,
// this.filters.splice(i, 1) })
// this.$emit('updated')
// } // this.$emit('updated')
// this.$e('a:filter:delete') } else {
// // }, // todo: return type correction
filters.value[i] = (await $api.dbTableFilter.create(view?.value?.id as string, {
...filter,
fk_parent_id: parentId,
})) as any
// this.$emit('updated')
}
reloadData?.()
}
const addFilter = () => {
filters.value.push({
comparison_op: 'eq',
value: '',
status: 'update',
logical_op: 'and',
})
} }
return { filters, loadFilters, sync, deleteFilter, saveOrUpdate } return { filters, loadFilters, sync, deleteFilter, saveOrUpdate, addFilter }
} }

26
packages/nc-gui-v2/composables/useViewSorts.ts

@ -2,23 +2,41 @@ import type { GalleryType, GridType, KanbanType, SortType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useNuxtApp } from '#imports' import { useNuxtApp } from '#imports'
export default function (view: Ref<(GridType | KanbanType | GalleryType) & { id?: string }>) { export default function (
view: Ref<(GridType | KanbanType | GalleryType) & { id?: string }> | undefined,
reloadData?: () => void,
) {
const sorts = ref<SortType[]>([]) const sorts = ref<SortType[]>([])
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const loadSorts = async () => { const loadSorts = async () => {
sorts.value = (await $api.dbTableSort.list(view?.value?.id as string)) as any[] if (!view?.value) return
// todo: api correction
sorts.value = ((await $api.dbTableSort.list(view?.value?.id as string)) as any)?.sorts?.list as any[]
} }
const sync = async (sort: SortType, i: number) => { const saveOrUpdate = async (sort: SortType, i: number) => {
if (!sorts?.value) return if (!sorts?.value) return
if (sort.id) { if (sort.id) {
await $api.dbTableSort.update(sort.id, sort) await $api.dbTableSort.update(sort.id, sort)
} else { } else {
sorts.value[i] = (await $api.dbTableSort.create(view?.value?.id as string, sort)) as any sorts.value[i] = (await $api.dbTableSort.create(view?.value?.id as string, sort)) as any
} }
reloadData?.()
}
const addSort = () => {
sorts.value.push({
direction: 'asc',
})
} }
return { sorts, loadSorts, sync } const deleteSort = async (sort: SortType, i: number) => {
// if (!this.shared && sort.id && this._isUIAllowed('sortSync')) {
if (sort.id) {
await $api.dbTableSort.delete(sort.id)
}
sorts.value.splice(i, 1)
}
return { sorts, loadSorts, addSort, deleteSort, saveOrUpdate }
} }

1
packages/nc-gui-v2/nuxt.config.ts

@ -62,7 +62,6 @@ export default defineNuxtConfig({
}, },
}, },
}, },
experimental: { experimental: {
reactivityTransform: true, reactivityTransform: true,
viteNode: false, viteNode: false,

Loading…
Cancel
Save