Browse Source

fix(nc-gui): update calendar (#8474)

* fix(nc-gui): update calendar

* fix(nc-gui): refactor components

* fix(nc-gui): tab action position not getting updated

* test: fix calendar flakiness
pull/8496/head
Anbarasu 6 months ago committed by GitHub
parent
commit
44aeaab28a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 95
      packages/nc-gui/components/nc/DateWeekSelector.vue
  2. 16
      packages/nc-gui/components/nc/MonthYearSelector.vue
  3. 13
      packages/nc-gui/components/smartsheet/Toolbar.vue
  4. 4
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  5. 4
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  6. 10
      packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
  7. 4
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
  8. 8
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
  9. 159
      packages/nc-gui/components/smartsheet/calendar/YearView/Month.vue
  10. 37
      packages/nc-gui/components/smartsheet/calendar/YearView/index.vue
  11. 7
      packages/nc-gui/components/smartsheet/calendar/index.vue
  12. 18
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue
  13. 13
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Range.vue
  14. 4
      packages/nc-gui/composables/useCalendarViewStore.ts
  15. 4
      tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts

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

@ -2,15 +2,12 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
interface Props { interface Props {
size?: 'medium' | 'large' | 'small' size?: 'medium'
selectedDate?: dayjs.Dayjs | null selectedDate?: dayjs.Dayjs | null
isDisabled?: boolean
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
hideCalendar?: boolean hideCalendar?: boolean
selectedWeek?: { selectedWeek?: {
start: dayjs.Dayjs start: dayjs.Dayjs
@ -19,19 +16,16 @@ interface Props {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
size: 'large', size: 'medium',
selectedDate: null, selectedDate: null,
isDisabled: false,
isMondayFirst: true, isMondayFirst: true,
disablePagination: false,
pageDate: dayjs(), pageDate: dayjs(),
isWeekPicker: false, isWeekPicker: false,
disableHeader: false,
activeDates: [] as Array<dayjs.Dayjs>, activeDates: [] as Array<dayjs.Dayjs>,
selectedWeek: null, selectedWeek: null,
hideCalendar: false, hideCalendar: false,
}) })
const emit = defineEmits(['change', 'dblClick', 'update:selectedDate', 'update:pageDate', 'update:selectedWeek']) const emit = defineEmits(['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)
@ -139,27 +133,12 @@ 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="flex flex-col">
:class="{ <div class="flex justify-between border-b-1 px-3 py-0.5 nc-date-week-header items-center">
'gap-1': size === 'small', <NcTooltip hide-on-click>
}"
class="flex flex-col"
>
<div
v-if="!disableHeader"
:class="{
'!justify-center': disablePagination,
}"
class="flex justify-between border-b-1 px-3 py-0.5 nc-date-week-header items-center"
>
<NcTooltip v-if="!disablePagination">
<NcButton class="!border-0" size="small" type="secondary" @click="paginate('prev')"> <NcButton class="!border-0" size="small" type="secondary" @click="paginate('prev')">
<component :is="iconMap.arrowLeft" class="h-4 w-4" /> <component :is="iconMap.arrowLeft" class="h-4 w-4" />
</NcButton> </NcButton>
@ -168,16 +147,9 @@ const emitDblClick = (date: dayjs.Dayjs) => {
</template> </template>
</NcTooltip> </NcTooltip>
<span <span class="text-gray-700 text-sm font-semibold">{{ currentMonthYear }}</span>
:class="{
'text-xs': size === 'small',
'text-sm': size === 'medium',
}"
class="text-gray-700 font-semibold"
>{{ currentMonthYear }}</span
>
<NcTooltip v-if="!disablePagination"> <NcTooltip hide-on-click>
<NcButton class="!border-0" data-testid="nc-calendar-next-btn" 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.arrowRight" class="h-4 w-4" /> <component :is="iconMap.arrowRight" class="h-4 w-4" />
</NcButton> </NcButton>
@ -186,42 +158,16 @@ const emitDblClick = (date: dayjs.Dayjs) => {
</template> </template>
</NcTooltip> </NcTooltip>
</div> </div>
<div <div v-if="!hideCalendar" class="max-w-[320px] rounded-y-xl">
v-if="!hideCalendar" <div class="flex py-1 gap-1 px-2.5 rounded-t-xl flex-row border-gray-200 justify-between">
:class="{
'rounded-lg': size === 'small',
'rounded-y-xl': size !== 'small',
}"
class="max-w-[320px]"
>
<div
:class="{
'gap-1 px-3.5': size === 'medium',
'gap-2': size === 'large',
'px-2 !rounded-t-lg': size === 'small',
'rounded-t-xl': size !== 'small',
}"
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"
:key="index" :key="index"
:class="{ class="flex w-8 h-8 items-center uppercase font-medium justify-center text-gray-500"
'w-9 h-9': size === 'large',
'w-8 h-8': size === 'medium',
'text-[10px]': size === 'small',
}"
class="flex items-center uppercase font-medium justify-center text-gray-500"
>{{ day[0] }}</span >{{ day[0] }}</span
> >
</div> </div>
<div <div class="grid gap-1 py-1 px-2.5 nc-date-week-grid-wrapper grid-cols-7">
:class="{
'gap-2 pt-2': size === 'large',
'gap-1 py-1 px-3.5': size === 'medium',
}"
class="grid nc-date-week-grid-wrapper grid-cols-7"
>
<span <span
v-for="(date, index) in dates" v-for="(date, index) in dates"
:key="index" :key="index"
@ -235,29 +181,20 @@ const emitDblClick = (date: dayjs.Dayjs) => {
'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 text-brand-500 !font-semibold nc-calendar-today ': 'rounded-md text-brand-500 !font-semibold nc-calendar-today': isSameDate(date, dayjs()) && isDateInCurrentMonth(date),
isSameDate(date, dayjs()) && isDateInCurrentMonth(date),
'h-9 w-9': size === 'large',
'text-gray-500': date.get('day') === 0 || date.get('day') === 6, 'text-gray-500': date.get('day') === 0 || date.get('day') === 6,
'h-8 w-8': size === 'medium',
'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 h-8 w-8 py-1 relative transition border-1 font-medium flex text-gray-700 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-1.5 w-1.5': size === 'medium',
'h-1.25 w-1.25 top-0.5 right-0.5': size === 'small',
'top-1 right-1': 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 border-1 rounded-full border-white bg-brand-500" class="absolute top-1 transition right-1 h-1.5 w-1.5 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') }}
@ -270,7 +207,7 @@ const emitDblClick = (date: dayjs.Dayjs) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.nc-selected-week { .nc-selected-week {
@apply relative; @apply relative transition-all;
} }
.nc-selected-week:before { .nc-selected-week:before {

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

@ -3,18 +3,14 @@ import dayjs from 'dayjs'
interface Props { interface Props {
selectedDate?: dayjs.Dayjs | null selectedDate?: dayjs.Dayjs | null
isDisabled?: boolean
pageDate?: dayjs.Dayjs pageDate?: dayjs.Dayjs
isYearPicker?: boolean isYearPicker?: boolean
hideHeader?: boolean
hideCalendar?: boolean hideCalendar?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
selectedDate: null, selectedDate: null,
isDisabled: false,
pageDate: dayjs(), pageDate: dayjs(),
hideHeader: false,
isYearPicker: false, isYearPicker: false,
hideCalendar: false, hideCalendar: false,
}) })
@ -90,9 +86,9 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
<template> <template>
<div class="flex flex-col"> <div class="flex flex-col">
<div v-if="!hideHeader" class="flex px-2 border-b-1 py-0.5 justify-between items-center"> <div class="flex px-2 border-b-1 py-0.5 justify-between items-center">
<div class="flex"> <div class="flex">
<NcTooltip> <NcTooltip hide-on-click>
<NcButton class="!border-0" size="small" type="secondary" @click="paginate('prev')"> <NcButton class="!border-0" size="small" type="secondary" @click="paginate('prev')">
<component :is="iconMap.arrowLeft" class="h-4 w-4" /> <component :is="iconMap.arrowLeft" class="h-4 w-4" />
</NcButton> </NcButton>
@ -106,7 +102,7 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
isYearPicker ? dayjs(selectedDate).year() : dayjs(pageDate).format('YYYY') isYearPicker ? dayjs(selectedDate).year() : dayjs(pageDate).format('YYYY')
}}</span> }}</span>
<div class="flex"> <div class="flex">
<NcTooltip> <NcTooltip hide-on-click>
<NcButton class="!border-0" size="small" type="secondary" @click="paginate('next')"> <NcButton class="!border-0" size="small" type="secondary" @click="paginate('next')">
<component :is="iconMap.arrowRight" class="h-4 w-4" /> <component :is="iconMap.arrowRight" class="h-4 w-4" />
</NcButton> </NcButton>
@ -123,10 +119,10 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
v-for="(month, id) in months" v-for="(month, id) in months"
:key="id" :key="id"
:class="{ :class="{
'!bg-gray-200 !text-brand-500 !font-bold ': isMonthSelected(month), '!bg-gray-200 !text-brand-900 !font-bold ': isMonthSelected(month),
'!text-brand-500': dayjs().isSame(month, '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-8 rounded-lg flex items-center transition-all font-medium justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-700 cursor-pointer"
@click="selectedDate = month" @click="selectedDate = month"
> >
{{ month.format('MMM') }} {{ month.format('MMM') }}
@ -140,7 +136,7 @@ const compareYear = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
'!bg-gray-200 !text-brand-500 !font-bold ': compareYear(year, selectedDate), '!bg-gray-200 !text-brand-500 !font-bold ': compareYear(year, selectedDate),
'!text-brand-500': dayjs().isSame(year, 'year'), '!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-8 rounded-lg flex items-center transition-all font-medium justify-center hover:(border-1 border-gray-200 bg-gray-100) text-gray-900 cursor-pointer"
@click="selectedDate = year" @click="selectedDate = year"
> >
{{ year.format('YYYY') }} {{ year.format('YYYY') }}

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

@ -22,9 +22,12 @@ const { allowCSVDownload } = useSharedView()
<template> <template>
<div <div
v-if="!isMobileMode" v-if="!isMobileMode || isCalendar"
ref="containerRef" ref="containerRef"
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" :class="{
'px-4': isMobileMode,
}"
class="nc-table-toolbar relative px-3 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" />
@ -76,10 +79,12 @@ const { allowCSVDownload } = useSharedView()
'w-full': isMobileMode, 'w-full': isMobileMode,
}" }"
/> />
<div v-if="isCalendar && isMobileMode" class="flex-1 pointer-events-none" />
<LazySmartsheetToolbarCalendarMode v-if="isCalendar && !isTab" :tab="isTab" /> <LazySmartsheetToolbarCalendarMode v-if="isCalendar && !isTab" :tab="isTab" />
<LazySmartsheetToolbarFieldsMenu v-if="isCalendar" :show-system-fields="false" /> <LazySmartsheetToolbarFieldsMenu v-if="isCalendar && !isMobileMode" :show-system-fields="false" />
<LazySmartsheetToolbarColumnFilterMenu v-if="isCalendar" /> <LazySmartsheetToolbarColumnFilterMenu v-if="isCalendar && !isMobileMode" />
</template> </template>
</div> </div>
</template> </template>

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

@ -889,7 +889,7 @@ watch(
:class="{ :class="{
'!border-brand-500': hour.isSame(selectedTime), '!border-brand-500': hour.isSame(selectedTime),
}" }"
class="flex w-full border-l-gray-100 h-13 nc-calendar-day-hour relative border-1 group hover:bg-gray-50 border-white border-b-gray-100" class="flex w-full border-l-gray-100 h-13 transition nc-calendar-day-hour relative border-1 group hover:bg-gray-50 border-white border-b-gray-100"
data-testid="nc-calendar-day-hour" data-testid="nc-calendar-day-hour"
@click="selectHour(hour)" @click="selectHour(hour)"
@dblclick="newRecord(hour)" @dblclick="newRecord(hour)"
@ -999,7 +999,7 @@ watch(
:data-testid="`nc-calendar-day-record-${record.row[displayField!.title!]}`" :data-testid="`nc-calendar-day-record-${record.row[displayField!.title!]}`"
:data-unique-id="record.rowMeta.id" :data-unique-id="record.rowMeta.id"
:style="record.rowMeta.style" :style="record.rowMeta.style"
class="absolute draggable-record group cursor-pointer pointer-events-auto" class="absolute draggable-record transition group cursor-pointer pointer-events-auto"
@mousedown="dragStart($event, record)" @mousedown="dragStart($event, record)"
@mouseleave="hoverRecord = null" @mouseleave="hoverRecord = null"
@mouseover="hoverRecord = record.rowMeta.id as string" @mouseover="hoverRecord = record.rowMeta.id as string"

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

@ -700,7 +700,7 @@ const addRecord = (date: dayjs.Dayjs) => {
'!text-gray-400': !isDayInPagedMonth(day), '!text-gray-400': !isDayInPagedMonth(day),
'!bg-gray-50': day.get('day') === 0 || day.get('day') === 6, '!bg-gray-50': day.get('day') === 0 || day.get('day') === 6,
}" }"
class="text-right relative group last:border-r-0 text-sm h-full border-r-1 border-b-1 border-gray-200 font-medium hover:bg-gray-50 text-gray-800 bg-white" class="text-right relative group last:border-r-0 transition text-sm h-full border-r-1 border-b-1 border-gray-200 font-medium hover:bg-gray-50 text-gray-800 bg-white"
data-testid="nc-calendar-month-day" data-testid="nc-calendar-month-day"
@click="selectDate(day)" @click="selectDate(day)"
@dblclick="addRecord(day)" @dblclick="addRecord(day)"
@ -812,7 +812,7 @@ const addRecord = (date: dayjs.Dayjs) => {
...record.rowMeta.style, ...record.rowMeta.style,
zIndex: record.rowMeta.id === draggingId ? 100 : 0, zIndex: record.rowMeta.id === draggingId ? 100 : 0,
}" }"
class="absolute group draggable-record cursor-pointer pointer-events-auto" class="absolute group draggable-record transition cursor-pointer pointer-events-auto"
@mouseleave="hoverRecord = null" @mouseleave="hoverRecord = null"
@mouseover="hoverRecord = record.rowMeta.id" @mouseover="hoverRecord = record.rowMeta.id"
@mousedown.stop="dragStart($event, record)" @mousedown.stop="dragStart($event, record)"

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

@ -315,25 +315,29 @@ onClickOutside(searchRef, toggleSearch)
<template> <template>
<NcTooltip <NcTooltip
:class="{ :class="{
'!right-26 top-[-36px]': showSideMenu && isMobileMode,
'right-2': !showSideMenu, 'right-2': !showSideMenu,
'right-74': showSideMenu, 'right-74': showSideMenu,
}" }"
class="absolute transition-all ease-in-out top-2 z-30" class="absolute transition-all ease-in-out z-9 top-2"
hide-on-click
> >
<template #title> {{ $t('activity.toggleSidebar') }}</template> <template #title> {{ $t('activity.toggleSidebar') }}</template>
<NcButton v-if="!isMobileMode" data-testid="nc-calendar-side-bar-btn" size="small" type="secondary" @click="toggleSideMenu"> <NcButton 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" /> <component :is="iconMap.sidebar" class="h-4 w-4 text-gray-600 transition-all" />
</NcButton> </NcButton>
</NcTooltip> </NcTooltip>
<div <div
:class="{ :class="{
'!min-w-[100svw]': props.visible && isMobileMode,
'!w-0 hidden': !props.visible, '!w-0 hidden': !props.visible,
'nc-calendar-side-menu-open block !min-w-[288px]': props.visible, 'nc-calendar-side-menu-open block !min-w-[288px]': props.visible,
}" }"
class="h-full relative 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 class="flex flex-col"> <div class="flex min-w-[288px] 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"

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

@ -551,14 +551,14 @@ const addRecord = (date: dayjs.Dayjs) => {
<template> <template>
<div class="flex relative flex-col prevent-select" data-testid="nc-calendar-week-view" @drop="dropEvent"> <div class="flex relative flex-col prevent-select" data-testid="nc-calendar-week-view" @drop="dropEvent">
<div class="flex"> <div class="flex h-6">
<div <div
v-for="(date, weekIndex) in weekDates" v-for="(date, weekIndex) in weekDates"
:key="weekIndex" :key="weekIndex"
: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 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" class="w-1/7 cursor-pointer text-center text-[10px] font-semibold leading-4 flex items-center justify-center uppercase 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)" @click="selectDate(date)"
@dblclick="addRecord(date)" @dblclick="addRecord(date)"
> >

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

@ -877,14 +877,14 @@ watch(
data-testid="nc-calendar-week-view" data-testid="nc-calendar-week-view"
@drop="dropEvent" @drop="dropEvent"
> >
<div class="flex sticky h-7.1 z-1 top-0 pl-16 bg-gray-50 w-full"> <div class="flex sticky h-6 z-1 top-0 pl-16 bg-gray-50 w-full">
<div <div
v-for="date in datesHours" v-for="date in datesHours"
:key="date[0].toISOString()" :key="date[0].toISOString()"
:class="{ :class="{
'text-brand-500': date[0].isSame(dayjs(), 'date'), 'text-brand-500': date[0].isSame(dayjs(), 'date'),
}" }"
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" class="w-1/7 text-center text-[10px] font-semibold leading-4 flex items-center justify-center uppercase 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>
@ -907,7 +907,7 @@ watch(
'border-1 !border-brand-500 bg-gray-50': hour.isSame(selectedTime, 'hour'), 'border-1 !border-brand-500 bg-gray-50': hour.isSame(selectedTime, 'hour'),
'!bg-gray-50': hour.get('day') === 0 || hour.get('day') === 6, '!bg-gray-50': hour.get('day') === 0 || hour.get('day') === 6,
}" }"
class="text-center relative h-13 text-sm text-gray-500 w-full hover:bg-gray-50 py-1 border-transparent border-1 border-x-gray-100 border-t-gray-100 border-l-gray-200" class="text-center relative transition h-13 text-sm text-gray-500 w-full hover:bg-gray-50 py-1 border-transparent border-1 border-x-gray-100 border-t-gray-100 border-l-gray-200"
data-testid="nc-calendar-week-hour" data-testid="nc-calendar-week-hour"
@dblclick="addRecord(hour)" @dblclick="addRecord(hour)"
@click=" @click="
@ -944,7 +944,7 @@ watch(
:data-testid="`nc-calendar-week-record-${record.row[displayField!.title!]}`" :data-testid="`nc-calendar-week-record-${record.row[displayField!.title!]}`"
:data-unique-id="record.rowMeta!.id" :data-unique-id="record.rowMeta!.id"
:style="record.rowMeta!.style " :style="record.rowMeta!.style "
class="absolute draggable-record w-1/7 group cursor-pointer pointer-events-auto" class="absolute transition draggable-record w-1/7 group cursor-pointer pointer-events-auto"
@mousedown.stop="dragStart($event, record)" @mousedown.stop="dragStart($event, record)"
@mouseleave="hoverRecord = null" @mouseleave="hoverRecord = null"
@mouseover="hoverRecord = record.rowMeta.id" @mouseover="hoverRecord = record.rowMeta.id"

159
packages/nc-gui/components/smartsheet/calendar/YearView/Month.vue

@ -0,0 +1,159 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
interface Props {
size?: 'medium' | 'small'
selectedDate?: dayjs.Dayjs | null
pageDate?: dayjs.Dayjs
activeDates?: Array<dayjs.Dayjs>
isMondayFirst?: boolean
}
const props = withDefaults(defineProps<Props>(), {
size: 'medium',
selectedDate: null,
isMondayFirst: true,
pageDate: dayjs(),
activeDates: [] as Array<dayjs.Dayjs>,
})
const emit = defineEmits(['dblClick', 'update:selectedDate', 'update:pageDate'])
// Page date is the date we use to manage which month/date that is currently being displayed
const pageDate = useVModel(props, 'pageDate', emit)
const selectedDate = useVModel(props, 'selectedDate', emit)
const activeDates = useVModel(props, 'activeDates', emit)
const days = computed(() => {
if (props.isMondayFirst) {
return ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
} else {
return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
}
})
const currentMonthYear = computed(() => {
return dayjs(pageDate.value).format('MMMM')
})
// Generates all dates should be displayed in the calendar
// Includes all blank days at the start and end of the month
const dates = computed(() => {
const startOfMonth = dayjs(pageDate.value).startOf('month')
const dayOffset = +props.isMondayFirst
const firstDayOfWeek = startOfMonth.day()
const startDay = startOfMonth.subtract((firstDayOfWeek - dayOffset + 7) % 7, 'day')
const datesArray = []
for (let i = 0; i < 42; i++) {
datesArray.push(startDay.add(i, 'day'))
}
return datesArray
})
// Used to check if two dates are the same
const isSameDate = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => {
if (!date1 || !date2) return false
return date1.isSame(date2, 'day')
}
// Used in DatePicker for checking if the date is currently selected
const isSelectedDate = (dObj: dayjs.Dayjs) => {
if (!selectedDate.value) return false
const propDate = dayjs(selectedDate.value)
return props.selectedDate ? isSameDate(propDate, dObj) : false
}
const isDayInPagedMonth = (date: dayjs.Dayjs) => {
return date.month() === dayjs(pageDate.value).month()
}
// Since we are using the same component for week picker and date picker we need to handle the date selection differently
const handleSelectDate = (date: dayjs.Dayjs) => {
if (!isDayInPagedMonth(date)) {
pageDate.value = date
emit('update:pageDate', date)
}
selectedDate.value = date
emit('update:selectedDate', date)
}
// Used to check if a date is in the current month
const isDateInCurrentMonth = (date: dayjs.Dayjs) => {
return date.month() === dayjs(pageDate.value).month()
}
// Used to Check if an event is in the date
const isActiveDate = (date: dayjs.Dayjs) => {
return activeDates.value.some((d) => isSameDate(d, date))
}
const emitDblClick = (date: dayjs.Dayjs) => {
emit('dblClick', date)
}
</script>
<template>
<div>
<div class="flex justify-center px-2 nc-date-week-header text-gray-700 text-sm py-2 font-semibold items-center">
{{ currentMonthYear }}
</div>
<div
:class="{
'rounded-lg': size === 'small',
'rounded-y-xl': size !== 'small',
}"
class="max-w-[320px]"
>
<div class="px-2.5">
<div class="flex border-b-1 justify-between gap-0.5 border-gray-200">
<span
v-for="(day, index) in days"
:key="index"
:class="{
'w-8 h-8 text-sm': size === 'medium',
'text-xs w-6 h-6': size === 'small',
}"
class="flex items-center uppercase py-1 font-medium justify-center text-gray-500"
>{{ day[0] }}</span
>
</div>
</div>
<div class="grid gap-x-0.5 gap-y-2 px-2.5 py-1 nc-date-week-grid-wrapper grid-cols-7">
<span
v-for="(date, index) in dates"
:key="index"
:class="{
'bg-gray-300 border-1 !font-semibold': isSelectedDate(date) && isDayInPagedMonth(date),
'hover:(border-1 border-gray-200 bg-gray-100)': !isSelectedDate(date),
'text-gray-400': !isDateInCurrentMonth(date),
'text-brand-500 !font-semibold nc-calendar-today': isSameDate(date, dayjs()) && isDateInCurrentMonth(date),
'text-gray-500': date.get('day') === 0 || date.get('day') === 6,
'h-8 w-8 text-sm': size === 'medium',
'h-6 w-6 text-xs': size === 'small',
}"
class="px-1 py-1.5 relative rounded border-transparent transition border-1 font-medium flex text-gray-700 items-center cursor-pointer justify-center"
data-testid="nc-calendar-date"
@click="handleSelectDate(date)"
@dblclick="emitDblClick(date)"
>
<span
v-if="isActiveDate(date)"
:class="{
'h-1.25 w-1.25 top-0.5 right-0.5': size === 'small',
'!border-white': isSelectedDate(date),
'!border-brand-50': isSameDate(date, dayjs()),
}"
class="absolute z-2 h-1.5 top-1 right-1 w-1.5 transition border-1 rounded-full border-white bg-brand-500"
></span>
<span class="z-2">
{{ date.get('date') }}
</span>
</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped></style>

37
packages/nc-gui/components/smartsheet/calendar/YearView.vue → packages/nc-gui/components/smartsheet/calendar/YearView/index.vue

@ -13,17 +13,27 @@ const months = computed(() => {
const calendarContainer = ref<HTMLElement | null>(null) const calendarContainer = ref<HTMLElement | null>(null)
const { width } = useWindowSize() const { width } = useElementSize(calendarContainer)
const size = ref('small') const size = ref<'small' | 'medium'>('small')
const cols = ref(4)
const handleResize = () => { const handleResize = () => {
if (width.value < 1608) { if (width.value > 1250) {
size.value = 'small' size.value = 'medium'
} else if (width.value < 2000) { cols.value = 4
} else if (width.value > 850) {
size.value = 'medium' size.value = 'medium'
cols.value = 3
} else if (width.value > 680) {
size.value = 'small'
cols.value = 3
} else if (width.value > 375) {
size.value = 'small'
cols.value = 2
} else { } else {
size.value = 'large' size.value = 'medium'
cols.value = 1
} }
} }
@ -40,15 +50,19 @@ watch(width, handleResize)
</script> </script>
<template> <template>
<div ref="calendarContainer" class="overflow-auto flex my-2 justify-center nc-scrollbar-md"> <div ref="calendarContainer" class="overflow-auto flex my-2 transition-all justify-center nc-scrollbar-md">
<div <div
:class="{ :class="{
'!gap-12': size === 'large', 'grid-cols-1': cols === 1,
'grid-cols-2': cols === 2,
'grid-cols-3': cols === 3,
'grid-cols-4': cols === 4,
'!gap-5': cols < 3 && size === 'small',
}" }"
class="grid grid-cols-4 justify-items-center gap-6 scale-1" class="grid justify-items-center gap-8"
data-testid="nc-calendar-year-view" data-testid="nc-calendar-year-view"
> >
<NcDateWeekSelector <LazySmartsheetCalendarYearViewMonth
v-for="(_, index) in months" v-for="(_, index) in months"
:key="index" :key="index"
v-model:active-dates="activeDates" v-model:active-dates="activeDates"
@ -57,7 +71,6 @@ watch(width, handleResize)
:size="size" :size="size"
class="nc-year-view-calendar" class="nc-year-view-calendar"
data-testid="nc-calendar-year-view-month-selector" data-testid="nc-calendar-year-view-month-selector"
disable-pagination
@dbl-click="changeView" @dbl-click="changeView"
/> />
</div> </div>
@ -67,7 +80,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 border-gray-200; @apply border-gray-200 h-8 py-2;
} }
} }
</style> </style>

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

