Browse Source

chore: run lint

pull/7611/head
DarkPhoenix2704 10 months ago
parent
commit
1ca038ff64
  1. 224
      packages/nc-gui/components/dlg/ViewCreate.vue
  2. 188
      packages/nc-gui/components/nc/DateWeekSelector.vue
  3. 159
      packages/nc-gui/components/nc/MonthYearSelector.vue
  4. 2
      packages/nc-gui/components/smartsheet/Gallery.vue
  5. 2
      packages/nc-gui/components/smartsheet/Kanban.vue
  6. 5
      packages/nc-gui/components/smartsheet/Toolbar.vue
  7. 43
      packages/nc-gui/components/smartsheet/calendar/DayView.vue
  8. 189
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  9. 50
      packages/nc-gui/components/smartsheet/calendar/RecordCard.vue
  10. 112
      packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
  11. 39
      packages/nc-gui/components/smartsheet/calendar/SideRecordCard.vue
  12. 20
      packages/nc-gui/components/smartsheet/calendar/YearView.vue
  13. 60
      packages/nc-gui/components/smartsheet/calendar/index.vue
  14. 2
      packages/nc-gui/components/smartsheet/grid/index.vue
  15. 35
      packages/nc-gui/components/smartsheet/toolbar/CalendarMode.vue
  16. 144
      packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue
  17. 41
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  18. 14
      packages/nc-gui/components/tabs/Smartsheet.vue
  19. 259
      packages/nc-gui/composables/useCalendarViewStore.ts
  20. 22
      packages/nocodb/src/controllers/calendars.controller.spec.ts
  21. 110
      packages/nocodb/src/controllers/calendars.controller.ts
  22. 12
      packages/nocodb/src/meta/meta.service.ts
  23. 4
      packages/nocodb/src/meta/migrations/v2/nc_041_calander_view.ts
  24. 265
      packages/nocodb/src/models/CalendarRange.ts
  25. 208
      packages/nocodb/src/models/CalendarView.ts
  26. 331
      packages/nocodb/src/models/CalendarViewColumn.ts
  27. 80
      packages/nocodb/src/models/View.ts
  28. 137
      packages/nocodb/src/services/calendars.service.ts

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

@ -1,20 +1,10 @@
<script setup lang="ts">
import type {ComponentPublicInstance} from '@vue/runtime-core'
import {capitalize} from '@vue/runtime-core'
import type {Form as AntForm, SelectProps} from 'ant-design-vue'
import {
CalendarType,
FormType,
GalleryType,
GridType,
isSystemColumn,
KanbanType,
MapType,
TableType,
UITypes,
ViewTypes
} from 'nocodb-sdk'
import {computed, message, nextTick, onBeforeMount, reactive, ref, useApi, useI18n, useVModel, watch} from '#imports'
import type { ComponentPublicInstance } from '@vue/runtime-core'
import { capitalize } from '@vue/runtime-core'
import type { Form as AntForm, SelectProps } from 'ant-design-vue'
import type { CalendarType, FormType, GalleryType, GridType, KanbanType, MapType, TableType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isSystemColumn } from 'nocodb-sdk'
import { computed, message, nextTick, onBeforeMount, reactive, ref, useApi, useI18n, useVModel, watch } from '#imports'
interface Props {
modelValue: boolean
@ -24,8 +14,8 @@ interface Props {
groupingFieldColumnId?: string
geoDataFieldColumnId?: string
tableId: string
calendar_range: Array<{
fk_from_column_id: string,
calendarRange: Array<{
fk_from_column_id: string
fk_to_column_id: string | null // for ee only
}>
}
@ -45,8 +35,8 @@ interface Form {
fk_geo_data_col_id: string | null
// for calendar view only
calendar_range: Array<{
fk_from_column_id: string,
calendarRange: Array<{
fk_from_column_id: string
fk_to_column_id: string | null // for ee only
}>
}
@ -55,7 +45,7 @@ const props = withDefaults(defineProps<Props>(), {
selectedViewId: undefined,
groupingFieldColumnId: undefined,
geoDataFieldColumnId: undefined,
calendar_range: undefined,
calendarRange: undefined,
})
const emits = defineEmits<Emits>()
@ -98,10 +88,15 @@ const form = reactive<Form>({
copy_from_id: null,
fk_grp_col_id: null,
fk_geo_data_col_id: null,
calendar_range: ViewTypes.CALENDAR === props.type ? props.calendar_range : [{
fk_from_column_id: '',
fk_to_column_id: null
}],
calendarRange:
ViewTypes.CALENDAR === props.type
? props.calendarRange
: [
{
fk_from_column_id: '',
fk_to_column_id: null,
},
],
})
const viewSelectFieldOptions = ref<SelectProps['options']>([])
@ -289,10 +284,12 @@ onMounted(async () => {
if (viewSelectFieldOptions.value?.length) {
// take the first option
form.calendar_range = [{
fk_from_column_id: viewSelectFieldOptions.value[0].value as string,
fk_to_column_id: null // for ee only
}]
form.calendarRange = [
{
fk_from_column_id: viewSelectFieldOptions.value[0].value as string,
fk_to_column_id: null, // for ee only
},
]
} else {
// if there is no grouping field column, disable the create button
isNecessaryColumnsPresent.value = false
@ -315,58 +312,62 @@ onMounted(async () => {
<template #header>
<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" />
<template v-if="form.type === ViewTypes.GRID">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateGridView') }}
<GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" />
<template v-if="form.type === ViewTypes.GRID">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateGridView') }}
</template>
<template v-else>
{{ $t('labels.createGridView') }}
</template>
</template>
<template v-else>
{{ $t('labels.createGridView') }}
</template>
</template>
<template v-else-if="form.type === ViewTypes.GALLERY">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateGalleryView') }}
</template>
<template v-else>
{{ $t('labels.createGalleryView') }}
</template>
</template>
<template v-else-if="form.type === ViewTypes.FORM">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateFormView') }}
</template>
<template v-else>
{{ $t('labels.createFormView') }}
</template>
</template>
<template v-else-if="form.type === ViewTypes.KANBAN">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateKanbanView') }}
</template>
<template v-else>
{{ $t('labels.createKanbanView') }}
<template v-else-if="form.type === ViewTypes.GALLERY">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateGalleryView') }}
</template>
<template v-else>
{{ $t('labels.createGalleryView') }}
</template>
</template>
</template>
<template v-else-if="form.type === ViewTypes.CALENDAR">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateCalendarView') }}
<template v-else-if="form.type === ViewTypes.FORM">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateFormView') }}
</template>
<template v-else>
{{ $t('labels.createFormView') }}
</template>
</template>
<template v-else>
{{ $t('labels.createCalendarView') }}
<template v-else-if="form.type === ViewTypes.KANBAN">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateKanbanView') }}
</template>
<template v-else>
{{ $t('labels.createKanbanView') }}
</template>
</template>
</template>
<template v-else>
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateMapView') }}
<template v-else-if="form.type === ViewTypes.CALENDAR">
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateCalendarView') }}
</template>
<template v-else>
{{ $t('labels.createCalendarView') }}
</template>
</template>
<template v-else>
{{ $t('labels.duplicateView') }}
<template v-if="form.copy_from_id">
{{ $t('labels.duplicateMapView') }}
</template>
<template v-else>
{{ $t('labels.duplicateView') }}
</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 ">
<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>
@ -415,47 +416,52 @@ onMounted(async () => {
:not-found-content="$t('placeholder.selectGeoFieldNotFound')"
/>
</a-form-item>
<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 gap-2 w-1/2">
<span>
{{ $t('labels.organizeRecordsBy') }}
</span>
<NcSelect
<template v-if="form.type === ViewTypes.CALENDAR">
<div v-for="(range, index) in form.calendarRange" :key="`range-${index}`" class="flex w-full gap-3">
<div class="flex flex-col gap-2 w-1/2">
<span>
{{ $t('labels.organizeRecordsBy') }}
</span>
<NcSelect
v-model:value="range.fk_from_column_id"
class="w-full"
:disabled="isMetaLoading"
:loading="isMetaLoading"
:options="viewSelectFieldOptions"
/>
</div>
<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') }}
class="w-full"
:disabled="isMetaLoading"
:loading="isMetaLoading"
:options="viewSelectFieldOptions"
/>
</div>
</div>
<div v-else-if="isEeUI" class="flex gap-2 flex-col w-1/2">
<div class="flex flex-row justify-between">
<span>
{{ $t('labels.endDateField') }}
</span>
<component :is="iconMap.delete" class="h-4 w-4 cursor-pointer text-red-500"
@click="() => {
range.fk_to_column_id = null
}"/>
<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>
<NcSelect
<div v-else-if="isEeUI" class="flex gap-2 flex-col w-1/2">
<div class="flex flex-row justify-between">
<span>
{{ $t('labels.endDateField') }}
</span>
<component
:is="iconMap.delete"
class="h-4 w-4 cursor-pointer text-red-500"
@click="
() => {
range.fk_to_column_id = null
}
"
/>
</div>
<NcSelect
v-model:value="range.fk_to_column_id"
class="w-full"
:disabled="isMetaLoading"
:loading="isMetaLoading"
:options="viewSelectFieldOptions"
:placeholder="$t('placeholder.notSelected')"
/>
class="w-full"
:disabled="isMetaLoading"
:loading="isMetaLoading"
:options="viewSelectFieldOptions"
:placeholder="$t('placeholder.notSelected')"
/>
</div>
</div>
</div>
</template>
</a-form>
<div v-else-if="!isNecessaryColumnsPresent" class="flex flex-row p-4 border-gray-200 border-1 gap-x-4 rounded-lg w-full">
<GeneralIcon icon="warning" class="!text-5xl text-orange-500" />

188
packages/nc-gui/components/nc/DateWeekSelector.vue

@ -1,20 +1,18 @@
<script lang="ts" setup>
interface Props {
selectedDate?: Date | null;
isDisabled?: boolean;
pageDate?: Date;
activeDates?: Date[];
isMondayFirst?: boolean;
weekPicker?: boolean;
disablePagination?: boolean;
selectedDate?: Date | null
isDisabled?: boolean
pageDate?: Date
activeDates?: Array<Date>
isMondayFirst?: boolean
weekPicker?: boolean
disablePagination?: boolean
selectedWeek?: {
start: Date;
end: Date;
start: Date
end: Date
} | null
}
const emit = defineEmits(['change', 'update:selected-date', 'update:page-date', 'update:selected-week']);
const props = withDefaults(defineProps<Props>(), {
selectedDate: null,
isDisabled: false,
@ -24,165 +22,164 @@ const props = withDefaults(defineProps<Props>(), {
disablePagination: false,
activeDates: [],
selectedWeek: null,
});
})
const emit = defineEmits(['change', 'update:selected-date', 'update:page-date', 'update:selected-week'])
// Page date is the date we use to manage which month/date that is currently being displayed
const pageDate = useVModel(props, 'pageDate', emit);
const selectedDate = useVModel(props, 'selectedDate', emit);
const activeDates = useVModel(props, 'activeDates', emit);
const selectedWeek = useVModel(props, 'selectedWeek', emit);
const pageDate = useVModel(props, 'pageDate', emit)
const selectedDate = useVModel(props, 'selectedDate', emit)
const activeDates = useVModel(props, 'activeDates', emit)
const selectedWeek = useVModel(props, 'selectedWeek', emit)
const days = computed(() => {
if (props.isMondayFirst) {
return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
} else {
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
}
})
// Used to display the current month and year
const currentMonth = computed(() => {
return pageDate.value.toLocaleString('default', {month: 'long'}) + ' ' + pageDate.value.getFullYear();
});
return `${pageDate.value.toLocaleString('default', { month: 'long' })} ${pageDate.value.getFullYear()}`
})
const selectWeek = (date: Date) => {
if (!date) return;
const dayOffset = props.isMondayFirst ? 1 : 0;
const dayOfWeek = (date.getDay() - dayOffset + 7) % 7;
const startDate = new Date(date);
startDate.setDate(date.getDate() - dayOfWeek);
if (!date) return
const dayOffset = props.isMondayFirst ? 1 : 0
const dayOfWeek = (date.getDay() - dayOffset + 7) % 7
const startDate = new Date(date)
startDate.setDate(date.getDate() - dayOfWeek)
selectedWeek.value = {
start: startDate,
end: new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 6)
};
end: new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 6),
}
}
// Generates all dates should be displayed in the calendar
// Includes all blank days at the start and end of the month
const dates = computed(() => {
const startOfMonth = new Date(pageDate.value.getFullYear(), pageDate.value.getMonth(), 1);
const dayOffset = props.isMondayFirst ? 1 : 0;
const dayOfWeek = (startOfMonth.getDay() - dayOffset + 7) % 7;
startOfMonth.setDate(startOfMonth.getDate() - dayOfWeek);
const datesArray = [];
const startOfMonth = new Date(pageDate.value.getFullYear(), pageDate.value.getMonth(), 1)
const dayOffset = props.isMondayFirst ? 1 : 0
const dayOfWeek = (startOfMonth.getDay() - dayOffset + 7) % 7
startOfMonth.setDate(startOfMonth.getDate() - dayOfWeek)
const datesArray = []
while (datesArray.length < 42) {
datesArray.push(new Date(startOfMonth));
startOfMonth.setDate(startOfMonth.getDate() + 1);
datesArray.push(new Date(startOfMonth))
startOfMonth.setDate(startOfMonth.getDate() + 1)
}
return datesArray;
});
return datesArray
})
// Check if the date is in the selected week
const isDateInSelectedWeek = (date: Date) => {
if (!selectedWeek.value) return false;
return date >= selectedWeek.value.start && date <= selectedWeek.value.end;
if (!selectedWeek.value) return false
return date >= selectedWeek.value.start && date <= selectedWeek.value.end
}
// Used to check if two dates are the same
const isSameDate = (date1: Date, date2: Date) => {
if (!date1 || !date2) return false
return (
date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth() && date1.getFullYear() === date2.getFullYear()
)
}
// Used in DatePicker for checking if the date is currently selected
const isSelectedDate = (dObj: Date) => {
if (!selectedDate.value) return false;
const propDate = new Date(selectedDate.value);
return props.selectedDate ? isSameDate(propDate, dObj) : false;
if (!selectedDate.value) return false
const propDate = new Date(selectedDate.value)
return props.selectedDate ? isSameDate(propDate, dObj) : false
}
const isDayInPagedMonth = (date: Date) => {
return date.getMonth() === pageDate.value.getMonth();
return date.getMonth() === pageDate.value.getMonth()
}
// Since we are using the same component for week picker and date picker we need to handle the date selection differently
const handleSelectDate = (date: Date) => {
if (props.weekPicker) {
selectWeek(date);
selectWeek(date)
} else {
if (!isDayInPagedMonth(date)) {
pageDate.value = new Date(date);
emit('update:page-date', date);
pageDate.value = new Date(date)
emit('update:page-date', date)
}
selectedDate.value = date;
emit('update:selected-date', date);
selectedDate.value = date
emit('update:selected-date', date)
}
};
}
// Used to check if a date is in the current month
const isDateInCurrentMonth = (date: Date) => {
return date.getMonth() === pageDate.value.getMonth();
};
// Used to check if two dates are the same
const isSameDate = (date1: Date, date2: Date) => {
if (!date1 || !date2) return false;
return date1.getDate() === date2.getDate() &&
date1.getMonth() === date2.getMonth() &&
date1.getFullYear() === date2.getFullYear();
};
return date.getMonth() === pageDate.value.getMonth()
}
// Used to Check if an event is in the date
const isActiveDate = (date: Date) => {
return activeDates.value.some((d) => isSameDate(d, date));
};
return activeDates.value.some((d) => isSameDate(d, date))
}
// Paginate the calendar
const paginate = (action: 'next' | 'prev') => {
const newDate = new Date(pageDate.value);
const newDate = new Date(pageDate.value)
if (action === 'next') {
newDate.setMonth(newDate.getMonth() + 1);
newDate.setMonth(newDate.getMonth() + 1)
} else {
newDate.setMonth(newDate.getMonth() - 1);
newDate.setMonth(newDate.getMonth() - 1)
}
pageDate.value = newDate;
emit('update:page-date', newDate);
};
pageDate.value = newDate
emit('update:page-date', newDate)
}
</script>
<template>
<div class="p-4 flex flex-col gap-4 ">
<div :class="{
'justify-center': disablePagination,
'justify-between': !disablePagination,
}" class="flex items-center">
<div class="p-4 flex flex-col gap-4">
<div
:class="{
'justify-center': disablePagination,
'justify-between': !disablePagination,
}"
class="flex items-center"
>
<NcButton v-if="!disablePagination" size="small" type="secondary" @click="paginate('prev')">
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4"/>
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4" />
</NcButton>
<span class="font-bold text-gray-700">{{ currentMonth }}</span>
<NcButton v-if="!disablePagination" size="small" type="secondary" @click="paginate('next')">
<component :is="iconMap.doubleRightArrow" class="h-4 w-4"/>
<component :is="iconMap.doubleRightArrow" class="h-4 w-4" />
</NcButton>
</div>
<div class="border-1 border-gray-200 rounded-y-xl max-w-[320px]">
<div class="flex flex-row bg-gray-100 gap-2 rounded-t-xl justify-between p-2">
<span v-for="day in days" class="h-9 flex items-center justify-center w-9 text-gray-500">{{ day }}</span>
<span v-for="day in days" :key="day" class="h-9 flex items-center justify-center w-9 text-gray-500">{{ day }}</span>
</div>
<div class="grid grid-cols-7 gap-2 p-2">
<span v-for="(date) in dates" :key="date" :class="{
'rounded-lg' : !weekPicker,
'bg-brand-50 border-2 !border-brand-500' : isSelectedDate(date) && !weekPicker && isDayInPagedMonth(date),
'hover:(border-1 border-gray-200 bg-gray-100)' : !isSelectedDate(date) && !weekPicker,
'nc-selected-week z-1': isDateInSelectedWeek(date) && weekPicker,
'text-gray-400': !isDateInCurrentMonth(date),
'nc-selected-week-start': isSameDate(date, selectedWeek?.start),
'nc-selected-week-end': isSameDate(date, selectedWeek?.end),
}" class="h-9 w-9 px-1 py-2 relative font-medium flex items-center cursor-pointer justify-center"
@click="handleSelectDate(date)"
<span
v-for="date in dates"
:key="date"
:class="{
'rounded-lg': !weekPicker,
'bg-brand-50 border-2 !border-brand-500': isSelectedDate(date) && !weekPicker && isDayInPagedMonth(date),
'hover:(border-1 border-gray-200 bg-gray-100)': !isSelectedDate(date) && !weekPicker,
'nc-selected-week z-1': isDateInSelectedWeek(date) && weekPicker,
'text-gray-400': !isDateInCurrentMonth(date),
'nc-selected-week-start': isSameDate(date, selectedWeek?.start),
'nc-selected-week-end': isSameDate(date, selectedWeek?.end),
}"
class="h-9 w-9 px-1 py-2 relative font-medium flex items-center cursor-pointer justify-center"
@click="handleSelectDate(date)"
>
<span v-if="isActiveDate(date)"
class="absolute z-2 h-1.5 w-1.5 rounded-full bg-brand-500 top-1 right-1"></span>
<span v-if="isActiveDate(date)" class="absolute z-2 h-1.5 w-1.5 rounded-full bg-brand-500 top-1 right-1"></span>
<span class="z-2">
{{ date.getDate() }}
{{ date.getDate() }}
</span>
</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.nc-selected-week {
@apply relative;
}
@ -202,5 +199,4 @@ const paginate = (action: 'next' | 'prev') => {
width: 100%;
@apply !border-r-2 !rounded-r-lg;
}
</style>

