Browse Source

fix(nc-gui): create, update calendar range

pull/7611/head
DarkPhoenix2704 7 months ago
parent
commit
27e2c935dc
  1. 49
      packages/nc-gui/components/dlg/ViewCreate.vue
  2. 65
      packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue
  3. 13
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  4. 3
      packages/nc-gui/lang/en.json

49
packages/nc-gui/components/dlg/ViewCreate.vue

@ -2,7 +2,7 @@
import type { ComponentPublicInstance } from '@vue/runtime-core' import type { ComponentPublicInstance } from '@vue/runtime-core'
import type { Form as AntForm, SelectProps } from 'ant-design-vue' import type { Form as AntForm, SelectProps } from 'ant-design-vue'
import { capitalize } from '@vue/runtime-core' import { capitalize } from '@vue/runtime-core'
import type { FormType, GalleryType, GridType, KanbanType, MapType, TableType } from 'nocodb-sdk' import {CalendarType, FormType, GalleryType, GridType, KanbanType, MapType, TableType} from 'nocodb-sdk'
import { UITypes, ViewTypes, isSystemColumn } from 'nocodb-sdk' import { UITypes, ViewTypes, isSystemColumn } from 'nocodb-sdk'
import { computed, message, nextTick, onBeforeMount, reactive, ref, useApi, useI18n, useVModel, watch } from '#imports' import { computed, message, nextTick, onBeforeMount, reactive, ref, useApi, useI18n, useVModel, watch } from '#imports'
@ -14,11 +14,15 @@ interface Props {
groupingFieldColumnId?: string groupingFieldColumnId?: string
geoDataFieldColumnId?: string geoDataFieldColumnId?: string
tableId: string tableId: string
calendar_range: Array<{
fk_from_column_id: string,
fk_to_column_id: string | null // for ee only
}>
} }
interface Emits { interface Emits {
(event: 'update:modelValue', value: boolean): void (event: 'update:modelValue', value: boolean): void
(event: 'created', value: GridType | KanbanType | GalleryType | FormType | MapType): void (event: 'created', value: GridType | KanbanType | GalleryType | FormType | MapType | CalendarType): void
} }
interface Form { interface Form {
@ -83,10 +87,10 @@ const form = reactive<Form>({
copy_from_id: null, copy_from_id: null,
fk_grp_col_id: null, fk_grp_col_id: null,
fk_geo_data_col_id: null, fk_geo_data_col_id: null,
calendar_range: ViewTypes.CALENDAR === props.type ? { calendar_range: ViewTypes.CALENDAR === props.type ? props.calendar_range : [{
fk_from_column_id: '', fk_from_column_id: '',
fk_to_column_id: null fk_to_column_id: null
} : null, }],
}) })
const viewSelectFieldOptions = ref<SelectProps['options']>([]) const viewSelectFieldOptions = ref<SelectProps['options']>([])
@ -276,7 +280,7 @@ onMounted(async () => {
// take the first option // take the first option
form.calendar_range = [{ form.calendar_range = [{
fk_from_column_id: viewSelectFieldOptions.value[0].value as string, fk_from_column_id: viewSelectFieldOptions.value[0].value as string,
fk_to_column_id: null fk_to_column_id: null // for ee only
}] }]
} else { } else {
// if there is no grouping field column, disable the create button // if there is no grouping field column, disable the create button
@ -298,7 +302,8 @@ onMounted(async () => {
:size="[ViewTypes.KANBAN, ViewTypes.MAP, ViewTypes.CALENDAR].includes(form.type) ? 'medium' : 'small'" :size="[ViewTypes.KANBAN, ViewTypes.MAP, ViewTypes.CALENDAR].includes(form.type) ? 'medium' : 'small'"
> >
<template #header> <template #header>
<div class="flex flex-row items-center gap-x-1.5"> <div class="flex w-full flex-row justify-between items-center">
<div class="flex gap-x-1.5 items-center">
<GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" /> <GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" />
<template v-if="form.type === ViewTypes.GRID"> <template v-if="form.type === ViewTypes.GRID">
<template v-if="form.copy_from_id"> <template v-if="form.copy_from_id">
@ -348,11 +353,15 @@ onMounted(async () => {
{{ $t('labels.duplicateView') }} {{ $t('labels.duplicateView') }}
</template> </template>
</template> </template>
</div>
<a v-if="!form.copy_from_id" href="https://docs.nocodb.com/views/view-types/calendar/" target="_blank" class="text-sm !no-underline !hover:text-brand-500 text-brand-500 ">
Go to Docs
</a>
</div> </div>
</template> </template>
<div class="mt-2"> <div class="mt-2">
<a-form v-if="isNecessaryColumnsPresent" ref="formValidator" layout="vertical" :model="form"> <a-form v-if="isNecessaryColumnsPresent" ref="formValidator" layout="vertical" :model="form">
<a-form-item name="title" :rules="viewNameRules" label="View Name"> <a-form-item name="title" :rules="viewNameRules">
<a-input <a-input
ref="inputEl" ref="inputEl"
v-model:value="form.title" v-model:value="form.title"
@ -395,24 +404,38 @@ onMounted(async () => {
/> />
</a-form-item> </a-form-item>
<div v-if="form.type === ViewTypes.CALENDAR" v-for="range in form.calendar_range" class="flex w-full gap-3"> <div v-if="form.type === ViewTypes.CALENDAR" v-for="range in form.calendar_range" class="flex w-full gap-3">
<div class="flex flex-col w-1/2"> <div class="flex flex-col gap-2 w-1/2">
<span> <span>
{{ $t('labels.selectDateField') }} {{ $t('labels.organizeRecordsBy') }}
</span> </span>
<NcSelect <NcSelect
:value="range.fk_from_col_id" v-model:value="range.fk_from_column_id"
class="w-full" class="w-full"
:disabled="isMetaLoading" :disabled="isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"
:options="viewSelectFieldOptions" :options="viewSelectFieldOptions"
/> />
</div> </div>
<div v-if="isEeUI" class="flex flex-col w-1/2"> <div v-if="range.fk_to_column_id === null && isEeUI" class="flex flex-col justify-end w-1/2">
<div class="cursor-pointer flex items-center font-medium gap-1 mb-1"
@click="range.fk_to_column_id = ''">
<component :is="iconMap.plus" class="h-4 w-4"/>
{{ $t('activity.setEndDate') }}
</div>
</div>
<div v-else-if="isEeUI" class="flex gap-2 flex-col w-1/2">
<div class="flex flex-row justify-between">
<span> <span>
{{ $t('labels.selectEndDateField') }} {{ $t('labels.endDateField') }}
</span> </span>
<component :is="iconMap.delete" class="h-4 w-4 cursor-pointer text-red-500"
@click="() => {
range.fk_to_column_id = null
}"/>
</div>
<NcSelect <NcSelect
:value="range.fk_from_col_id" v-model:value="range.fk_to_column_id"
class="w-full" class="w-full"
:disabled="isMetaLoading" :disabled="isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"

65
packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue

@ -19,7 +19,7 @@ const {eventBus} = useSmartsheetStoreOrThrow()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
const { $e } = useNuxtApp() const { $e, $api } = useNuxtApp()
const activeView = inject(ActiveViewInj, ref()) const activeView = inject(ActiveViewInj, ref())
@ -37,44 +37,41 @@ watch(
async (newVal, oldVal) => { async (newVal, oldVal) => {
if (newVal !== oldVal && meta.value) { if (newVal !== oldVal && meta.value) {
await loadViewColumns() await loadViewColumns()
// For now we are adding a calendar range by default
// TODO: Remove this when we have a way to add calendar range
await addCalendarRange()
} }
}, },
{immediate: true}, {immediate: true},
) )
// TODO: Fetch calendar range from viewColumnsComposable
const calendarRange = computed<{ fk_from_column_id: string; fk_to_column_id: string | null }[]>(() => { const calendarRange = computed<{ fk_from_column_id: string; fk_to_column_id: string | null }[]>(() => {
const tempCalendarRange: { fk_from_column_id: string; fk_to_column_id: string | null }[] = [] const tempCalendarRange: { fk_from_column_id: string; fk_to_column_id: string | null }[] = [];
// Object.values(fields.value).forEach((col) => {
// if (col.calendar_range) { if (!activeView.value || !activeView.value.view) return tempCalendarRange;
// tempCalendarRange.push({ activeView.value.view.calendar_range?.forEach((range) => {
// fk_from_col_id: col.fk_from_column_id, tempCalendarRange.push({
// fk_to_column_id: col.fk_to_column_id, fk_from_column_id: range.fk_from_column_id,
// }) fk_to_column_id: range.fk_to_column_id,
// } })
// }) })
return tempCalendarRange return tempCalendarRange
}) })
// We keep the calendar range here and update it when the user selects a new range // We keep the calendar range here and update it when the user selects a new range
const _calendar_ranges = ref<{ fk_from_column_id: string; fk_to_column_id: string | null }[]>([]) const _calendar_ranges = ref<{ fk_from_column_id: string; fk_to_column_id: string | null }[]>(calendarRange.value)
const saveCalendarRanges = async () => { const saveCalendarRanges = async () => {
if(activeView.value) { if(activeView.value) {
try { try {
for(const range of _calendar_ranges.value) { const calRanges = _calendar_ranges.value.map((range) => {
if(!range.fk_from_column_id) continue; if (range.fk_from_column_id) {
// TODO: Update calendar range in viewColumnsComposable return {
fk_from_column_id: range.fk_from_column_id,
$e('c:calendar:change-calendar-range', { fk_to_column_id: range.fk_to_column_id,
viewId: activeView.value.id, }
fk_from_column_id: range.fk_from_column_id,
fk_to_column_id: range.fk_to_column_id,
})
} }
})
await $api.dbView.calendarUpdate(activeView.value?.id as string, {
calendar_range: calRanges as { fk_from_column_id: string; fk_to_column_id: string | null }[],
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
message.error('There was an error while updating view!') message.error('There was an error while updating view!')
@ -131,19 +128,19 @@ const dateFieldOptions = computed<SelectProps['options']>(() => {
<a-divider class="!my-2"/> <a-divider class="!my-2"/>
</div> </div>
<div v-for="cal in _calendar_ranges" class="flex w-full gap-3"> <div v-for="cal in _calendar_ranges" class="flex w-full gap-3">
<div class="flex flex-col w-1/2"> <div class="flex flex-col gap-2 w-1/2">
<span> <span>
{{ $t('labels.selectDateField') }} {{ $t('labels.organizeRecordsBy') }}
</span> </span>
<NcSelect <NcSelect
:value="cal.fk_from_column_id" v-model:value="cal.fk_from_column_id"
:options="dateFieldOptions" :options="dateFieldOptions"
class="w-full" class="w-full"
@click.stop @click.stop
@change="saveCalendarRanges" @change="saveCalendarRanges"
/> />
</div> </div>
<div v-if="cal.fk_to_column_id === null" class="flex flex-col justify-end w-1/2"> <div v-if="cal.fk_to_column_id === null && isEeUI" class="flex flex-col justify-end w-1/2">
<div class="cursor-pointer flex items-center font-medium gap-1 mb-1" <div class="cursor-pointer flex items-center font-medium gap-1 mb-1"
@click="cal.fk_to_column_id = ''"> @click="cal.fk_to_column_id = ''">
<component :is="iconMap.plus" class="h-4 w-4"/> <component :is="iconMap.plus" class="h-4 w-4"/>
@ -151,17 +148,19 @@ const dateFieldOptions = computed<SelectProps['options']>(() => {
</div> </div>
</div> </div>
<div v-if="isEeUI && cal.fk_to_column_id !== null" class="flex flex-col w-1/2"> <div v-else-if="isEeUI" class="flex flex-col gap-2 w-1/2">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<span> <span>
{{ $t('labels.selectEndDateField') }} {{ $t('labels.endDateField') }}
</span> </span>
<component :is="iconMap.delete" class="h-4 w-4 cursor-pointer text-red-500" <component :is="iconMap.delete" class="h-4 w-4 cursor-pointer text-red-500"
@click="cal.fk_to_column_id = null"/> @click="() => {
cal.fk_to_column_id = null
saveCalendarRanges()
}"/>
</div> </div>
<NcSelect <NcSelect
:value="cal.fk_to_column_id" v-model:value="cal.fk_to_column_id"
:options="dateFieldOptions" :options="dateFieldOptions"
:disabled="!cal.fk_from_column_id" :disabled="!cal.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')" :placeholder="$t('placeholder.notSelected')"

13
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, GalleryType, KanbanType } from 'nocodb-sdk' import {CalendarType, ColumnType, GalleryType, KanbanType} from 'nocodb-sdk'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
@ -151,7 +151,7 @@ const coverOptions = computed<SelectProps['options']>(() => {
const updateCoverImage = async (val?: string | null) => { const updateCoverImage = async (val?: string | null) => {
if ( if (
(activeView.value?.type === ViewTypes.GALLERY || activeView.value?.type === ViewTypes.KANBAN) && (activeView.value?.type === ViewTypes.GALLERY || activeView.value?.type === ViewTypes.KANBAN || activeView.value?.type === ViewTypes.CALENDAR) &&
activeView.value?.id && activeView.value?.id &&
activeView.value?.view activeView.value?.view
) { ) {
@ -165,6 +165,11 @@ const updateCoverImage = async (val?: string | null) => {
fk_cover_image_col_id: val, fk_cover_image_col_id: val,
}) })
;(activeView.value.view as KanbanType).fk_cover_image_col_id = val ;(activeView.value.view as KanbanType).fk_cover_image_col_id = val
} else if (activeView.value?.type === ViewTypes.CALENDAR) {
await $api.dbView.calendarUpdate(activeView.value?.id, {
fk_cover_image_col_id: val,
})
;(activeView.value.view as CalendarType).fk_cover_image_col_id = val
} }
reloadViewMetaHook?.trigger() reloadViewMetaHook?.trigger()
} }
@ -173,7 +178,7 @@ const updateCoverImage = async (val?: string | null) => {
const coverImageColumnId = computed({ const coverImageColumnId = computed({
get: () => { get: () => {
const fk_cover_image_col_id = const fk_cover_image_col_id =
(activeView.value?.type === ViewTypes.GALLERY || activeView.value?.type === ViewTypes.KANBAN) && activeView.value?.view (activeView.value?.type === ViewTypes.GALLERY || activeView.value?.type === ViewTypes.KANBAN || activeView.value?.type === ViewTypes.CALENDAR) && activeView.value?.view
? (activeView.value?.view as GalleryType).fk_cover_image_col_id ? (activeView.value?.view as GalleryType).fk_cover_image_col_id
: undefined : undefined
// check if `fk_cover_image_col_id` is in `coverOptions` // check if `fk_cover_image_col_id` is in `coverOptions`
@ -305,7 +310,7 @@ useMenuCloseOnEsc(open)
<!-- Fields --> <!-- Fields -->
<span v-if="!isMobileMode" class="text-capitalize text-sm font-medium"> <span v-if="!isMobileMode" class="text-capitalize text-sm font-medium">
<template v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY || ViewTypes.CALENDAR"> <template v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.CALENDAR">
{{ $t('title.editCards') }} {{ $t('title.editCards') }}
</template> </template>
<template v-else> <template v-else>

3
packages/nc-gui/lang/en.json

@ -435,6 +435,7 @@
"newProvider": "New Provider", "newProvider": "New Provider",
"generalSettings": "General Settings", "generalSettings": "General Settings",
"ssoSettings": "SSO Settings", "ssoSettings": "SSO Settings",
"organizeRecordsBy": "Organize records by",
"heading1": "Heading 1", "heading1": "Heading 1",
"heading2": "Heading 2", "heading2": "Heading 2",
"heading3": "Heading 3", "heading3": "Heading 3",
@ -479,7 +480,7 @@
"singularLabel": "Singular Label", "singularLabel": "Singular Label",
"pluralLabel": "Plural Label", "pluralLabel": "Plural Label",
"selectDateField": "Select a date field", "selectDateField": "Select a date field",
"selectEndDateField": "Select end date field", "endDateField": "End date field",
"optional": "(Optional)", "optional": "(Optional)",
"clickToMake": "Click to make", "clickToMake": "Click to make",
"visibleForRole": "visible for role:", "visibleForRole": "visible for role:",

Loading…
Cancel
Save