mirror of https://github.com/nocodb/nocodb
Browse Source
* feat(nc-gui): new date picker setup * feat(nc-gui): new date picker * fix(nc-gui): date cell form view validation issue * fix(nc-gui): disable date cell type support in mobile view * fix(nc-gui): small changes * feat(nc-gui): new cell year and month picker * fix(nc-gui): add updated date time picker setup * feat: update date time cell picker * fix(nc-gui): add now option in time picker * fix(nc-gui): small changes * fix(nc-gui): add support to update month, year from date picker * fix(nc-gui): update date picker select mont/year flow * fix(test): date selector test case * fix(nc-gui): update dateTime cell time picker * fix(test): update time picker test case * chore(nc-gui): lint * fix(nc-gui): invalid date issue * fix(nc-gui): date time picker tab issue * fix(nc-gui): year cell test fail issue * fix(nc-gui): date picker filter test fail issue * fix(test): survey form test fail issue * fix(test): update year field fill handler test case * fix(test): update bulk update test * fix(nc-gui): datetime multiple api call issue * fix(test): update timezone related test * fix(test): timezone related test update * fix(nc-gui): tab focus issue * fix(test): filter datetime test udpate * fix(test): ai review changes * fix(nc-gui): date picker font weight issue * fix(nc-gui): update year picker font weight * fix(nc-gui): show full date from date time cell instead of truncate * fix(nc-gui): date time picker ui changes * fix(nc-gui): date time cell width issue * fix(nc-gui): update datetime time option width according to time format * fix(nc-gui): disable datetime input if cell is readonly * fic(nc-gui): add new time picker * feat(nc-gui): update time picker * chore(nc-gui): cleanup unwanted code * fix(test): update time cell test cases * fix(nc-gui): multiple api calls * fix(test): update time cell filter & bulk update test cases * fix(test): revert unrelated changes * fix(nc-gui): pr review changes * fix(nc-gui): add clear datetime cell icon in non grid viewpull/8559/head
Ramesh Mane
6 months ago
committed by
GitHub
21 changed files with 1194 additions and 282 deletions
@ -0,0 +1,150 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import dayjs from 'dayjs' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
size?: 'medium' |
||||||
|
selectedDate?: dayjs.Dayjs | null |
||||||
|
pageDate?: dayjs.Dayjs |
||||||
|
isCellInputField?: boolean |
||||||
|
type: 'date' | 'time' | 'year' | 'month' |
||||||
|
isOpen: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
size: 'medium', |
||||||
|
selectedDate: null, |
||||||
|
pageDate: () => dayjs(), |
||||||
|
isCellInputField: false, |
||||||
|
type: 'date', |
||||||
|
isOpen: false, |
||||||
|
}) |
||||||
|
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 |
||||||
|
const pageDate = useVModel(props, 'pageDate', emit) |
||||||
|
|
||||||
|
const selectedDate = useVModel(props, 'selectedDate', emit) |
||||||
|
|
||||||
|
const { type, isOpen } = toRefs(props) |
||||||
|
|
||||||
|
const localPageDate = ref() |
||||||
|
|
||||||
|
const localSelectedDate = ref() |
||||||
|
|
||||||
|
const pickerType = ref<Props['type'] | undefined>() |
||||||
|
|
||||||
|
const pickerStack = ref<Props['type'][]>([]) |
||||||
|
|
||||||
|
const tempPickerType = computed(() => pickerType.value || type.value) |
||||||
|
|
||||||
|
const handleUpdatePickerType = (value?: Props['type']) => { |
||||||
|
if (value) { |
||||||
|
pickerType.value = value |
||||||
|
pickerStack.value.push(value) |
||||||
|
} else { |
||||||
|
if (pickerStack.value.length > 1) { |
||||||
|
pickerStack.value.pop() |
||||||
|
const lastPicker = pickerStack.value.pop() |
||||||
|
pickerType.value = lastPicker |
||||||
|
} else { |
||||||
|
pickerStack.value = [] |
||||||
|
pickerType.value = type.value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const localStatePageDate = computed({ |
||||||
|
get: () => { |
||||||
|
if (localPageDate.value) { |
||||||
|
return localPageDate.value |
||||||
|
} |
||||||
|
return pageDate.value |
||||||
|
}, |
||||||
|
set: (value) => { |
||||||
|
pageDate.value = value |
||||||
|
localPageDate.value = value |
||||||
|
emit('update:pageDate', value) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
const localStateSelectedDate = computed({ |
||||||
|
get: () => { |
||||||
|
if (localSelectedDate.value) { |
||||||
|
return localSelectedDate.value |
||||||
|
} |
||||||
|
return pageDate.value |
||||||
|
}, |
||||||
|
set: (value: dayjs.Dayjs) => { |
||||||
|
if (!value.isValid()) return |
||||||
|
|
||||||
|
if (pickerType.value === type.value) { |
||||||
|
localPageDate.value = value |
||||||
|
emit('update:selectedDate', value) |
||||||
|
localSelectedDate.value = undefined |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (['date', 'month'].includes(type.value)) { |
||||||
|
if (pickerType.value === 'year') { |
||||||
|
localSelectedDate.value = dayjs(localPageDate.value ?? localSelectedDate.value ?? selectedDate.value ?? dayjs()).year( |
||||||
|
+value.format('YYYY'), |
||||||
|
) |
||||||
|
} |
||||||
|
if (type.value !== 'month' && pickerType.value === 'month') { |
||||||
|
localSelectedDate.value = dayjs(localPageDate.value ?? localSelectedDate.value ?? selectedDate.value ?? dayjs()).month( |
||||||
|
+value.format('MM') - 1, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
localPageDate.value = localSelectedDate.value |
||||||
|
|
||||||
|
handleUpdatePickerType() |
||||||
|
} |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
watch(isOpen, (next) => { |
||||||
|
if (!next) { |
||||||
|
pickerType.value = type.value |
||||||
|
localPageDate.value = undefined |
||||||
|
localSelectedDate.value = undefined |
||||||
|
pickerStack.value = [] |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
onUnmounted(() => { |
||||||
|
pickerType.value = type.value |
||||||
|
localPageDate.value = undefined |
||||||
|
localSelectedDate.value = undefined |
||||||
|
pickerStack.value = [] |
||||||
|
}) |
||||||
|
onMounted(() => { |
||||||
|
localPageDate.value = undefined |
||||||
|
localSelectedDate.value = undefined |
||||||
|
pickerStack.value = [] |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<NcDateWeekSelector |
||||||
|
v-if="tempPickerType === 'date'" |
||||||
|
v-model:page-date="localStatePageDate" |
||||||
|
v-model:selected-date="localStateSelectedDate" |
||||||
|
:picker-type="pickerType" |
||||||
|
:is-monday-first="false" |
||||||
|
is-cell-input-field |
||||||
|
size="medium" |
||||||
|
@update:picker-type="handleUpdatePickerType" |
||||||
|
/> |
||||||
|
<NcMonthYearSelector |
||||||
|
v-if="['month', 'year'].includes(tempPickerType)" |
||||||
|
v-model:page-date="localStatePageDate" |
||||||
|
v-model:selected-date="localStateSelectedDate" |
||||||
|
:picker-type="pickerType" |
||||||
|
:is-year-picker="tempPickerType === 'year'" |
||||||
|
is-cell-input-field |
||||||
|
size="medium" |
||||||
|
@update:picker-type="handleUpdatePickerType" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped></style> |
@ -0,0 +1,104 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import dayjs from 'dayjs' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
selectedDate: dayjs.Dayjs | null |
||||||
|
is12hrFormat?: boolean |
||||||
|
isMinGranularityPicker?: boolean |
||||||
|
minGranularity?: number |
||||||
|
isOpen?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
selectedDate: null, |
||||||
|
is12hrFormat: false, |
||||||
|
isMinGranularityPicker: false, |
||||||
|
minGranularity: 30, |
||||||
|
isOpen: false, |
||||||
|
}) |
||||||
|
const emit = defineEmits(['update:selectedDate']) |
||||||
|
|
||||||
|
const pageDate = ref<dayjs.Dayjs>(dayjs()) |
||||||
|
|
||||||
|
const selectedDate = useVModel(props, 'selectedDate', emit) |
||||||
|
|
||||||
|
const { is12hrFormat, isMinGranularityPicker, minGranularity, isOpen } = toRefs(props) |
||||||
|
|
||||||
|
const timeOptionsWrapperRef = ref<HTMLDivElement>() |
||||||
|
|
||||||
|
const compareTime = (date1: dayjs.Dayjs, date2: dayjs.Dayjs) => { |
||||||
|
if (!date1 || !date2) return false |
||||||
|
|
||||||
|
return date1.format('HH:mm') === date2.format('HH:mm') |
||||||
|
} |
||||||
|
|
||||||
|
const handleSelectTime = (time: dayjs.Dayjs) => { |
||||||
|
pageDate.value = dayjs().set('hour', time.get('hour')).set('minute', time.get('minute')) |
||||||
|
|
||||||
|
selectedDate.value = pageDate.value |
||||||
|
|
||||||
|
// emit('update:selectedDate', pageDate.value) |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: 12hr time format & regular time picker |
||||||
|
const timeOptions = computed(() => { |
||||||
|
return Array.from({ length: is12hrFormat.value ? 12 : 24 }).flatMap((_, h) => { |
||||||
|
return (isMinGranularityPicker.value ? [0, minGranularity.value] : Array.from({ length: 60 })).map((_m, m) => { |
||||||
|
const time = dayjs() |
||||||
|
.set('hour', h) |
||||||
|
.set('minute', isMinGranularityPicker.value ? (_m as number) : m) |
||||||
|
|
||||||
|
return time |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
const handleAutoScroll = (behavior: ScrollBehavior = 'instant') => { |
||||||
|
if (!timeOptionsWrapperRef.value || !selectedDate.value) return |
||||||
|
|
||||||
|
setTimeout(() => { |
||||||
|
const timeEl = timeOptionsWrapperRef.value?.querySelector( |
||||||
|
`[data-testid="time-option-${selectedDate.value?.format('HH:mm')}"]`, |
||||||
|
) |
||||||
|
|
||||||
|
timeEl?.scrollIntoView({ behavior, block: 'center' }) |
||||||
|
}, 50) |
||||||
|
} |
||||||
|
|
||||||
|
watch([selectedDate, isOpen], () => { |
||||||
|
if (timeOptionsWrapperRef.value && isOpen.value && selectedDate.value) { |
||||||
|
handleAutoScroll() |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
handleAutoScroll() |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="flex flex-col max-w-[350px]"> |
||||||
|
<div v-if="isMinGranularityPicker" ref="timeOptionsWrapperRef" class="h-[180px] overflow-y-auto nc-scrollbar-thin"> |
||||||
|
<div |
||||||
|
v-for="time of timeOptions" |
||||||
|
:key="time.format('HH:mm')" |
||||||
|
class="hover:bg-gray-100 py-1 px-3 text-sm text-gray-600 font-weight-500 text-center cursor-pointer" |
||||||
|
:class="{ |
||||||
|
'nc-selected bg-gray-100': selectedDate && compareTime(time, selectedDate), |
||||||
|
}" |
||||||
|
:data-testid="`time-option-${time.format('HH:mm')}`" |
||||||
|
@click="handleSelectTime(time)" |
||||||
|
> |
||||||
|
{{ time.format('HH:mm') }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div v-else></div> |
||||||
|
<div class="px-2 py-1 box-border flex items-center justify-center"> |
||||||
|
<NcButton :tabindex="-1" class="!h-7" size="small" type="secondary" @click="handleSelectTime(dayjs())"> |
||||||
|
<span class="text-small"> {{ $t('general.now') }} </span> |
||||||
|
</NcButton> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped></style> |
Loading…
Reference in new issue