159
packages/nc-gui/components/nc/MonthYearSelector.vue

@ -1,176 +1,179 @@
<script lang="ts" setup>
interface Props {
selectedDate?: Date | null;
isDisabled?: boolean;
pageDate?: Date;
yearPicker?: boolean;
selectedDate?: Date | null
isDisabled?: boolean
pageDate?: Date
yearPicker?: boolean
}
const emit = defineEmits(['update:selected-date', 'update:page-date']);
const props = withDefaults(defineProps<Props>(), {
selectedDate: null,
isDisabled: false,
pageDate: new Date(),
yearPicker: false
});
const pageDate = useVModel(props, 'pageDate', emit);
const selectedDate = useVModel(props, 'selectedDate', emit);
yearPicker: false,
})
const emit = defineEmits(['update:selected-date', 'update:page-date'])
const pageDate = useVModel(props, 'pageDate', emit)
const selectedDate = useVModel(props, 'selectedDate', emit)
const years = computed(() => {
const date = new Date(pageDate.value);
const startYear = date.getFullYear();
const endYear = date.getFullYear() + 11;
const years = [];
const date = new Date(pageDate.value)
const startYear = date.getFullYear()
const endYear = date.getFullYear() + 11
const years = []
for (let i = startYear; i <= endYear; i++) {
years.push({
label: i,
value: new Date(i, 0, 1)
});
value: new Date(i, 0, 1),
})
}
return years;
});
return years
})
const currentYear = computed(() => {
return pageDate.value.getFullYear();
});
return pageDate.value.getFullYear()
})
const months = computed(() => {
const date = new Date(pageDate.value);
const date = new Date(pageDate.value)
return [
{
label: 'January',
value: new Date(date.getFullYear(), 0, 1)
value: new Date(date.getFullYear(), 0, 1),
},
{
label: 'February',
value: new Date(date.getFullYear(), 1, 1)
value: new Date(date.getFullYear(), 1, 1),
},
{
label: 'March',
value: new Date(date.getFullYear(), 2, 1)
value: new Date(date.getFullYear(), 2, 1),
},
{
label: 'April',
value: new Date(date.getFullYear(), 3, 1)
value: new Date(date.getFullYear(), 3, 1),
},
{
label: 'May',
value: new Date(date.getFullYear(), 4, 1)
value: new Date(date.getFullYear(), 4, 1),
},
{
label: 'June',
value: new Date(date.getFullYear(), 5, 1)
value: new Date(date.getFullYear(), 5, 1),
},
{
label: 'July',
value: new Date(date.getFullYear(), 6, 1)
value: new Date(date.getFullYear(), 6, 1),
},
{
label: 'August',
value: new Date(date.getFullYear(), 7, 1)
value: new Date(date.getFullYear(), 7, 1),
},
{
label: 'September',
value: new Date(date.getFullYear(), 8, 1)
value: new Date(date.getFullYear(), 8, 1),
},
{
label: 'October',
value: new Date(date.getFullYear(), 9, 1)
value: new Date(date.getFullYear(), 9, 1),
},
{
label: 'November',
value: new Date(date.getFullYear(), 10, 1)
value: new Date(date.getFullYear(), 10, 1),
},
{
label: 'December',
value: new Date(date.getFullYear(), 11, 1)
}
value: new Date(date.getFullYear(), 11, 1),
},
]
})
const compareDates = (date1: Date, date2: Date) => {
if (!date1 || !date2) return false;
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth();
if (!date1 || !date2) return false
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth()
}
const isMonthSelected = (date: Date) => {
if (!selectedDate.value) return false;
return compareDates(date, selectedDate.value);
if (!selectedDate.value) return false
return compareDates(date, selectedDate.value)
}
const paginateMonth = (action: 'next' | 'prev') => {
const date = new Date(pageDate.value);
const date = new Date(pageDate.value)
if (action === 'next') {
date.setFullYear(date.getFullYear() + 1);
date.setFullYear(date.getFullYear() + 1)
} else {
date.setFullYear(date.getFullYear() - 1);
date.setFullYear(date.getFullYear() - 1)
}
pageDate.value = date;
};
pageDate.value = date
}
const paginateYear = (action: 'next' | 'prev') => {
const date = new Date(pageDate.value);
const date = new Date(pageDate.value)
if (action === 'next') {
date.setFullYear(date.getFullYear() + 12);
date.setFullYear(date.getFullYear() + 12)
} else {
date.setFullYear(date.getFullYear() - 12);
date.setFullYear(date.getFullYear() - 12)
}
pageDate.value = date;
};
pageDate.value = date
}
const paginate = (action: 'next' | 'prev') => {
if (props.yearPicker) {
paginateYear(action);
paginateYear(action)
} else {
paginateMonth(action);
paginateMonth(action)
}
};
}
const compareYear = (date1: Date, date2: Date) => {
if (!date1 || !date2) return false;
return date1.getFullYear() === date2.getFullYear();
if (!date1 || !date2) return false
return date1.getFullYear() === date2.getFullYear()
}
</script>
<template>
<div class="p-4 flex flex-col gap-4">
<div class="flex justify-between items-center">
<NcButton size="small" type="secondary" @click="paginate('prev')">
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4"/>
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4" />
</NcButton>
<span class="font-bold text-gray-700">{{ yearPicker ? 'Select Year' : currentYear }}</span>
<NcButton size="small" type="secondary" @click="paginate('next')">
<component :is="iconMap.doubleRightArrow" class="h-4 w-4"/>
<component :is="iconMap.doubleRightArrow" class="h-4 w-4" />
</NcButton>
</div>
<div class="border-1 border-gray-200 rounded-y-xl max-w-[350px]">
<div class="grid grid-cols-4 gap-2 p-2">
<span v-for="(month) in months" v-if="!yearPicker" :key="month.value" :class="{
'!bg-brand-50 !border-2 !border-brand-500' : isMonthSelected(month.value),
}"
class="h-9 rounded-lg flex font-medium items-center justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-500 cursor-pointer"
@click="selectedDate = month.value"
>
{{ month.label.slice(0, 3) }}
</span>
<span v-for="(year) in years" v-if="yearPicker" :key="year" :class="{
'!bg-brand-50 !border-2 !border-brand-500' : compareYear(year.value, selectedDate)
}"
class="h-9 rounded-lg flex font-medium items-center justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-500 cursor-pointer"
@click="selectedDate = year.value"
>
{{ year.label }}
</span>
<template v-if="!yearPicker">
<span
v-for="month in months"
:key="month.value"
:class="{
'!bg-brand-50 !border-2 !border-brand-500': isMonthSelected(month.value),
}"
class="h-9 rounded-lg flex font-medium items-center justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-500 cursor-pointer"
@click="selectedDate = month.value"
>
{{ month.label.slice(0, 3) }}
</span>
</template>
<template v-else>
<span
v-for="year in years"
:key="year"
:class="{
'!bg-brand-50 !border-2 !border-brand-500': compareYear(year.value, selectedDate),
}"
class="h-9 rounded-lg flex font-medium items-center justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-500 cursor-pointer"
@click="selectedDate = year.value"
>
{{ year.label }}
</span>
</template>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

2
packages/nc-gui/components/smartsheet/Gallery.vue

