Browse Source

feat: remove calendar top bar (#8379)

* feat: remove calendar top bar

* fix: remove debug logs

* fix: update styles

* fix: update styles

* fix: posthog telementry

* fix: calendar tests

* fix: updates ui

* test: reorder options

* fix: month view - use local time with timezone

* fix: update styles and move components fix: tests

* fix: update styles and move components

* fix: update styles

* test: fix tests

* fix: update toolbar styles

* fix: failing tests

* fix: cmd f search shortcut

* fix: change side menu sizes

* fix: calendar test corrections

* fix(nc-gui): update size logic

* fix(nc-gui): update styles

* fix(nc-gui): update some more styles

* fix(nc-gui): update toolbar styles

* fix(nc-gui): update select component

* fix: update styles

* fix: calendar test

* fix: ux changes

* fix: final changes

* fix: calendar tests
pull/8475/head
Anbarasu 7 months ago committed by GitHub
parent
commit
fb67cafde7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      packages/nc-gui/assets/style.scss
  2. 4
      packages/nc-gui/components/cmd-k/index.vue
  3. 2
      packages/nc-gui/components/cmd-l/index.vue
  4. 2
      packages/nc-gui/components/dashboard/TreeView/CreateViewBtn.vue
  5. 2
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  6. 64
      packages/nc-gui/components/dlg/ViewCreate.vue
  7. 2
      packages/nc-gui/components/dlg/ViewDelete.vue
  8. 67
      packages/nc-gui/components/nc/DateWeekSelector.vue
  9. 10
      packages/nc-gui/components/nc/Divider.vue
  10. 2
      packages/nc-gui/components/nc/Modal.vue
  11. 57
      packages/nc-gui/components/nc/MonthYearSelector.vue
  12. 2
      packages/nc-gui/components/nc/Select.vue
  13. 62
      packages/nc-gui/components/smartsheet/Toolbar.vue
  14. 7
      packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue
  15. 9
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  16. 34
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  17. 212
      packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
  18. 7
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
  19. 18
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
  20. 28
      packages/nc-gui/components/smartsheet/calendar/YearView.vue
  21. 158
      packages/nc-gui/components/smartsheet/calendar/index.vue
  22. 11
      packages/nc-gui/components/smartsheet/toolbar/Calendar/ActiveView.vue
  23. 126
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Header.vue
  24. 69
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue
  25. 64
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Range.vue
  26. 39
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Today.vue
  27. 13
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue
  28. 40
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  29. 21
      packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue
  30. 12
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  31. 4
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  32. 31
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  33. 10
      packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue
  34. 4
      packages/nc-gui/components/smartsheet/topbar/SelectMode.vue
  35. 2
      packages/nc-gui/components/tabs/Smartsheet.vue
  36. 75
      packages/nc-gui/composables/useCalendarViewStore.ts
  37. 1
      packages/nc-gui/composables/useViewColumns.ts
  38. 3
      packages/nc-gui/lang/en.json
  39. 3
      packages/nc-gui/utils/iconUtils.ts
  40. 1
      packages/nc-gui/windi.config.ts
  41. 30
      tests/playwright/pages/Dashboard/Calendar/CalendarSideMenu.ts
  42. 63
      tests/playwright/pages/Dashboard/Calendar/CalendarTopBar.ts
  43. 11
      tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts
  44. 1
      tests/playwright/pages/Dashboard/Calendar/CalendarYear.ts
  45. 8
      tests/playwright/pages/Dashboard/Calendar/index.ts
  46. 2
      tests/playwright/pages/Dashboard/ViewSidebar/index.ts
  47. 5
      tests/playwright/pages/Dashboard/common/Toolbar/CalendarViewMode.ts
  48. 17
      tests/playwright/pages/Dashboard/common/Toolbar/index.ts
  49. 2
      tests/playwright/tests/db/general/toolbarOperations.spec.ts
  50. 70
      tests/playwright/tests/db/views/viewCalendar.spec.ts

3
packages/nc-gui/assets/style.scss

@ -11,6 +11,7 @@ body {
} }
:root { :root {
--toolbar-height: 2.25rem;
--topbar-height: 3.1rem; --topbar-height: 3.1rem;
--sidebar-bottom-height: 8.5rem; --sidebar-bottom-height: 8.5rem;
--new-header-height: 3.5rem; --new-header-height: 3.5rem;
@ -515,7 +516,7 @@ a {
} }
.nc-toolbar-btn { .nc-toolbar-btn {
@apply !shadow-none rounded hover:(ring-1 ring-gray-200 ring-opacity-100 bg-gray-100 !text-gray-800) focus:(ring-1 ring-gray-300 ring-opacity-100 !text-gray-800 bg-gray-100) text-gray-600 text-xs font-medium px-2 border-0; @apply !shadow-none rounded hover:(bg-gray-50 !text-gray-800) focus:(!text-gray-800 bg-gray-50) text-gray-600 text-xs font-medium px-2 border-0;
} }
.nc-toolbar-btn[disabled] { .nc-toolbar-btn[disabled] {
@apply !text-gray-400 !cursor-not-allowed !hover:ring-0; @apply !text-gray-400 !cursor-not-allowed !hover:ring-0;

4
packages/nc-gui/components/cmd-k/index.vue

@ -427,14 +427,14 @@ defineExpose({
<component <component
:is="(iconMap as any)[act.icon]" :is="(iconMap as any)[act.icon]"
v-if="act.icon && typeof act.icon === 'string' && (iconMap as any)[act.icon]" v-if="act.icon && typeof act.icon === 'string' && (iconMap as any)[act.icon]"
class="cmdk-action-icon"
:class="{ :class="{
'!text-blue-500': act.icon === 'grid', '!text-blue-500': act.icon === 'grid',
'!text-purple-500': act.icon === 'form', '!text-purple-500': act.icon === 'form',
'!text-[#FF9052]': act.icon === 'kanban', '!text-[#FF9052]': act.icon === 'kanban',
'!text-pink-500': act.icon === 'gallery', '!text-pink-500': act.icon === 'gallery',
'!text-maroon-500': act.icon === 'calendar', '!text-maroon-500 w-4 h-4': act.icon === 'calendar',
}" }"
class="cmdk-action-icon"
/> />
<div v-else-if="act.icon" class="cmdk-action-icon max-w-4 flex items-center justify-center"> <div v-else-if="act.icon" class="cmdk-action-icon max-w-4 flex items-center justify-center">
<LazyGeneralEmojiPicker class="!text-sm !h-4 !w-4" size="small" :emoji="act.icon" readonly /> <LazyGeneralEmojiPicker class="!text-sm !h-4 !w-4" size="small" :emoji="act.icon" readonly />

2
packages/nc-gui/components/cmd-l/index.vue

@ -206,7 +206,7 @@ onMounted(() => {
<div class="cmdk-action-content"> <div class="cmdk-action-content">
<div class="flex w-1/2 items-center"> <div class="flex w-1/2 items-center">
<div class="flex gap-2"> <div class="flex gap-2">
<GeneralViewIcon :meta="{ type: cmdOption.viewType }" class="mt-0.5" /> <GeneralViewIcon :meta="{ type: cmdOption.viewType }" class="mt-0.5 w-4 !min-h-4" />
<a-tooltip overlay-class-name="!px-2 !py-1 !rounded-lg"> <a-tooltip overlay-class-name="!px-2 !py-1 !rounded-lg">
<template #title> <template #title>
{{ cmdOption.viewName }} {{ cmdOption.viewName }}

2
packages/nc-gui/components/dashboard/TreeView/CreateViewBtn.vue

@ -158,7 +158,7 @@ async function onOpenModal({
<NcMenuItem data-testid="sidebar-view-create-calendar" @click="onOpenModal({ type: ViewTypes.CALENDAR })"> <NcMenuItem data-testid="sidebar-view-create-calendar" @click="onOpenModal({ type: ViewTypes.CALENDAR })">
<div class="item"> <div class="item">
<div class="item-inner"> <div class="item-inner">
<GeneralViewIcon :meta="{ type: ViewTypes.CALENDAR }" /> <GeneralViewIcon :meta="{ type: ViewTypes.CALENDAR }" class="!w-4 !h-4" />
<div>{{ $t('objects.viewType.calendar') }}</div> <div>{{ $t('objects.viewType.calendar') }}</div>
</div> </div>

2
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -234,7 +234,7 @@ watch(isDropdownOpen, async () => {
@emoji-selected="emits('selectIcon', $event)" @emoji-selected="emits('selectIcon', $event)"
> >
<template #default> <template #default>
<GeneralViewIcon :meta="props.view" class="nc-view-icon !text-[16px]"></GeneralViewIcon> <GeneralViewIcon :meta="props.view" class="nc-view-icon w-4 !text-[16px]"></GeneralViewIcon>
</template> </template>
</LazyGeneralEmojiPicker> </LazyGeneralEmojiPicker>
</div> </div>

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

@ -137,7 +137,6 @@ function init() {
if (repeatCount) { if (repeatCount) {
form.title = `${form.title}-${repeatCount}` form.title = `${form.title}-${repeatCount}`
} }
if (selectedViewId.value) { if (selectedViewId.value) {
form.copy_from_id = selectedViewId?.value form.copy_from_id = selectedViewId?.value
} }
@ -320,11 +319,11 @@ onMounted(async () => {
<template> <template>
<NcModal <NcModal
v-model:visible="vModel" v-model:visible="vModel"
:size="[ViewTypes.KANBAN, ViewTypes.MAP, ViewTypes.CALENDAR].includes(form.type) ? 'small' : 'small'" :size="[ViewTypes.KANBAN, ViewTypes.MAP, ViewTypes.CALENDAR].includes(form.type) ? 'medium' : 'small'"
> >
<template #header> <template #header>
<div class="flex w-full flex-row justify-between items-center"> <div class="flex w-full flex-row justify-between items-center">
<div class="flex gap-x-1.5 items-center"> <div class="flex font-bold text-base gap-x-3 items-center">
<GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" /> <GeneralViewIcon :meta="{ type: form.type }" class="nc-view-icon !text-xl" />
<template v-if="form.type === ViewTypes.GRID"> <template v-if="form.type === ViewTypes.GRID">
<template v-if="form.copy_from_id"> <template v-if="form.copy_from_id">
@ -377,7 +376,7 @@ onMounted(async () => {
</div> </div>
<a <a
v-if="!form.copy_from_id" v-if="!form.copy_from_id"
class="text-sm !text-gray-600 !hover:text-gray-600" class="text-sm !text-gray-600 !font-default !hover:text-gray-600"
:href="`https://docs.nocodb.com/views/view-types/${typeAlias}`" :href="`https://docs.nocodb.com/views/view-types/${typeAlias}`"
target="_blank" target="_blank"
> >
@ -434,7 +433,12 @@ onMounted(async () => {
<span> <span>
{{ $t('labels.organiseBy') }} {{ $t('labels.organiseBy') }}
</span> </span>
<NcSelect v-model:value="range.fk_from_column_id" :disabled="isMetaLoading" :loading="isMetaLoading"> <NcSelect
v-model:value="range.fk_from_column_id"
:disabled="isMetaLoading"
:loading="isMetaLoading"
class="nc-from-select"
>
<a-select-option <a-select-option
v-for="(option, id) in [...viewSelectFieldOptions!].filter((f) => { v-for="(option, id) in [...viewSelectFieldOptions!].filter((f) => {
// If the fk_from_column_id of first range is Date, then all the other ranges should be Date // If the fk_from_column_id of first range is Date, then all the other ranges should be Date
@ -444,14 +448,24 @@ onMounted(async () => {
return firstRange?.uidt === f.uidt return firstRange?.uidt === f.uidt
})" })"
:key="id" :key="id"
class="w-40"
:value="option.value" :value="option.value"
> >
<div class="flex items-center"> <div class="flex w-full gap-2 justify-between items-center">
<SmartsheetHeaderIcon :column="option" /> <div class="flex gap-2 items-center">
<NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only> <SmartsheetHeaderIcon :column="option" />
<template #title>{{ option.label }}</template> <NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only>
{{ option.label }} <template #title>{{ option.label }}</template>
</NcTooltip> {{ option.label }}
</NcTooltip>
</div>
<div class="flex-1" />
<component
:is="iconMap.check"
v-if="option.value === range.fk_from_column_id"
id="nc-selected-item-icon"
class="text-primary min-w-4 h-4"
/>
</div> </div>
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
@ -473,8 +487,8 @@ onMounted(async () => {
v-model:value="range.fk_to_column_id" v-model:value="range.fk_to_column_id"
:disabled="isMetaLoading" :disabled="isMetaLoading"
:loading="isMetaLoading" :loading="isMetaLoading"
:placeholder="$t('placeholder.notSelected')" :placeholder="$t('placenc-to-seleholder.notSelected')"
class="!rounded-r-none nc-to-select" class="!rounded-r-none ct"
> >
<a-select-option <a-select-option
v-for="(option, id) in [...viewSelectFieldOptions].filter((f) => { v-for="(option, id) in [...viewSelectFieldOptions].filter((f) => {
@ -523,10 +537,12 @@ onMounted(async () => {
</template> </template>
</a-form> </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"> <div v-else-if="!isNecessaryColumnsPresent" class="flex flex-row p-4 border-gray-200 border-1 gap-x-4 rounded-lg w-full">
<GeneralIcon class="!text-5xl text-orange-500" icon="warning" /> <div class="text-gray-500 flex gap-4">
<div class="text-gray-500"> <GeneralIcon class="min-w-6 h-6 text-orange-500" icon="warning" />
<h2 class="font-semibold text-sm text-gray-800">Suitable fields not present</h2> <div class="flex flex-col gap-1">
{{ errorMessages[form.type] }} <h2 class="font-semibold text-sm mb-0 text-gray-800">Suitable fields not present</h2>
<span class="text-gray-500 font-default"> {{ errorMessages[form.type] }}</span>
</div>
</div> </div>
</div> </div>
@ -550,7 +566,7 @@ onMounted(async () => {
</NcModal> </NcModal>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.ant-form-item-required { .ant-form-item-required {
@apply !text-gray-800 font-medium; @apply !text-gray-800 font-medium;
&:before { &:before {
@ -558,7 +574,19 @@ onMounted(async () => {
} }
} }
.nc-from-select .ant-select-selector {
@apply !mr-2;
}
.nc-to-select .ant-select-selector { .nc-to-select .ant-select-selector {
@apply !rounded-r-none; @apply !rounded-r-none;
} }
.ant-input {
@apply border-gray-200;
}
.ant-form-item {
@apply !mb-6;
}
</style> </style>

2
packages/nc-gui/components/dlg/ViewDelete.vue

@ -47,7 +47,7 @@ async function onDelete() {
<GeneralDeleteModal v-model:visible="vModel" :entity-name="$t('objects.view')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="vModel" :entity-name="$t('objects.view')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="view" class="flex flex-row items-center py-2 px-3 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="view" class="flex flex-row items-center py-2 px-3 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralViewIcon :meta="props.view" class="nc-view-icon"></GeneralViewIcon> <GeneralViewIcon :meta="props.view" class="nc-view-icon w-4 min-h-4"></GeneralViewIcon>
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-3" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-3"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"

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

@ -8,9 +8,10 @@ interface Props {
pageDate?: dayjs.Dayjs pageDate?: dayjs.Dayjs
activeDates?: Array<dayjs.Dayjs> activeDates?: Array<dayjs.Dayjs>
isMondayFirst?: boolean isMondayFirst?: boolean
disablePagination?: boolean
isWeekPicker?: boolean isWeekPicker?: boolean
disableHeader?: boolean disableHeader?: boolean
disablePagination?: boolean hideCalendar?: boolean
selectedWeek?: { selectedWeek?: {
start: dayjs.Dayjs start: dayjs.Dayjs
end: dayjs.Dayjs end: dayjs.Dayjs
@ -22,14 +23,15 @@ const props = withDefaults(defineProps<Props>(), {
selectedDate: null, selectedDate: null,
isDisabled: false, isDisabled: false,
isMondayFirst: true, isMondayFirst: true,
disablePagination: false,
pageDate: dayjs(), pageDate: dayjs(),
isWeekPicker: false, isWeekPicker: false,
disableHeader: false, disableHeader: false,
disablePagination: false,
activeDates: [] as Array<dayjs.Dayjs>, activeDates: [] as Array<dayjs.Dayjs>,
selectedWeek: null, selectedWeek: null,
hideCalendar: false,
}) })
const emit = defineEmits(['change', 'update:selectedDate', 'update:pageDate', 'update:selectedWeek']) const emit = defineEmits(['change', 'dblClick', 'update:selectedDate', 'update:pageDate', 'update:selectedWeek'])
// Page date is the date we use to manage which month/date that is currently being displayed // Page date is the date we use to manage which month/date that is currently being displayed
const pageDate = useVModel(props, 'pageDate', emit) const pageDate = useVModel(props, 'pageDate', emit)
@ -47,7 +49,6 @@ const days = computed(() => {
} }
}) })
// Used to display the current month and year
const currentMonthYear = computed(() => { const currentMonthYear = computed(() => {
return dayjs(pageDate.value).format('MMMM YYYY') return dayjs(pageDate.value).format('MMMM YYYY')
}) })
@ -56,10 +57,12 @@ const selectWeek = (date: dayjs.Dayjs) => {
const dayOffset = +props.isMondayFirst const dayOffset = +props.isMondayFirst
const dayOfWeek = (date.day() - dayOffset + 7) % 7 const dayOfWeek = (date.day() - dayOffset + 7) % 7
const startDate = date.subtract(dayOfWeek, 'day') const startDate = date.subtract(dayOfWeek, 'day')
selectedWeek.value = { const newWeek = {
start: startDate, start: startDate,
end: startDate.endOf('week'), end: startDate.endOf('week'),
} }
selectedWeek.value = newWeek
emit('update:selectedWeek', newWeek)
} }
// Generates all dates should be displayed in the calendar // Generates all dates should be displayed in the calendar
@ -136,30 +139,32 @@ const paginate = (action: 'next' | 'prev') => {
pageDate.value = newDate pageDate.value = newDate
emit('update:pageDate', newDate) emit('update:pageDate', newDate)
} }
const emitDblClick = (date: dayjs.Dayjs) => {
emit('dblClick', date)
}
</script> </script>
<template> <template>
<div <div
:class="{ :class="{
'gap-1': size === 'small', 'gap-1': size === 'small',
'gap-4': size === 'medium' || size === 'large',
}" }"
class="flex flex-col" class="flex flex-col"
> >
<div <div
v-if="!disableHeader" v-if="!disableHeader"
:class="{ :class="{
' justify-between': !disablePagination, '!justify-center': disablePagination,
' justify-center': disablePagination,
}" }"
class="flex items-center" class="flex justify-between border-b-1 px-3 py-0.5 nc-date-week-header items-center"
> >
<NcTooltip v-if="!disablePagination"> <NcTooltip v-if="!disablePagination">
<NcButton size="small" type="secondary" @click="paginate('prev')"> <NcButton class="!border-0" size="small" type="secondary" @click="paginate('prev')">
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4" /> <component :is="iconMap.arrowLeft" class="h-4 w-4" />
</NcButton> </NcButton>
<template #title> <template #title>
<span>{{ $t('labels.previousMonth') }}</span> <span>{{ $t('labels.next') }}</span>
</template> </template>
</NcTooltip> </NcTooltip>
@ -168,19 +173,21 @@ const paginate = (action: 'next' | 'prev') => {
'text-xs': size === 'small', 'text-xs': size === 'small',
'text-sm': size === 'medium', 'text-sm': size === 'medium',
}" }"
class="text-gray-700" class="text-gray-700 font-semibold"
>{{ currentMonthYear }}</span >{{ currentMonthYear }}</span
> >
<NcTooltip v-if="!disablePagination"> <NcTooltip v-if="!disablePagination">
<NcButton size="small" type="secondary" @click="paginate('next')"> <NcButton class="!border-0" data-testid="nc-calendar-next-btn" size="small" type="secondary" @click="paginate('next')">
<component :is="iconMap.doubleRightArrow" class="h-4 w-4" /> <component :is="iconMap.arrowRight" class="h-4 w-4" />
</NcButton> </NcButton>
<template #title> <template #title>
<span>{{ $t('labels.nextMonth') }}</span> <span>{{ $t('labels.next') }}</span>
</template> </template>
</NcTooltip> </NcTooltip>
</div> </div>
<div <div
v-if="!hideCalendar"
:class="{ :class="{
'rounded-lg': size === 'small', 'rounded-lg': size === 'small',
'rounded-y-xl': size !== 'small', 'rounded-y-xl': size !== 'small',
@ -189,12 +196,12 @@ const paginate = (action: 'next' | 'prev') => {
> >
<div <div
:class="{ :class="{
'gap-1 px-1': size === 'medium', 'gap-1 px-3.5': size === 'medium',
'gap-2': size === 'large', 'gap-2': size === 'large',
'px-2 py-1 !rounded-t-lg': size === 'small', 'px-2 !rounded-t-lg': size === 'small',
'rounded-t-xl': size !== 'small', 'rounded-t-xl': size !== 'small',
}" }"
class="flex flex-row border-b-1 nc-date-week-header border-gray-200 justify-between" class="flex py-1 flex-row nc-date-week-header border-gray-200 justify-between"
> >
<span <span
v-for="(day, index) in days" v-for="(day, index) in days"
@ -211,7 +218,7 @@ const paginate = (action: 'next' | 'prev') => {
<div <div
:class="{ :class="{
'gap-2 pt-2': size === 'large', 'gap-2 pt-2': size === 'large',
'gap-1 p-1': size === 'medium', 'gap-1 py-1 px-3.5': size === 'medium',
}" }"
class="grid nc-date-week-grid-wrapper grid-cols-7" class="grid nc-date-week-grid-wrapper grid-cols-7"
> >
@ -220,35 +227,37 @@ const paginate = (action: 'next' | 'prev') => {
:key="index" :key="index"
:class="{ :class="{
'rounded-lg': !isWeekPicker, 'rounded-lg': !isWeekPicker,
'bg-gray-200 border-1 font-bold text-brand-500': isSelectedDate(date) && !isWeekPicker && isDayInPagedMonth(date), 'bg-gray-200 border-1 font-bold ': isSelectedDate(date) && !isWeekPicker && isDayInPagedMonth(date),
'hover:(border-1 border-gray-200 bg-gray-100)': !isSelectedDate(date) && !isWeekPicker, 'hover:(border-1 border-gray-200 bg-gray-100)': !isSelectedDate(date) && !isWeekPicker,
'nc-selected-week z-1': isDateInSelectedWeek(date) && isWeekPicker, 'nc-selected-week !font-semibold z-1': isDateInSelectedWeek(date) && isWeekPicker,
'border-none': isWeekPicker, 'border-none': isWeekPicker,
'border-transparent': !isWeekPicker, 'border-transparent': !isWeekPicker,
'text-gray-400': !isDateInCurrentMonth(date), 'text-gray-400': !isDateInCurrentMonth(date),
'nc-selected-week-start': isSameDate(date, selectedWeek?.start), 'nc-selected-week-start': isSameDate(date, selectedWeek?.start),
'nc-selected-week-end': isSameDate(date, selectedWeek?.end), 'nc-selected-week-end': isSameDate(date, selectedWeek?.end),
'rounded-md bg-brand-50 text-brand-500 nc-calendar-today ': isSameDate(date, dayjs()) && isDateInCurrentMonth(date), 'rounded-md text-brand-500 !font-semibold nc-calendar-today ':
isSameDate(date, dayjs()) && isDateInCurrentMonth(date),
'h-9 w-9': size === 'large', 'h-9 w-9': size === 'large',
'text-gray-500': date.get('day') === 0 || date.get('day') === 6,
'h-8 w-8': size === 'medium', 'h-8 w-8': size === 'medium',
'h-6 w-6 text-[10px]': size === 'small', 'h-6 w-6 text-[10px]': size === 'small',
}" }"
class="px-1 py-1 relative border-1 font-medium flex items-center cursor-pointer justify-center" class="px-1 py-1 relative border-1 font-medium flex items-center cursor-pointer justify-center"
data-testid="nc-calendar-date" data-testid="nc-calendar-date"
@dblclick="emitDblClick(date)"
@click="handleSelectDate(date)" @click="handleSelectDate(date)"
> >
<span <span
v-if="isActiveDate(date)" v-if="isActiveDate(date)"
:class="{ :class="{
'h-2 w-2': size === 'large', 'h-2 w-2': size === 'large',
'h-1 w-1': size === 'medium', 'h-1.5 w-1.5': size === 'medium',
'h-0.75 w-0.75': size === 'small', 'h-1.25 w-1.25 top-0.5 right-0.5': size === 'small',
'top-1 right-1': size !== 'small', 'top-1 right-1': size !== 'small',
'top-0.5 right-0.5': size === 'small',
'!border-white': isSelectedDate(date), '!border-white': isSelectedDate(date),
'border-brand-50': isSameDate(date, dayjs()), '!border-brand-50': isSameDate(date, dayjs()),
}" }"
class="absolute z-2 rounded-full border-2 border-white bg-brand-500" class="absolute z-2 border-1 rounded-full border-white bg-brand-500"
></span> ></span>
<span class="z-2"> <span class="z-2">
{{ date.get('date') }} {{ date.get('date') }}
@ -267,7 +276,7 @@ const paginate = (action: 'next' | 'prev') => {
.nc-selected-week:before { .nc-selected-week:before {
@apply absolute top-0 left-0 w-full h-full bg-gray-200; @apply absolute top-0 left-0 w-full h-full bg-gray-200;
content: ''; content: '';
width: 124%; width: 134%;
height: 100%; height: 100%;
} }

10
packages/nc-gui/components/nc/Divider.vue

@ -1,5 +1,13 @@
<script lang="ts" setup>
const props = defineProps<{
dividerClass?: string
}>()
const dividerClass = toRef(props, 'dividerClass')
</script>
<template> <template>
<a-divider class="nc-divider" /> <a-divider :class="dividerClass" class="nc-divider" />
</template> </template>
<style lang="scss"> <style lang="scss">

2
packages/nc-gui/components/nc/Modal.vue

@ -103,7 +103,7 @@ const slots = useSlots()
<div <div
v-if="slots.header" v-if="slots.header"
:class="{ :class="{
'border-b-1 border-gray-100': showSeparator, 'border-b-1 border-gray-200': showSeparator,
}" }"
class="flex pb-2 mb-2 text-lg font-medium" class="flex pb-2 mb-2 text-lg font-medium"
> >

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

@ -7,6 +7,7 @@ interface Props {
pageDate?: dayjs.Dayjs pageDate?: dayjs.Dayjs
isYearPicker?: boolean isYearPicker?: boolean
hideHeader?: boolean hideHeader?: boolean
hideCalendar?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -15,6 +16,7 @@ const props = withDefaults(defineProps<Props>(), {
pageDate: dayjs(), pageDate: dayjs(),
hideHeader: false, hideHeader: false,
isYearPicker: false, isYearPicker: false,
hideCalendar: false,
}) })
const emit = defineEmits(['update:selectedDate', 'update:pageDate']) const emit = defineEmits(['update:selectedDate', 'update:pageDate'])
@ -87,34 +89,42 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
</script> </script>
<template> <template>
<div class="px-3 pb-3 pt-2 flex flex-col"> <div class="flex flex-col">
<div v-if="!hideHeader" class="flex justify-between items-center"> <div v-if="!hideHeader" class="flex px-2 border-b-1 py-0.5 justify-between items-center">
<NcTooltip> <div class="flex">
<NcButton size="small" type="secondary" @click="paginate('prev')"> <NcTooltip>
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4" /> <NcButton class="!border-0" size="small" type="secondary" @click="paginate('prev')">
</NcButton> <component :is="iconMap.arrowLeft" class="h-4 w-4" />
<template #title> </NcButton>
<span>{{ $t('labels.previous') }}</span> <template #title>
</template> <span>{{ $t('labels.next') }}</span>
</NcTooltip> </template>
<span class="text-gray-700">{{ isYearPicker ? $t('labels.selectYear') : pageDate.year() }}</span> </NcTooltip>
<NcTooltip> </div>
<NcButton size="small" type="secondary" @click="paginate('next')">
<component :is="iconMap.doubleRightArrow" class="h-4 w-4" /> <span class="text-gray-700 font-semibold">{{
</NcButton> isYearPicker ? dayjs(selectedDate).year() : dayjs(pageDate).format('YYYY')
<template #title> }}</span>
<span>{{ $t('labels.next') }}</span> <div class="flex">
</template> <NcTooltip>
</NcTooltip> <NcButton class="!border-0" size="small" type="secondary" @click="paginate('next')">
<component :is="iconMap.arrowRight" class="h-4 w-4" />
</NcButton>
<template #title>
<span>{{ $t('labels.next') }}</span>
</template>
</NcTooltip>
</div>
</div> </div>
<div class="rounded-y-xl max-w-[350px]"> <div v-if="!hideCalendar" class="rounded-y-xl px-2.5 py-1 max-w-[350px]">
<div class="grid grid-cols-4 gap-2 py-3"> <div class="grid grid-cols-4 gap-2">
<template v-if="!isYearPicker"> <template v-if="!isYearPicker">
<span <span
v-for="(month, id) in months" v-for="(month, id) in months"
:key="id" :key="id"
:class="{ :class="{
'!bg-gray-200 !font-bold !text-brand-500': isMonthSelected(month), '!bg-gray-200 !text-brand-500 !font-bold ': isMonthSelected(month),
'!text-brand-500': dayjs().isSame(month, 'month'),
}" }"
class="h-9 rounded-lg flex items-center font-medium justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-900 cursor-pointer" class="h-9 rounded-lg flex items-center font-medium justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-900 cursor-pointer"
@click="selectedDate = month" @click="selectedDate = month"
@ -127,7 +137,8 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
v-for="(year, id) in years" v-for="(year, id) in years"
:key="id" :key="id"
:class="{ :class="{
'!bg-gray-200 !font-bold !text-brand-500': compareYear(year, selectedDate), '!bg-gray-200 !text-brand-500 !font-bold ': compareYear(year, selectedDate),
'!text-brand-500': dayjs().isSame(year, 'year'),
}" }"
class="h-9 rounded-lg flex items-center font-medium justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-900 cursor-pointer" class="h-9 rounded-lg flex items-center font-medium justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-900 cursor-pointer"
@click="selectedDate = year" @click="selectedDate = year"

2
packages/nc-gui/components/nc/Select.vue

@ -84,7 +84,7 @@ const onChange = (value: string) => {
height: fit-content; height: fit-content;
.ant-select-selector { .ant-select-selector {
box-shadow: 0px 5px 3px -2px rgba(0, 0, 0, 0.02), 0px 3px 1px -2px rgba(0, 0, 0, 0.06); box-shadow: 0px 5px 3px -2px rgba(0, 0, 0, 0.02), 0px 3px 1px -2px rgba(0, 0, 0, 0.06);
@apply border-1 border-gray-200 rounded-lg; @apply border-1 border-gray-200 rounded-lg !px-3;
} }
.ant-select-selection-item { .ant-select-selection-item {

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

@ -3,24 +3,18 @@ const { isGrid, isGallery, isKanban, isMap, isCalendar } = useSmartsheetStoreOrT
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
const { isViewsLoading } = storeToRefs(useViewsStore())
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const containerRef = ref<HTMLElement>() const { isViewsLoading } = storeToRefs(useViewsStore())
const isTab = ref(true)
const handleResize = () => { const containerRef = ref<HTMLElement>()
isTab.value = containerRef.value.offsetWidth > 810
}
onMounted(() => { const { width } = useElementSize(containerRef)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => { const isTab = computed(() => {
window.removeEventListener('resize', handleResize) if (!isCalendar.value) return false
return width.value > 1200
}) })
const { allowCSVDownload } = useSharedView() const { allowCSVDownload } = useSharedView()
@ -28,32 +22,40 @@ const { allowCSVDownload } = useSharedView()
<template> <template>
<div <div
v-if="!isMobileMode || !isCalendar" v-if="!isMobileMode"
ref="containerRef" ref="containerRef"
class="nc-table-toolbar relative py-1 px-2.25 xs:(px-1) flex gap-2 items-center border-b border-gray-200 overflow-hidden xs:(min-h-14) max-h-[var(--topbar-height)] min-h-[var(--topbar-height)] z-7" class="nc-table-toolbar relative px-3 xs:(px-1) flex gap-2 items-center border-b border-gray-200 overflow-hidden xs:(min-h-14) min-h-9 max-h-9 z-7"
> >
<template v-if="isViewsLoading"> <template v-if="isViewsLoading">
<a-skeleton-input :active="true" class="!w-44 !h-4 ml-2 !rounded overflow-hidden" /> <a-skeleton-input :active="true" class="!w-44 !h-4 ml-2 !rounded overflow-hidden" />
</template> </template>
<template v-else> <template v-else>
<LazySmartsheetToolbarMappedBy v-if="isMap" /> <div
<LazySmartsheetToolbarCalendarRange v-if="isCalendar" /> :class="{
'min-w-34/100': !isMobileMode && isLeftSidebarOpen && isCalendar,
'min-w-39/100': !isMobileMode && !isLeftSidebarOpen && isCalendar,
'gap-1': isCalendar,
}"
class="flex items-center gap-3"
>
<LazySmartsheetToolbarMappedBy v-if="isMap" />
<LazySmartsheetToolbarCalendarHeader v-if="isCalendar" />
<LazySmartsheetToolbarCalendarToday v-if="isCalendar" />
<LazySmartsheetToolbarFieldsMenu <LazySmartsheetToolbarCalendarRange v-if="isCalendar" />
v-if="isGrid || isGallery || isKanban || isMap || isCalendar"
:show-system-fields="false"
/>
<LazySmartsheetToolbarStackedBy v-if="isKanban" /> <LazySmartsheetToolbarFieldsMenu v-if="isGrid || isGallery || isKanban || isMap" :show-system-fields="false" />
<LazySmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery || isKanban || isMap || isCalendar" /> <LazySmartsheetToolbarStackedBy v-if="isKanban" />
<LazySmartsheetToolbarGroupByMenu v-if="isGrid" /> <LazySmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery || isKanban || isMap" />
<LazySmartsheetToolbarSortListMenu v-if="isGrid || isGallery || isKanban || isCalendar" /> <LazySmartsheetToolbarGroupByMenu v-if="isGrid" />
<div v-if="isCalendar && isTab" class="flex-1" /> <LazySmartsheetToolbarSortListMenu v-if="isGrid || isGallery || isKanban" />
<LazySmartsheetToolbarCalendarMode v-if="isCalendar" v-model:tab="isTab" /> </div>
<LazySmartsheetToolbarCalendarMode v-if="isCalendar && isTab" :tab="isTab" />
<template v-if="!isMobileMode"> <template v-if="!isMobileMode">
<LazySmartsheetToolbarRowHeight v-if="isGrid" /> <LazySmartsheetToolbarRowHeight v-if="isGrid" />
@ -65,6 +67,8 @@ const { allowCSVDownload } = useSharedView()
<div class="flex-1" /> <div class="flex-1" />
</template> </template>
<LazySmartsheetToolbarCalendarActiveView v-if="isCalendar" />
<LazySmartsheetToolbarSearchData <LazySmartsheetToolbarSearchData
v-if="isGrid || isGallery || isKanban" v-if="isGrid || isGallery || isKanban"
:class="{ :class="{
@ -72,6 +76,10 @@ const { allowCSVDownload } = useSharedView()
'w-full': isMobileMode, 'w-full': isMobileMode,
}" }"
/> />
<LazySmartsheetToolbarCalendarMode v-if="isCalendar && !isTab" :tab="isTab" />
<LazySmartsheetToolbarFieldsMenu v-if="isCalendar" :show-system-fields="false" />
<LazySmartsheetToolbarColumnFilterMenu v-if="isCalendar" />
</template> </template>
</div> </div>
</template> </template>

7
packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue

@ -183,6 +183,7 @@ const dropEvent = (event: DragEvent) => {
dragElement.value = null dragElement.value = null
} }
updateRowProperty(newRow, updateProperty, false) updateRowProperty(newRow, updateProperty, false)
$e('c:calendar:day:drag-record')
} }
} }
@ -202,7 +203,7 @@ const newRecord = () => {
<div <div
v-if="recordsAcrossAllRange.length" v-if="recordsAcrossAllRange.length"
ref="container" ref="container"
class="w-full relative h-[calc(100vh-10.8rem)] overflow-y-auto nc-scrollbar-md" class="w-full cursor-pointer relative h-[calc(100vh-10.8rem)] overflow-y-auto nc-scrollbar-md"
data-testid="nc-calendar-day-view" data-testid="nc-calendar-day-view"
@dblclick="newRecord" @dblclick="newRecord"
@drop="dropEvent" @drop="dropEvent"
@ -223,7 +224,7 @@ const newRecord = () => {
:resize="false" :resize="false"
color="blue" color="blue"
size="small" size="small"
@click="emit('expandRecord', record)" @click.prevent="emit('expandRecord', record)"
> >
<template v-for="(field, id) in fields" :key="id"> <template v-for="(field, id) in fields" :key="id">
<LazySmartsheetPlainCell <LazySmartsheetPlainCell
@ -244,7 +245,7 @@ const newRecord = () => {
<div <div
v-else v-else
ref="container" ref="container"
class="w-full h-full flex text-md font-bold text-gray-500 items-center justify-center" class="w-full h-full cursor-pointer flex text-md font-bold text-gray-500 items-center justify-center"
@drop="dropEvent" @drop="dropEvent"
@dblclick="newRecord" @dblclick="newRecord"
> >

9
packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue

@ -16,6 +16,8 @@ const {
showSideMenu, showSideMenu,
} = useCalendarViewStoreOrThrow() } = useCalendarViewStoreOrThrow()
const { $e } = useNuxtApp()
const container = ref<null | HTMLElement>(null) const container = ref<null | HTMLElement>(null)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
@ -672,6 +674,8 @@ const stopDrag = (event: MouseEvent) => {
if (!newRow) return if (!newRow) return
updateRowProperty(newRow, updateProperty, false) updateRowProperty(newRow, updateProperty, false)
$e('c:calendar:day:drag-record')
document.removeEventListener('mousemove', onDrag) document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
} }
@ -802,6 +806,7 @@ const dropEvent = (event: DragEvent) => {
dragElement.value = null dragElement.value = null
} }
updateRowProperty(newRow, updateProperty, false) updateRowProperty(newRow, updateProperty, false)
$e('c:calendar:day:drag-record')
} }
} }
@ -859,7 +864,7 @@ watch(
<template> <template>
<div <div
ref="container" ref="container"
class="w-full flex relative no-selection h-[calc(100vh-10rem)] overflow-y-auto nc-scrollbar-md" class="w-full flex relative no-selection h-[calc(100vh-5.3rem)] overflow-y-auto nc-scrollbar-md"
data-testid="nc-calendar-day-view" data-testid="nc-calendar-day-view"
@drop="dropEvent" @drop="dropEvent"
> >
@ -872,7 +877,7 @@ watch(
@click="selectHour(hour)" @click="selectHour(hour)"
@dblclick="newRecord(hour)" @dblclick="newRecord(hour)"
> >
<div class="w-16 border-b-0 pr-3 pl-2 text-right text-xs text-gray-400 font-semibold h-13"> <div class="w-16 border-b-0 pr-2 pl-2 text-right text-xs text-gray-400 font-semibold h-13">
{{ dayjs(hour).format('hh a') }} {{ dayjs(hour).format('hh a') }}
</div> </div>
</div> </div>

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

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
const emit = defineEmits(['newRecord', 'expandRecord']) const emit = defineEmits(['newRecord', 'expandRecord'])
@ -9,6 +10,7 @@ const {
selectedMonth, selectedMonth,
formattedData, formattedData,
formattedSideBarData, formattedSideBarData,
calDataType,
sideBarFilterOption, sideBarFilterOption,
displayField, displayField,
calendarRange, calendarRange,
@ -16,6 +18,8 @@ const {
updateRowProperty, updateRowProperty,
} = useCalendarViewStoreOrThrow() } = useCalendarViewStoreOrThrow()
const { $e } = useNuxtApp()
const isMondayFirst = ref(true) const isMondayFirst = ref(true)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
@ -115,7 +119,7 @@ const recordsToDisplay = computed<{
const perHeight = gridContainerHeight.value / dates.value.length const perHeight = gridContainerHeight.value / dates.value.length
const perRecordHeight = 24 const perRecordHeight = 24
const spaceBetweenRecords = 26 const spaceBetweenRecords = 27
// This object is used to keep track of the number of records in a day // This object is used to keep track of the number of records in a day
// The key is the date in the format YYYY-MM-DD // The key is the date in the format YYYY-MM-DD
@ -357,19 +361,28 @@ const calculateNewRow = (event: MouseEvent, updateSideBar?: boolean, skipChangeC
const fromCol = dragRecord.value?.rowMeta.range?.fk_from_col const fromCol = dragRecord.value?.rowMeta.range?.fk_from_col
const toCol = dragRecord.value?.rowMeta.range?.fk_to_col const toCol = dragRecord.value?.rowMeta.range?.fk_to_col
if (!fromCol) return { newRow: null, updateProperty: [] }
const week = Math.floor(percentY * dates.value.length) const week = Math.floor(percentY * dates.value.length)
const day = Math.floor(percentX * 7) const day = Math.floor(percentX * 7)
const newStartDate = dates.value[week] ? dayjs(dates.value[week][day]) : null let newStartDate = dates.value[week] ? dayjs(dates.value[week][day]) : null
if (!newStartDate) return { newRow: null, updateProperty: [] } if (!newStartDate) return { newRow: null, updateProperty: [] }
const fromDate = dayjs(dragRecord.value.row[fromCol.title!])
newStartDate = newStartDate.add(fromDate.hour(), 'hour').add(fromDate.minute(), 'minute').add(fromDate.second(), 'second')
let endDate let endDate
const newRow = { const newRow = {
...dragRecord.value, ...dragRecord.value,
row: { row: {
...dragRecord.value?.row, ...dragRecord.value?.row,
[fromCol!.title!]: dayjs(newStartDate).utc().format('YYYY-MM-DD HH:mm:ssZ'), [fromCol!.title!]:
calDataType.value === UITypes.Date
? dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ')
: dayjs(newStartDate).utc().format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
@ -389,7 +402,10 @@ const calculateNewRow = (event: MouseEvent, updateSideBar?: boolean, skipChangeC
endDate = newStartDate.clone() endDate = newStartDate.clone()
} }
newRow.row[toCol!.title!] = dayjs(endDate).utc().format('YYYY-MM-DD HH:mm:ssZ') newRow.row[toCol!.title!] =
calDataType.value === UITypes.Date
? dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
: dayjs(endDate).utc().format('YYYY-MM-DD HH:mm:ssZ')
updateProperty.push(toCol!.title!) updateProperty.push(toCol!.title!)
} }
@ -544,6 +560,8 @@ const stopDrag = (event: MouseEvent) => {
updateRowProperty(newRow, updateProperty, false) updateRowProperty(newRow, updateProperty, false)
focusedDate.value = null focusedDate.value = null
$e('c:calendar:month:drag-record')
document.removeEventListener('mousemove', onDrag) document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
} }
@ -611,6 +629,7 @@ const dropEvent = (event: DragEvent) => {
dragElement.value = null dragElement.value = null
} }
updateRowProperty(newRow, updateProperty, false) updateRowProperty(newRow, updateProperty, false)
$e('c:calendar:day:drag-record')
} }
} }
@ -655,7 +674,7 @@ const addRecord = (date: dayjs.Dayjs) => {
<div <div
v-for="(day, index) in days" v-for="(day, index) in days"
:key="index" :key="index"
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-500" class="text-center bg-gray-50 py-1 border-b-1 border-r-1 last:border-r-0 border-gray-200 font-regular uppercase text-xs text-gray-500"
> >
{{ day }} {{ day }}
</div> </div>
@ -667,7 +686,8 @@ const addRecord = (date: dayjs.Dayjs) => {
'grid-rows-6': dates.length === 6, 'grid-rows-6': dates.length === 6,
'grid-rows-7': dates.length === 7, 'grid-rows-7': dates.length === 7,
}" }"
class="grid h-full pb-7.5" class="grid"
style="height: calc(100% - 1.59rem)"
@drop="dropEvent" @drop="dropEvent"
> >
<div v-for="(week, weekIndex) in dates" :key="weekIndex" class="grid grid-cols-7 grow" data-testid="nc-calendar-month-week"> <div v-for="(week, weekIndex) in dates" :key="weekIndex" class="grid grid-cols-7 grow" data-testid="nc-calendar-month-week">
@ -807,7 +827,7 @@ const addRecord = (date: dayjs.Dayjs) => {
@resize-start="onResizeStart" @resize-start="onResizeStart"
@dblclick.stop="emit('expandRecord', record)" @dblclick.stop="emit('expandRecord', record)"
> >
<template #time> <template v-if="calDataType === UITypes.DateTime" #time>
<span class="text-xs font-medium text-gray-400"> <span class="text-xs font-medium text-gray-400">
{{ dayjs(record.row[record.rowMeta.range?.fk_from_col!.title!]).format('h:mma').slice(0, -1) }} {{ dayjs(record.row[record.rowMeta.range?.fk_from_col!.title!]).format('h:mma').slice(0, -1) }}
</span> </span>

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

@ -13,7 +13,11 @@ const INFINITY_SCROLL_THRESHOLD = 100
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const { appInfo } = useGlobal() const { $e } = useNuxtApp()
const { appInfo, isMobileMode } = useGlobal()
const { height } = useWindowSize()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -36,6 +40,7 @@ const {
loadMoreSidebarData, loadMoreSidebarData,
searchQuery, searchQuery,
sideBarFilterOption, sideBarFilterOption,
showSideMenu,
} = useCalendarViewStoreOrThrow() } = useCalendarViewStoreOrThrow()
const sideBarListRef = ref<VNodeRef | null>(null) const sideBarListRef = ref<VNodeRef | null>(null)
@ -269,82 +274,119 @@ const newRecord = () => {
emit('newRecord', { row, oldRow: {}, rowMeta: { new: true } }) emit('newRecord', { row, oldRow: {}, rowMeta: { new: true } })
} }
const width = ref(0) const toggleSideMenu = () => {
$e('c:calendar:toggle-sidebar', showSideMenu.value)
showSideMenu.value = !showSideMenu.value
}
const showSearch = ref(false)
const searchRef = ref()
const widthListener = () => { const clickSearch = () => {
width.value = window.innerWidth showSearch.value = true
nextTick(() => {
searchRef.value?.focus()
})
} }
onMounted(() => { const toggleSearch = () => {
window.addEventListener('resize', widthListener) if (!searchQuery.value.length) {
}) showSearch.value = false
} else {
searchRef.value?.blur()
}
}
onUnmounted(() => { useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
window.removeEventListener('resize', widthListener) const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (cmdOrCtrl) {
switch (e.key.toLowerCase()) {
case 'f':
e.preventDefault()
clickSearch()
break
}
}
}) })
onClickOutside(searchRef, toggleSearch)
</script> </script>
<template> <template>
<NcTooltip
:class="{
'right-2': !showSideMenu,
'right-74': showSideMenu,
}"
class="absolute transition-all ease-in-out top-2 z-30"
>
<template #title> {{ $t('activity.toggleSidebar') }}</template>
<NcButton v-if="!isMobileMode" data-testid="nc-calendar-side-bar-btn" size="small" type="secondary" @click="toggleSideMenu">
<component :is="iconMap.sidebar" class="h-4 w-4 text-gray-600 transition-all" />
</NcButton>
</NcTooltip>
<div <div
:class="{ :class="{
'!w-0': !props.visible, '!w-0 hidden': !props.visible,
'min-w-[324px]': width > 1440 && props.visible, 'nc-calendar-side-menu-open block !min-w-[288px]': props.visible,
'min-w-[288px]': width <= 1440 && props.visible,
'nc-calendar-side-menu-open': props.visible,
}" }"
class="h-full border-l-1 border-gray-200 transition-all" class="h-full relative border-l-1 border-gray-200 transition-all"
data-testid="nc-calendar-side-menu" data-testid="nc-calendar-side-menu"
> >
<div <div class="flex flex-col">
:class="{
'!hidden': width <= 1440,
'px-3 py-3 ': activeCalendarView === ('day' as const) || activeCalendarView === ('week' as const),
}"
class="flex flex-col"
>
<NcDateWeekSelector <NcDateWeekSelector
v-if="activeCalendarView === ('day' as const)" v-if="activeCalendarView === ('day' as const)"
v-model:active-dates="activeDates" v-model:active-dates="activeDates"
v-model:page-date="pageDate" v-model:page-date="pageDate"
v-model:selected-date="selectedDate" v-model:selected-date="selectedDate"
size="medium"
:hide-calendar="height < 700"
/> />
<NcDateWeekSelector <NcDateWeekSelector
v-else-if="activeCalendarView === ('week' as const)" v-else-if="activeCalendarView === ('week' as const)"
v-model:active-dates="activeDates" v-model:active-dates="activeDates"
v-model:page-date="pageDate" v-model:page-date="pageDate"
v-model:selected-week="selectedDateRange" v-model:selected-week="selectedDateRange"
:hide-calendar="height < 700"
is-week-picker is-week-picker
size="medium"
/> />
<NcMonthYearSelector <NcMonthYearSelector
v-else-if="activeCalendarView === ('month' as const)" v-else-if="activeCalendarView === ('month' as const)"
v-model:page-date="pageDate" v-model:page-date="pageDate"
v-model:selected-date="selectedMonth" v-model:selected-date="selectedMonth"
:hide-calendar="height < 700"
size="medium"
/> />
<NcMonthYearSelector <NcMonthYearSelector
v-else-if="activeCalendarView === ('year' as const)" v-else-if="activeCalendarView === ('year' as const)"
v-model:page-date="pageDate" v-model:page-date="pageDate"
v-model:selected-date="selectedDate" v-model:selected-date="selectedDate"
:hide-calendar="height < 700"
is-year-picker is-year-picker
size="medium"
/> />
</div> </div>
<div <div
:class="{ :class="{
'!border-t-0': width <= 1440, '!border-t-0 ': height < 700,
'pt-6': height >= 700,
}" }"
class="border-t-1 border-gray-200 relative flex flex-col gap-y-4 pt-3" class="border-t-1 !pt-3 border-gray-200 relative flex flex-col gap-y-3"
> >
<div class="flex px-4 items-center gap-3"> <div class="flex px-4 items-center gap-3">
<span class="capitalize text-base font-bold">{{ $t('objects.records') }}</span> <span class="capitalize font-medium text-gray-700">{{ $t('objects.records') }}</span>
<NcSelect v-model:value="sideBarFilterOption" class="w-full !text-gray-600" data-testid="nc-calendar-sidebar-filter"> <NcSelect v-model:value="sideBarFilterOption" class="w-full !text-gray-600" data-testid="nc-calendar-sidebar-filter">
<a-select-option v-for="option in options" :key="option.value" :value="option.value" class="!text-gray-600"> <a-select-option v-for="option in options" :key="option.value" :value="option.value" class="!text-gray-600">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center w-full justify-between gap-2">
<div class="truncate flex-1"> <div class="truncate">
<NcTooltip :title="option.label" placement="top" show-on-truncate-only> <NcTooltip :title="option.label" placement="top" show-on-truncate-only>
<template #title>{{ option.label }}</template> <template #title>{{ option.label }}</template>
{{ option.label }} {{ option.label }}
</NcTooltip> </NcTooltip>
</div> </div>
<component <component
:is="iconMap.check" :is="iconMap.check"
v-if="sideBarFilterOption === option.value" v-if="sideBarFilterOption === option.value"
@ -355,47 +397,87 @@ onUnmounted(() => {
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
</div> </div>
<div class="flex px-4 items-center gap-2"> <div
:class="{
hidden: !showSearch,
}"
class="mx-4"
>
<a-input <a-input
ref="searchRef"
v-model:value="searchQuery.value" v-model:value="searchQuery.value"
:class="{ :class="{
'!border-brand-500': searchQuery.value.length > 0, '!border-brand-500': searchQuery.value.length > 0,
'!hidden': !showSearch,
}" }"
class="!rounded-lg !h-8 !placeholder:text-gray-500 !border-gray-200 !px-4" class="!rounded-lg !h-8 !placeholder:text-gray-500 !border-gray-200 !px-4"
data-testid="nc-calendar-sidebar-search" data-testid="nc-calendar-sidebar-search"
placeholder="Search records" placeholder="Search records"
@keydown.esc="toggleSearch"
> >
<template #prefix> <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> </template>
</a-input> </a-input>
</div> </div>
<div class="mx-4 gap-2 flex items-center">
<NcButton
v-if="!showSearch"
data-testid="nc-calendar-sidebar-search-btn"
size="small"
class="!h-7"
type="secondary"
@click="clickSearch"
>
<component :is="iconMap.search" />
</NcButton>
<LazySmartsheetToolbarSortListMenu />
<div class="flex-1" />
<div
v-if="calendarRange?.length && !isCalendarMetaLoading"
:ref="sideBarListRef"
:class="{
'!h-[calc(100vh-13.5rem)]': width <= 1440,
'h-[calc(100vh-36.2rem)]': activeCalendarView === ('day' as const) || activeCalendarView === ('week' as const) && width >= 1440,
'h-[calc(100vh-25.1rem)]': activeCalendarView === ('month' as const) || activeCalendarView === ('year' as const) && width >= 1440,
}"
class="nc-scrollbar-md pl-4 pr-4 overflow-y-auto"
data-testid="nc-calendar-side-menu-list"
@scroll="sideBarListScrollHandle"
>
<NcButton <NcButton
v-if="isUIAllowed('dataEdit') && props.visible" v-if="isUIAllowed('dataEdit') && props.visible"
v-e="['c:calendar:calendar-sidemenu-new-record-btn']" v-e="['c:calendar:calendar-sidemenu-new-record-btn']"
class="!absolute right-5 !border-brand-500 bottom-5 !h-12 !w-12"
data-testid="nc-calendar-side-menu-new-btn" data-testid="nc-calendar-side-menu-new-btn"
class="!h-7"
size="small"
type="secondary" type="secondary"
@click="newRecord" @click="newRecord"
> >
<div class="px-4 flex items-center gap-2 justify-center"> <div class="flex items-center gap-2">
<component :is="iconMap.plus" class="h-6 w-6 text-lg text-brand-500" /> <component :is="iconMap.plus" />
Record
</div> </div>
</NcButton> </NcButton>
</div>
<div
v-if="calendarRange?.length && !isCalendarMetaLoading"
:ref="sideBarListRef"
:class="{
'!h-[calc(100svh-22.15rem)]':
height > 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && !showSearch,
'!h-[calc(100svh-24.9rem)]':
height > 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && showSearch,
'!h-[calc(100svh-13.85rem)]':
height <= 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && !showSearch,
'!h-[calc(100svh-16.61rem)]':
height <= 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && showSearch,
'!h-[calc(100svh-30.15rem)]':
height > 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && !showSearch,
' !h-[calc(100svh-32.9rem)]':
height > 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && showSearch,
'!h-[calc(100svh-13.8rem)]':
height <= 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && !showSearch,
'!h-[calc(100svh-16.6rem)]':
height <= 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && showSearch,
}"
class="nc-scrollbar-md pl-4 pr-4 overflow-y-auto"
data-testid="nc-calendar-side-menu-list"
@scroll="sideBarListScrollHandle"
>
<div v-if="renderData.length === 0 || isSidebarLoading" class="flex h-full items-center justify-center"> <div v-if="renderData.length === 0 || isSidebarLoading" class="flex h-full items-center justify-center">
<GeneralLoader v-if="isSidebarLoading" size="large" /> <GeneralLoader v-if="isSidebarLoading" size="large" />
@ -431,8 +513,8 @@ onUnmounted(() => {
" "
color="blue" color="blue"
data-testid="nc-sidebar-record-card" data-testid="nc-sidebar-record-card"
@dragstart="dragStart($event, record)"
@click="emit('expandRecord', record)" @click="emit('expandRecord', record)"
@dragstart="dragStart($event, record)"
@dragover.prevent @dragover.prevent
> >
<template v-if="!isRowEmpty(record, displayField)"> <template v-if="!isRowEmpty(record, displayField)">
@ -446,10 +528,23 @@ onUnmounted(() => {
<template v-else-if="isCalendarMetaLoading"> <template v-else-if="isCalendarMetaLoading">
<div <div
:class="{ :class="{
'!h-[calc(100vh-13.5rem)]': width <= 1440, '!h-[calc(100svh-22.15rem)]':
'h-[calc(100vh-36.2rem)]': activeCalendarView === ('day' as const) || activeCalendarView === ('week' as const) && width >= 1440, height > 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && !showSearch,
'h-[calc(100vh-25.1rem)]': activeCalendarView === ('month' as const) || activeCalendarView === ('year' as const) && width >= 1440, '!h-[calc(100svh-24.9rem)]':
}" height > 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && showSearch,
'!h-[calc(100svh-13.85rem)]':
height <= 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && !showSearch,
'!h-[calc(100svh-16.61rem)]':
height <= 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && showSearch,
'!h-[calc(100svh-30.15rem)]':
height > 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && !showSearch,
' !h-[calc(100svh-32.9rem)]':
height > 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && showSearch,
'!h-[calc(100svh-13.8rem)]':
height <= 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && !showSearch,
'!h-[calc(100svh-16.6rem)]':
height <= 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && showSearch,
}"
class="flex items-center justify-center h-full" class="flex items-center justify-center h-full"
> >
<GeneralLoader size="xlarge" /> <GeneralLoader size="xlarge" />
@ -458,10 +553,23 @@ onUnmounted(() => {
<div <div
v-else v-else
:class="{ :class="{
'!h-[calc(100vh-13.5rem)]': width <= 1440, '!h-[calc(100svh-22.15rem)]':
'h-[calc(100vh-36.2rem)]': activeCalendarView === ('day' as const) || activeCalendarView === ('week' as const) && width >= 1440, height > 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && !showSearch,
'h-[calc(100vh-25.1rem)]': activeCalendarView === ('month' as const) || activeCalendarView === ('year' as const) && width >= 1440, '!h-[calc(100svh-24.9rem)]':
}" height > 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && showSearch,
'!h-[calc(100svh-13.85rem)]':
height <= 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && !showSearch,
'!h-[calc(100svh-16.61rem)]':
height <= 700 && (activeCalendarView === 'month' || activeCalendarView === 'year') && showSearch,
'!h-[calc(100svh-30.15rem)]':
height > 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && !showSearch,
' !h-[calc(100svh-32.9rem)]':
height > 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && showSearch,
'!h-[calc(100svh-13.8rem)]':
height <= 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && !showSearch,
'!h-[calc(100svh-16.6rem)]':
height <= 700 && (activeCalendarView === 'day' || activeCalendarView === 'week') && showSearch,
}"
class="flex items-center justify-center h-full" class="flex items-center justify-center h-full"
> >
{{ $t('activity.noRange') }} {{ $t('activity.noRange') }}

7
packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue

@ -526,6 +526,7 @@ const dropEvent = (event: DragEvent) => {
dragElement.value = null dragElement.value = null
} }
updateRowProperty(newRow, updateProperty, false) updateRowProperty(newRow, updateProperty, false)
$e('c:calendar:day:drag-record')
} }
} }
@ -557,7 +558,9 @@ const addRecord = (date: dayjs.Dayjs) => {
:class="{ :class="{
'!border-brand-500 !border-b-gray-200': dayjs(date).isSame(selectedDate, 'day'), '!border-brand-500 !border-b-gray-200': dayjs(date).isSame(selectedDate, 'day'),
}" }"
class="w-1/7 text-center text-sm text-gray-500 w-full py-1 border-gray-200 border-l-gray-50 border-t-gray-50 last:border-r-0 border-1 bg-gray-50" class="w-1/7 cursor-pointer text-center font-regular uppercase text-xs text-gray-500 w-full py-1 border-gray-200 border-l-gray-50 border-t-gray-50 last:border-r-0 border-1 bg-gray-50"
@click="selectDate(date)"
@dblclick="addRecord(date)"
> >
{{ dayjs(date).format('DD ddd') }} {{ dayjs(date).format('DD ddd') }}
</div> </div>
@ -570,7 +573,7 @@ const addRecord = (date: dayjs.Dayjs) => {
'!border-1 !border-t-0 border-brand-500': dayjs(date).isSame(selectedDate, 'day'), '!border-1 !border-t-0 border-brand-500': dayjs(date).isSame(selectedDate, 'day'),
'!bg-gray-50': date.get('day') === 0 || date.get('day') === 6, '!bg-gray-50': date.get('day') === 0 || date.get('day') === 6,
}" }"
class="flex flex-col border-r-1 min-h-[100vh] last:border-r-0 items-center w-1/7" class="flex cursor-pointer flex-col border-r-1 min-h-[100vh] last:border-r-0 items-center w-1/7"
data-testid="nc-calendar-week-day" data-testid="nc-calendar-week-day"
@click="selectDate(date)" @click="selectDate(date)"
@dblclick="addRecord(date)" @dblclick="addRecord(date)"

18
packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue

@ -17,6 +17,8 @@ const {
showSideMenu, showSideMenu,
} = useCalendarViewStoreOrThrow() } = useCalendarViewStoreOrThrow()
const { $e } = useNuxtApp()
const container = ref<null | HTMLElement>(null) const container = ref<null | HTMLElement>(null)
const scrollContainer = ref<null | HTMLElement>(null) const scrollContainer = ref<null | HTMLElement>(null)
@ -181,7 +183,6 @@ const getMaxOverlaps = ({
const dayIndex = row.rowMeta.dayIndex const dayIndex = row.rowMeta.dayIndex
const overlapIndex = columnArray[dayIndex].findIndex((column) => column.findIndex((r) => r.rowMeta.id === id) !== -1) + 1 const overlapIndex = columnArray[dayIndex].findIndex((column) => column.findIndex((r) => r.rowMeta.id === id) !== -1) + 1
const dfs = (id: string): number => { const dfs = (id: string): number => {
visited.add(id) visited.add(id)
let maxOverlaps = 1 let maxOverlaps = 1
@ -194,6 +195,7 @@ const getMaxOverlaps = ({
} }
} }
} }
return maxOverlaps return maxOverlaps
} }
@ -476,7 +478,11 @@ const recordsAcrossAllRange = computed<{
} }
} }
for (const record of recordsToDisplay) { for (const record of recordsToDisplay) {
const { maxOverlaps, overlapIndex } = getMaxOverlaps({ const {
maxOverlaps,
overlapIndex,
dayIndex: tDayIndex,
} = getMaxOverlaps({
row: record, row: record,
columnArray, columnArray,
graph: graph.get(record.rowMeta.dayIndex!) ?? new Map(), graph: graph.get(record.rowMeta.dayIndex!) ?? new Map(),
@ -749,6 +755,7 @@ const stopDrag = (event: MouseEvent) => {
if (newRow) { if (newRow) {
updateRowProperty(newRow, updatedProperty, false) updateRowProperty(newRow, updatedProperty, false)
} }
$e('c:calendar:week:drag-record')
document.removeEventListener('mousemove', onDrag) document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
@ -811,6 +818,7 @@ const dropEvent = (event: DragEvent) => {
if (newRow) { if (newRow) {
updateRowProperty(newRow, updatedProperty, false) updateRowProperty(newRow, updatedProperty, false)
$e('c:calendar:day:drag-record')
} }
} }
} }
@ -865,7 +873,7 @@ watch(
<template> <template>
<div <div
ref="scrollContainer" ref="scrollContainer"
class="h-[calc(100vh-9.9rem)] prevent-select relative flex w-full overflow-y-auto nc-scrollbar-md" class="h-[calc(100vh-5.4rem)] prevent-select relative flex w-full overflow-y-auto nc-scrollbar-md"
data-testid="nc-calendar-week-view" data-testid="nc-calendar-week-view"
@drop="dropEvent" @drop="dropEvent"
> >
@ -876,7 +884,7 @@ watch(
:class="{ :class="{
'text-brand-500': date[0].isSame(dayjs(), 'date'), 'text-brand-500': date[0].isSame(dayjs(), 'date'),
}" }"
class="w-1/7 text-center text-sm text-gray-500 w-full py-1 border-gray-200 last:border-r-0 border-b-1 border-l-1 border-r-0 bg-gray-50" class="w-1/7 text-center font-regular uppercase text-xs text-gray-500 w-full py-1 border-gray-200 last:border-r-0 border-b-1 border-l-1 border-r-0 bg-gray-50"
> >
{{ dayjs(date[0]).format('DD ddd') }} {{ dayjs(date[0]).format('DD ddd') }}
</div> </div>
@ -885,7 +893,7 @@ watch(
<div <div
v-for="(hour, index) in datesHours[0]" v-for="(hour, index) in datesHours[0]"
:key="index" :key="index"
class="h-13 first:mt-0 pt-7.1 nc-calendar-day-hour text-center font-semibold text-xs text-gray-400 py-1" class="h-13 first:mt-0 pt-7.1 nc-calendar-day-hour text-right pr-2 font-semibold text-xs text-gray-400 py-1"
> >
{{ hour.format('hh a') }} {{ hour.format('hh a') }}
</div> </div>

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

@ -1,5 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
const { selectedDate, activeDates } = useCalendarViewStoreOrThrow() import type dayjs from 'dayjs'
const { selectedDate, activeDates, activeCalendarView } = useCalendarViewStoreOrThrow()
const months = computed(() => { const months = computed(() => {
const months = [] const months = []
@ -25,6 +27,11 @@ const handleResize = () => {
} }
} }
const changeView = (date: dayjs.Dayjs) => {
selectedDate.value = date
activeCalendarView.value = 'day'
}
onMounted(() => { onMounted(() => {
handleResize() handleResize()
}) })
@ -33,18 +40,25 @@ watch(width, handleResize)
</script> </script>
<template> <template>
<div ref="calendarContainer" class="overflow-auto flex my-3 justify-center nc-scrollbar-md"> <div ref="calendarContainer" class="overflow-auto flex my-2 justify-center nc-scrollbar-md">
<div class="grid grid-cols-4 justify-items-center gap-3" data-testid="nc-calendar-year-view"> <div
:class="{
'!gap-12': size === 'large',
}"
class="grid grid-cols-4 justify-items-center gap-6 scale-1"
data-testid="nc-calendar-year-view"
>
<NcDateWeekSelector <NcDateWeekSelector
v-for="(_, index) in months" v-for="(_, index) in months"
:key="index" :key="index"
v-model:active-dates="activeDates" v-model:active-dates="activeDates"
v-model:page-date="months[index]" v-model:page-date="months[index]"
v-model:selected-date="selectedDate" v-model:selected-date="selectedDate"
class="nc-year-view-calendar"
:size="size" :size="size"
class="nc-year-view-calendar"
data-testid="nc-calendar-year-view-month-selector" data-testid="nc-calendar-year-view-month-selector"
disable-pagination disable-pagination
@dbl-click="changeView"
/> />
</div> </div>
</div> </div>
@ -53,11 +67,7 @@ watch(width, handleResize)
<style lang="scss" scoped> <style lang="scss" scoped>
.nc-year-view-calendar { .nc-year-view-calendar {
:deep(.nc-date-week-header) { :deep(.nc-date-week-header) {
@apply !bg-gray-100 border-x-1 border-t-1; @apply border-gray-200;
}
:deep(.nc-date-week-grid-wrapper) {
@apply !border-x-1 border-b-1 rounded-b-lg;
} }
} }
</style> </style>

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

@ -1,8 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from 'dayjs'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import type { Row as RowType } from '#imports' import type { Row as RowType } from '#imports'
const { $e } = useNuxtApp()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
const view = inject(ActiveViewInj, ref()) const view = inject(ActiveViewInj, ref())
@ -32,18 +33,10 @@ const {
loadSidebarData, loadSidebarData,
isCalendarDataLoading, isCalendarDataLoading,
isCalendarMetaLoading, isCalendarMetaLoading,
selectedDate,
selectedMonth,
activeDates,
pageDate,
fetchActiveDates, fetchActiveDates,
showSideMenu, showSideMenu,
selectedDateRange,
paginateCalendarView,
} = useCalendarViewStoreOrThrow() } = useCalendarViewStoreOrThrow()
const calendarRangeDropdown = ref(false)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -86,8 +79,7 @@ const expandRecord = (row: RowType, state?: Record<string, any>) => {
} }
const newRecord = (row: RowType) => { const newRecord = (row: RowType) => {
// TODO: The default values has to be filled based on the active calendar view $e('c:calendar:new-record', activeCalendarView.value)
// and selected sidebar filter option
expandRecord({ expandRecord({
row: { row: {
...rowDefaultData(meta.value?.columns), ...rowDefaultData(meta.value?.columns),
@ -119,153 +111,11 @@ reloadViewDataHook?.on(async (params: void | { shouldShowLoading?: boolean }) =>
fetchActiveDates(), fetchActiveDates(),
]) ])
}) })
const goToToday = () => {
selectedDate.value = dayjs()
pageDate.value = dayjs()
selectedMonth.value = dayjs()
selectedDateRange.value = {
start: dayjs().startOf('week'),
end: dayjs().endOf('week'),
}
document?.querySelector('.nc-calendar-today')?.scrollIntoView({
behavior: 'smooth',
block: 'center',
})
}
const headerText = computed(() => {
switch (activeCalendarView.value) {
case 'day':
return dayjs(selectedDate.value).format('D MMMM YYYY')
case 'week':
if (selectedDateRange.value.start.isSame(selectedDateRange.value.end, 'month')) {
return `${selectedDateRange.value.start.format('D')} - ${selectedDateRange.value.end.format('D MMM YY')}`
} else if (selectedDateRange.value.start.isSame(selectedDateRange.value.end, 'year')) {
return `${selectedDateRange.value.start.format('D MMM')} - ${selectedDateRange.value.end.format('D MMM YY')}`
} else {
return `${selectedDateRange.value.start.format('D MMM YY')} - ${selectedDateRange.value.end.format('D MMM YY')}`
}
case 'month':
return dayjs(selectedMonth.value).format('MMMM YYYY')
case 'year':
return dayjs(selectedDate.value).format('YYYY')
}
})
</script> </script>
<template> <template>
<div class="flex h-full flex-row" data-testid="nc-calendar-wrapper"> <div class="flex h-full relative flex-row" data-testid="nc-calendar-wrapper">
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class="flex justify-between p-2 items-center border-b-1 border-gray-200" data-testid="nc-calendar-topbar">
<div class="flex justify-start gap-3 items-center">
<NcTooltip>
<template #title> {{ $t('labels.previous') }}</template>
<NcButton
v-e="`['c:calendar:calendar-${activeCalendarView}-prev-btn']`"
data-testid="nc-calendar-prev-btn"
size="small"
type="secondary"
@click="paginateCalendarView('prev')"
>
<component :is="iconMap.doubleLeftArrow" class="h-4 w-4" />
</NcButton>
</NcTooltip>
<NcDropdown v-model:visible="calendarRangeDropdown" :auto-close="false" :trigger="['click']">
<NcButton :class="{ '!w-22': activeCalendarView === 'year' }" class="w-45" full-width size="small" type="secondary">
<div class="flex px-2 w-full items-center justify-between">
<div class="flex gap-1 text-brand-500" data-testid="nc-calendar-active-date">
<span class="font-bold text-center">{{
activeCalendarView === 'month' ? headerText.split(' ')[0] : headerText
}}</span>
<span v-if="activeCalendarView === 'month'">
{{ ` ${headerText.split(' ')[1]}` }}
</span>
</div>
<component :is="iconMap.arrowDown" class="h-4 w-4 text-gray-700" />
</div>
</NcButton>
<template #overlay>
<div
v-if="calendarRangeDropdown"
:class="{
'px-4 pt-3 pb-4 ': activeCalendarView === 'week' || activeCalendarView === 'day',
}"
class="min-w-[22.1rem]"
@click.stop
>
<NcDateWeekSelector
v-if="activeCalendarView === ('day' as const)"
v-model:active-dates="activeDates"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
/>
<NcDateWeekSelector
v-else-if="activeCalendarView === ('week' as const)"
v-model:active-dates="activeDates"
v-model:page-date="pageDate"
v-model:selected-week="selectedDateRange"
is-week-picker
/>
<NcMonthYearSelector
v-else-if="activeCalendarView === ('month' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedMonth"
/>
<NcMonthYearSelector
v-else-if="activeCalendarView === ('year' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
is-year-picker
/>
</div>
</template>
</NcDropdown>
<NcTooltip>
<template #title> {{ $t('labels.next') }}</template>
<NcButton
v-e="`['c:calendar:calendar-${activeCalendarView}-next-btn']`"
data-testid="nc-calendar-next-btn"
size="small"
type="secondary"
@click="paginateCalendarView('next')"
>
<component :is="iconMap.doubleRightArrow" class="h-4 w-4" />
</NcButton>
</NcTooltip>
<NcButton
v-e="`['c:calendar:calendar-${activeCalendarView}-today-btn']`"
data-testid="nc-calendar-today-btn"
size="small"
type="secondary"
@click="goToToday"
>
<span class="text-gray-600 !text-sm">
{{ $t('activity.goToToday') }}
</span>
</NcButton>
<span class="opacity-0" data-testid="nc-active-calendar-view">
{{ activeCalendarView }}
</span>
</div>
<NcTooltip>
<template #title> {{ $t('activity.toggleSidebar') }}</template>
<NcButton
v-if="!isMobileMode"
v-e="`['c:calendar:calendar-${activeCalendarView}-toggle-sidebar']`"
data-testid="nc-calendar-side-bar-btn"
size="small"
type="secondary"
@click="showSideMenu = !showSideMenu"
>
<component :is="iconMap.sidebar" class="h-4 w-4 text-gray-600 transition-all" />
</NcButton>
</NcTooltip>
</div>
<template v-if="calendarRange?.length && !isCalendarMetaLoading"> <template v-if="calendarRange?.length && !isCalendarMetaLoading">
<LazySmartsheetCalendarYearView v-if="activeCalendarView === 'year'" /> <LazySmartsheetCalendarYearView v-if="activeCalendarView === 'year'" />
<template v-if="!isCalendarDataLoading"> <template v-if="!isCalendarDataLoading">

11
packages/nc-gui/components/smartsheet/toolbar/Calendar/ActiveView.vue

@ -0,0 +1,11 @@
<script lang="ts" setup>
const { activeCalendarView } = useCalendarViewStoreOrThrow()
</script>
<template>
<span class="opacity-0" data-testid="nc-active-calendar-view">
{{ activeCalendarView }}
</span>
</template>
<style lang="scss" scoped></style>

126
packages/nc-gui/components/smartsheet/toolbar/Calendar/Header.vue

@ -0,0 +1,126 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
import { computed } from '#imports'
const { selectedDate, selectedMonth, selectedDateRange, activeCalendarView, paginateCalendarView, activeDates, pageDate } =
useCalendarViewStoreOrThrow()
const calendarRangeDropdown = ref(false)
const headerText = computed(() => {
switch (activeCalendarView.value) {
case 'day':
return dayjs(selectedDate.value).format('D MMM YYYY')
case 'week':
if (selectedDateRange.value.start.isSame(selectedDateRange.value.end, 'month')) {
return `${selectedDateRange.value.start.format('D')} - ${selectedDateRange.value.end.format('D MMM YY')}`
} else if (selectedDateRange.value.start.isSame(selectedDateRange.value.end, 'year')) {
return `${selectedDateRange.value.start.format('D MMM')} - ${selectedDateRange.value.end.format('D MMM YY')}`
} else {
return `${selectedDateRange.value.start.format('D MMM YY')} - ${selectedDateRange.value.end.format('D MMM YY')}`
}
case 'month':
return dayjs(selectedMonth.value).format('MMM YYYY')
case 'year':
return dayjs(selectedDate.value).format('YYYY')
default:
return ''
}
})
</script>
<template>
<div class="flex gap-1">
<NcTooltip>
<template #title> {{ $t('labels.previous') }}</template>
<a-button
v-e="`['c:calendar:calendar-${activeCalendarView}-prev-btn']`"
class="w-6 h-6 !rounded-lg flex items-center justify-center !bg-gray-100 !border-0"
data-testid="nc-calendar-prev-btn"
size="small"
@click="paginateCalendarView('prev')"
>
<component :is="iconMap.arrowLeft" class="h-4 !mb-0.9 !-ml-0.8 w-4" />
</a-button>
</NcTooltip>
<NcDropdown v-model:visible="calendarRangeDropdown" :auto-close="false" :trigger="['click']">
<NcButton
:class="{
'w-20': activeCalendarView === 'year',
'w-26.5': activeCalendarView === 'month',
'w-29': activeCalendarView === 'day',
'w-38': activeCalendarView === 'week',
}"
class="!h-6 !bg-gray-100 !border-0"
full-width
size="small"
type="secondary"
>
<div class="flex w-full px-1 items-center justify-between">
<span
:class="{
'max-w-38 truncate': activeCalendarView === 'week',
}"
class="font-bold text-[13px] text-center text-gray-800"
data-testid="nc-calendar-active-date"
>{{ headerText }}</span
>
<div class="flex-1" />
<component :is="iconMap.arrowDown" class="h-4 min-w-4 text-gray-700" />
</div>
</NcButton>
<template #overlay>
<div v-if="calendarRangeDropdown" class="w-[287px]" @click.stop>
<NcDateWeekSelector
v-if="activeCalendarView === ('day' as const)"
v-model:active-dates="activeDates"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
size="medium"
/>
<NcDateWeekSelector
v-else-if="activeCalendarView === ('week' as const)"
v-model:active-dates="activeDates"
v-model:page-date="pageDate"
v-model:selected-week="selectedDateRange"
is-week-picker
size="medium"
/>
<NcMonthYearSelector
v-else-if="activeCalendarView === ('month' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedMonth"
size="medium"
/>
<NcMonthYearSelector
v-else-if="activeCalendarView === ('year' as const)"
v-model:page-date="pageDate"
v-model:selected-date="selectedDate"
is-year-picker
size="medium"
/>
</div>
</template>
</NcDropdown>
<NcTooltip>
<template #title> {{ $t('labels.next') }}</template>
<a-button
v-e="`['c:calendar:calendar-${activeCalendarView}-next-btn']`"
class="w-6 h-6 !rounded-lg flex items-center !bg-gray-100 !border-0 justify-center"
data-testid="nc-calendar-next-btn"
size="small"
@click="paginateCalendarView('next')"
>
<component :is="iconMap.arrowRight" class="h-4 !mb-0.8 !-ml-0.5 w-4" />
</a-button>
</NcTooltip>
</div>
</template>
<style lang="scss" scoped>
.nc-cal-toolbar-header {
@apply !h-6 !w-6;
}
</style>

69
packages/nc-gui/components/smartsheet/toolbar/CalendarMode.vue → packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue

@ -31,67 +31,78 @@ watch(activeCalendarView, () => {
<template> <template>
<div <div
v-if="props.tab" v-if="props.tab"
class="flex flex-row relative px-1 mx-3 mt-3 rounded-lg gap-x-0.5 nc-calendar-mode-tab" class="flex flex-row px-1 pointer-events-auto mx-3 mt-3 rounded-lg gap-x-0.5 nc-calendar-mode-tab"
data-testid="nc-calendar-view-mode" data-testid="nc-calendar-view-mode"
> >
<div :style="highlightStyle" class="highlight"></div> <div :style="highlightStyle" class="highlight"></div>
<div <div
v-for="mode in ['day', 'week', 'month', 'year']" v-for="mode in ['day', 'week', 'month', 'year']"
:key="mode" :key="mode"
v-e="`['c:calendar:change-calendar-view-${mode}']`"
:class="{ active: activeCalendarView === mode }" :class="{ active: activeCalendarView === mode }"
:data-testid="`nc-calendar-view-mode-${mode}`" :data-testid="`nc-calendar-view-mode-${mode}`"
class="tab" class="tab"
@click="setActiveCalendarMode(mode, $event)" @click="setActiveCalendarMode(mode, $event)"
> >
<div class="tab-title nc-tab">{{ $t(`objects.${mode}`) }}</div> <div class="tab-title !text-xs nc-tab">{{ $t(`objects.${mode}`) }}</div>
</div> </div>
</div> </div>
<div v-else>
<NcDropdown :trigger="['click']"> <NcSelect v-else v-model:value="activeCalendarView" class="!w-22" data-testid="nc-calendar-view-mode" size="small">
<NcButton size="small" type="secondary"> <a-select-option v-for="option in ['day', 'week', 'month', 'year']" :key="option" :value="option" class="!h-7 !w-20">
{{ $t(`objects.${activeCalendarView}`) }} <div class="flex gap-2 mt-0.5 items-center">
<component :is="iconMap.arrowDown" /> <NcTooltip class="truncate !capitalize flex-1 max-w-18" placement="top" show-on-truncate-only>
</NcButton> <template #title>
<template #overlay> <span class="capitalize">
<NcMenu> {{ option }}
<NcMenuItem </span>
v-for="mode in ['day', 'week', 'month', 'year']" </template>
:key="mode" {{ option }}
v-e="`['c:calendar:change-calendar-view-${mode}']`" </NcTooltip>
@click="changeCalendarView(mode)"
> <component
{{ $t(`objects.${mode}`) }} :is="iconMap.check"
</NcMenuItem> v-if="option === activeCalendarView"
</NcMenu> id="nc-selected-item-icon"
</template> class="text-primary w-4 h-4"
</NcDropdown> />
</div> </div>
</a-select-option>
</NcSelect>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.highlight { .highlight {
@apply absolute h-9 w-20 transition-all border-b-2 border-brand-500 duration-200; @apply absolute h-6.5 w-14 transition-all border-b-2 border-brand-500 duration-200;
z-index: 0; z-index: 0;
} }
.nc-calendar-mode-menu {
:deep(.nc-menu-item-inner) {
@apply !text-[13px];
}
}
.tab { .tab {
@apply flex items-center h-9 w-20 z-10 justify-center px-2 py-1 rounded-lg gap-x-1.5 text-gray-500 hover:text-black cursor-pointer transition-all duration-300 select-none; @apply flex items-center h-7 w-14 z-10 justify-center px-2 py-1 rounded-lg gap-x-1.5 text-gray-500 hover:text-black cursor-pointer transition-all duration-300 select-none;
} }
.tab .tab-title { .tab .tab-title {
@apply min-w-0 mb-3 pointer-events-none; @apply min-w-0 mb-3 pointer-events-none;
word-break: 'keep-all'; word-break: keep-all;
white-space: 'nowrap'; white-space: 'nowrap';
display: 'inline'; display: 'inline';
line-height: 0.95; line-height: 0.95;
} }
.active { .active {
@apply !text-brand-500 font-medium bg-transparent; @apply !text-brand-500 !font-bold bg-transparent;
} }
.nc-calendar-mode-tab { .nc-calendar-mode-tab {
@apply mr-120 relative; @apply relative;
}
:deep(.ant-select-selector) {
@apply !h-7;
} }
</style> </style>

64
packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue → packages/nc-gui/components/smartsheet/toolbar/Calendar/Range.vue

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { UITypes, isSystemColumn } from 'nocodb-sdk' import { type CalendarRangeType, UITypes, isSystemColumn } from 'nocodb-sdk'
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
import { type CalendarRangeType } from '~/lib/types'
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -101,22 +100,42 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
<template> <template>
<NcDropdown v-if="!IsPublic" v-model:visible="calendarRangeDropdown" :trigger="['click']" class="!xs:hidden"> <NcDropdown v-if="!IsPublic" v-model:visible="calendarRangeDropdown" :trigger="['click']" class="!xs:hidden">
<div class="nc-calendar-btn"> <div class="nc-calendar-btn">
<a-button <NcButton
v-e="['c:calendar:change-calendar-range']" v-e="['c:calendar:change-calendar-range']"
:disabled="isLocked" :disabled="isLocked"
class="nc-toolbar-btn" class="nc-toolbar-btn !border-0 group !h-6"
size="small"
type="secondary"
data-testid="nc-calendar-range-btn" data-testid="nc-calendar-range-btn"
> >
<div class="flex items-center gap-2"> <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 transition-all group-hover:text-brand-500" />
<span class="text-capitalize !text-sm font-medium"> <span class="text-capitalize !group-hover:text-brand-500 !text-[13px] font-medium">
{{ $t('activity.viewSettings') }} {{ $t('activity.settings') }}
</span> </span>
</div> </div>
</a-button> </NcButton>
</div> </div>
<template #overlay> <template #overlay>
<div v-if="calendarRangeDropdown" class="w-full p-6" data-testid="nc-calendar-range-menu" @click.stop> <div v-if="calendarRangeDropdown" class="w-98 space-y-6 rounded-2xl p-6" data-testid="nc-calendar-range-menu" @click.stop>
<div>
<div class="flex justify-between">
<div class="flex items-center gap-3">
<component :is="iconMap.calendar" class="text-maroon-500 w-5 h-5" />
<span class="font-bold"> {{ `${$t('activity.calendar')} ${$t('activity.viewSettings')}` }}</span>
</div>
<a
class="text-sm !text-gray-600 !font-default !hover:text-gray-600"
href="`https://docs.nocodb.com/views/view-types/calendar`"
target="_blank"
>
Go to Docs
</a>
</div>
<NcDivider divider-class="!border-gray-200" />
</div>
<div <div
v-for="(range, id) in _calendar_ranges" v-for="(range, id) in _calendar_ranges"
:key="id" :key="id"
@ -139,14 +158,24 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
return firstRange?.uidt === r.uidt return firstRange?.uidt === r.uidt
})" })"
:key="opId" :key="opId"
class="w-40"
:value="option.value" :value="option.value"
> >
<div class="flex items-center"> <div class="flex w-full gap-2 justify-between items-center">
<SmartsheetHeaderIcon :column="option" /> <div class="flex items-center">
<NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only> <SmartsheetHeaderIcon :column="option" />
<template #title>{{ option.label }}</template> <NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only>
{{ option.label }} <template #title>{{ option.label }}</template>
</NcTooltip> {{ option.label }}
</NcTooltip>
</div>
<component
:is="iconMap.check"
v-if="option.value === range.fk_from_column_id"
id="nc-selected-item-icon"
class="text-primary min-w-4 h-4"
/>
</div> </div>
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
@ -201,6 +230,11 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
</NcButton> </NcButton>
--> -->
</div> </div>
<!--
<div class="text-[13px] text-gray-500 py-2">Records in this view will be based on the specified date field.</div>
-->
<NcButton <NcButton
v-if="_calendar_ranges.length === 0" v-if="_calendar_ranges.length === 0"
class="mt-2" class="mt-2"

39
packages/nc-gui/components/smartsheet/toolbar/Calendar/Today.vue

@ -0,0 +1,39 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
const { selectedDate, selectedMonth, selectedDateRange, pageDate, activeCalendarView } = useCalendarViewStoreOrThrow()
const { $e } = useNuxtApp()
const goToToday = () => {
$e('c:calendar:calendar-today-btn', activeCalendarView.value)
selectedDate.value = dayjs()
pageDate.value = dayjs()
selectedMonth.value = dayjs()
selectedDateRange.value = {
start: dayjs().startOf('week'),
end: dayjs().endOf('week'),
}
document?.querySelector('.nc-calendar-today')?.scrollIntoView({
behavior: 'smooth',
block: 'center',
})
}
</script>
<template>
<NcButton
class="!border-0 !h-6 !bg-gray-100"
data-testid="nc-calendar-today-btn"
size="small"
type="secondary"
@click="goToToday"
>
<span class="text-gray-700 !text-[13px]">
{{ $t('labels.today') }}
</span>
</NcButton>
</template>
<style lang="scss" scoped></style>

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

@ -61,17 +61,16 @@ eventBus.on(async (event, column: ColumnType) => {
overlay-class-name="nc-dropdown-filter-menu nc-toolbar-dropdown" overlay-class-name="nc-dropdown-filter-menu nc-toolbar-dropdown"
class="!xs:hidden" class="!xs:hidden"
> >
<div :class="{ 'nc-active-btn': filtersLength }"> <NcButton v-e="['c:filter']" :disabled="isLocked" class="nc-filter-menu-btn nc-toolbar-btn !border-0 !h-7" size="small" type="secondary">
<a-button v-e="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked"> <div class="flex items-center gap-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<component :is="iconMap.filter" class="h-4 w-4" /> <component :is="iconMap.filter" class="h-4 w-4" />
<!-- Filter --> <!-- Filter -->
<span v-if="!isMobileMode" class="text-capitalize !text-sm font-medium">{{ $t('activity.filter') }}</span> <span v-if="!isMobileMode" class="text-capitalize !text-[13px] font-medium">{{ $t('activity.filter') }}</span>
<span v-if="filtersLength" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">{{ filtersLength }}</span>
</div> </div>
</a-button> <span v-if="filtersLength" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">{{ filtersLength }}</span>
</div> </div>
</NcButton>
<template #overlay> <template #overlay>
<SmartsheetToolbarColumnFilter <SmartsheetToolbarColumnFilter

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

@ -311,29 +311,31 @@ useMenuCloseOnEsc(open)
overlay-class-name="nc-dropdown-fields-menu nc-toolbar-dropdown" overlay-class-name="nc-dropdown-fields-menu nc-toolbar-dropdown"
> >
<div :class="{ 'nc-active-btn': numberOfHiddenFields }"> <div :class="{ 'nc-active-btn': numberOfHiddenFields }">
<a-button v-e="['c:fields']" :disabled="isLocked" class="nc-fields-menu-btn nc-toolbar-btn"> <NcButton v-e="['c:fields']" :disabled="isLocked" class="nc-fields-menu-btn nc-toolbar-btn !h-7 !border-0" size="small" type="secondary">
<div class="flex items-center gap-2"> <div class="flex items-center gap-1">
<GeneralIcon <div class="flex items-center gap-2">
v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY" <GeneralIcon
class="h-4 w-4" v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY"
icon="creditCard" class="h-4 w-4"
/> icon="creditCard"
<component :is="iconMap.fields" v-else class="h-4 w-4" /> />
<component :is="iconMap.fields" v-else class="h-4 w-4" />
<!-- Fields -->
<span v-if="!isMobileMode" class="text-capitalize text-sm font-medium"> <!-- Fields -->
<template v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY"> <span v-if="!isMobileMode" class="text-capitalize !text-[13px] font-medium">
{{ $t('title.editCards') }} <template v-if="activeView?.type === ViewTypes.KANBAN || activeView?.type === ViewTypes.GALLERY">
</template> {{ $t('title.editCards') }}
<template v-else> </template>
{{ $t('objects.fields') }} <template v-else>
</template> {{ $t('objects.fields') }}
</span> </template>
</span>
</div>
<span v-if="numberOfHiddenFields" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md"> <span v-if="numberOfHiddenFields" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">
{{ numberOfHiddenFields }} {{ numberOfHiddenFields }}
</span> </span>
</div> </div>
</a-button> </NcButton>
</div> </div>
<template #overlay> <template #overlay>

21
packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue

@ -164,18 +164,25 @@ eventBus.on(async (event, column) => {
overlay-class-name="nc-dropdown-group-by-menu nc-toolbar-dropdown overflow-hidden" overlay-class-name="nc-dropdown-group-by-menu nc-toolbar-dropdown overflow-hidden"
> >
<div :class="{ 'nc-active-btn': groupedByColumnIds?.length }"> <div :class="{ 'nc-active-btn': groupedByColumnIds?.length }">
<a-button v-e="['c:group-by']" class="nc-group-by-menu-btn nc-toolbar-btn" :disabled="isLocked"> <NcButton
<div class="flex items-center gap-2"> v-e="['c:group-by']"
<component :is="iconMap.group" class="h-4 w-4" /> :disabled="isLocked"
class="nc-group-by-menu-btn nc-toolbar-btn !border-0 !h-7"
<!-- Group By --> size="small"
<span v-if="!isMobileMode" class="text-capitalize !text-sm font-medium">{{ $t('activity.group') }}</span> type="secondary"
>
<div class="flex items-center gap-1">
<div class="flex items-center gap-2">
<component :is="iconMap.group" class="h-4 w-4" />
<!-- Group By -->
<span v-if="!isMobileMode" class="text-capitalize !text-[13px] font-medium">{{ $t('activity.group') }}</span>
</div>
<span v-if="groupedByColumnIds?.length" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">{{ <span v-if="groupedByColumnIds?.length" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">{{
groupedByColumnIds.length groupedByColumnIds.length
}}</span> }}</span>
</div> </div>
</a-button> </NcButton>
</div> </div>
<template #overlay> <template #overlay>
<SmartsheetToolbarCreateGroupBy <SmartsheetToolbarCreateGroupBy

12
packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { OrgUserRoles, ProjectRoles, extractRolesObj } from 'nocodb-sdk'
import type { GridType } from 'nocodb-sdk' import type { GridType } from 'nocodb-sdk'
import { OrgUserRoles, ProjectRoles, extractRolesObj } from 'nocodb-sdk'
const rowHeightOptions: { icon: keyof typeof iconMap; heightClass: string }[] = [ const rowHeightOptions: { icon: keyof typeof iconMap; heightClass: string }[] = [
{ {
@ -84,12 +84,18 @@ useMenuCloseOnEsc(open)
<template> <template>
<a-dropdown v-model:visible="open" offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-height-menu"> <a-dropdown v-model:visible="open" offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-height-menu">
<div> <div>
<a-button v-e="['c:row-height']" class="nc-height-menu-btn nc-toolbar-btn" :disabled="isLocked"> <NcButton
v-e="['c:row-height']"
:disabled="isLocked"
class="nc-height-menu-btn nc-toolbar-btn !border-0 !h-7"
size="small"
type="secondary"
>
<div class="flex items-center gap-0.5"> <div class="flex items-center gap-0.5">
<component :is="iconMap.rowHeight" class="!h-3.75 !w-3.75" /> <component :is="iconMap.rowHeight" class="!h-3.75 !w-3.75" />
<!-- <span v-if="!isMobileMode" class="!text-sm !font-medium">{{ $t('objects.rowHeight') }}</span> --> <!-- <span v-if="!isMobileMode" class="!text-sm !font-medium">{{ $t('objects.rowHeight') }}</span> -->
</div> </div>
</a-button> </NcButton>
</div> </div>
<template #overlay> <template #overlay>
<div <div

4
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, TableType } from 'nocodb-sdk' import type { ColumnType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
const reloadData = inject(ReloadViewDataHookInj)! const reloadData = inject(ReloadViewDataHookInj)!
@ -95,7 +95,7 @@ onClickOutside(globalSearchWrapperRef, (e) => {
</a-button> </a-button>
<div <div
v-else v-else
class="flex flex-row border-1 rounded-lg h-8 xs:(h-10 ml-0) ml-1 border-gray-200 overflow-hidden focus-within:border-primary" class="flex flex-row border-1 rounded-lg h-7 xs:(h-10 ml-0) ml-1 border-gray-200 overflow-hidden focus-within:border-primary"
:class="{ 'border-primary': search.query.length !== 0 }" :class="{ 'border-primary': search.query.length !== 0 }"
> >
<NcDropdown <NcDropdown

31
packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { PlanLimitTypes, RelationTypes, UITypes, getEquivalentUIType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { PlanLimitTypes, RelationTypes, UITypes, getEquivalentUIType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
const view = inject(ActiveViewInj, ref()) const view = inject(ActiveViewInj, ref())
@ -20,6 +20,8 @@ const { isMobileMode } = useGlobal()
const { getPlanLimit } = useWorkspace() const { getPlanLimit } = useWorkspace()
const isCalendar = inject(IsCalendarInj, ref(false))
eventBus.on((event) => { eventBus.on((event) => {
if (event === SmartsheetStoreEvents.SORT_RELOAD) { if (event === SmartsheetStoreEvents.SORT_RELOAD) {
loadSorts() loadSorts()
@ -110,16 +112,27 @@ onMounted(() => {
overlay-class-name="nc-dropdown-sort-menu nc-toolbar-dropdown" overlay-class-name="nc-dropdown-sort-menu nc-toolbar-dropdown"
> >
<div :class="{ 'nc-active-btn': sorts?.length }"> <div :class="{ 'nc-active-btn': sorts?.length }">
<a-button v-e="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked"> <NcButton
<div class="flex items-center gap-2"> v-e="['c:sort']"
<component :is="iconMap.sort" class="h-4 w-4 text-inherit" /> :class="{
'!border-1 !rounded-lg !h-7': isCalendar,
<!-- Sort --> '!border-0 ': !isCalendar,
<span v-if="!isMobileMode" class="text-capitalize !text-sm font-medium">{{ $t('activity.sort') }}</span> }"
:disabled="isLocked"
class="nc-sort-menu-btn nc-toolbar-btn"
size="small"
type="secondary"
>
<div class="flex items-center gap-1">
<div class="flex items-center gap-2">
<component :is="iconMap.sort" class="h-4 w-4 text-inherit" />
<!-- Sort -->
<span v-if="!isMobileMode" class="text-capitalize !text-[13px] font-medium">{{ $t('activity.sort') }}</span>
</div>
<span v-if="sorts?.length" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">{{ sorts.length }}</span> <span v-if="sorts?.length" class="bg-brand-50 text-brand-500 py-1 px-2 text-md rounded-md">{{ sorts.length }}</span>
</div> </div>
</a-button> </NcButton>
</div> </div>
<template #overlay> <template #overlay>
<SmartsheetToolbarCreateSort v-if="!sorts.length" :is-parent-open="open" @created="addSort" /> <SmartsheetToolbarCreateSort v-if="!sorts.length" :is-parent-open="open" @created="addSort" />

10
packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { UITypes } from 'nocodb-sdk'
import type { KanbanType } from 'nocodb-sdk' import type { KanbanType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
provide(IsKanbanInj, ref(true)) provide(IsKanbanInj, ref(true))
@ -96,9 +96,11 @@ const handleChange = () => {
class="!xs:hidden" class="!xs:hidden"
> >
<div class="nc-kanban-btn"> <div class="nc-kanban-btn">
<a-button <NcButton
v-e="['c:kanban:change-grouping-field']" v-e="['c:kanban:change-grouping-field']"
class="nc-kanban-stacked-by-menu-btn nc-toolbar-btn" class="nc-kanban-stacked-by-menu-btn nc-toolbar-btn !border-0 !h-7"
size="small"
type="secondary"
:disabled="isLocked" :disabled="isLocked"
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@ -108,7 +110,7 @@ const handleChange = () => {
<span class="font-bold ml-0.25">{{ groupingField }}</span> <span class="font-bold ml-0.25">{{ groupingField }}</span>
</span> </span>
</div> </div>
</a-button> </NcButton>
</div> </div>
<template #overlay> <template #overlay>
<div v-if="open" class="p-6 w-90 bg-white shadow-lg nc-table-toolbar-menu !border-1 border-gray-50 rounded-2xl" @click.stop> <div v-if="open" class="p-6 w-90 bg-white shadow-lg nc-table-toolbar-menu !border-1 border-gray-50 rounded-2xl" @click.stop>

4
packages/nc-gui/components/smartsheet/topbar/SelectMode.vue

@ -24,7 +24,7 @@ const onClickDetails = () => {
}" }"
@click="onViewsTabChange('view')" @click="onViewsTabChange('view')"
> >
<GeneralViewIcon v-if="activeView?.type" class="tab-icon" :meta="{ type: activeView?.type }" ignore-color /> <GeneralViewIcon v-if="activeView?.type" :meta="{ type: activeView?.type }" class="tab-icon" ignore-color />
<GeneralLoader v-else class="tab-icon" /> <GeneralLoader v-else class="tab-icon" />
<div class="tab-title nc-tab">{{ $t('general.data') }}</div> <div class="tab-title nc-tab">{{ $t('general.data') }}</div>
</div> </div>
@ -56,7 +56,7 @@ const onClickDetails = () => {
.tab-icon { .tab-icon {
font-size: 1.1rem !important; font-size: 1.1rem !important;
@apply min-w-4.5; @apply w-4.5;
} }
.tab .tab-title { .tab .tab-title {
@apply min-w-0; @apply min-w-0;

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

@ -162,7 +162,7 @@ const onResize = (sizes: { min: number; max: number; size: number }[]) => {
<Splitpanes v-if="openedViewsTab === 'view'" class="nc-extensions-content-resizable-wrapper" @resized="onResize"> <Splitpanes v-if="openedViewsTab === 'view'" class="nc-extensions-content-resizable-wrapper" @resized="onResize">
<Pane class="flex flex-col h-full flex-1 min-w-0" size="60"> <Pane class="flex flex-col h-full flex-1 min-w-0" size="60">
<LazySmartsheetToolbar v-if="!isForm" /> <LazySmartsheetToolbar v-if="!isForm" />
<div class="flex flex-row w-full" :style="{ height: isForm ? '100%' : 'calc(100% - var(--topbar-height))' }"> <div :style="{ height: isForm ? '100%' : 'calc(100% - var(--toolbar-height))' }" class="flex flex-row w-full">
<Transition name="layout" mode="out-in"> <Transition name="layout" mode="out-in">
<div v-if="openedViewsTab === 'view'" class="flex flex-1 min-h-0 w-3/4"> <div v-if="openedViewsTab === 'view'" class="flex flex-1 min-h-0 w-3/4">
<div class="h-full flex-1 min-w-0 min-h-0 bg-white"> <div class="h-full flex-1 min-w-0 min-h-0 bg-white">

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

@ -15,7 +15,7 @@ const formatData = (list: Record<string, any>[]) =>
const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState( const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
( (
meta: Ref<((CalendarType & { id: string }) | TableType) | undefined>, meta: Ref<TableType | undefined>,
viewMeta: viewMeta:
| Ref<(ViewType | CalendarType | undefined) & { id: string }> | Ref<(ViewType | CalendarType | undefined) & { id: string }>
| ComputedRef< | ComputedRef<
@ -54,7 +54,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const isCalendarMetaLoading = ref<boolean>(false) const isCalendarMetaLoading = ref<boolean>(false)
const showSideMenu = ref(false) const showSideMenu = ref(true)
const selectedDateRange = ref<{ const selectedDateRange = ref<{
start: dayjs.Dayjs start: dayjs.Dayjs
@ -80,7 +80,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const { base } = storeToRefs(useBase()) const { base } = storeToRefs(useBase())
const { $api } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { t } = useI18n() const { t } = useI18n()
@ -371,7 +371,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
sortsArr: sorts.value, sortsArr: sorts.value,
filtersArr: activeDateFilter, filtersArr: activeDateFilter,
}) })
activeDates.value = res.dates.map((dateObj: unknown) => dayjs(dateObj)) activeDates.value = res.dates.map((dateObj: unknown) => dayjs(dateObj as string))
if (res.count > 3000 && activeCalendarView.value !== 'year') { if (res.count > 3000 && activeCalendarView.value !== 'year') {
message.warning( message.warning(
@ -380,12 +380,20 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
} }
} catch (e) { } catch (e) {
activeDates.value = [] activeDates.value = []
message.error(`${t('msg.error.fetchingActiveDates')} ${await extractSdkResponseErrorMsg(e)}`) message.error(
`${t('msg.error.fetchingActiveDates')} ${await extractSdkResponseErrorMsg(
e as Error & {
response: { data: { message: string } }
},
)}`,
)
console.log(e) console.log(e)
} }
} }
const changeCalendarView = async (view: 'month' | 'year' | 'day' | 'week') => { const changeCalendarView = async (view: 'month' | 'year' | 'day' | 'week') => {
$e('c:calendar:change-calendar-view', view)
try { try {
activeCalendarView.value = view activeCalendarView.value = view
await updateCalendarMeta({ await updateCalendarMeta({
@ -414,15 +422,27 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const calMeta = typeof res.meta === 'string' ? JSON.parse(res.meta) : res.meta const calMeta = typeof res.meta === 'string' ? JSON.parse(res.meta) : res.meta
activeCalendarView.value = calMeta?.active_view activeCalendarView.value = calMeta?.active_view
if (!activeCalendarView.value) activeCalendarView.value = 'month' if (!activeCalendarView.value) activeCalendarView.value = 'month'
calendarRange.value = res?.calendar_range?.map((range: CalendarRangeType) => { calendarRange.value = res?.calendar_range?.map(
return { (
id: range.id, range: CalendarRangeType & {
fk_from_col: meta.value?.columns?.find((col) => col.id === range.fk_from_column_id), id?: string
fk_to_col: range.fk_to_column_id ? meta.value?.columns?.find((col) => col.id === range.fk_to_column_id) : null, },
} ) => {
}) as any return {
} catch (e) { id: range?.id,
message.error(`Error loading calendar meta ${await extractSdkResponseErrorMsg(e)}`) fk_from_col: meta.value?.columns?.find((col) => col.id === range.fk_from_column_id),
fk_to_col: range.fk_to_column_id ? meta.value?.columns?.find((col) => col.id === range.fk_to_column_id) : null,
}
},
) as any
} catch (e: unknown) {
message.error(
`Error loading calendar meta ${await extractSdkResponseErrorMsg(
e as Error & {
response: { data: { message: string } }
},
)}`,
)
} finally { } finally {
isCalendarMetaLoading.value = false isCalendarMetaLoading.value = false
} }
@ -459,12 +479,6 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
nextDate = toDate.add(1, 'day').startOf('day') nextDate = toDate.add(1, 'day').startOf('day')
break break
} }
case 'year':
fromDate = selectedDate.value.startOf('year')
toDate = selectedDate.value.endOf('year')
prevDate = fromDate.subtract(1, 'day').endOf('day')
nextDate = toDate.add(1, 'day').startOf('day')
break
case 'day': case 'day':
fromDate = selectedDate.value.startOf('day') fromDate = selectedDate.value.startOf('day')
toDate = selectedDate.value.endOf('day') toDate = selectedDate.value.endOf('day')
@ -504,7 +518,13 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
}) })
formattedData.value = formatData(res!.list) formattedData.value = formatData(res!.list)
} catch (e) { } catch (e) {
message.error(`${t('msg.error.fetchingCalendarData')} ${await extractSdkResponseErrorMsg(e)}`) message.error(
`${t('msg.error.fetchingCalendarData')} ${await extractSdkResponseErrorMsg(
e as Error & {
response: { data: { message: string } }
},
)}`,
)
console.log(e) console.log(e)
} finally { } finally {
isCalendarDataLoading.value = false isCalendarDataLoading.value = false
@ -604,7 +624,13 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
formattedSideBarData.value = formatData(res!.list) formattedSideBarData.value = formatData(res!.list)
} catch (e) { } catch (e) {
message.error(`${t('msg.error.fetchingCalendarData')} ${await extractSdkResponseErrorMsg(e)}`) message.error(
`${t('msg.error.fetchingCalendarData')} ${await extractSdkResponseErrorMsg(
e as Error & {
response: { data: { message: string } }
},
)}`,
)
console.log(e) console.log(e)
} finally { } finally {
isSidebarLoading.value = false isSidebarLoading.value = false
@ -635,9 +661,6 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
viewMeta?.value?.id as string, viewMeta?.value?.id as string,
id, id,
updateObj, updateObj,
{
query: { ignoreWebhook: !undo },
},
// todo: // todo:
// { // {
// query: { ignoreWebhook: !saved } // query: { ignoreWebhook: !saved }
@ -769,6 +792,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
}) })
watch(sideBarFilterOption, async () => { watch(sideBarFilterOption, async () => {
$e('a:calendar:sidebar-filter', sideBarFilterOption.value)
await loadSidebarData() await loadSidebarData()
}) })
@ -801,7 +825,6 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
isSidebarLoading, isSidebarLoading,
showSideMenu, showSideMenu,
selectedTime, selectedTime,
updateCalendarMeta,
calendarMetaData, calendarMetaData,
updateRowProperty, updateRowProperty,
activeCalendarView, activeCalendarView,

1
packages/nc-gui/composables/useViewColumns.ts

@ -291,6 +291,7 @@ const [useProvideViewColumns, useViewColumns] = useInjectionState(
const fieldIndex = fields.value?.findIndex((f) => f.fk_column_id === field.fk_column_id) const fieldIndex = fields.value?.findIndex((f) => f.fk_column_id === field.fk_column_id)
if (!fieldIndex && fieldIndex !== 0) return if (!fieldIndex && fieldIndex !== 0) return
field[style] = status field[style] = status
$e('a:fields:style', { style, status })
saveOrUpdate(field, fieldIndex, true) saveOrUpdate(field, fieldIndex, true)
} }

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

@ -448,6 +448,7 @@
"noResultsMatchedYourSearch": "Your search did not yield any matching results" "noResultsMatchedYourSearch": "Your search did not yield any matching results"
}, },
"labels": { "labels": {
"today": "Today",
"txt": "TXT Record value", "txt": "TXT Record value",
"transferOwnership": "Transfer Ownership", "transferOwnership": "Transfer Ownership",
"recentActivity": "Recent Activity", "recentActivity": "Recent Activity",
@ -585,7 +586,7 @@
"untitledToken": "Untitled token", "untitledToken": "Untitled token",
"tableName": "Table name", "tableName": "Table name",
"dashboardName": "Dashboard name", "dashboardName": "Dashboard name",
"createView": "Create a View", "createView": "Create view",
"creatingView": "Creating View", "creatingView": "Creating View",
"duplicateView": "Duplicate View", "duplicateView": "Duplicate View",
"duplicateGridView": "Duplicate Grid View", "duplicateGridView": "Duplicate Grid View",

3
packages/nc-gui/utils/iconUtils.ts

@ -74,6 +74,7 @@ import Record from '~icons/nc-icons/record'
import Project from '~icons/nc-icons/project' import Project from '~icons/nc-icons/project'
import LookupIcon from '~icons/nc-icons/lookup' import LookupIcon from '~icons/nc-icons/lookup'
import FileImageIcon from '~icons/nc-icons/file-image' import FileImageIcon from '~icons/nc-icons/file-image'
import Calendar from '~icons/lucide/calendar'
import PhUsers from '~icons/ph/users' import PhUsers from '~icons/ph/users'
import PhUser from '~icons/ph/user' import PhUser from '~icons/ph/user'
@ -389,6 +390,7 @@ export const iconMap = {
workspaceDefault: MsGroup, workspaceDefault: MsGroup,
project: Project, project: Project,
search: NcSearch, search: NcSearch,
calendar: Calendar,
error: h('span', { class: 'material-symbols' }, 'error'), error: h('span', { class: 'material-symbols' }, 'error'),
info: h(MsInfo, {}, () => 'info'), info: h(MsInfo, {}, () => 'info'),
inbox: h('span', { class: 'material-symbols' }, 'inbox'), inbox: h('span', { class: 'material-symbols' }, 'inbox'),
@ -493,7 +495,6 @@ export const iconMap = {
web: h('span', { class: 'material-symbols' }, 'web'), web: h('span', { class: 'material-symbols' }, 'web'),
webhook: h('span', { class: 'material-symbols' }, 'webhook'), webhook: h('span', { class: 'material-symbols' }, 'webhook'),
boolean: h('span', { class: 'material-symbols' }, 'check_box'), boolean: h('span', { class: 'material-symbols' }, 'check_box'),
calendar: h('span', { class: 'material-symbols' }, 'event_note'),
singleSelect: h('span', { class: 'material-symbols' }, 'radio_button_checked'), singleSelect: h('span', { class: 'material-symbols' }, 'radio_button_checked'),
multiSelect: h('span', { class: 'material-symbols' }, 'check_box_outline_blank'), multiSelect: h('span', { class: 'material-symbols' }, 'check_box_outline_blank'),
datetime: h('span', { class: 'material-symbols' }, 'date_range'), datetime: h('span', { class: 'material-symbols' }, 'date_range'),

1
packages/nc-gui/windi.config.ts

@ -80,6 +80,7 @@ export default defineConfig({
extraLight: 250, extraLight: 250,
light: 350, light: 350,
normal: 450, normal: 450,
default: 500,
medium: 550, medium: 550,
bold: 650, bold: 650,
black: 750, black: 750,

30
tests/playwright/pages/Dashboard/Calendar/CalendarSideMenu.ts

@ -7,11 +7,19 @@ export class CalendarSideMenuPage extends BasePage {
readonly new_record_btn: Locator; readonly new_record_btn: Locator;
readonly prev_btn: Locator;
readonly next_btn: Locator;
readonly searchToggleBtn: Locator;
constructor(parent: CalendarPage) { constructor(parent: CalendarPage) {
super(parent.rootPage); super(parent.rootPage);
this.parent = parent; this.parent = parent;
this.new_record_btn = this.get().getByTestId('nc-calendar-side-menu-new-btn'); this.new_record_btn = this.get().getByTestId('nc-calendar-side-menu-new-btn');
this.next_btn = this.parent.toolbar.get().getByTestId('nc-calendar-next-btn');
this.prev_btn = this.parent.toolbar.get().getByTestId('nc-calendar-prev-btn');
this.searchToggleBtn = this.get().getByTestId('nc-calendar-sidebar-search-btn');
} }
get() { get() {
@ -25,10 +33,32 @@ export class CalendarSideMenuPage extends BasePage {
} }
async searchRecord({ query }: { query: string }) { async searchRecord({ query }: { query: string }) {
if (await this.searchToggleBtn.isVisible()) {
await this.searchToggleBtn.click();
}
const searchInput = this.get().getByTestId('nc-calendar-sidebar-search'); const searchInput = this.get().getByTestId('nc-calendar-sidebar-search');
await searchInput.fill(query); await searchInput.fill(query);
} }
async clickPrev() {
await this.prev_btn.click();
}
async clickNext() {
await this.next_btn.click();
}
async moveToDate({ date, action }: { date: string; action: 'prev' | 'next' }) {
console.log(await this.parent.toolbar.getActiveDate());
while ((await this.parent.toolbar.getActiveDate()) !== date) {
if (action === 'prev') {
await this.clickPrev();
} else {
await this.clickNext();
}
}
}
async verifySideBarRecords({ records }: { records: string[] }) { async verifySideBarRecords({ records }: { records: string[] }) {
let attempts = 0; let attempts = 0;
let sideBarRecords: Locator; let sideBarRecords: Locator;

63
tests/playwright/pages/Dashboard/Calendar/CalendarTopBar.ts

@ -1,63 +0,0 @@
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base';
import { CalendarPage } from './index';
export class CalendarTopbarPage extends BasePage {
readonly parent: CalendarPage;
readonly today_btn: Locator;
readonly prev_btn: Locator;
readonly next_btn: Locator;
readonly side_bar_btn: Locator;
constructor(parent: CalendarPage) {
super(parent.rootPage);
this.parent = parent;
this.next_btn = this.get().getByTestId('nc-calendar-next-btn');
this.prev_btn = this.get().getByTestId('nc-calendar-prev-btn');
this.today_btn = this.get().getByTestId('nc-calendar-today-btn');
this.side_bar_btn = this.get().getByTestId('nc-calendar-side-bar-btn');
}
get() {
return this.rootPage.getByTestId('nc-calendar-topbar');
}
async getActiveDate() {
return this.get().getByTestId('nc-calendar-active-date').textContent();
}
async verifyActiveCalendarView({ view }: { view: string }) {
const activeView = this.get().getByTestId('nc-active-calendar-view');
await expect(activeView).toContainText(view);
}
async clickPrev() {
await this.prev_btn.click();
}
async clickNext() {
await this.next_btn.click();
}
async clickToday() {
await this.today_btn.click();
}
async moveToDate({ date, action }: { date: string; action: 'prev' | 'next' }) {
while ((await this.getActiveDate()) !== date) {
if (action === 'prev') {
await this.clickPrev();
} else {
await this.clickNext();
}
}
}
async toggleSideBar() {
await this.side_bar_btn.click();
await this.rootPage.waitForTimeout(500);
}
}

11
tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts

@ -29,6 +29,8 @@ export class CalendarWeekDateTimePage extends BasePage {
}) { }) {
const recordContainer = this.getRecordContainer(); const recordContainer = this.getRecordContainer();
const recordCard = recordContainer.getByTestId(`nc-calendar-week-record-${record}`); const recordCard = recordContainer.getByTestId(`nc-calendar-week-record-${record}`);
await recordCard.scrollIntoViewIfNeeded();
const toDay = this.get() const toDay = this.get()
.getByTestId('nc-calendar-week-day') .getByTestId('nc-calendar-week-day')
.nth(to.dayIndex) .nth(to.dayIndex)
@ -40,7 +42,10 @@ export class CalendarWeekDateTimePage extends BasePage {
await this.rootPage.mouse.down(); await this.rootPage.mouse.down();
await this.rootPage.waitForTimeout(500); await this.rootPage.waitForTimeout(500);
await this.rootPage.mouse.move(cord.x + cord.width / 2, cord.y + cord.height / 2); await this.rootPage.mouse.move(cord.x + Math.ceil(cord.width / 2), cord.y + Math.ceil(cord.height / 2));
// await toDay.scrollIntoViewIfNeeded();
await this.rootPage.waitForTimeout(500);
await this.rootPage.mouse.up(); await this.rootPage.mouse.up();
} }
@ -54,8 +59,8 @@ export class CalendarWeekDateTimePage extends BasePage {
hour.click({ hour.click({
force: true, force: true,
position: { position: {
x: -1, x: 1,
y: -1, y: 1,
}, },
}), }),
requestUrlPathToMatch: '/api/v1/db/data/noco', requestUrlPathToMatch: '/api/v1/db/data/noco',

1
tests/playwright/pages/Dashboard/Calendar/CalendarYear.ts

@ -1,4 +1,3 @@
import { expect, Locator } from '@playwright/test';
import BasePage from '../../Base'; import BasePage from '../../Base';
import { CalendarPage } from './index'; import { CalendarPage } from './index';

8
tests/playwright/pages/Dashboard/Calendar/index.ts

@ -3,7 +3,6 @@ import BasePage from '../../Base';
import { ToolbarPage } from '../common/Toolbar'; import { ToolbarPage } from '../common/Toolbar';
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import { TopbarPage } from '../common/Topbar'; import { TopbarPage } from '../common/Topbar';
import { CalendarTopbarPage } from './CalendarTopBar';
import { CalendarSideMenuPage } from './CalendarSideMenu'; import { CalendarSideMenuPage } from './CalendarSideMenu';
import { CalendarMonthPage } from './CalendarMonth'; import { CalendarMonthPage } from './CalendarMonth';
import { CalendarYearPage } from './CalendarYear'; import { CalendarYearPage } from './CalendarYear';
@ -16,7 +15,6 @@ export class CalendarPage extends BasePage {
readonly dashboard: DashboardPage; readonly dashboard: DashboardPage;
readonly toolbar: ToolbarPage; readonly toolbar: ToolbarPage;
readonly topbar: TopbarPage; readonly topbar: TopbarPage;
readonly calendarTopbar: CalendarTopbarPage;
readonly sideMenu: CalendarSideMenuPage; readonly sideMenu: CalendarSideMenuPage;
readonly calendarMonth: CalendarMonthPage; readonly calendarMonth: CalendarMonthPage;
readonly calendarYear: CalendarYearPage; readonly calendarYear: CalendarYearPage;
@ -30,7 +28,6 @@ export class CalendarPage extends BasePage {
this.dashboard = dashboard; this.dashboard = dashboard;
this.toolbar = new ToolbarPage(this); this.toolbar = new ToolbarPage(this);
this.topbar = new TopbarPage(this); this.topbar = new TopbarPage(this);
this.calendarTopbar = new CalendarTopbarPage(this);
this.sideMenu = new CalendarSideMenuPage(this); this.sideMenu = new CalendarSideMenuPage(this);
this.calendarMonth = new CalendarMonthPage(this); this.calendarMonth = new CalendarMonthPage(this);
this.calendarYear = new CalendarYearPage(this); this.calendarYear = new CalendarYearPage(this);
@ -60,4 +57,9 @@ export class CalendarPage extends BasePage {
async waitLoading() { async waitLoading() {
await this.rootPage.waitForTimeout(2000); await this.rootPage.waitForTimeout(2000);
} }
async toggleSideBar() {
await this.rootPage.getByTestId('nc-calendar-side-bar-btn').click();
await this.rootPage.waitForTimeout(500);
}
} }

2
tests/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -161,7 +161,7 @@ export class ViewSidebarPage extends BasePage {
force: true, force: true,
}); });
const submitAction = () => const submitAction = () =>
this.rootPage.locator('.ant-modal-content').locator('button:has-text("Create a View"):visible').click(); this.rootPage.locator('.ant-modal-content').locator('button:has-text("Create view"):visible').click();
await this.waitForResponse({ await this.waitForResponse({
httpMethodsToMatch: ['POST'], httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/tables/', requestUrlPathToMatch: '/api/v1/db/meta/tables/',

5
tests/playwright/pages/Dashboard/common/Toolbar/CalendarViewMode.ts

@ -13,6 +13,9 @@ export class ToolbarCalendarViewModePage extends BasePage {
} }
async changeCalendarView({ title }: { title: string }) { async changeCalendarView({ title }: { title: string }) {
await this.get().getByTestId(`nc-calendar-view-mode-${title}`).click(); await this.get().click({ force: true });
await this.rootPage.waitForTimeout(500);
await this.rootPage.locator('.rc-virtual-list-holder-inner > div').locator(`text="${title}"`).click();
} }
} }

17
tests/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -42,6 +42,7 @@ export class ToolbarPage extends BasePage {
readonly btn_rowHeight: Locator; readonly btn_rowHeight: Locator;
readonly btn_groupBy: Locator; readonly btn_groupBy: Locator;
readonly btn_calendarSettings: Locator; readonly btn_calendarSettings: Locator;
readonly today_btn: Locator;
constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage | MapPage | CalendarPage) { constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage | MapPage | CalendarPage) {
super(parent.rootPage); super(parent.rootPage);
@ -65,6 +66,8 @@ export class ToolbarPage extends BasePage {
this.btn_rowHeight = this.get().locator(`button.nc-height-menu-btn`); this.btn_rowHeight = this.get().locator(`button.nc-height-menu-btn`);
this.btn_groupBy = this.get().locator(`button.nc-group-by-menu-btn`); this.btn_groupBy = this.get().locator(`button.nc-group-by-menu-btn`);
this.btn_calendarSettings = this.get().getByTestId('nc-calendar-range-btn'); this.btn_calendarSettings = this.get().getByTestId('nc-calendar-range-btn');
this.today_btn = this.get().getByTestId('nc-calendar-today-btn');
} }
get() { get() {
@ -88,6 +91,10 @@ export class ToolbarPage extends BasePage {
if (menuOpen) await this.calendarRange.get().waitFor({ state: 'hidden' }); if (menuOpen) await this.calendarRange.get().waitFor({ state: 'hidden' });
} }
async getActiveDate() {
return this.get().getByTestId('nc-calendar-active-date').textContent();
}
async clickFields() { async clickFields() {
const menuOpen = await this.fields.get().isVisible(); const menuOpen = await this.fields.get().isVisible();
@ -145,6 +152,12 @@ export class ToolbarPage extends BasePage {
} }
} }
async verifyActiveCalendarView({ view }: { view: string }) {
const activeView = this.get().getByTestId('nc-active-calendar-view');
await expect(activeView).toContainText(view);
}
async clickFilter({ async clickFilter({
// `networkValidation` is used to verify that api calls are made when the button is clicked // `networkValidation` is used to verify that api calls are made when the button is clicked
// which happens when the filter is opened for the first time // which happens when the filter is opened for the first time
@ -206,6 +219,10 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`.nc-toolbar-btn.nc-height-menu-btn`).click(); await this.get().locator(`.nc-toolbar-btn.nc-height-menu-btn`).click();
} }
async clickToday() {
await this.today_btn.click();
}
async verifyStackByButton({ title }: { title: string }) { async verifyStackByButton({ title }: { title: string }) {
await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).waitFor({ state: 'visible' }); await this.get().locator(`.nc-toolbar-btn.nc-kanban-stacked-by-menu-btn`).waitFor({ state: 'visible' });
await expect( await expect(

2
tests/playwright/tests/db/general/toolbarOperations.spec.ts

@ -506,7 +506,7 @@ test.describe('Toolbar operations (GRID)', () => {
}); });
test('Duplicate View and Verify GroupBy', async () => { test('Duplicate View and Verify GroupBy', async () => {
if (enableQuickRun()) test.skip(); // if (enableQuickRun()) test.skip();
await dashboard.treeView.openTable({ title: 'Film' }); await dashboard.treeView.openTable({ title: 'Film' });
await dashboard.viewSidebar.createGridView({ title: 'Film Grid' }); await dashboard.viewSidebar.createGridView({ title: 'Film Grid' });

70
tests/playwright/tests/db/views/viewCalendar.spec.ts

@ -36,7 +36,7 @@ const dateRecords = [
{ {
Id: 1, Id: 1,
Title: 'Team Catchup', Title: 'Team Catchup',
StartDate: '2024-01-01 09:00', StartDate: '2024-01-01 08:00',
EndDate: '2024-01-01 10:00', EndDate: '2024-01-01 10:00',
}, },
{ {
@ -161,40 +161,38 @@ test.describe('Calendar View', () => {
// Verify Sidebar // Verify Sidebar
const calendar = dashboard.calendar; const calendar = dashboard.calendar;
await calendar.calendarTopbar.toggleSideBar();
await calendar.verifySideBarOpen(); await calendar.verifySideBarOpen();
await calendar.calendarTopbar.toggleSideBar(); await calendar.toggleSideBar();
await calendar.verifySideBarClosed(); await calendar.verifySideBarClosed();
await calendar.calendarTopbar.toggleSideBar(); await calendar.toggleSideBar();
await calendar.verifySideBarOpen(); await calendar.verifySideBarOpen();
// Verify Calendar View Modes // Verify Calendar View Modes
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'month' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'month' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'week' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'week' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'week' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'week' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'day' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'day' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'day' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'day' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'month' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'month' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'month' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'month' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'year' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'year' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'year' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'year' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'month' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'month' });
await calendar.calendarTopbar.moveToDate({ date: 'January 2024', action: 'prev' }); await calendar.sideMenu.moveToDate({ date: 'Jan 2024', action: 'prev' });
// Verify Sidebar Records & Filters // Verify Sidebar Records & Filters
@ -250,25 +248,25 @@ test.describe('Calendar View', () => {
await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'day' }); await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'day' });
await calendar.calendarTopbar.moveToDate({ await calendar.sideMenu.moveToDate({
date: '1 January 2024', date: '1 Jan 2024',
action: 'prev', action: 'prev',
}); });
await calendar.sideMenu.verifySideBarRecords({ records: dateRecords.filter(f => f.Title).map(f => f.Title) }); await calendar.sideMenu.verifySideBarRecords({ records: dateRecords.filter(f => f.Title).map(f => f.Title) });
await calendar.calendarDayDateTime.selectHour({ hourIndex: 10 });
await calendar.sideMenu.updateFilter({ filter: 'In selected hours' }); await calendar.sideMenu.updateFilter({ filter: 'In selected hours' });
await calendar.calendarDayDateTime.selectHour({ hourIndex: 10 });
await calendar.sideMenu.verifySideBarRecords({ records: ['Team Catchup'] }); await calendar.sideMenu.verifySideBarRecords({ records: ['Team Catchup'] });
await calendar.calendarDayDateTime.selectHour({ hourIndex: 1 }); await calendar.calendarDayDateTime.selectHour({ hourIndex: 1 });
await calendar.sideMenu.verifySideBarRecords({ records: [] }); await calendar.sideMenu.verifySideBarRecords({ records: [] });
await calendar.calendarTopbar.moveToDate({ await calendar.sideMenu.moveToDate({
date: '3 January 2024', date: '3 Jan 2024',
action: 'next', action: 'next',
}); });
@ -276,10 +274,10 @@ test.describe('Calendar View', () => {
await toolbar.calendarViewMode.changeCalendarView({ title: 'week' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'week' });
await calendar.calendarWeekDateTime.selectHour({ dayIndex: 0, hourIndex: 10 });
await calendar.sideMenu.updateFilter({ filter: 'In selected hours' }); await calendar.sideMenu.updateFilter({ filter: 'In selected hours' });
await calendar.calendarWeekDateTime.selectHour({ dayIndex: 0, hourIndex: 10 });
await calendar.sideMenu.verifySideBarRecords({ records: ['Team Catchup'] }); await calendar.sideMenu.verifySideBarRecords({ records: ['Team Catchup'] });
await calendar.calendarWeekDateTime.selectHour({ dayIndex: 0, hourIndex: 1 }); await calendar.calendarWeekDateTime.selectHour({ dayIndex: 0, hourIndex: 1 });
@ -307,9 +305,9 @@ test.describe('Calendar View', () => {
const calendar = dashboard.calendar; const calendar = dashboard.calendar;
await calendar.calendarTopbar.toggleSideBar(); // await calendar.toggleSideBar();
await calendar.calendarTopbar.moveToDate({ date: 'January 2024', action: 'prev' }); await calendar.sideMenu.moveToDate({ date: 'Jan 2024', action: 'prev' });
await calendar.calendarMonth.dragAndDrop({ await calendar.calendarMonth.dragAndDrop({
record: 'Team Catchup', record: 'Team Catchup',
@ -330,7 +328,7 @@ test.describe('Calendar View', () => {
await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'week' }); await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'week' });
await calendar.calendarTopbar.moveToDate({ await calendar.sideMenu.moveToDate({
date: '1 - 7 Jan 24', date: '1 - 7 Jan 24',
action: 'prev', action: 'prev',
}); });
@ -339,13 +337,13 @@ test.describe('Calendar View', () => {
record: 'Team Catchup', record: 'Team Catchup',
to: { to: {
dayIndex: 0, dayIndex: 0,
hourIndex: 5, hourIndex: 7,
}, },
}); });
await calendar.sideMenu.updateFilter({ filter: 'In selected hours' }); await calendar.sideMenu.updateFilter({ filter: 'In selected hours' });
await calendar.calendarWeekDateTime.selectHour({ dayIndex: 0, hourIndex: 5 }); await calendar.calendarWeekDateTime.selectHour({ dayIndex: 0, hourIndex: 7 });
await calendar.sideMenu.verifySideBarRecords({ records: ['Team Catchup'] }); await calendar.sideMenu.verifySideBarRecords({ records: ['Team Catchup'] });
@ -392,29 +390,29 @@ test.describe('Calendar View', () => {
const calendar = dashboard.calendar; const calendar = dashboard.calendar;
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'month' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'month' });
await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'week' }); await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'week' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'week' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'week' });
await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'day' }); await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'day' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'day' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'day' });
await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'month' }); await calendar.toolbar.calendarViewMode.changeCalendarView({ title: 'month' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'month' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'month' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'year' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'year' });
await calendar.calendarTopbar.verifyActiveCalendarView({ view: 'year' }); await calendar.toolbar.verifyActiveCalendarView({ view: 'year' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'month' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'month' });
await calendar.calendarTopbar.moveToDate({ date: 'January 2024', action: 'prev' }); // await calendar.toggleSideBar();
await calendar.calendarTopbar.toggleSideBar(); await calendar.sideMenu.moveToDate({ date: 'Jan 2024', action: 'prev' });
await calendar.sideMenu.verifySideBarRecords({ records: dateRecords.filter(f => f.Title).map(f => f.Title) }); await calendar.sideMenu.verifySideBarRecords({ records: dateRecords.filter(f => f.Title).map(f => f.Title) });
@ -452,21 +450,21 @@ test.describe('Calendar View', () => {
await toolbar.calendarViewMode.changeCalendarView({ title: 'week' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'week' });
await dashboard.calendar.calendarTopbar.verifyActiveCalendarView({ view: 'week' }); await dashboard.calendar.toolbar.verifyActiveCalendarView({ view: 'week' });
await toolbar.calendarViewMode.changeCalendarView({ title: 'day' }); await toolbar.calendarViewMode.changeCalendarView({ title: 'day' });
await dashboard.calendar.calendarTopbar.verifyActiveCalendarView({ view: 'day' }); await dashboard.calendar.toolbar.verifyActiveCalendarView({ view: 'day' });
const calendar = dashboard.calendar; const calendar = dashboard.calendar;
await calendar.calendarTopbar.moveToDate({ date: '1 January 2024', action: 'prev' }); // await calendar.toggleSideBar();
await calendar.calendarTopbar.toggleSideBar(); await calendar.sideMenu.moveToDate({ date: '1 Jan 2024', action: 'prev' });
await calendar.sideMenu.verifySideBarRecords({ records: dateRecords.filter(f => f.Title).map(f => f.Title) }); await calendar.sideMenu.verifySideBarRecords({ records: dateRecords.filter(f => f.Title).map(f => f.Title) });
await calendar.calendarTopbar.moveToDate({ date: '2 January 2024', action: 'next' }); await calendar.sideMenu.moveToDate({ date: '2 Jan 2024', action: 'next' });
await calendar.calendarDayDate.verifyRecord({ records: [] }); await calendar.calendarDayDate.verifyRecord({ records: [] });

Loading…
Cancel
Save