Browse Source

fix: spanning records feat: updated styles

pull/9831/head
DarkPhoenix2704 6 days ago
parent
commit
748e4d97f6
  1. 304
      packages/nc-gui/components/smartsheet/calendar/DateTimeSpanningContainer.vue
  2. 8
      packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue
  3. 63
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  4. 22
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  5. 20
      packages/nc-gui/components/smartsheet/calendar/RecordCard.vue
  6. 12
      packages/nc-gui/components/smartsheet/calendar/VRecordCard.vue
  7. 3
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
  8. 48
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
  9. 63
      packages/nc-gui/components/smartsheet/toolbar/Calendar/Range.vue

304
packages/nc-gui/components/smartsheet/calendar/DateTimeSpanningContainer.vue

@ -0,0 +1,304 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import type { Row } from '#imports'
const props = defineProps<{
records: Row[]
}>()
const emit = defineEmits(['expandRecord', 'newRecord'])
const container = ref<null | HTMLElement>(null)
const { $e } = useNuxtApp()
const { width: containerWidth } = useElementSize(container)
const meta = inject(MetaInj, ref())
const { isUIAllowed } = useRoles()
const records = toRef(props, 'records')
const fields = inject(FieldsInj, ref())
const { fields: _fields } = useViewColumnsOrThrow()
const fieldStyles = computed(() => {
return (_fields.value ?? []).reduce((acc, field) => {
acc[field.fk_column_id!] = {
bold: !!field.bold,
italic: !!field.italic,
underline: !!field.underline,
}
return acc
}, {} as Record<string, { bold?: boolean; italic?: boolean; underline?: boolean }>)
})
const {
selectedDate,
formattedData,
formattedSideBarData,
calendarRange,
updateRowProperty,
selectedDateRange,
viewMetaProperties,
displayField,
activeCalendarView,
} = useCalendarViewStoreOrThrow()
const maxVisibleDays = computed(() => {
return activeCalendarView.value === 'week' ? (viewMetaProperties.value?.hide_weekend ? 5 : 7) : 1
})
// This function is used to find the first suitable row for a record
// It takes the recordsInDay object, the start day index and the span of the record in days
// It returns the first suitable row for the entire span of the record
const findFirstSuitableRow = (recordsInDay: any, startDayIndex: number, spanDays: number) => {
let row = 0
while (true) {
let isRowSuitable = true
// Check if the row is suitable for the entire span
for (let i = 0; i < spanDays; i++) {
const dayIndex = startDayIndex + i
if (!recordsInDay[dayIndex]) {
recordsInDay[dayIndex] = {}
}
// If the row is occupied, the entire span is not suitable
if (recordsInDay[dayIndex][row]) {
isRowSuitable = false
break
}
}
// If the row is suitable, return it
if (isRowSuitable) {
return row
}
row++
}
}
const viewStartDate = computed(() => {
if (activeCalendarView.value === 'week') {
return selectedDateRange.value.start
} else {
return selectedDate.value
}
})
const isInRange = (date: dayjs.Dayjs) => {
if (activeCalendarView.value === 'day') {
return date.isSame(selectedDate.value, 'day')
} else {
const rangeEndDate =
maxVisibleDays.value === 5 ? dayjs(selectedDateRange.value.end).subtract(2, 'day') : dayjs(selectedDateRange.value.end)
return (
date && date.isBetween(dayjs(selectedDateRange.value.start).startOf('day'), dayjs(rangeEndDate).endOf('day'), 'day', '[]')
)
}
}
const calendarData = computed(() => {
if (!records.value?.length || !calendarRange.value) return []
const recordsInDay = Array.from({ length: 7 }, () => ({})) as Record<number, Record<number, boolean>>
const perDayWidth = containerWidth.value / maxVisibleDays.value
const recordsInRange = [] as Row[]
calendarRange.value.forEach(({ fk_from_col, fk_to_col }) => {
if (!fk_from_col || !fk_to_col) return
for (const record of records.value) {
const id = record.rowMeta.id ?? generateRandomNumber()
const startDate = dayjs(record.row[fk_from_col.title!])
const endDate = dayjs(record.row[fk_to_col.title!])
const startDayIndex = Math.max(startDate.diff(viewStartDate.value, 'day'), 0)
const endDayIndex = Math.min(endDate.diff(viewStartDate.value, 'day'), maxVisibleDays.value - 1)
const spanDays = endDayIndex - startDayIndex + 1
const row = findFirstSuitableRow(recordsInDay, startDayIndex, spanDays)
for (let i = 0; i < spanDays; i++) {
const dayIndex = startDayIndex + i
recordsInDay[dayIndex][row] = true
}
const isStartInRange = isInRange(startDate)
const isEndInRange = isInRange(endDate)
let position = 'none'
if (isStartInRange && isEndInRange) position = 'rounded'
else if (isStartInRange) position = 'leftRounded'
else if (isEndInRange) position = 'rightRounded'
const style: Partial<CSSStyleDeclaration> = {
top: `${row * 28 + row * 8}px`,
left: `${startDayIndex * perDayWidth}px`,
width: `${spanDays * perDayWidth + 8}px`,
}
recordsInRange.push({
...record,
rowMeta: {
...record.rowMeta,
style,
position,
range: { fk_from_col, fk_to_col },
id,
},
})
}
})
return recordsInRange
})
const dragElement = ref<HTMLElement | null>(null)
const resizeInProgress = ref(false)
const dragTimeout = ref<ReturnType<typeof setTimeout>>()
const isDragging = ref(false)
const dragRecord = ref<Row>()
const resizeDirection = ref<'right' | 'left' | null>()
const resizeRecord = ref<Row | null>(null)
const hoverRecord = ref<string | null>()
const onResizeStart = (record: Row, direction: 'right' | 'left') => {
resizeDirection.value = direction
resizeRecord.value = record
}
const dragStart = (event: MouseEvent, record: Row) => {
if (resizeInProgress.value) return
let target = event.target as HTMLElement
if (activeCalendarView.value === 'day') {
emit('expandRecord', record)
return
}
isDragging.value = false
dragTimeout.value = setTimeout(() => {
if (!isUIAllowed('dataEdit')) return
isDragging.value = true
while (!target.classList.contains('draggable-record')) {
target = target.parentElement as HTMLElement
}
// TODO: Implement dragOffset @DarkPhoenix2704
/* dragOffset.value = {
x: event.clientX - target.getBoundingClientRect().left,
y: event.clientY - target.getBoundingClientRect().top,
} */
const allRecords = document.querySelectorAll('.draggable-record')
allRecords.forEach((el) => {
if (!el.getAttribute('data-unique-id').includes(record.rowMeta.id!)) {
el.style.opacity = '30%'
}
})
isDragging.value = true
dragElement.value = target
dragRecord.value = record
// document.addEventListener('mousemove', onDrag)
// document.addEventListener('mouseup', stopDrag)
}, 200)
const onMouseUp = () => {
clearTimeout(dragTimeout.value!)
document.removeEventListener('mouseup', onMouseUp)
if (!isDragging.value) {
emit('expandRecord', record)
}
}
document.addEventListener('mouseup', onMouseUp)
}
</script>
<template>
<div style="z-index: 100" class="sticky flex top-0 bg-white border-b-1 border-gray-100 prevent-select">
<div
style="width: 64px"
:style="{
width: `${activeCalendarView === 'week' ? '64px' : '66px'}`,
}"
class="text-xs top-0 text-right z-50 !sticky h-full left-0 text-[#6A7184] p-2"
>
All day
</div>
<div
ref="container"
:style="{
height: `min(64px)`,
width: 'calc(100% - 67px)',
}"
:class="{
'border-gray-100': activeCalendarView === 'day',
'border-gray-200': activeCalendarView === 'week',
}"
class="relative border-l-1 z-30 py-1"
>
<div class="pointer-events-none inset-y-0 relative">
<template v-for="(record, id) in calendarData" :key="id">
<div
v-if="record.rowMeta.style?.display !== 'none'"
:data-testid="`nc-calendar-week-record-${record.row[displayField!.title!]}`"
:data-unique-id="record.rowMeta.id"
:style="{
...record.rowMeta.style,
}"
class="absolute group draggable-record pointer-events-auto nc-calendar-week-record-card"
@mouseleave="hoverRecord = null"
@mouseover="hoverRecord = record.rowMeta.id"
@mousedown.stop="dragStart($event, record)"
>
<LazySmartsheetRow :row="record">
<LazySmartsheetCalendarRecordCard
:hover="hoverRecord === record.rowMeta.id"
:position="record.rowMeta.position"
:record="record"
:resize="activeCalendarView === 'week'"
@dblclick.stop="emit('expandRecord', record)"
@resize-start="onResizeStart"
>
<template v-for="(field, index) in fields" :key="index">
<LazySmartsheetPlainCell
v-if="!isRowEmpty(record, field!)"
v-model="record.row[field!.title!]"
class="text-xs"
:column="field"
:bold="!!fieldStyles[field.id]?.bold"
:italic="!!fieldStyles[field.id]?.italic"
:underline="!!fieldStyles[field.id]?.underline"
/>
</template>
</LazySmartsheetCalendarRecordCard>
</LazySmartsheetRow>
</div>
</template>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.prevent-select {
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>

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

@ -54,7 +54,7 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
dayRecordCount++
const style: Partial<CSSStyleDeclaration> = {
top: `${(dayRecordCount - 1) * perRecordHeight}px`,
top: `${(dayRecordCount - 1) * perRecordHeight + dayRecordCount * 8}px`,
width: '100%',
}
@ -97,7 +97,7 @@ const recordsAcrossAllRange = computed<Row[]>(() => {
style: {
width: '100%',
left: '0',
top: `${(dayRecordCount - 1) * perRecordHeight}px`,
top: `${(dayRecordCount - 1) * perRecordHeight + dayRecordCount * 8}px`,
},
position: 'rounded',
},
@ -212,17 +212,15 @@ const newRecord = () => {
v-for="(record, rowIndex) in recordsAcrossAllRange"
:key="rowIndex"
:style="record.rowMeta.style"
class="absolute mt-2"
class="absolute"
data-testid="nc-calendar-day-record-card"
@mouseleave="hoverRecord = null"
@mouseover="hoverRecord = record.rowMeta.id as string"
>
<LazySmartsheetRow :row="record">
<LazySmartsheetCalendarRecordCard
:position="record.rowMeta.position"
:record="record"
:resize="false"
color="blue"
size="small"
@click.prevent="emit('expandRecord', record)"
>

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

@ -73,6 +73,10 @@ const overlayTop = computed(() => {
return top
})
const shouldEnableOverlay = computed(() => {
return !isPublic.value && dayjs().isSame(selectedDate.value, 'day')
})
onMounted(() => {
const intervalId = setInterval(() => {
currTime.value = dayjs()
@ -213,6 +217,7 @@ const getMaxOverlaps = ({
const recordsAcrossAllRange = computed<{
record: Row[]
spanningRecords: Row[]
gridTimeMap: Map<
number,
{
@ -254,6 +259,7 @@ const recordsAcrossAllRange = computed<{
if (fromDate?.isValid() && toDate?.isValid()) {
if (!fromDate.isSame(toDate, 'day')) {
// TODO: If multiple range is introduced, we have to make sure no duplicate records are inserted
recordSpanningDays.push(record)
return false
}
@ -288,30 +294,10 @@ const recordsAcrossAllRange = computed<{
top: `${topInPixels + 1}px`,
}
// This property is used to determine which side the record should be rounded. It can be top, bottom, both or none
// We use the start and end date to determine the position of the record
let position = 'none'
const isSelectedDay = (date: dayjs.Dayjs) => date.isSame(selectedDate.value, 'day')
const isBeforeSelectedDay = (date: dayjs.Dayjs) => date.isBefore(selectedDate.value, 'day')
const isAfterSelectedDay = (date: dayjs.Dayjs) => date.isAfter(selectedDate.value, 'day')
if (isSelectedDay(startDate) && isSelectedDay(endDate)) {
position = 'rounded'
} else if (isBeforeSelectedDay(startDate) && isAfterSelectedDay(endDate)) {
position = 'none'
} else if (isSelectedDay(startDate) && isAfterSelectedDay(endDate)) {
position = 'topRounded'
} else if (isBeforeSelectedDay(startDate) && isSelectedDay(endDate)) {
position = 'bottomRounded'
} else {
position = 'none'
}
recordsByRange.push({
...record,
rowMeta: {
...record.rowMeta,
position,
style,
id,
range: range as any,
@ -329,7 +315,6 @@ const recordsAcrossAllRange = computed<{
// The top of the record is calculated based on the start hour
// Update such that it is also based on Minutes
const topInPixels = (startDate.minute() / 60 + startDate.hour()) * perRecordHeight
// A minimum height of 80px is set for each record
@ -347,7 +332,6 @@ const recordsAcrossAllRange = computed<{
range: range as any,
style,
id,
position: 'rounded',
},
})
}
@ -460,14 +444,15 @@ const recordsAcrossAllRange = computed<{
record.rowMeta.style = {
...record.rowMeta.style,
display,
width: `${width.toFixed(2)}%`,
left: `${left.toFixed(2)}%`,
width: `calc(${width.toFixed(2)}% - 8px)`,
left: `calc(${left.toFixed(2)}% + 4px)`,
}
}
return {
gridTimeMap,
record: recordsByRange,
spanningRecords: recordSpanningDays,
}
})
@ -902,17 +887,18 @@ watch(
},
{ immediate: true },
)
const expandRecord = (record: Row) => {
emit('expandRecord', record)
}
</script>
<template>
<div class="h-[calc(100vh-5.3rem)] overflow-y-auto nc-scrollbar-md">
<SmartsheetCalendarDateTimeSpanningContainer :records="recordsAcrossAllRange.spanningRecords" @expand-record="expandRecord" />
<div ref="container" class="w-full flex relative no-selection" data-testid="nc-calendar-day-view" @drop="dropEvent">
<div
ref="container"
class="w-full flex relative no-selection h-[calc(100vh-5.3rem)] overflow-y-auto nc-scrollbar-md"
data-testid="nc-calendar-day-view"
@drop="dropEvent"
>
<div
v-if="!isPublic && dayjs().isSame(selectedDate, 'day')"
v-if="shouldEnableOverlay"
class="absolute ml-2 pointer-events-none w-full z-4"
:style="{
top: `${overlayTop}px`,
@ -948,7 +934,7 @@ watch(
v-for="(hour, index) in hours"
:key="index"
:class="{
'!border-brand-500': hour.isSame(selectedTime),
'selected-hour': hour.isSame(selectedTime),
}"
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"
@ -1071,10 +1057,8 @@ watch(
<LazySmartsheetCalendarVRecordCard
:hover="hoverRecord === record.rowMeta.id || record.rowMeta.id === dragRecord?.rowMeta?.id"
:selected="record.rowMeta.id === dragRecord?.rowMeta?.id"
:position="record.rowMeta!.position"
:record="record"
:resize="!!record.rowMeta.range?.fk_to_col && isUIAllowed('dataEdit')"
color="blue"
@resize-start="onResizeStart"
>
<template v-for="(field, id) in fields" :key="id">
@ -1100,6 +1084,7 @@ watch(
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@ -1110,4 +1095,14 @@ watch(
-ms-user-select: none;
user-select: none;
}
.selected-hour {
@apply relative;
&:after {
@apply rounded-sm pointer-events-none absolute inset-0 w-full h-full;
content: '';
z-index: 3;
box-shadow: 0 0 0 2px #3366ff !important;
}
}
</style>

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

@ -146,10 +146,10 @@ const recordsToDisplay = computed<{
const perWidth = gridContainerWidth.value / maxVisibleDays.value
const perHeight = gridContainerHeight.value / calendarData.value.weeks.length
const perRecordHeight = 24
const perRecordHeight = 28
const spaceBetweenRecords = 27
const maxLanes = Math.floor((perHeight - spaceBetweenRecords) / (perRecordHeight + 4))
const maxLanes = Math.floor((perHeight - spaceBetweenRecords) / (perRecordHeight + 8))
// Track records and lanes for each day
const recordsInDay: {
@ -202,7 +202,8 @@ const recordsToDisplay = computed<{
const endCol = range.fk_to_col
// Filter out records that don't satisfy the range and sort them by start date
const sortedFormattedData = [...formattedData.value].filter((record) => {
const sortedFormattedData = [...formattedData.value]
.filter((record) => {
if (startCol && endCol) {
const fromDate = record.row[startCol.title!] ? dayjs(record.row[startCol.title!]) : null
const toDate = record.row[endCol.title!] ? dayjs(record.row[endCol.title!]) : null
@ -213,6 +214,14 @@ const recordsToDisplay = computed<{
}
return false
})
.sort((a, b) => {
const aStart = dayjs(a.row[startCol.title!])
const aEnd = endCol ? dayjs(a.row[endCol.title!]) : aStart
const bStart = dayjs(b.row[startCol.title!])
const bEnd = endCol ? dayjs(b.row[endCol.title!]) : bStart
return bEnd.diff(bStart) - aEnd.diff(aStart)
})
sortedFormattedData.forEach((record: Row) => {
if (!endCol && startCol) {
@ -922,7 +931,12 @@ const addRecord = (date: dayjs.Dayjs) => {
}
.selected-date {
z-index: 2;
@apply relative;
&:after {
@apply rounded-sm pointer-events-none absolute inset-0 w-full h-full;
content: '';
z-index: 3;
box-shadow: 0 0 0 2px #3366ff !important;
}
}
</style>

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

@ -14,7 +14,7 @@ interface Props {
withDefaults(defineProps<Props>(), {
resize: true,
hover: false,
color: 'blue',
color: 'gray',
size: 'small',
position: 'rounded',
})
@ -25,21 +25,22 @@ const emit = defineEmits(['resize-start'])
<template>
<div
:class="{
'h-6': size === 'small',
'h-7': size === 'small',
'h-full': size === 'auto',
'rounded-l-md ml-1': position === 'leftRounded',
'rounded-r-md mr-1': position === 'rightRounded',
'rounded-md mx-1': position === 'rounded',
'rounded-none': position === 'none',
'rounded-l-[4px] !border-r-0 ml-1': position === 'leftRounded',
'rounded-r-[4px] !border-l-0 mr-1': position === 'rightRounded',
'rounded-[4px] mx-1': position === 'rounded',
'rounded-none !border-x-0': position === 'none',
'bg-maroon-50': color === 'maroon',
'bg-blue-50': color === 'blue',
'bg-green-50': color === 'green',
'bg-yellow-50': color === 'yellow',
'bg-pink-50': color === 'pink',
'bg-purple-50': color === 'purple',
'bg-white border-gray-200': color === 'gray',
'shadow-md': hover,
}"
class="relative transition-all flex items-center gap-2 group"
class="relative transition-all border-1 flex items-center gap-2 group"
>
<div
v-if="position === 'leftRounded' || position === 'rounded'"
@ -50,8 +51,9 @@ const emit = defineEmits(['resize-start'])
'bg-yellow-500': color === 'yellow',
'bg-pink-500': color === 'pink',
'bg-purple-500': color === 'purple',
'bg-gray-900': color === 'gray',
}"
class="w-1 min-h-6 bg-blue-500 rounded-x rounded-l-md"
class="w-1 min-h-6.5 rounded-l-[4px] bg-blue-500"
></div>
<div
@ -76,7 +78,7 @@ const emit = defineEmits(['resize-start'])
</template>
</NcTooltip>
</div>
<span v-if="position === 'leftRounded' || position === 'none'" class="absolute my-0 right-5"> .... </span>
<span v-if="position === 'leftRounded' || position === 'none'" class="absolute my-0 z-10 right-5"> .... </span>
</div>
<div

12
packages/nc-gui/components/smartsheet/calendar/VRecordCard.vue

@ -11,7 +11,7 @@ withDefaults(defineProps<Props>(), {
resize: true,
selected: false,
hover: false,
color: 'blue',
color: 'gray',
})
const emit = defineEmits(['resize-start'])
@ -26,9 +26,10 @@ const emit = defineEmits(['resize-start'])
'bg-yellow-50': color === 'yellow',
'bg-pink-50': color === 'pink',
'bg-purple-50': color === 'purple',
'bg-white border-gray-200': color === 'gray',
'shadow-md': hover,
}"
class="relative flex gap-2 relative rounded-md h-full"
class="relative flex gap-2 border-1 relative rounded-md h-full"
>
<div
v-if="resize"
@ -43,6 +44,7 @@ const emit = defineEmits(['resize-start'])
'bg-yellow-500': color === 'yellow',
'bg-pink-500': color === 'pink',
'bg-purple-500': color === 'purple',
'bg-gray-900': color === 'gray',
}"
class="h-full min-h-3 w-1.25 -ml-0.25 rounded-l-md"
></div>
@ -71,4 +73,10 @@ const emit = defineEmits(['resize-start'])
.cursor-row-resize {
cursor: ns-resize;
}
.plain-cell {
.bold {
@apply !text-gray-800 font-semibold;
}
}
</style>

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

@ -157,7 +157,7 @@ const calendarData = computed(() => {
style: {
width: `calc(max(${spanDays} * ${perDayWidth}px, ${perDayWidth}px))`,
left: `${startDaysDiff * perDayWidth}px`,
top: `${suitableRow * 28}px`,
top: `${suitableRow * 28 + suitableRow * 8}px`,
},
},
})
@ -560,7 +560,6 @@ const addRecord = (date: dayjs.Dayjs) => {
:position="record.rowMeta.position"
:record="record"
:resize="!!record.rowMeta.range?.fk_to_col && isUIAllowed('dataEdit')"
color="blue"
@dblclick.stop="emits('expandRecord', record)"
@resize-start="onResizeStart"
>

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

@ -268,11 +268,13 @@ const recordsAcrossAllRange = computed<{
}
>
>
spanningRecords: Row[]
}>(() => {
if (!formattedData.value || !calendarRange.value || !container.value || !scrollContainer.value)
return {
records: [],
gridTimeMap: new Map(),
spanningRecords: [],
}
const perWidth = containerWidth.value / maxVisibleDays.value
const perHeight = 52
@ -529,8 +531,8 @@ const recordsAcrossAllRange = computed<{
}
record.rowMeta.style = {
...record.rowMeta.style,
left: `calc(${majorLeft}px + ${left}%)`,
width: `calc(${width}%)`,
left: `calc(${majorLeft}px + ${left}% + 4px)`,
width: `calc(${width}% - 8px)`,
display,
}
}
@ -539,6 +541,7 @@ const recordsAcrossAllRange = computed<{
return {
records: recordsToDisplay,
gridTimeMap,
spanningRecords: recordSpanningDays,
}
})
@ -895,18 +898,24 @@ watch(
},
{ immediate: true },
)
const expandRecord = (record: Row) => {
emits('expandRecord', record)
}
</script>
<template>
<div
ref="scrollContainer"
class="h-[calc(100vh-5.4rem)] prevent-select relative flex w-full overflow-y-auto nc-scrollbar-md"
class="prevent-select h-[calc(100vh-5.4rem)] overflow-y-auto nc-scrollbar-md relative flex flex-col w-full"
data-testid="nc-calendar-week-view"
@drop="dropEvent"
>
<!-- mt-23 should be dynamic TODO: @DarkPhoenix2704 -->
<div
v-if="!isPublic && dayjs().isBetween(selectedDateRange.start, selectedDateRange.end)"
class="absolute ml-16 mt-7 pointer-events-none z-4"
class="absolute top-16 ml-16 mt-23 pointer-events-none z-2"
:style="overlayStyle"
>
<div class="flex w-full items-center">
@ -919,8 +928,7 @@ watch(
<div class="flex-1 border-b-1 border-brand-500"></div>
</div>
</div>
<div class="flex sticky h-6 z-1 top-0 pl-16 bg-gray-50 w-full">
<div class="flex sticky h-6 z-2 top-0 pl-16 bg-gray-50 w-full">
<div
v-for="date in datesHours"
:key="date[0].toISOString()"
@ -934,16 +942,25 @@ watch(
{{ dayjs(date[0]).format('DD ddd') }}
</div>
</div>
<div class="absolute bg-white w-16 z-1">
<!-- top-16 should be dynamic TODO: @DarkPhoenix2704 -->
<div class="absolute top-16 bg-white w-16 z-1">
<div
v-for="(hour, index) in datesHours[0]"
:key="index"
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"
class="h-13 first:mt-0 pt-7.1 nc-calendar-day-hour text-right pr-2 font-semibold text-xs text-gray-500 py-1"
>
{{ hour.format('hh a') }}
</div>
</div>
<div ref="container" class="absolute ml-16 flex w-[calc(100%-64px)]">
<div class="sticky top-6 bg-white z-3 inset-x-0 w-full">
<SmartsheetCalendarDateTimeSpanningContainer
:records="recordsAcrossAllRange.spanningRecords"
@expand-record="expandRecord"
/>
</div>
<!-- mt-16 should be dynamic TODO: @DarkPhoenix2704 -->
<div ref="container" class="absolute !mt-16 ml-16 flex w-[calc(100%-64px)]">
<div
v-for="(date, index) in datesHours"
:key="index"
@ -960,7 +977,7 @@ watch(
:class="{
'border-1 !border-brand-500 !bg-gray-100':
hour.isSame(selectedTime, 'hour') && (hour.get('day') === 6 || hour.get('day') === 0),
'border-1 !border-brand-500 bg-gray-50': hour.isSame(selectedTime, 'hour'),
'selected-hour': hour.isSame(selectedTime, 'hour'),
'bg-gray-50 hover:bg-gray-100': hour.get('day') === 0 || hour.get('day') === 6,
'hover:bg-gray-50': hour.get('day') !== 0 && hour.get('day') !== 6,
}"
@ -1015,7 +1032,6 @@ watch(
:position="record.rowMeta!.position"
:resize="!!record.rowMeta.range?.fk_to_col && isUIAllowed('dataEdit')"
:record="record"
color="blue"
:selected="record.rowMeta!.id === dragRecord?.rowMeta?.id"
@resize-start="onResizeStart"
>
@ -1050,4 +1066,14 @@ watch(
-ms-user-select: none;
user-select: none;
}
.selected-hour {
@apply relative;
&:after {
@apply rounded-sm pointer-events-none absolute inset-0 w-full h-full;
content: '';
z-index: 3;
box-shadow: 0 0 0 2px #3366ff !important;
}
}
</style>

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

@ -190,13 +190,18 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
<span class="text-gray-800">
{{ $t('labels.organiseBy') }}
</span>
<NcSelect
<a-select
v-model:value="range.fk_from_column_id"
class="nc-select-shadow w-full !rounded-lg"
dropdown-class-name="!rounded-lg"
:placeholder="$t('placeholder.notSelected')"
data-testid="nc-calendar-range-from-field-select"
:disabled="isLocked"
@change="saveCalendarRanges"
@click.stop
>
<template #suffixIcon><GeneralIcon icon="arrowDown" class="text-gray-700" /></template>
<a-select-option
v-for="(option, opId) in [...(dateFieldOptions ?? [])].filter((r) => {
if (id === 0) return true
@ -204,27 +209,28 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
return firstRange?.uidt === r.uidt
})"
:key="opId"
class="w-40"
:value="option.value"
>
<div class="flex w-full gap-2 justify-between items-center">
<div class="flex items-center">
<div class="w-full flex gap-2 items-center justify-between" :title="option.label">
<div class="flex items-center gap-1 max-w-[calc(100%_-_20px)]">
<SmartsheetHeaderIcon :column="option" />
<NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only>
<template #title>{{ option.label }}</template>
<NcTooltip class="flex-1 max-w-[calc(100%_-_20px)] truncate" show-on-truncate-only>
<template #title>
{{ option.label }}
</template>
<template #default>{{ option.label }}</template>
</NcTooltip>
</div>
<component
:is="iconMap.check"
<GeneralIcon
v-if="option.value === range.fk_from_column_id"
id="nc-selected-item-icon"
class="text-primary min-w-4 h-4"
icon="check"
class="flex-none text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
</a-select>
<NcButton
v-if="range.fk_to_column_id === null && isRangeEnabled"
@ -245,14 +251,18 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
{{ $t('activity.withEndDate') }}
</span>
<div class="flex">
<NcSelect
<a-select
v-model:value="range.fk_to_column_id"
class="!rounded-r-none nc-select-shadow w-full flex-1 nc-to-select"
:disabled="!range.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')"
class="!rounded-r-none flex-1 nc-to-select"
data-testid="nc-calendar-range-to-field-select"
dropdown-class-name="!rounded-lg"
@change="saveCalendarRanges"
@click.stop
>
<template #suffixIcon><GeneralIcon icon="arrowDown" class="text-gray-700" /></template>
<a-select-option
v-for="(option, opId) in [...dateFieldOptions].filter((f) => {
const firstRange = dateFieldOptions.find((f) => f.value === calendarRange[0].fk_from_column_id)
@ -261,16 +271,33 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
:key="opId"
:value="option.value"
>
<div class="flex items-center">
<div class="w-full flex gap-2 items-center justify-between" :title="option.label">
<div class="flex items-center gap-1 max-w-[calc(100%_-_20px)]">
<SmartsheetHeaderIcon :column="option" />
<NcTooltip class="truncate flex-1 max-w-18" placement="top" show-on-truncate-only>
<template #title>{{ option.label }}</template>
<NcTooltip class="flex-1 max-w-[calc(100%_-_20px)] truncate" show-on-truncate-only>
<template #title>
{{ option.label }}
</template>
<template #default>{{ option.label }}</template>
</NcTooltip>
</div>
<GeneralIcon
v-if="option.value === range.fk_from_column_id"
id="nc-selected-item-icon"
icon="check"
class="flex-none text-primary w-4 h-4"
/>
</div>
</a-select-option>
</NcSelect>
<NcButton class="!rounded-l-none !border-l-0" size="small" type="secondary" @click="saveCalendarRange(range, null)">
</a-select>
<NcButton
class="!rounded-l-none nc-select-shadow !border-l-0"
size="small"
type="secondary"
@click="saveCalendarRange(range, null)"
>
<component :is="iconMap.delete" class="h-4 w-4" />
</NcButton>
</div>

Loading…
Cancel
Save