@ -4,9 +4,9 @@ import type { Row as RowType } from '#imports'
import {
ActiveViewInj,
FieldsInj,
IsCalendarInj,
IsFormInj,
IsGalleryInj,
IsCalendarInj,
IsGridInj,
MetaInj,
NavigateDir,

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

@ -7,7 +7,6 @@ import {
IsFormInj,
IsGalleryInj,
IsGridInj,
IsCalendarInj,
IsKanbanInj,
IsLockedInj,
IsPublicInj,
@ -105,7 +104,6 @@ provide(IsGridInj, ref(false))
provide(IsKanbanInj, ref(true))
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const fields = inject(FieldsInj, ref([]))

5
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -32,7 +32,10 @@ const { allowCSVDownload } = useSharedView()
<LazySmartsheetToolbarMappedBy v-if="isMap" />
<LazySmartsheetToolbarCalendarRange v-if="isCalendar" />
<LazySmartsheetToolbarFieldsMenu v-if="isGrid || isGallery || isKanban || isMap || isCalendar" :show-system-fields="false" />
<LazySmartsheetToolbarFieldsMenu
v-if="isGrid || isGallery || isKanban || isMap || isCalendar"
:show-system-fields="false"
/>
<LazySmartsheetToolbarStackedBy v-if="isKanban" />

43
packages/nc-gui/components/smartsheet/calendar/DayView.vue

@ -1,33 +1,38 @@
<script setup lang="ts">
import { UITypes } from 'nocodb-sdk'
import {UITypes} from "nocodb-sdk";
const emit = defineEmits(['expand-record'])
const { pageDate, selectedDate, calDataType } = useCalendarViewStoreOrThrow();
const emit = defineEmits(['expand-record']);
const { pageDate, selectedDate, calDataType } = useCalendarViewStoreOrThrow()
const events = ref([
{
"Id": 1,
"Title": "Event 01",
"from_date_time": "2023-12-15",
"to_date_time": "2023-12-20"
Id: 1,
Title: 'Event 01',
from_date_time: '2023-12-15',
to_date_time: '2023-12-20',
},
{
"Id": 2,
"Title": "Event 02",
"from_date_time": "2023-12-20",
"to_date_time": "2023-12-25"
}
Id: 2,
Title: 'Event 02',
from_date_time: '2023-12-20',
to_date_time: '2023-12-25',
},
])
</script>
<template>
<div v-if="calDataType === UITypes.Date" class="flex flex-col px-2 gap-4 pt-4">
<LazySmartsheetCalendarRecordCard v-for="event in events" :name="event.Title" :date="event.from_date_time" color="blue" size="medium" @click="emit('expand-record', 'xxx')" />
</div>
<div v-if="calDataType === UITypes.Date" class="flex flex-col px-2 gap-4 pt-4">
<LazySmartsheetCalendarRecordCard
v-for="event in events"
:key="event.Id"
:name="event.Title"
:date="event.from_date_time"
color="blue"
size="medium"
@click="emit('expand-record', 'xxx')"
/>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

189
packages/nc-gui/components/smartsheet/calendar/MonthView.vue

@ -1,162 +1,175 @@
<script setup lang="ts">
const { pageDate, selectedDate } = useCalendarViewStoreOrThrow();
const { pageDate, selectedDate } = useCalendarViewStoreOrThrow()
const events = ref([
{
"Id": 1,
"Title": "Event 01",
"from_date_time": "2023-12-15",
"to_date_time": "2023-12-20"
Id: 1,
Title: 'Event 01',
from_date_time: '2023-12-15',
to_date_time: '2023-12-20',
},
{
"Id": 2,
"Title": "Event 02",
"from_date_time": "2023-12-20",
"to_date_time": "2023-12-25"
}
Id: 2,
Title: 'Event 02',
from_date_time: '2023-12-20',
to_date_time: '2023-12-25',
},
])
interface EventData {
id: string;
from_col_id: string;
to_col_id: string | null;
id: string
from_col_id: string
to_col_id: string | null
}
const isMondayFirst = ref(false);
const isMondayFirst = ref(false)
const fields = inject(FieldsInj, ref([]))
const days = computed(() => {
if (isMondayFirst.value) {
return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
} else {
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
}
})
const isDayInPagedMonth = (date: Date) => {
return date.getMonth() === pageDate.value.getMonth();
return date.getMonth() === pageDate.value.getMonth()
}
const dates = computed(() => {
const startOfMonth = new Date(pageDate.value.getFullYear(), pageDate.value.getMonth(), 1);
const dayOffset = isMondayFirst.value ? 1 : 0;
const dayOfWeek = (startOfMonth.getDay() - dayOffset + 7) % 7;
startOfMonth.setDate(startOfMonth.getDate() - dayOfWeek);
const datesArray = [];
const startOfMonth = new Date(pageDate.value.getFullYear(), pageDate.value.getMonth(), 1)
const dayOffset = isMondayFirst.value ? 1 : 0
const dayOfWeek = (startOfMonth.getDay() - dayOffset + 7) % 7
startOfMonth.setDate(startOfMonth.getDate() - dayOfWeek)
const datesArray = []
while (datesArray.length < 42) {
datesArray.push(new Date(startOfMonth));
startOfMonth.setDate(startOfMonth.getDate() + 1);
datesArray.push(new Date(startOfMonth))
startOfMonth.setDate(startOfMonth.getDate() + 1)
}
return datesArray;
});
return datesArray
})
const getGridPosition = (event) => {
const firstDayOfMonth = new Date(pageDate.value.getFullYear(), pageDate.value.getMonth(), 1).getDay()
const firstDayOfMonth = new Date(pageDate.value.getFullYear(), pageDate.value.getMonth(), 1).getDay();
const startDate = new Date(event.from_date_time);
const startDayIndex = startDate.getDate() - 1 + firstDayOfMonth;
const endDate = new Date(event.to_date_time);
const endDayIndex = endDate.getDate() - 1 + firstDayOfMonth;
const startDate = new Date(event.from_date_time)
const startDayIndex = startDate.getDate() - 1 + firstDayOfMonth
const endDate = new Date(event.to_date_time)
const endDayIndex = endDate.getDate() - 1 + firstDayOfMonth
const startRow = Math.floor(startDayIndex / 7) + 1;
let endRow = Math.floor(endDayIndex / 7) + 1;
const startRow = Math.floor(startDayIndex / 7) + 1
let endRow = Math.floor(endDayIndex / 7) + 1
if (endDate.getMonth() !== pageDate.value.getMonth()) {
endRow = Math.ceil((new Date(pageDate.value.getFullYear(), pageDate.value.getMonth() + 1, 0).getDate() + firstDayOfMonth) / 7);
endRow = Math.ceil((new Date(pageDate.value.getFullYear(), pageDate.value.getMonth() + 1, 0).getDate() + firstDayOfMonth) / 7)
}
const startCol = startDayIndex % 7 + 1;
let endCol = endDayIndex % 7 + 1;
const startCol = (startDayIndex % 7) + 1
let endCol = (endDayIndex % 7) + 1
if (endCol === 1) {
endRow++;
endCol = 8;
endRow++
endCol = 8
}
return {
colStart: startCol,
colEnd: endCol,
rowStart: startRow,
rowEnd: endRow
};
};
rowEnd: endRow,
}
}
const selectDate = (date: Date) => {
if (!date) return;
selectedDate.value = date;
if (!date) return
selectedDate.value = date
}
const isSameDate = (date1: Date, date2: Date) => {
if(!date1 || !date2) return false;
return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
if (!date1 || !date2) return false
return (
date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate()
)
}
const isDateSelected = (date: Date) => {
if (!selectedDate.value) return false;
const propDate = new Date(selectedDate.value);
return isSameDate(propDate, date);
if (!selectedDate.value) return false
const propDate = new Date(selectedDate.value)
return isSameDate(propDate, date)
}
const handleScroll = (event) => {
if (event.deltaY > 0) {
pageDate.value.setMonth(pageDate.value.getMonth() + 1);
pageDate.value.setMonth(pageDate.value.getMonth() + 1)
} else {
pageDate.value.setMonth(pageDate.value.getMonth() - 1);
pageDate.value.setMonth(pageDate.value.getMonth() - 1)
}
}
</script>
<template>
<div class="h-full" @="handleScroll">
<div class="grid grid-cols-7">
<div v-for="day in days" :key="day" class="text-center bg-gray-50 py-1 text-sm border-b-1 border-r-1 last:border-r-0 border-gray-200 font-semibold text-gray-800">
<div
v-for="day in days"
:key="day"
class="text-center bg-gray-50 py-1 text-sm border-b-1 border-r-1 last:border-r-0 border-gray-200 font-semibold text-gray-800"
>
{{ day }}
</div>
</div>
<div class="grid relative grid-cols-7 h-full" >
<div v-for="date in dates" :key="date" :class="{
'!border-x-2 !border-y-2 border-brand-500': isDateSelected(date),
'!bg-gray-50 !text-gray-400': !isDayInPagedMonth(date),
}" class="text-right !h-[100%] group grid-cell py-1 text-sm border-b-1 border-r-1 bg-white last:border-r-0 border-gray-200 font-semibold text-gray-800"
@click="selectDate(date)"
<div class="grid relative grid-cols-7 h-full">
<div
v-for="date in dates"
:key="date"
:class="{
'!border-x-2 !border-y-2 border-brand-500': isDateSelected(date),
'!bg-gray-50 !text-gray-400': !isDayInPagedMonth(date),
}"
class="text-right !h-[100%] group grid-cell py-1 text-sm border-b-1 border-r-1 bg-white last:border-r-0 border-gray-200 font-semibold text-gray-800"
@click="selectDate(date)"
>
<div class="h-full">
<div class="flex justify-between p-1">
<span :class="{
'block': !isDateSelected(date),
'hidden': isDateSelected(date),
}" class="group-hover:hidden"></span>
<NcButton type="secondary" size="small" :class="{
'!block': isDateSelected(date),
'!hidden': !isDateSelected(date),
}" class="!group-hover:block">
<component :is="iconMap.plus" class="h-4 w-4"/>
</NcButton>
<span class="px-1 py-2">{{ date.getDate() }}</span>
</div>
<div class="flex justify-between p-1">
<span
:class="{
block: !isDateSelected(date),
hidden: isDateSelected(date),
}"
class="group-hover:hidden"
></span>
<NcButton
type="secondary"
size="small"
:class="{
'!block': isDateSelected(date),
'!hidden': !isDateSelected(date),
}"
class="!group-hover:block"
>
<component :is="iconMap.plus" class="h-4 w-4" />
</NcButton>
<span class="px-1 py-2">{{ date.getDate() }}</span>
</div>
</div>
</div>
<div v-for="event in events" :key="event.Id" :class="[
'absolute w-full mt-16 px-2',
`!col-start-[${getGridPosition(event, pageDate).colStart}]`,
`!col-span-[${getGridPosition(event, pageDate).colEnd - getGridPosition(event, pageDate).colStart}]`,
`!row-start-[${getGridPosition(event, pageDate).rowStart}]`,
`!row-span-[${getGridPosition(event, pageDate).rowEnd - getGridPosition(event, pageDate).rowStart}]`
]" class="event-display">
<LazySmartsheetCalendarRecordCard :name="event.Title" :date="event.from_date_time" color="blue" />
<div
v-for="event in events"
:key="event.Id"
:class="[
`!col-start-[${getGridPosition(event, pageDate).colStart}]`,
`!col-span-[${getGridPosition(event, pageDate).colEnd - getGridPosition(event, pageDate).colStart}]`,
`!row-start-[${getGridPosition(event, pageDate).rowStart}]`,
`!row-span-[${getGridPosition(event, pageDate).rowEnd - getGridPosition(event, pageDate).rowStart}]`,
]"
class="event-display absolute w-full mt-16 px-2"
>
<LazySmartsheetCalendarRecordCard :name="event.Title" :date="event.from_date_time" color="blue" />
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

50
packages/nc-gui/components/smartsheet/calendar/RecordCard.vue

@ -1,11 +1,10 @@
<script setup lang="ts">
interface Props {
name: string;
date?: string;
color?: string;
size? : 'small' | 'medium' | 'large';
showDate?: boolean;
name: string
date?: string
color?: string
size?: 'small' | 'medium' | 'large'
showDate?: boolean
}
const props = withDefaults(defineProps<Props>(), {
@ -14,13 +13,13 @@ const props = withDefaults(defineProps<Props>(), {
color: 'blue',
size: 'small',
showDate: true,
});
})
</script>
<template>
<div :class="{
'bg-maroon-50': props.color === 'maroon',
<div
:class="{
'bg-maroon-50': props.color === 'maroon',
'bg-blue-50': props.color === 'blue',
'bg-green-50': props.color === 'green',
'bg-yellow-50': props.color === 'yellow',
@ -29,22 +28,25 @@ const props = withDefaults(defineProps<Props>(), {
'h-12': props.size === 'medium',
'h-16': props.size === 'large',
'h-8': props.size === 'small',
}" class="flex items-center w-full border-1 cursor-pointer border-gray-200 gap-2 items-center px-2 py-3 rounded-lg">
<span :class="{
'bg-maroon-500': props.color === 'maroon',
'bg-blue-500': props.color === 'blue',
'bg-green-500': props.color === 'green',
'bg-yellow-500': props.color === 'yellow',
'bg-pink-500': props.color === 'pink',
'bg-purple-500': props.color === 'purple',
}" class="block h-5 w-1 rounded"></span>
}"
class="flex items-center w-full border-1 cursor-pointer border-gray-200 gap-2 items-center px-2 py-3 rounded-lg"
>
<span
:class="{
'bg-maroon-500': props.color === 'maroon',
'bg-blue-500': props.color === 'blue',
'bg-green-500': props.color === 'green',
'bg-yellow-500': props.color === 'yellow',
'bg-pink-500': props.color === 'pink',
'bg-purple-500': props.color === 'purple',
}"
class="block h-5 w-1 rounded"
></span>
<div class="flex flex-row items-baseline gap-2">
<span class="text-sm font-bold text-gray-800">{{name}}</span>
<span v-if="showDate" class="text-xs text-gray-600">{{date}}</span>
<span class="text-sm font-bold text-gray-800">{{ name }}</span>
<span v-if="showDate" class="text-xs text-gray-600">{{ date }}</span>
</div>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

112
packages/nc-gui/components/smartsheet/calendar/SideMenu.vue