@ -161,12 +161,7 @@ reloadViewDataHook?.on(async (params: void | { shouldShowLoading?: boolean }) =>
</div> </div>
</template> </template>
</div> </div>
<LazySmartsheetCalendarSideMenu <LazySmartsheetCalendarSideMenu :visible="showSideMenu" @expand-record="expandRecord" @new-record="newRecord" />
v-if="!isMobileMode"
:visible="showSideMenu"
@expand-record="expandRecord"
@new-record="newRecord"
/>
</div> </div>
<Suspense> <Suspense>

18
packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue

@ -5,6 +5,8 @@ const props = defineProps<{
const { changeCalendarView, activeCalendarView } = useCalendarViewStoreOrThrow() const { changeCalendarView, activeCalendarView } = useCalendarViewStoreOrThrow()
const isTab = computed(() => props.tab)
const highlightStyle = ref({ left: '0px' }) const highlightStyle = ref({ left: '0px' })
const setActiveCalendarMode = (mode: 'day' | 'week' | 'month' | 'year', event: MouseEvent) => { const setActiveCalendarMode = (mode: 'day' | 'week' | 'month' | 'year', event: MouseEvent) => {
@ -22,15 +24,19 @@ const updateHighlightPosition = () => {
}) })
} }
onMounted(() => {
updateHighlightPosition()
})
watch(activeCalendarView, () => { watch(activeCalendarView, () => {
if (!props.tab) return if (!isTab.value) return
updateHighlightPosition() updateHighlightPosition()
}) })
</script> </script>
<template> <template>
<div <div
v-if="props.tab" v-if="isTab"
class="flex flex-row px-1 pointer-events-auto 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"
> >
@ -47,12 +53,12 @@ watch(activeCalendarView, () => {
</div> </div>
</div> </div>
<NcSelect v-else v-model:value="activeCalendarView" class="!w-22" data-testid="nc-calendar-view-mode" size="small"> <NcSelect v-else v-model:value="activeCalendarView" class="!w-21" data-testid="nc-calendar-view-mode" size="small">
<a-select-option v-for="option in ['day', 'week', 'month', 'year']" :key="option" :value="option" class="!h-7 !w-20"> <a-select-option v-for="option in ['day', 'week', 'month', 'year']" :key="option" :value="option" class="!h-7 !w-21">
<div class="flex gap-2 mt-0.5 items-center"> <div class="flex gap-2 mt-0.5 items-center">
<NcTooltip class="truncate !capitalize flex-1 max-w-18" placement="top" show-on-truncate-only> <NcTooltip class="!capitalize flex-1 max-w-21" placement="top" show-on-truncate-only>
<template #title> <template #title>
<span class="capitalize"> <span class="capitalize min-w-21">
{{ option }} {{ option }}
</span> </span>
</template> </template>

13
packages/nc-gui/components/smartsheet/toolbar/Calendar/Range.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type CalendarRangeType, UITypes, isSystemColumn } from 'nocodb-sdk' import { type CalendarRangeType, UITypes, ViewTypes, isSystemColumn } from 'nocodb-sdk'
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -119,10 +119,15 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
<template #overlay> <template #overlay>
<div v-if="calendarRangeDropdown" class="w-98 space-y-6 rounded-2xl 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>
<div class="flex justify-between"> <div class="flex mb-3 justify-between">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<component :is="iconMap.calendar" class="text-maroon-500 w-5 h-5" /> <GeneralViewIcon
<span class="font-bold"> {{ `${$t('activity.calendar')} ${$t('activity.viewSettings')}` }}</span> :meta="{
type: ViewTypes.CALENDAR,
}"
class="w-6 h-6"
/>
<span class="font-bold text-base"> {{ `${$t('activity.calendar')} ${$t('activity.viewSettings')}` }}</span>
</div> </div>
<a <a

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

@ -35,6 +35,8 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const { isMobileMode } = useGlobal()
const displayField = computed(() => meta.value?.columns?.find((c) => c.pv)) const displayField = computed(() => meta.value?.columns?.find((c) => c.pv))
const activeCalendarView = ref<'month' | 'year' | 'day' | 'week'>() const activeCalendarView = ref<'month' | 'year' | 'day' | 'week'>()
@ -54,7 +56,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const isCalendarMetaLoading = ref<boolean>(false) const isCalendarMetaLoading = ref<boolean>(false)
const showSideMenu = ref(true) const showSideMenu = ref(!isMobileMode.value)
const selectedDateRange = ref<{ const selectedDateRange = ref<{
start: dayjs.Dayjs start: dayjs.Dayjs

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

@ -59,8 +59,8 @@ export class CalendarWeekDateTimePage extends BasePage {
hour.click({ hour.click({
force: true, force: true,
position: { position: {
x: 1, x: 0,
y: 1, y: 0,
}, },
}), }),
requestUrlPathToMatch: '/api/v1/db/data/noco', requestUrlPathToMatch: '/api/v1/db/data/noco',

Loading…
Cancel
Save