@ -1,91 +1,115 @@
<script setup lang="ts">
import { CalendarViewTypeInj } from "#imports";
import { CalendarViewTypeInj } from '#imports'
const props = defineProps<{
visible: boolean
}>()
const activeCalendarView = inject(CalendarViewTypeInj, ref<'month' | 'day' | 'year' | 'week'>('month' as const))
const emit = defineEmits(['expand-record'])
const {t} = useI18n()
const activeCalendarView = inject(CalendarViewTypeInj, ref<'month' | 'day' | 'year' | 'week'>('month' as const))
const emit = defineEmits(['expand-record']);
const { t } = useI18n()
const options = computed(() => {
switch (activeCalendarView.value) {
case 'day' as const:
return [
{label: 'in this day', value: 'day'},
{label: 'without dates', value: 'withoutDates'},
{label: 'in selected hours', value: 'selectedHours'},
{label: 'all records', value: 'allRecords'},
{ label: 'in this day', value: 'day' },
{ label: 'without dates', value: 'withoutDates' },
{ label: 'in selected hours', value: 'selectedHours' },
{ label: 'all records', value: 'allRecords' },
]
case 'week' as const:
return [
{label: 'in this week', value: 'week'},
{label: 'without dates', value: 'withoutDates'},
{label: 'in selected hours', value: 'selectedHours'},
{label: 'all records', value: 'allRecords'},
{ label: 'in this week', value: 'week' },
{ label: 'without dates', value: 'withoutDates' },
{ label: 'in selected hours', value: 'selectedHours' },
{ label: 'all records', value: 'allRecords' },
]
case 'month' as const:
return [
{label: 'in this month', value: 'month'},
{label: 'without dates', value: 'withoutDates'},
{label: 'all records', value: 'allRecords'},
{label: 'in selected date', value: 'selectedDate'},
{ label: 'in this month', value: 'month' },
{ label: 'without dates', value: 'withoutDates' },
{ label: 'all records', value: 'allRecords' },
{ label: 'in selected date', value: 'selectedDate' },
]
case 'year' as const:
return [
{label: 'in this year', value: 'year'},
{label: 'without dates', value: 'withoutDates'},
{label: 'all records', value: 'allRecords'},
{label: 'in selected date', value: 'selectedDate'},
{ label: 'in this year', value: 'year' },
{ label: 'without dates', value: 'withoutDates' },
{ label: 'all records', value: 'allRecords' },
{ label: 'in selected date', value: 'selectedDate' },
]
}
})
const { pageDate, selectedDate, selectedDateRange } = useCalendarViewStoreOrThrow()
const { pageDate, selectedDate, selectedDateRange } = useCalendarViewStoreOrThrow()
const activeDates = ref([new Date()])
</script>
<template>
<div :class="{
'w-0': !props.visible,
'w-1/4 min-w-[22.1rem]': props.visible,
'transition-all': true,
}" class="h-full border-l-1 border-gray-200">
<NcDateWeekSelector v-if="activeCalendarView === ('day' as const)" v-model:page-date="pageDate" v-model:selected-date="selectedDate" :active-dates="activeDates" />
<NcDateWeekSelector v-else-if="activeCalendarView === ('week' as const)" v-model:page-date="pageDate" week-picker v-model:selected-week="selectedDateRange" />
<NcMonthYearSelector v-else-if="activeCalendarView === ('month' as const)" v-model:page-date="pageDate" v-model:selected-date="selectedDate" />
<NcMonthYearSelector v-else-if="activeCalendarView === ('year' as const)" year-picker v-model:page-date="pageDate" v-model:selected-date="selectedDate" />
<div
:class="{
'w-0': !props.visible,
'w-1/4 min-w-[22.1rem]': props.visible,
}"
class="h-full border-l-1 border-gray-200 transition-all"
>
<NcDateWeekSelector
v-if="activeCalendarView === ('day' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
:active-dates="activeDates"
/>
<NcDateWeekSelector
v-else-if="activeCalendarView === ('week' as const)"
v-model:page-date="pageDate"
v-model:selected-week="selectedDateRange"
week-picker
/>
<NcMonthYearSelector
v-else-if="activeCalendarView === ('month' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
/>
<NcMonthYearSelector
v-else-if="activeCalendarView === ('year' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
year-picker
/>
<div class="px-4 flex flex-col gap-y-6 pt-4">
<div class="flex justify-between items-center">
<span class="text-2xl font-bold">{{ t('objects.Records') }}</span>
<NcSelect :options="options" value="all records"/>
<NcSelect :options="options" value="all records" />
</div>
<a-input class="!rounded-lg !border-gray-200 !px-4 !py-2" placeholder="Search your records">
<a-input class="!rounded-lg !border-gray-200 !px-4 !py-2" placeholder="Search your records">
<template #prefix>
<component :is="iconMap.search" class="h-4 w-4 mr-1 text-gray-500"/>
<component :is="iconMap.search" class="h-4 w-4 mr-1 text-gray-500" />
</template>
</a-input>
<div :class="{
<div
:class="{
'h-[calc(100vh-40rem)]': activeCalendarView === ('day' as const) || activeCalendarView === ('week' as const),
'h-[calc(100vh-29rem)]': activeCalendarView === ('month' as const) || activeCalendarView === ('year' as const),
}" class="gap-2 flex flex-col nc-scrollbar-md overflow-y-auto nc-calendar-top-height">
<LazySmartsheetCalendarSideRecordCard v-for="(x, id) in Array(50)" :color="x%2 === 0 ? 'maroon': 'blue'"
date="27 April 2003" name="Saturday HackNight" @click="emit('expand-record', id)"/>
}"
class="gap-2 flex flex-col nc-scrollbar-md overflow-y-auto nc-calendar-top-height"
>
<LazySmartsheetCalendarSideRecordCard
v-for="(x, id) in Array(50)"
:key="id"
:color="x % 2 === 0 ? 'maroon' : 'blue'"
date="27 April 2003"
name="Saturday HackNight"
@click="emit('expand-record', id)"
/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

39
packages/nc-gui/components/smartsheet/calendar/SideRecordCard.vue

@ -1,10 +1,9 @@
<script setup lang="ts">
interface Props {
name: string;
date?: string;
color?: string;
showDate?: boolean;
name: string
date?: string
color?: string
showDate?: boolean
}
const props = withDefaults(defineProps<Props>(), {
@ -12,27 +11,27 @@ const props = withDefaults(defineProps<Props>(), {
date: '',
color: 'blue',
showDate: true,
});
})
</script>
<template>
<div class="flex border-1 cursor-pointer border-gray-200 items-center px-2 py-3 rounded-lg">
<span :class="{
'bg-maroon-500': props.color === 'maroon',
'bg-blue-500': props.color === 'blue',
'bg-green-500': props.color === 'green',
'bg-yellow-500': props.color === 'yellow',
'bg-pink-500': props.color === 'pink',
'bg-purple-500': props.color === 'purple',
}" class="block h-10 w-1 rounded"></span>
<span
:class="{
'bg-maroon-500': props.color === 'maroon',
'bg-blue-500': props.color === 'blue',
'bg-green-500': props.color === 'green',
'bg-yellow-500': props.color === 'yellow',
'bg-pink-500': props.color === 'pink',
'bg-purple-500': props.color === 'purple',
}"
class="block h-10 w-1 rounded"
></span>
<div class="flex flex-col gap-1 ml-3">
<span class="text-sm font-bold text-gray-700">{{name}}</span>
<span v-if="showDate" class="text-xs text-gray-500">{{date}}</span>
<span class="text-sm font-bold text-gray-700">{{ name }}</span>
<span v-if="showDate" class="text-xs text-gray-500">{{ date }}</span>
</div>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

20
packages/nc-gui/components/smartsheet/calendar/YearView.vue

@ -1,8 +1,4 @@
<script setup lang="ts">
const {t} = useI18n()
const { pageDate, selectedDate, selectedDateRange } = useCalendarViewStoreOrThrow()
const months = computed(() => {
@ -13,17 +9,19 @@ const months = computed(() => {
}
return months
})
</script>
<template>
<div class="grid justify-items-center gap-6 grid-cols-3 overflow-auto nc-scrollbar-md">
<NcDateWeekSelector v-for="(month, index) in months" disable-pagination :key="month" v-model:page-date="months[index]" v-model:selected-date="selectedDate" class="max-w-[350px]"/>
<NcDateWeekSelector
v-for="(month, index) in months"
:key="month"
v-model:page-date="months[index]"
v-model:selected-date="selectedDate"
disable-pagination
class="max-w-[350px]"
/>
</div>
</template>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

60
packages/nc-gui/components/smartsheet/calendar/index.vue

@ -1,20 +1,19 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
import {
ActiveViewInj,
CalendarViewTypeInj,
inject,
IsCalendarInj,
IsFormInj,
IsGalleryInj,
IsGridInj,
IsKanbanInj,
MetaInj,
inject,
provide,
ref,
useI18n,
} from '#imports'
import dayjs from "dayjs";
const meta = inject(MetaInj, ref())
@ -22,7 +21,7 @@ const view = inject(ActiveViewInj, ref())
const { isMobileMode } = useGlobal()
const {t} = useI18n()
const { t } = useI18n()
provide(IsFormInj, ref(false))
@ -44,17 +43,16 @@ const {
activeCalendarView,
addEmptyRow,
paginationData,
paginateCalendarView
paginateCalendarView,
} = useCalendarViewStoreOrThrow()
provide(CalendarViewTypeInj, activeCalendarView)
const showSideMenu = ref(true)
const isExpanded = ref(false)
const expandedRecordId = ref<string | null>(null);
const expandedRecordId = ref<string | null>(null)
const expandRecord = (id: string) => {
isExpanded.value = true
@ -66,52 +64,56 @@ const headerText = computed(() => {
case 'day':
return dayjs(selectedDate.value).format('D MMMM YYYY')
case 'week':
return dayjs(selectedDateRange.value.start).format('D MMMM YYYY') + ' - ' + dayjs(selectedDateRange.value.end).format('D MMMM YYYY')
return `${dayjs(selectedDateRange.value.start).format('D MMMM YYYY')} - ${dayjs(selectedDateRange.value.end).format(
'D MMMM YYYY',
)}`
case 'month':
return dayjs(selectedDate.value).format('MMMM YYYY')
case 'year':
return dayjs(selectedDate.value).format('YYYY')
}
})
</script>
<template>
<div class="flex h-full flex-row">
<div class="flex flex-col w-full">
<div class="flex justify-between p-3 items-center border-b-1 border-gray-200">
<div class="flex justify-start gap-3 items-center">
<NcButton size="small" type="secondary" @click="paginateCalendarView('prev')">
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4"/>
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4" />
</NcButton>
<span class="font-bold text-gray-700">{{headerText}}</span>
<NcButton size="small" type="secondary" @click="paginateCalendarView('next')" >
<component :is="iconMap.doubleRightArrow" class="h-4 w-4"/>
<span class="font-bold text-gray-700">{{ headerText }}</span>
<NcButton size="small" type="secondary" @click="paginateCalendarView('next')">
<component :is="iconMap.doubleRightArrow" class="h-4 w-4" />
</NcButton>
</div>
<NcButton v-if="!isMobileMode" size="small" type="secondary" @click="showSideMenu = !showSideMenu">
<component :is="iconMap.sidebarMinimise" :class="{
'transform rotate-180': showSideMenu,
}" class="h-4 w-4 transition-all"/>
<component
:is="iconMap.sidebarMinimise"
:class="{
'transform rotate-180': showSideMenu,
}"
class="h-4 w-4 transition-all"
/>
</NcButton>
</div>
<LazySmartsheetCalendarYearView v-if="activeCalendarView === 'year'" class="flex-grow-1" />
<LazySmartsheetCalendarMonthView v-else-if="activeCalendarView === 'month'" class="flex-grow-1" />
<LazySmartsheetCalendarDayView v-else-if="activeCalendarView === 'day'" class="flex-grow-1" />
</div>
<LazySmartsheetCalendarSideMenu v-if="!isMobileMode" :visible="showSideMenu" @expand-record="expandRecord"/>
<LazySmartsheetCalendarSideMenu v-if="!isMobileMode" :visible="showSideMenu" @expand-record="expandRecord" />
</div>
<LazySmartsheetExpandedForm v-model="isExpanded" :view="view" :row="{
row: {},
rowMeta: {
new: !expandedRecordId,
},
}" :meta="meta" />
<LazySmartsheetExpandedForm
v-model="isExpanded"
:view="view"
:row="{
row: {},
rowMeta: {
new: !expandedRecordId,
},
}"
:meta="meta"
/>
</template>

2
packages/nc-gui/components/smartsheet/grid/index.vue

@ -5,8 +5,8 @@ import GroupBy from './GroupBy.vue'
import {
ActiveViewInj,
FieldsInj,
IsFormInj,
IsCalendarInj,
IsFormInj,
IsGalleryInj,
IsGridInj,
MetaInj,

35
packages/nc-gui/components/smartsheet/toolbar/CalendarMode.vue

@ -1,36 +1,33 @@
<script lang="ts" setup>
import { useCalendarViewStoreOrThrow } from '#imports'
import { useCalendarViewStoreOrThrow } from "#imports";
const highlightStyle = ref({ left: '0px' });
const { changeCalendarView, activeCalendarView } = useCalendarViewStoreOrThrow()
const highlightStyle = ref({ left: '0px' })
const setActiveCalendarMode = (mode: 'day' | 'week' | 'month' | 'year', event: MouseEvent) => {
changeCalendarView(mode);
const tabElement = event.target as HTMLElement;
highlightStyle.value.left = `${tabElement.offsetLeft}px`;
};
const { changeCalendarView, activeCalendarView } = useCalendarViewStoreOrThrow()
changeCalendarView(mode)
const tabElement = event.target as HTMLElement
highlightStyle.value.left = `${tabElement.offsetLeft}px`
}
</script>
<template>
<div class="flex flex-row relative p-1 mx-3 mt-3 mb-3 bg-gray-100 rounded-lg gap-x-0.5 nc-calendar-mode-tab">
<div class="highlight" :style="highlightStyle"></div>
<div v-for="mode in ['day', 'week', 'month', 'year']" :key="mode"
:class="{ active: activeCalendarView === mode }"
class="tab"
@click="setActiveCalendarMode(mode, $event)"
<div
v-for="mode in ['day', 'week', 'month', 'year']"
:key="mode"
:class="{ active: activeCalendarView === mode }"
class="tab"
@click="setActiveCalendarMode(mode, $event)"
>
<div class="tab-title nc-tab">{{ $t(`objects.${mode}`) }}</div>
<div class="tab-title nc-tab">{{ $t(`objects.${mode}`) }}</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.highlight {
@apply absolute h-7.5 w-20 shadow bg-white rounded-lg transition-all duration-300;
z-index: 0;
@ -55,6 +52,4 @@ const { changeCalendarView, activeCalendarView } = useCalendarViewStoreOrThrow()
.nc-calendar-mode-tab {
@apply -ml-120 relative;
}
</style>

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

@ -1,25 +1,23 @@
<script lang="ts" setup>
import {isSystemColumn, UITypes} from 'nocodb-sdk'
import type {SelectProps} from 'ant-design-vue'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import type { SelectProps } from 'ant-design-vue'
import {
ActiveViewInj,
computed,
iconMap,
inject,
IsLockedInj,
IsPublicInj,
MetaInj,
computed,
iconMap,
inject,
ref,
useSmartsheetStoreOrThrow,
useViewColumnsOrThrow,
watch,
} from '#imports'
const {eventBus} = useSmartsheetStoreOrThrow()
const meta = inject(MetaInj, ref())
const {$e, $api} = useNuxtApp()
const { $api } = useNuxtApp()
const activeView = inject(ActiveViewInj, ref())
@ -27,25 +25,24 @@ const isLocked = inject(IsLockedInj, ref(false))
const IsPublic = inject(IsPublicInj, ref(false))
const {fields, loadViewColumns, metaColumnById} = useViewColumnsOrThrow()
const { fields, loadViewColumns } = useViewColumnsOrThrow()
const calendarRangeDropdown = ref(false)
watch(
() => activeView.value?.id,
async (newVal, oldVal) => {
if (newVal !== oldVal && meta.value) {
await loadViewColumns()
}
},
{immediate: true},
() => activeView.value?.id,
async (newVal, oldVal) => {
if (newVal !== oldVal && meta.value) {
await loadViewColumns()
}
},
{ immediate: true },
)
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 }[] = []
if (!activeView.value || !activeView.value.view) return tempCalendarRange;
if (!activeView.value || !activeView.value.view) return tempCalendarRange
activeView.value.view.calendar_range?.forEach((range) => {
tempCalendarRange.push({
fk_from_column_id: range.fk_from_column_id,
@ -59,23 +56,21 @@ const calendarRange = computed<{ fk_from_column_id: string; fk_to_column_id: str
const _calendar_ranges = ref<{ fk_from_column_id: string; fk_to_column_id: string | null }[]>(calendarRange.value)
const saveCalendarRanges = async () => {
if(activeView.value) {
try {
const calRanges = _calendar_ranges.value.map((range) => {
if (range.fk_from_column_id) {
return {
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) {
console.log(e)
message.error('There was an error while updating view!')
}
if (activeView.value) {
try {
const calRanges = _calendar_ranges.value
.filter((range) => range.fk_from_column_id)
.map((range) => ({
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) {
console.log(e)
message.error('There was an error while updating view!')
}
} else {
message.error('Please select a view first')
}
@ -90,27 +85,23 @@ const addCalendarRange = async () => {
}
const dateFieldOptions = computed<SelectProps['options']>(() => {
return meta.value?.columns
?.filter((c) => {
if (c.uidt === UITypes.Date || c.uidt === UITypes.DateTime && !isSystemColumn(c)) {
return true
}
})
return (
meta.value?.columns
?.filter((c) => c.uidt === UITypes.Date || (c.uidt === UITypes.DateTime && !isSystemColumn(c)))
.map((c) => ({
label: c.title,
value: c.id,
})) ?? []
)
})
</script>
<template>
<NcDropdown v-if="!IsPublic" v-model:visible="calendarRangeDropdown" :trigger="['click']" class="!xs:hidden">
<div class="nc-calendar-btn">
<a-button v-e="['c:calendar:change-calendar-range']" :disabled="isLocked"
class="nc-calendar-range-btn nc-toolbar-btn">
<a-button v-e="['c:calendar:change-calendar-range']" :disabled="isLocked" class="nc-calendar-range-btn nc-toolbar-btn">
<div class="flex items-center gap-2">
<component :is="iconMap.calendar" class="h-4 w-4"/>
<component :is="iconMap.calendar" class="h-4 w-4" />
<span class="text-capitalize !text-sm font-medium">
{{ $t('activity.viewSettings') }}
</span>
@ -118,55 +109,54 @@ const dateFieldOptions = computed<SelectProps['options']>(() => {
</a-button>
</div>
<template #overlay>
<div
v-if="calendarRangeDropdown"
class="flex flex-col w-full p-6 w-[40rem]"
@click.stop
>
<div v-if="calendarRangeDropdown" class="flex flex-col w-full p-6 w-[40rem]" @click.stop>
<div>
<span class="font-bold"> {{ $t('activity.calendar') + $t('activity.viewSettings') }}</span>
<a-divider class="!my-2"/>
<a-divider class="!my-2" />
</div>
<div v-for="cal in _calendar_ranges" class="flex w-full gap-3">
<div v-for="(cal, id) in _calendar_ranges" :key="id" class="flex w-full gap-3">
<div class="flex flex-col gap-2 w-1/2">
<span>
{{ $t('labels.organizeRecordsBy') }}
</span>
<NcSelect
v-model:value="cal.fk_from_column_id"
:options="dateFieldOptions"
class="w-full"
@click.stop
@change="saveCalendarRanges"
v-model:value="cal.fk_from_column_id"
:options="dateFieldOptions"
class="w-full"
@click.stop
@change="saveCalendarRanges"
/>
</div>
<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"
@click="cal.fk_to_column_id = ''">
<component :is="iconMap.plus" class="h-4 w-4"/>
<div class="cursor-pointer flex items-center font-medium gap-1 mb-1" @click="cal.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 flex-col gap-2 w-1/2">
<div class="flex flex-row justify-between">
<span>
{{ $t('labels.endDateField') }}
</span>
<component :is="iconMap.delete" class="h-4 w-4 cursor-pointer text-red-500"
@click="() => {
cal.fk_to_column_id = null
saveCalendarRanges()
}"/>
<span>
{{ $t('labels.endDateField') }}
</span>
<component
:is="iconMap.delete"
class="h-4 w-4 cursor-pointer text-red-500"
@click="
() => {
cal.fk_to_column_id = null
saveCalendarRanges()
}
"
/>
</div>
<NcSelect
v-model:value="cal.fk_to_column_id"
:options="dateFieldOptions"
:disabled="!cal.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')"
class="w-full"
@click.stop
@change="saveCalendarRanges"
v-model:value="cal.fk_to_column_id"
:options="dateFieldOptions"
:disabled="!cal.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')"
class="w-full"
@click.stop
@change="saveCalendarRanges"
/>
</div>
</div>

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

@ -1,17 +1,18 @@
<script setup lang="ts">
import {CalendarType, ColumnType, GalleryType, isVirtualCol, KanbanType, UITypes, ViewTypes} from 'nocodb-sdk'
import type { CalendarType, ColumnType, GalleryType, KanbanType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import Draggable from 'vuedraggable'
import type {SelectProps} from 'ant-design-vue'
import type { SelectProps } from 'ant-design-vue'
import {
ActiveViewInj,
computed,
FieldsInj,
iconMap,
inject,
IsLockedInj,
IsPublicInj,
computed,
iconMap,
inject,
ref,
resolveComponent,
useMenuCloseOnEsc,
@ -150,7 +151,9 @@ const coverOptions = computed<SelectProps['options']>(() => {
const updateCoverImage = async (val?: string | null) => {
if (
(activeView.value?.type === ViewTypes.GALLERY || activeView.value?.type === ViewTypes.KANBAN || activeView.value?.type === ViewTypes.CALENDAR) &&
(activeView.value?.type === ViewTypes.GALLERY ||
activeView.value?.type === ViewTypes.KANBAN ||
activeView.value?.type === ViewTypes.CALENDAR) &&
activeView.value?.id &&
activeView.value?.view
) {
@ -177,7 +180,10 @@ const updateCoverImage = async (val?: string | null) => {
const coverImageColumnId = computed({
get: () => {
const fk_cover_image_col_id =
(activeView.value?.type === ViewTypes.GALLERY || activeView.value?.type === ViewTypes.KANBAN || activeView.value?.type === ViewTypes.CALENDAR) && 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
: undefined
// check if `fk_cover_image_col_id` is in `coverOptions`
@ -301,7 +307,11 @@ useMenuCloseOnEsc(open)
<a-button v-e="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-2">
<GeneralIcon
v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.CALENDAR"
v-if="
activeView?.type === ViewTypes.KANBAN ||
activeView?.type === ViewTypes.GALLERY ||
activeView?.type === ViewTypes.CALENDAR
"
icon="creditCard"
class="h-4 w-4"
/>
@ -310,7 +320,12 @@ useMenuCloseOnEsc(open)
<!-- Fields -->
<span v-if="!isMobileMode" class="text-capitalize text-sm font-medium">
<template
v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.CALENDAR">
v-if="
activeView?.type === ViewTypes.KANBAN ||
activeView?.type === ViewTypes.GALLERY ||
activeView?.type === ViewTypes.CALENDAR
"
>
{{ $t('title.editCards') }}
</template>
<template v-else>
@ -327,7 +342,13 @@ useMenuCloseOnEsc(open)
<template #overlay>
<div class="p-4 pr-0 bg-white w-90 rounded-2xl nc-table-toolbar-menu" data-testid="nc-fields-menu" @click.stop>
<div
v-if="!filterQuery && !isPublic && (activeView?.type === ViewTypes.GALLERY || activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.CALENDAR)"
v-if="
!filterQuery &&
!isPublic &&
(activeView?.type === ViewTypes.GALLERY ||
activeView?.type === ViewTypes.KANBAN ||
activeView?.type === ViewTypes.CALENDAR)
"
class="flex flex-col gap-y-2 pr-4 mb-6"
>
<div class="flex text-sm select-none">Select cover image field</div>

14
packages/nc-gui/components/tabs/Smartsheet.vue

@ -1,23 +1,23 @@
<script setup lang="ts">
import type {ColumnType, LinkToAnotherRecordType, TableType} from 'nocodb-sdk'
import {isLinksOrLTAR, UITypes} from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isLinksOrLTAR } from 'nocodb-sdk'
import type {TabItem} from '#imports'
import type { TabItem } from '#imports'
import {
ActiveViewInj,
computed,
createEventHook,
FieldsInj,
IsFormInj,
IsLockedInj,
MetaInj,
OpenNewRecordFormHookInj,
provide,
ReadonlyInj,
ref,
ReloadViewDataHookInj,
ReloadViewMetaHookInj,
TabMetaInj,
computed,
createEventHook,
provide,
ref,
toRef,
useMetas,
useProvideCalendarViewStore,

259
packages/nc-gui/composables/useCalendarViewStore.ts

@ -1,162 +1,169 @@
import type { ComputedRef, Ref } from 'vue'
import {ColumnType, CalendarType, PaginatedType, TableType, ViewType, UITypes} from 'nocodb-sdk'
import type { CalendarType, PaginatedType, ViewType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { addDays, addMonths, addYears } from '../utils'
import { IsPublicInj, ref, storeToRefs, useBase, useInjectionState, useMetas } from '#imports'
import type { Row } from '#imports'
import {addDays, addMonths, addWeeks, addYears} from "../utils";
const formatData = (list: Record<string, any>[]) =>
list.map(
(row) =>
({
row: { ...row },
oldRow: { ...row },
rowMeta: {},
} as Row),
)
list.map(
(row) =>
({
row: { ...row },
oldRow: { ...row },
rowMeta: {},
} as Row),
)
const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
(
meta: Ref<(CalendarType & { id: string }) | undefined>,
viewMeta: Ref<(ViewType | CalendarType | undefined) & { id: string }> | ComputedRef<(ViewType & { id: string }) | undefined>,
shared = false,
where?: ComputedRef<string | undefined>,
) => {
if (!meta) {
throw new Error('Table meta is not available')
}
(
meta: Ref<(CalendarType & { id: string }) | undefined>,
viewMeta: Ref<(ViewType | CalendarType | undefined) & { id: string }> | ComputedRef<(ViewType & { id: string }) | undefined>,
shared = false,
where?: ComputedRef<string | undefined>,
) => {
if (!meta) {
throw new Error('Table meta is not available')
}
const pageDate = ref<Date> (new Date())
const pageDate = ref<Date>(new Date())
const activeCalendarView = ref<'month' | 'year' | 'day' | 'week'>('month')
const activeCalendarView = ref<'month' | 'year' | 'day' | 'week'>('month')
const calDataType = ref<UITypes.Date | UITypes.DateTime>(UITypes.Date)
const calDataType = ref<UITypes.Date | UITypes.DateTime>(UITypes.Date)
const selectedDate = ref<Date>(new Date())
const selectedDateRange = ref<{
start: Date | null
end: Date | null
}>({start: new Date(), end: null})
const selectedDate = ref<Date>(new Date())
const selectedDateRange = ref<{
start: Date | null
end: Date | null
}>({ start: new Date(), end: null })
const defaultPageSize = 1000
const defaultPageSize = 1000
const formattedData = ref<Row[]>([])
const formattedData = ref<Row[]>([])
const { api } = useApi()
const { api } = useApi()
const { base } = storeToRefs(useBase())
const { base } = storeToRefs(useBase())
const { $api } = useNuxtApp()
const { $api } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRoles()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false))
const isPublic = ref(shared) || inject(IsPublicInj, ref(false))
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { sharedView, fetchSharedViewData } = useSharedView()
const { sharedView, fetchSharedViewData } = useSharedView()
const calendarMetaData = ref<CalendarType>({})
const calendarMetaData = ref<CalendarType>({})
// Not Required in Calendar View TODO: Remove
// const geoDataFieldColumn = ref<ColumnType | undefined>()
// Not Required in Calendar View TODO: Remove
// const geoDataFieldColumn = ref<ColumnType | undefined>()
const paginationData = ref<PaginatedType>({ page: 1, pageSize: defaultPageSize })
const paginationData = ref<PaginatedType>({ page: 1, pageSize: defaultPageSize })
const queryParams = computed(() => ({
limit: paginationData.value.pageSize ?? defaultPageSize,
where: where?.value ?? '',
}))
const queryParams = computed(() => ({
limit: paginationData.value.pageSize ?? defaultPageSize,
where: where?.value ?? '',
}))
const changeCalendarView = (view: 'month' | 'year' | 'day' | 'week') => {
activeCalendarView.value = view
}
const changeCalendarView = (view: 'month' | 'year' | 'day' | 'week') => {
activeCalendarView.value = view
}
async function loadCalendarMeta() {
if (!viewMeta?.value?.id || !meta?.value?.columns) return
// TODO: Fetch Calendar Meta
// calendarMetaData.value = isPublic.value ? (sharedView.value?.view as CalendarType) : await $api.dbView.calendarRead(viewMeta.value.id)
calendarMetaData.value = isPublic.value ? (sharedView.value?.view as CalendarType) : await $api.dbView.mapRead(viewMeta.value.id)
async function loadCalendarMeta() {
if (!viewMeta?.value?.id || !meta?.value?.columns) return
// TODO: Fetch Calendar Meta
// calendarMetaData.value = isPublic.value ? (sharedView.value?.view as CalendarType) : await $api.dbView.calendarRead(viewMeta.value.id)
calendarMetaData.value = isPublic.value
? (sharedView.value?.view as CalendarType)
: await $api.dbView.mapRead(viewMeta.value.id)
/*geoDataFieldColumn.value =
/* geoDataFieldColumn.value =
(meta.value.columns as ColumnType[]).filter((f) => f.id === mapMetaData.value.fk_geo_data_col_id)[0] || {}
*/ }
async function loadCalendarData() {
if ((!base?.value?.id || !meta.value?.id || !viewMeta.value?.id) && !isPublic?.value) return
const res = !isPublic.value
? await api.dbViewRow.list('noco', base.value.id!, meta.value!.id!, viewMeta.value!.id!, {
...queryParams.value,
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: where?.value,
})
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value })
formattedData.value = formatData(res!.list)
}
async function updateCalendarMeta(updateObj: Partial<CalendarType>) {
if (!viewMeta?.value?.id || !isUIAllowed('dataEdit')) return
// await $api.dbView.calendarUpdate(viewMeta.value.id, updateObj)
await $api.dbView.mapUpdate(viewMeta.value.id, updateObj)
}
const paginateCalendarView = (action: 'next' | 'prev') => {
switch (activeCalendarView.value) {
case 'month':
selectedDate.value = action === 'next' ? addMonths(selectedDate.value, 1) : addMonths(selectedDate.value, -1)
if(pageDate.value.getFullYear() !== selectedDate.value.getFullYear()) {
pageDate.value = selectedDate.value
}
break
case 'year':
selectedDate.value = action === 'next' ? addYears(selectedDate.value, 1) : addYears(selectedDate.value, -1)
if(pageDate.value.getFullYear() !== selectedDate.value.getFullYear()) {
pageDate.value = selectedDate.value
}
break
case 'day':
selectedDate.value = action === 'next' ? addDays(selectedDate.value, 1) : addDays(selectedDate.value, -1)
if(pageDate.value.getFullYear() !== selectedDate.value.getFullYear()) {
pageDate.value = selectedDate.value
} else if(pageDate.value.getMonth() !== selectedDate.value.getMonth()) {
pageDate.value = selectedDate.value
}
break
case 'week':
selectedDateRange.value = action === 'next' ? {
start: addDays(selectedDateRange.value.start!, 7),
end: addDays(selectedDateRange.value.end!, 7)
} : {
start: addDays(selectedDateRange.value.start!, -7),
end: addDays(selectedDateRange.value.end!, -7)
}
break
}
}
return {
formattedData,
changeCalendarView,
calDataType,
loadCalendarMeta,
updateCalendarMeta,
calendarMetaData,
activeCalendarView,
pageDate,
paginationData,
selectedDate,
selectedDateRange,
paginateCalendarView,
}
},
*/
}
async function loadCalendarData() {
if ((!base?.value?.id || !meta.value?.id || !viewMeta.value?.id) && !isPublic?.value) return
const res = !isPublic.value
? await api.dbViewRow.list('noco', base.value.id!, meta.value!.id!, viewMeta.value!.id!, {
...queryParams.value,
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
where: where?.value,
})
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value })
formattedData.value = formatData(res!.list)
}
async function updateCalendarMeta(updateObj: Partial<CalendarType>) {
if (!viewMeta?.value?.id || !isUIAllowed('dataEdit')) return
// await $api.dbView.calendarUpdate(viewMeta.value.id, updateObj)
await $api.dbView.mapUpdate(viewMeta.value.id, updateObj)
}
const paginateCalendarView = (action: 'next' | 'prev') => {
switch (activeCalendarView.value) {
case 'month':
selectedDate.value = action === 'next' ? addMonths(selectedDate.value, 1) : addMonths(selectedDate.value, -1)
if (pageDate.value.getFullYear() !== selectedDate.value.getFullYear()) {
pageDate.value = selectedDate.value
}
break
case 'year':
selectedDate.value = action === 'next' ? addYears(selectedDate.value, 1) : addYears(selectedDate.value, -1)
if (pageDate.value.getFullYear() !== selectedDate.value.getFullYear()) {
pageDate.value = selectedDate.value
}
break
case 'day':
selectedDate.value = action === 'next' ? addDays(selectedDate.value, 1) : addDays(selectedDate.value, -1)
if (pageDate.value.getFullYear() !== selectedDate.value.getFullYear()) {
pageDate.value = selectedDate.value
} else if (pageDate.value.getMonth() !== selectedDate.value.getMonth()) {
pageDate.value = selectedDate.value
}
break
case 'week':
selectedDateRange.value =
action === 'next'
? {
start: addDays(selectedDateRange.value.start!, 7),
end: addDays(selectedDateRange.value.end!, 7),
}
: {
start: addDays(selectedDateRange.value.start!, -7),
end: addDays(selectedDateRange.value.end!, -7),
}
break
}
}
return {
formattedData,
changeCalendarView,
calDataType,
loadCalendarMeta,
updateCalendarMeta,
calendarMetaData,
activeCalendarView,
pageDate,
paginationData,
selectedDate,
selectedDateRange,
paginateCalendarView,
}
},
)
export { useProvideCalendarViewStore }
export function useCalendarViewStoreOrThrow() {
const calendarViewStore = useCalendarViewStore()
const calendarViewStore = useCalendarViewStore()
if (calendarViewStore == null) throw new Error('Please call `useProvideCalendarViewStore` on the appropriate parent component')
if (calendarViewStore == null) throw new Error('Please call `useProvideCalendarViewStore` on the appropriate parent component')
return calendarViewStore
return calendarViewStore
}

22
packages/nocodb/src/controllers/calendars.controller.spec.ts

@ -4,18 +4,18 @@ import { CalendarsController } from './calendars.controller';
import type { TestingModule } from '@nestjs/testing';
describe('CalendarsController', () => {
let controller: CalendarsController;
let controller: CalendarsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CalendarsController],
providers: [CalendarsService],
}).compile();
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CalendarsController],
providers: [CalendarsService],
}).compile();
controller = module.get<CalendarsController>(CalendarsController);
});
controller = module.get<CalendarsController>(CalendarsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

110
packages/nocodb/src/controllers/calendars.controller.ts

@ -1,13 +1,13 @@
import {
Body,
Controller,
Get,
HttpCode,
Param,
Patch,
Post,
Req,
UseGuards,
Body,
Controller,
Get,
HttpCode,
Param,
Patch,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { ViewCreateReqType } from 'nocodb-sdk';
@ -19,54 +19,54 @@ import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
@Controller()
@UseGuards(MetaApiLimiterGuard, GlobalGuard)
export class CalendarsController {
constructor(private readonly calendarsService: CalendarsService) {}
constructor(private readonly calendarsService: CalendarsService) {}
@Get([
'/api/v1/db/meta/calendars/:calendarViewId',
'/api/v2/meta/calendars/:calendarViewId',
])
@Acl('calendarViewGet')
async calendarViewGet(@Param('calendarViewId') calendarViewId: string) {
return await this.calendarsService.calendarViewGet({
calendarViewId,
});
}
@Get([
'/api/v1/db/meta/calendars/:calendarViewId',
'/api/v2/meta/calendars/:calendarViewId',
])
@Acl('calendarViewGet')
async calendarViewGet(@Param('calendarViewId') calendarViewId: string) {
return await this.calendarsService.calendarViewGet({
calendarViewId,
});
}
@Post([
'/api/v1/db/meta/tables/:tableId/calendars',
'/api/v2/meta/tables/:tableId/calendars',
])
@HttpCode(200)
@Acl('calendarViewCreate')
async calendarViewCreate(
@Param('tableId') tableId: string,
@Body() body: ViewCreateReqType,
@Req() req: Request,
) {
return await this.calendarsService.calendarViewCreate({
tableId,
calendar: body,
user: req.user,
req,
});
}
@Post([
'/api/v1/db/meta/tables/:tableId/calendars',
'/api/v2/meta/tables/:tableId/calendars',
])
@HttpCode(200)
@Acl('calendarViewCreate')
async calendarViewCreate(
@Param('tableId') tableId: string,
@Body() body: ViewCreateReqType,
@Req() req: Request,
) {
return await this.calendarsService.calendarViewCreate({
tableId,
calendar: body,
user: req.user,
req,
});
}
@Patch([
'/api/v1/db/meta/calendars/:calendarViewId',
'/api/v2/meta/calendars/:calendarViewId',
])
// #TODO Enable ACL Later
// @Acl('calendarViewUpdate')
async calendarViewUpdate(
@Param('calendarViewId') calendarViewId: string,
@Body() body,
@Patch([
'/api/v1/db/meta/calendars/:calendarViewId',
'/api/v2/meta/calendars/:calendarViewId',
])
// #TODO Enable ACL Later
// @Acl('calendarViewUpdate')
async calendarViewUpdate(
@Param('calendarViewId') calendarViewId: string,
@Body() body,
@Req() req: Request,
) {
return await this.calendarsService.calendarViewUpdate({
calendarViewId,
calendar: body,
req,
});
}
@Req() req: Request,
) {
return await this.calendarsService.calendarViewUpdate({
calendarViewId,
calendar: body,
req,
});
}
}

12
packages/nocodb/src/meta/meta.service.ts

@ -1,16 +1,16 @@
import {Injectable, Optional} from '@nestjs/common';
import {customAlphabet} from 'nanoid';
import { Injectable, Optional } from '@nestjs/common';
import { customAlphabet } from 'nanoid';
import CryptoJS from 'crypto-js';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import type * as knex from 'knex';
import type {Knex} from 'knex';
import type { Knex } from 'knex';
import XcMigrationSource from '~/meta/migrations/XcMigrationSource';
import XcMigrationSourcev2 from '~/meta/migrations/XcMigrationSourcev2';
import {XKnex} from '~/db/CustomKnex';
import {NcConfig} from '~/utils/nc-config';
import {MetaTable} from '~/utils/globals';
import { XKnex } from '~/db/CustomKnex';
import { NcConfig } from '~/utils/nc-config';
import { MetaTable } from '~/utils/globals';
dayjs.extend(utc);
dayjs.extend(timezone);

4
packages/nocodb/src/meta/migrations/v2/nc_041_calander_view.ts

@ -1,5 +1,5 @@
import type {Knex} from 'knex';
import {MetaTable} from '~/utils/globals';
import type { Knex } from 'knex';
import { MetaTable } from '~/utils/globals';
const up = async (knex: Knex) => {
await knex.schema.createTable(MetaTable.CALENDAR_VIEW, (table) => {

265
packages/nocodb/src/models/CalendarRange.ts

@ -1,145 +1,146 @@
import type {CalendarRangeType} from 'nocodb-sdk';
import type { CalendarRangeType } from 'nocodb-sdk';
import Noco from '~/Noco';
import NocoCache from '~/cache/NocoCache';
import {extractProps} from '~/helpers/extractProps';
import {CacheGetType, CacheScope, MetaTable} from '~/utils/globals';
import { extractProps } from '~/helpers/extractProps';
import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals';
export default class CalendarRange implements CalendarRangeType {
id?: string;
fk_from_column_id?: string;
fk_to_column_id?: string | null;
fk_view_id?: string;
constructor(data: Partial<CalendarRange>) {
Object.assign(this, data);
id?: string;
fk_from_column_id?: string;
fk_to_column_id?: string | null;
fk_view_id?: string;
constructor(data: Partial<CalendarRange>) {
Object.assign(this, data);
}
public static async insert(
data: Partial<CalendarRange>,
ncMeta = Noco.ncMeta,
) {
const insertObj = extractProps(data, [
'fk_from_column_id',
'fk_to_column_id',
'fk_view_id',
]);
const { id } = await ncMeta.metaInsert2(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
insertObj,
);
await NocoCache.appendToList(
CacheScope.CALENDAR_VIEW_RANGE,
[data.fk_view_id],
`${CacheScope.CALENDAR_VIEW_RANGE}:${id}`,
);
return this.get(id, ncMeta);
}
public static async bulkInsert(
data: Partial<CalendarRange>[],
ncMeta = Noco.ncMeta,
) {
const insertObj = [];
for (const d of data) {
const tempObj = extractProps(d, [
'fk_from_column_id',
'fk_to_column_id',
'fk_view_id',
]);
insertObj.push(tempObj);
}
public static async insert(
data: Partial<CalendarRange>,
ncMeta = Noco.ncMeta,
) {
const insertObj = extractProps(data, [
'fk_from_column_id',
'fk_to_column_id',
'fk_view_id',
]);
const { id } = await ncMeta.metaInsert2(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
insertObj,
);
await NocoCache.appendToList(
CacheScope.CALENDAR_VIEW_RANGE,
[data.fk_view_id],
`${CacheScope.CALENDAR_VIEW_RANGE}:${id}`,
);
return this.get(id, ncMeta);
const bulkData = await ncMeta.bulkMetaInsert(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
insertObj,
);
for (const d of bulkData) {
await NocoCache.appendToList(
CacheScope.CALENDAR_VIEW_RANGE,
[d.fk_view_id],
`${CacheScope.CALENDAR_VIEW_RANGE}:${d.id}`,
);
await NocoCache.set(`${CacheScope.CALENDAR_VIEW_RANGE}:${d.id}`, d);
}
public static async bulkInsert(
data: Partial<CalendarRange>[],
ncMeta = Noco.ncMeta,
) {
const insertObj = [];
for (const d of data) {
const tempObj = extractProps(d, [
'fk_from_column_id',
'fk_to_column_id',
'fk_view_id',
]);
insertObj.push(tempObj);
}
const bulkData = await ncMeta.bulkMetaInsert(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
insertObj,
);
for (const d of bulkData) {
await NocoCache.appendToList(
CacheScope.CALENDAR_VIEW_RANGE,
[d.fk_view_id],
`${CacheScope.CALENDAR_VIEW_RANGE}:${d.id}`,
);
await NocoCache.set(`${CacheScope.CALENDAR_VIEW_RANGE}:${d.id}`, d);
}
return true;
return true;
}
public static async get(
calendarRangeId: string,
ncMeta = Noco.ncMeta,
): Promise<CalendarRange> {
let data =
calendarRangeId &&
(await NocoCache.get(
`${CacheScope.CALENDAR_VIEW_RANGE}:${calendarRangeId}`,
CacheGetType.TYPE_OBJECT,
));
if (!data) {
data = await ncMeta.metaGet2(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
calendarRangeId,
);
await NocoCache.set(
`${CacheScope.CALENDAR_VIEW_RANGE}:${calendarRangeId}`,
data,
);
}
public static async get(
calendarRangeId: string,
ncMeta = Noco.ncMeta,
): Promise<CalendarRange> {
let data =
calendarRangeId &&
(await NocoCache.get(
`${CacheScope.CALENDAR_VIEW_RANGE}:${calendarRangeId}`,
CacheGetType.TYPE_OBJECT,
));
if (!data) {
data = await ncMeta.metaGet2(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
calendarRangeId,
);
await NocoCache.set(
`${CacheScope.CALENDAR_VIEW_RANGE}:${calendarRangeId}`,
data,
);
}
return data && new CalendarRange(data);
return data && new CalendarRange(data);
}
public static async read(fk_view_id: string, ncMeta = Noco.ncMeta) {
const cachedList = await NocoCache.getList(CacheScope.CALENDAR_VIEW_RANGE, [
fk_view_id,
]);
let { list: ranges } = cachedList;
const { isNoneList } = cachedList;
if (!isNoneList && !ranges.length) {
ranges = await ncMeta.metaList2(
null, //,
null, //model.db_alias,
MetaTable.CALENDAR_VIEW_RANGE,
{ condition: { fk_view_id } },
);
await NocoCache.setList(
CacheScope.CALENDAR_VIEW_RANGE,
[fk_view_id],
ranges.map(({ created_at, updated_at, ...others }) => others),
);
}
public static async read(fk_view_id: string, ncMeta = Noco.ncMeta) {
const cachedList = await NocoCache.getList(CacheScope.CALENDAR_VIEW_RANGE, [
fk_view_id,
]);
let { list: ranges } = cachedList;
const { isNoneList } = cachedList;
if (!isNoneList && !ranges.length) {
ranges = await ncMeta.metaList2(
null, //,
null, //model.db_alias,
MetaTable.CALENDAR_VIEW_RANGE,
{ condition: { fk_view_id } },
);
await NocoCache.setList(
CacheScope.CALENDAR_VIEW_RANGE,
[fk_view_id],
ranges.map(({ created_at, updated_at, ...others }) => others),
);
return ranges?.length
? {
ranges: ranges.map(
({ created_at, updated_at, ...c }) => new CalendarRange(c),
),
}
return ranges?.length
? {
ranges: ranges
.map(({ created_at, updated_at, ...c }) => new CalendarRange(c))
}
: null;
}
public static async find(
fk_view_id: string,
ncMeta = Noco.ncMeta,
): Promise<CalendarRange> {
const data = await ncMeta.metaGet2(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
{
fk_view_id,
},
);
return data && new CalendarRange(data);
}
: null;
}
public static async find(
fk_view_id: string,
ncMeta = Noco.ncMeta,
): Promise<CalendarRange> {
const data = await ncMeta.metaGet2(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
{
fk_view_id,
},
);
return data && new CalendarRange(data);
}
}

208
packages/nocodb/src/models/CalendarView.ts

@ -1,115 +1,135 @@
import type {CalendarType} from 'nocodb-sdk';
import {BoolType, MetaType} from 'nocodb-sdk';
import type { BoolType, MetaType } from 'nocodb-sdk';
import type { CalendarType } from 'nocodb-sdk';
import View from '~/models/View';
import {extractProps} from '~/helpers/extractProps';
import { extractProps } from '~/helpers/extractProps';
import NocoCache from '~/cache/NocoCache';
import Noco from '~/Noco';
import {CacheGetType, CacheScope, MetaTable} from '~/utils/globals';
import CalendarRange from "~/models/CalendarRange";
import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals';
import CalendarRange from '~/models/CalendarRange';
export default class CalendarView implements CalendarType {
fk_view_id: string;
title: string;
base_id?: string;
source_id?: string;
meta?: MetaType;
calendar_range?: Array<Partial<CalendarRange>>
fk_cover_image_col_id?: string;
// below fields are not in use at this moment
// keep them for time being
show?: BoolType;
public?: BoolType;
password?: string;
show_all_fields?: BoolType;
fk_view_id: string;
title: string;
base_id?: string;
source_id?: string;
meta?: MetaType;
calendar_range?: Array<Partial<CalendarRange>>;
fk_cover_image_col_id?: string;
// below fields are not in use at this moment
// keep them for time being
show?: BoolType;
public?: BoolType;
password?: string;
show_all_fields?: BoolType;
constructor(data: CalendarView) {
Object.assign(this, data);
}
public static async get(viewId: string, ncMeta = Noco.ncMeta) {
let view =
viewId &&
(await NocoCache.get(
`${CacheScope.CALENDAR_VIEW}:${viewId}`,
CacheGetType.TYPE_OBJECT,
));
if (view) {
const calendarRange = await CalendarRange.read(viewId, ncMeta);
if (calendarRange) {
view.calendar_range = calendarRange.ranges;
} else {
view.calendar_range = [];
}
} else {
view = await ncMeta.metaGet2(null, null, MetaTable.CALENDAR_VIEW, {
fk_view_id: viewId,
});
const calendarRange = await CalendarRange.read(viewId);
if (calendarRange) {
view.calendar_range = calendarRange.ranges;
}
await NocoCache.set(`${CacheScope.CALENDAR_VIEW}:${viewId}`, view);
}
constructor(data: CalendarView) {
Object.assign(this, data);
}
return view && new CalendarView(view);
public static async get(viewId: string, ncMeta = Noco.ncMeta) {
let view =
viewId &&
(await NocoCache.get(
`${CacheScope.CALENDAR_VIEW}:${viewId}`,
CacheGetType.TYPE_OBJECT,
));
if (view) {
const calendarRange = await CalendarRange.read(viewId, ncMeta);
if (calendarRange) {
view.calendar_range = calendarRange.ranges;
} else {
view.calendar_range = [];
}
} else {
view = await ncMeta.metaGet2(null, null, MetaTable.CALENDAR_VIEW, {
fk_view_id: viewId,
});
const calendarRange = await CalendarRange.read(viewId);
if (calendarRange) {
view.calendar_range = calendarRange.ranges;
}
await NocoCache.set(`${CacheScope.CALENDAR_VIEW}:${viewId}`, view);
}
static async insert(view: Partial<CalendarView>, ncMeta = Noco.ncMeta) {
const insertObj = {
base_id: view.base_id,
source_id: view.source_id,
fk_view_id: view.fk_view_id,
meta: view.meta,
};
return view && new CalendarView(view);
}
const viewRef = await View.get(view.fk_view_id);
static async insert(view: Partial<CalendarView>, ncMeta = Noco.ncMeta) {
const insertObj = {
base_id: view.base_id,
source_id: view.source_id,
fk_view_id: view.fk_view_id,
meta: view.meta,
};
if (!(view.base_id && view.source_id)) {
insertObj.base_id = viewRef.base_id;
insertObj.source_id = viewRef.source_id;
}
const viewRef = await View.get(view.fk_view_id);
await ncMeta.metaInsert2(null, null, MetaTable.CALENDAR_VIEW, insertObj, true);
return this.get(view.fk_view_id, ncMeta);
if (!(view.base_id && view.source_id)) {
insertObj.base_id = viewRef.base_id;
insertObj.source_id = viewRef.source_id;
}
static async update(
calendarId: string,
body: Partial<CalendarView>,
ncMeta = Noco.ncMeta,
) {
// get existing cache
const key = `${CacheScope.CALENDAR_VIEW_COLUMN}:${calendarId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
await ncMeta.metaInsert2(
null,
null,
MetaTable.CALENDAR_VIEW,
insertObj,
true,
);
return this.get(view.fk_view_id, ncMeta);
}
const updateObj = extractProps(body, ['fk_cover_image_col_id', 'meta']);
static async update(
calendarId: string,
body: Partial<CalendarView>,
ncMeta = Noco.ncMeta,
) {
// get existing cache
const key = `${CacheScope.CALENDAR_VIEW_COLUMN}:${calendarId}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (updateObj.meta && typeof updateObj.meta === 'object') {
updateObj.meta = JSON.stringify(updateObj.meta ?? {});
}
const updateObj = extractProps(body, ['fk_cover_image_col_id', 'meta']);
if (o) {
o = { ...o, ...updateObj };
// set cache
await NocoCache.set(key, o);
}
if (updateObj.meta && typeof updateObj.meta === 'object') {
updateObj.meta = JSON.stringify(updateObj.meta ?? {});
}
if (o) {
o = { ...o, ...updateObj };
// set cache
await NocoCache.set(key, o);
}
if (body.calendar_range) {
await NocoCache.del(`${CacheScope.CALENDAR_VIEW_RANGE}:${calendarId}`);
await ncMeta.metaDelete(null, null, MetaTable.CALENDAR_VIEW_RANGE, {}, {
fk_view_id: calendarId,
});
await CalendarRange.bulkInsert(body.calendar_range.map((range) => {
return {
fk_view_id: calendarId,
...range
}
}));
}
// update meta
return await ncMeta.metaUpdate(null, null, MetaTable.CALENDAR_VIEW, updateObj, {
if (body.calendar_range) {
await NocoCache.del(`${CacheScope.CALENDAR_VIEW_RANGE}:${calendarId}`);
await ncMeta.metaDelete(
null,
null,
MetaTable.CALENDAR_VIEW_RANGE,
{},
{
fk_view_id: calendarId,
},
);
await CalendarRange.bulkInsert(
body.calendar_range.map((range) => {
return {
fk_view_id: calendarId,
});
...range,
};
}),
);
}
// update meta
return await ncMeta.metaUpdate(
null,
null,
MetaTable.CALENDAR_VIEW,
updateObj,
{
fk_view_id: calendarId,
},
);
}
}

331
packages/nocodb/src/models/CalendarViewColumn.ts

@ -1,173 +1,180 @@
import type {BoolType, MetaType,} from 'nocodb-sdk';
import type { BoolType, MetaType } from 'nocodb-sdk';
import View from '~/models/View';
import Noco from '~/Noco';
import NocoCache from '~/cache/NocoCache';
import {extractProps} from '~/helpers/extractProps';
import {deserializeJSON} from '~/utils/serialize';
import {CacheGetType, CacheScope, MetaTable} from '~/utils/globals';
import { extractProps } from '~/helpers/extractProps';
import { deserializeJSON } from '~/utils/serialize';
import { CacheGetType, CacheScope, MetaTable } from '~/utils/globals';
export default class CalendarViewColumn {
id?: string;
fk_view_id?: string;
fk_column_id?: string;
base_id?: string;
source_id?: string;
show?: BoolType;
underline?: BoolType;
bold?: BoolType;
italic?: BoolType;
order?: number;
meta?: MetaType;
constructor(data: CalendarViewColumn) {
Object.assign(this, data);
id?: string;
fk_view_id?: string;
fk_column_id?: string;
base_id?: string;
source_id?: string;
show?: BoolType;
underline?: BoolType;
bold?: BoolType;
italic?: BoolType;
order?: number;
meta?: MetaType;
constructor(data: CalendarViewColumn) {
Object.assign(this, data);
}
public static async get(calendarViewColumnId: string, ncMeta = Noco.ncMeta) {
let viewColumn =
calendarViewColumnId &&
(await NocoCache.get(
`${CacheScope.CALENDAR_VIEW_COLUMN}:${calendarViewColumnId}`,
CacheGetType.TYPE_OBJECT,
));
if (!viewColumn) {
viewColumn = await ncMeta.metaGet2(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
calendarViewColumnId,
);
viewColumn.meta =
viewColumn.meta && typeof viewColumn.meta === 'string'
? JSON.parse(viewColumn.meta)
: viewColumn.meta;
}
public static async get(calendarViewColumnId: string, ncMeta = Noco.ncMeta) {
let viewColumn =
calendarViewColumnId &&
(await NocoCache.get(
`${CacheScope.CALENDAR_VIEW_COLUMN}:${calendarViewColumnId}`,
CacheGetType.TYPE_OBJECT,
));
if (!viewColumn) {
viewColumn = await ncMeta.metaGet2(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
calendarViewColumnId,
);
viewColumn.meta =
viewColumn.meta && typeof viewColumn.meta === 'string'
? JSON.parse(viewColumn.meta)
: viewColumn.meta;
}
await NocoCache.set(
`${CacheScope.CALENDAR_VIEW_COLUMN}:${calendarViewColumnId}`,
viewColumn,
);
return viewColumn && new CalendarViewColumn(viewColumn);
}
static async insert(column: Partial<CalendarViewColumn>, ncMeta = Noco.ncMeta) {
const insertObj = extractProps(column, [
'fk_view_id',
'fk_column_id',
'show',
'base_id',
'source_id',
'underline',
'bold',
'italic',
]);
insertObj.order = await ncMeta.metaGetNextOrder(
MetaTable.CALENDAR_VIEW_COLUMNS,
{
fk_view_id: insertObj.fk_view_id,
},
);
if (!(insertObj.base_id && insertObj.source_id)) {
const viewRef = await View.get(insertObj.fk_view_id, ncMeta);
insertObj.base_id = viewRef.base_id;
insertObj.source_id = viewRef.source_id;
}
const { id, fk_column_id } = await ncMeta.metaInsert2(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
insertObj,
);
await NocoCache.set(`${CacheScope.CALENDAR_VIEW_COLUMN}:${fk_column_id}`, id);
// if cache is not present skip pushing it into the list to avoid unexpected behaviour
const { list } = await NocoCache.getList(CacheScope.CALENDAR_VIEW_COLUMN, [
column.fk_view_id,
]);
if (list?.length)
await NocoCache.appendToList(
CacheScope.CALENDAR_VIEW_COLUMN,
[column.fk_view_id],
`${CacheScope.CALENDAR_VIEW_COLUMN}:${id}`,
);
return this.get(id, ncMeta);
await NocoCache.set(
`${CacheScope.CALENDAR_VIEW_COLUMN}:${calendarViewColumnId}`,
viewColumn,
);
return viewColumn && new CalendarViewColumn(viewColumn);
}
static async insert(
column: Partial<CalendarViewColumn>,
ncMeta = Noco.ncMeta,
) {
const insertObj = extractProps(column, [
'fk_view_id',
'fk_column_id',
'show',
'base_id',
'source_id',
'underline',
'bold',
'italic',
]);
insertObj.order = await ncMeta.metaGetNextOrder(
MetaTable.CALENDAR_VIEW_COLUMNS,
{
fk_view_id: insertObj.fk_view_id,
},
);
if (!(insertObj.base_id && insertObj.source_id)) {
const viewRef = await View.get(insertObj.fk_view_id, ncMeta);
insertObj.base_id = viewRef.base_id;
insertObj.source_id = viewRef.source_id;
}
public static async list(
viewId: string,
ncMeta = Noco.ncMeta,
): Promise<CalendarViewColumn[]> {
const cachedList = await NocoCache.getList(CacheScope.CALENDAR_VIEW_COLUMN, [
viewId,
]);
let { list: viewColumns } = cachedList;
const { isNoneList } = cachedList;
if (!isNoneList && !viewColumns.length) {
viewColumns = await ncMeta.metaList2(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
{
condition: {
fk_view_id: viewId,
},
orderBy: {
order: 'asc',
},
},
);
for (const viewColumn of viewColumns) {
viewColumn.meta = deserializeJSON(viewColumn.meta);
}
await NocoCache.setList(
CacheScope.CALENDAR_VIEW_COLUMN,
[viewId],
viewColumns,
);
}
viewColumns.sort(
(a, b) =>
(a.order != null ? a.order : Infinity) -
(b.order != null ? b.order : Infinity),
);
return viewColumns?.map((v) => new CalendarViewColumn(v));
const { id, fk_column_id } = await ncMeta.metaInsert2(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
insertObj,
);
await NocoCache.set(
`${CacheScope.CALENDAR_VIEW_COLUMN}:${fk_column_id}`,
id,
);
// if cache is not present skip pushing it into the list to avoid unexpected behaviour
const { list } = await NocoCache.getList(CacheScope.CALENDAR_VIEW_COLUMN, [
column.fk_view_id,
]);
if (list?.length)
await NocoCache.appendToList(
CacheScope.CALENDAR_VIEW_COLUMN,
[column.fk_view_id],
`${CacheScope.CALENDAR_VIEW_COLUMN}:${id}`,
);
return this.get(id, ncMeta);
}
public static async list(
viewId: string,
ncMeta = Noco.ncMeta,
): Promise<CalendarViewColumn[]> {
const cachedList = await NocoCache.getList(
CacheScope.CALENDAR_VIEW_COLUMN,
[viewId],
);
let { list: viewColumns } = cachedList;
const { isNoneList } = cachedList;
if (!isNoneList && !viewColumns.length) {
viewColumns = await ncMeta.metaList2(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
{
condition: {
fk_view_id: viewId,
},
orderBy: {
order: 'asc',
},
},
);
for (const viewColumn of viewColumns) {
viewColumn.meta = deserializeJSON(viewColumn.meta);
}
await NocoCache.setList(
CacheScope.CALENDAR_VIEW_COLUMN,
[viewId],
viewColumns,
);
}
static async update(
columnId: string,
body: Partial<CalendarViewColumn>,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, [
'show',
'order',
'underline',
'bold',
'italic',
]);
// get existing cache
const key = `${CacheScope.CALENDAR_VIEW_COLUMN}:${columnId}`;
const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) {
Object.assign(o, updateObj);
// set cache
await NocoCache.set(key, o);
}
// update meta
return await ncMeta.metaUpdate(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
updateObj,
columnId,
);
viewColumns.sort(
(a, b) =>
(a.order != null ? a.order : Infinity) -
(b.order != null ? b.order : Infinity),
);
return viewColumns?.map((v) => new CalendarViewColumn(v));
}
static async update(
columnId: string,
body: Partial<CalendarViewColumn>,
ncMeta = Noco.ncMeta,
) {
const updateObj = extractProps(body, [
'show',
'order',
'underline',
'bold',
'italic',
]);
// get existing cache
const key = `${CacheScope.CALENDAR_VIEW_COLUMN}:${columnId}`;
const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) {
Object.assign(o, updateObj);
// set cache
await NocoCache.set(key, o);
}
// update meta
return await ncMeta.metaUpdate(
null,
null,
MetaTable.CALENDAR_VIEW_COLUMNS,
updateObj,
columnId,
);
}
}

80
packages/nocodb/src/models/View.ts

@ -1,14 +1,14 @@
import type {BoolType, ColumnReqType, ViewType} from 'nocodb-sdk';
import {isSystemColumn, UITypes, ViewTypes} from 'nocodb-sdk';
import { isSystemColumn, UITypes, ViewTypes } from 'nocodb-sdk';
import type { BoolType, ColumnReqType, ViewType } from 'nocodb-sdk';
import Model from '~/models/Model';
import FormView from '~/models/FormView';
import GridView from '~/models/GridView';
import KanbanView from '~/models/KanbanView';
import GalleryView from '~/models/GalleryView';
import CalendarView from "~/models/CalendarView";
import CalendarView from '~/models/CalendarView';
import GridViewColumn from '~/models/GridViewColumn';
import CalendarViewColumn from '~/models/CalendarViewColumn';
import CalendarRange from "~/models/CalendarRange";
import CalendarRange from '~/models/CalendarRange';
import Sort from '~/models/Sort';
import Filter from '~/models/Filter';
import GalleryViewColumn from '~/models/GalleryViewColumn';
@ -17,11 +17,16 @@ import KanbanViewColumn from '~/models/KanbanViewColumn';
import Column from '~/models/Column';
import MapView from '~/models/MapView';
import MapViewColumn from '~/models/MapViewColumn';
import {extractProps} from '~/helpers/extractProps';
import { extractProps } from '~/helpers/extractProps';
import NocoCache from '~/cache/NocoCache';
import {CacheDelDirection, CacheGetType, CacheScope, MetaTable,} from '~/utils/globals';
import {
CacheDelDirection,
CacheGetType,
CacheScope,
MetaTable,
} from '~/utils/globals';
import Noco from '~/Noco';
import {parseMetaProp, stringifyMetaProp} from '~/utils/modelUtils';
import { parseMetaProp, stringifyMetaProp } from '~/utils/modelUtils';
const { v4: uuidv4 } = require('uuid');
@ -54,14 +59,20 @@ export default class View implements ViewType {
fk_model_id: string;
model?: Model;
view?: FormView | GridView | KanbanView | GalleryView | MapView | CalendarView;
view?:
| FormView
| GridView
| KanbanView
| GalleryView
| MapView
| CalendarView;
columns?: Array<
| FormViewColumn
| GridViewColumn
| GalleryViewColumn
| KanbanViewColumn
| MapViewColumn
| CalendarViewColumn
| CalendarViewColumn
>;
sorts: Sort[];
@ -277,7 +288,9 @@ export default class View implements ViewType {
static async insert(
view: Partial<View> &
Partial<FormView | GridView | GalleryView | KanbanView | MapView | CalendarView> & {
Partial<
FormView | GridView | GalleryView | KanbanView | MapView | CalendarView
> & {
copy_from_id?: string;
fk_grp_col_id?: string;
calendar_range?: Partial<CalendarRange>[];
@ -386,18 +399,21 @@ export default class View implements ViewType {
);
break;
case ViewTypes.CALENDAR:
const obj = extractProps(view, ["calendar_range"])
const obj = extractProps(view, ['calendar_range']);
if (!obj.calendar_range) break;
const calendarRange = obj.calendar_range as Partial<CalendarRange>[];
calendarRange.forEach((range) => {
range.fk_view_id = view_id;
})
});
await CalendarView.insert({
...(copyFromView?.view || {}),
...view,
fk_view_id: view_id,
}, ncMeta,)
await CalendarView.insert(
{
...(copyFromView?.view || {}),
...view,
fk_view_id: view_id,
},
ncMeta,
);
await CalendarRange.bulkInsert(calendarRange, ncMeta);
}
@ -456,12 +472,12 @@ export default class View implements ViewType {
if (view.type === ViewTypes.CALENDAR) {
const calRange = await CalendarRange.read(view_id, ncMeta);
if (calRange) {
const calIds: Set<string> = new Set()
const calIds: Set<string> = new Set();
calRange.ranges.forEach((range) => {
calIds.add(range.fk_from_column_id);
if (!range.fk_to_column_id) return;
calIds.add(range.fk_to_column_id);
})
});
calendarRanges = Array.from(calIds) as Array<string>;
}
}
@ -491,9 +507,9 @@ export default class View implements ViewType {
for (const vCol of columns) {
let show = 'show' in vCol ? vCol.show : true;
let underline = false;
let bold = false;
let italic = false;
const underline = false;
const bold = false;
const italic = false;
if (view.type === ViewTypes.GALLERY) {
const galleryView = await GalleryView.get(view_id, ncMeta);
@ -644,7 +660,8 @@ export default class View implements ViewType {
italic?;
fk_column_id;
id?: string;
} & Partial<FormViewColumn> & Partial<CalendarViewColumn>,
} & Partial<FormViewColumn> &
Partial<CalendarViewColumn>,
ncMeta = Noco.ncMeta,
) {
const view = await this.get(param.view_id, ncMeta);
@ -706,8 +723,9 @@ export default class View implements ViewType {
);
}
break;
case ViewTypes.CALENDAR: {
col = await CalendarViewColumn.insert(
case ViewTypes.CALENDAR:
{
col = await CalendarViewColumn.insert(
{
...param,
fk_view_id: view.id,
@ -739,7 +757,7 @@ export default class View implements ViewType {
| GalleryViewColumn
| KanbanViewColumn
| MapViewColumn
| CalendarViewColumn
| CalendarViewColumn
>
> {
let columns: Array<GridViewColumn | any> = [];
@ -1002,7 +1020,7 @@ export default class View implements ViewType {
show: colData.show,
});
case ViewTypes.CALENDAR:
// todo: calendar view column
// todo: calendar view column
}
return await ncMeta.metaInsert2(view.base_id, view.source_id, table, {
fk_view_id: viewId,
@ -1223,8 +1241,12 @@ export default class View implements ViewType {
if (view.type === ViewTypes.CALENDAR) {
await ncMeta.metaDelete(null, null, MetaTable.CALENDAR_VIEW_RANGE, {
fk_view_id: viewId,
})
await NocoCache.deepDel(CacheScope.CALENDAR_VIEW_RANGE, `${CacheScope.CALENDAR_VIEW_RANGE}:${viewId}`, CacheDelDirection.CHILD_TO_PARENT)
});
await NocoCache.deepDel(
CacheScope.CALENDAR_VIEW_RANGE,
`${CacheScope.CALENDAR_VIEW_RANGE}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT,
);
}
await NocoCache.deepDel(
`${columnTableScope}:${viewId}`,

137
packages/nocodb/src/services/calendars.service.ts

@ -1,85 +1,86 @@
import {Injectable} from '@nestjs/common';
import type {CalendarUpdateReqType, UserType, ViewCreateReqType,} from 'nocodb-sdk';
import {AppEvents, ViewTypes} from 'nocodb-sdk';
import type {NcRequest} from '~/interface/config';
import {AppHooksService} from '~/services/app-hooks/app-hooks.service';
import {validatePayload} from '~/helpers';
import {NcError} from '~/helpers/catchError';
import {CalendarView, View} from '~/models';
import CalendarRange from "~/models/CalendarRange";
import { Injectable } from '@nestjs/common';
import { AppEvents, ViewTypes } from 'nocodb-sdk';
import type {
CalendarUpdateReqType,
UserType,
ViewCreateReqType,
} from 'nocodb-sdk';
import type { NcRequest } from '~/interface/config';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { validatePayload } from '~/helpers';
import { NcError } from '~/helpers/catchError';
import { CalendarView, View } from '~/models';
import CalendarRange from '~/models/CalendarRange';
@Injectable()
export class CalendarsService {
constructor(private readonly appHooksService: AppHooksService) {
}
async calendarViewGet(param: { calendarViewId: string }) {
const calendarView = await CalendarView.get(param.calendarViewId);
if (!calendarView) {
NcError.badRequest('Calendar view not found');
}
const calendarRanges = await CalendarRange.read(param.calendarViewId)
return {
...calendarView,
calendar_range: calendarRanges
};
constructor(private readonly appHooksService: AppHooksService) {}
async calendarViewGet(param: { calendarViewId: string }) {
const calendarView = await CalendarView.get(param.calendarViewId);
if (!calendarView) {
NcError.badRequest('Calendar view not found');
}
const calendarRanges = await CalendarRange.read(param.calendarViewId);
return {
...calendarView,
calendar_range: calendarRanges,
};
}
async calendarViewCreate(param: {
tableId: string;
calendar: ViewCreateReqType;
user: UserType;
req: NcRequest;
}) {
-
validatePayload(
'swagger.json#/components/schemas/ViewCreateReq',
param.calendar,
);
async calendarViewCreate(param: {
tableId: string;
calendar: ViewCreateReqType;
user: UserType;
req: NcRequest;
}) {
-validatePayload(
'swagger.json#/components/schemas/ViewCreateReq',
param.calendar,
);
const view = await View.insert({
...param.calendar,
// todo: sanitize
fk_model_id: param.tableId,
type: ViewTypes.CALENDAR,
});
const view = await View.insert({
...param.calendar,
// todo: sanitize
fk_model_id: param.tableId,
type: ViewTypes.CALENDAR,
});
this.appHooksService.emit(AppEvents.VIEW_CREATE, {
view,
showAs: 'calendar',
user: param.user,
this.appHooksService.emit(AppEvents.VIEW_CREATE, {
view,
showAs: 'calendar',
user: param.user,
req: param.req,
});
req: param.req,
});
return view;
}
return view;
}
async calendarViewUpdate(param: {
calendarViewId: string;
calendar: CalendarUpdateReqType;
req: NcRequest;
}) {
validatePayload(
'swagger.json#/components/schemas/CalendarUpdateReq',
param.calendar,
);
async calendarViewUpdate(param: {
calendarViewId: string;
calendar: CalendarUpdateReqType;
req: NcRequest;
}) {
validatePayload(
'swagger.json#/components/schemas/CalendarUpdateReq',
param.calendar,
);
const view = await View.get(param.calendarViewId);
const view = await View.get(param.calendarViewId);
if (!view) {
NcError.badRequest('View not found');
}
if (!view) {
NcError.badRequest('View not found');
}
const res = await CalendarView.update(param.calendarViewId, param.calendar);
const res = await CalendarView.update(param.calendarViewId, param.calendar);
this.appHooksService.emit(AppEvents.VIEW_UPDATE, {
view,
showAs: 'calendar',
req: param.req,
});
this.appHooksService.emit(AppEvents.VIEW_UPDATE, {
view,
showAs: 'calendar',
req: param.req,
});
return res;
}
return res;
}
}

Loading…
Cancel
Save