Browse Source

fix: calendar datetime overlap fix (#8247)

* fix: some perf fixes

* fix: date time overlap issue

* fix: show hidden records count
pull/8250/head
Anbarasu 6 months ago committed by GitHub
parent
commit
fdd2884da6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 298
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue

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

@ -7,7 +7,6 @@ import { generateRandomNumber, isRowEmpty } from '~/utils'
const emit = defineEmits(['expandRecord', 'newRecord']) const emit = defineEmits(['expandRecord', 'newRecord'])
const { const {
// activeCalendarView,
selectedDate, selectedDate,
selectedTime, selectedTime,
formattedData, formattedData,
@ -81,7 +80,7 @@ const calculateNewDates = ({
return { endDate, startDate } return { endDate, startDate }
} }
/* const getGridTime = (date: dayjs.Dayjs, round = false) => { const getGridTime = (date: dayjs.Dayjs, round = false) => {
const gridCalc = date.hour() * 60 + date.minute() const gridCalc = date.hour() * 60 + date.minute()
if (round) { if (round) {
return Math.ceil(gridCalc) return Math.ceil(gridCalc)
@ -95,10 +94,9 @@ const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
from: getGridTime(from, false), from: getGridTime(from, false),
to: getGridTime(to, true) - 1, to: getGridTime(to, true) - 1,
} }
} */ }
/* const hasSlotForRecord = ( const hasSlotForRecord = (
record: Row,
columnArray: Row[], columnArray: Row[],
dates: { dates: {
fromDate: dayjs.Dayjs fromDate: dayjs.Dayjs
@ -117,7 +115,9 @@ const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
const { startDate: columnFromDate, endDate: columnToDate } = calculateNewDates({ const { startDate: columnFromDate, endDate: columnToDate } = calculateNewDates({
startDate: dayjs(column.row[columnFromCol.title!]), startDate: dayjs(column.row[columnFromCol.title!]),
endDate: columnToCol ? dayjs(column.row[columnToCol.title!]) : dayjs(column.row[columnFromCol.title!]).add(1, 'hour'), endDate: columnToCol
? dayjs(column.row[columnToCol.title!])
: dayjs(column.row[columnFromCol.title!]).add(1, 'hour').subtract(1, 'minute'),
scheduleStart: dayjs(selectedDate.value).startOf('day'), scheduleStart: dayjs(selectedDate.value).startOf('day'),
scheduleEnd: dayjs(selectedDate.value).endOf('day'), scheduleEnd: dayjs(selectedDate.value).endOf('day'),
}) })
@ -130,65 +130,60 @@ const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
} }
} }
return true return true
} */ }
const getMaxOverlaps = ({
/* const getMaxOfGrid = ( row,
{ gridTimeMap,
fromDate, columnArray,
toDate, }: {
}: { row: Row
fromDate: dayjs.Dayjs gridTimeMap: Map<
toDate: dayjs.Dayjs number,
}, {
gridTimeMap: Map<number, number>, count: number
) => { id: string[]
let max = 0 }
const gridTimes = getGridTimeSlots(fromDate, toDate) >
columnArray: Array<Array<Row>>
for (let gridCounter = gridTimes.from; gridCounter <= gridTimes.to; gridCounter++) { }) => {
if (gridTimeMap.has(gridCounter) && gridTimeMap.get(gridCounter) > max) { const visited: Set<string> = new Set()
max = gridTimeMap.get(gridCounter) const graph: Map<string, Set<string>> = new Map()
// Build the graph
for (const [_gridTime, { id: ids }] of gridTimeMap) {
for (const id1 of ids) {
if (!graph.has(id1)) {
graph.set(id1, new Set())
}
for (const id2 of ids) {
if (id1 !== id2) {
graph.get(id1)!.add(id2)
}
}
} }
} }
return max
} */
/* const isOverlaps = (row1: Row, row2: Row) => {
const fromCol1 = row1.rowMeta.range?.fk_from_col
const toCol1 = row1.rowMeta.range?.fk_to_col
const fromCol2 = row2.rowMeta.range?.fk_from_col
const toCol2 = row2.rowMeta.range?.fk_to_col
if (!fromCol1 || !fromCol2) return false
const { startDate: startDate1, endDate: endDate1 } = calculateNewDates({
endDate: toCol1 ? dayjs(row1.row[toCol1.title!]) : dayjs(row1.row[fromCol1.title!]).add(1, 'hour'),
startDate: dayjs(row1.row[fromCol1.title!]),
scheduleStart: dayjs(selectedDate.value).startOf('day'),
scheduleEnd: dayjs(selectedDate.value).endOf('day'),
})
const { startDate: startDate2, endDate: endDate2 } = calculateNewDates({
endDate: toCol2 ? dayjs(row2.row[toCol2.title!]) : dayjs(row2.row[fromCol2.title!]).add(1, 'hour'),
startDate: dayjs(row2.row[fromCol2.title!]),
scheduleStart: dayjs(selectedDate.value).startOf('day'),
scheduleEnd: dayjs(selectedDate.value).endOf('day'),
})
return startDate1.isBetween(startDate2, endDate2, null, '[]') || endDate1.isBetween(startDate2, endDate2, null, '[]') const dfs = (id: string): number => {
} */ visited.add(id)
let maxOverlaps = 1
/* const getMaxOverlaps = ({ row, rowArray }: { row: Row; rowArray: Row[] }) => { const neighbors = graph.get(id)
let maxOverlaps = row.rowMeta.numberOfOverlaps if (neighbors) {
for (const record of rowArray) { for (const neighbor of neighbors) {
if (isOverlaps(row, record)) { if (!visited.has(neighbor)) {
if (!record.rowMeta.numberOfOverlaps || !row.rowMeta.numberOfOverlaps) continue maxOverlaps = Math.min(Math.max(maxOverlaps, dfs(neighbor) + 1), columnArray.length)
if (record.rowMeta.numberOfOverlaps > row.rowMeta.numberOfOverlaps) { }
maxOverlaps = record.rowMeta.numberOfOverlaps
} }
} }
return maxOverlaps
}
let maxOverlaps = 1
const id = row.rowMeta.id as string
if (graph.has(id)) {
maxOverlaps = dfs(id)
} }
return maxOverlaps return maxOverlaps
} */ }
const recordsAcrossAllRange = computed<{ const recordsAcrossAllRange = computed<{
record: Row[] record: Row[]
@ -220,10 +215,16 @@ const recordsAcrossAllRange = computed<{
const perRecordHeight = 60 const perRecordHeight = 60
/* const columnArray: Array<Array<Row>> = [[]] const columnArray: Array<Array<Row>> = [[]]
const gridTimeMap = new Map() */ const gridTimeMap = new Map<
number,
{
count: number
id: string[]
}
>()
let recordsByRange: Array<Row> = [] const recordsByRange: Array<Row> = []
calendarRange.value.forEach((range) => { calendarRange.value.forEach((range) => {
const fromCol = range.fk_from_col const fromCol = range.fk_from_col
@ -268,36 +269,11 @@ const recordsAcrossAllRange = computed<{
// The height of the record is calculated based on the difference between the start and end date // The height of the record is calculated based on the difference between the start and end date
const heightInPixels = Math.max(endDate.diff(startDate, 'minute'), perRecordHeight) const heightInPixels = Math.max(endDate.diff(startDate, 'minute'), perRecordHeight)
const startHour = startDate.hour()
let _startDate = startDate.clone()
const style: Partial<CSSStyleDeclaration> = { const style: Partial<CSSStyleDeclaration> = {
height: `${heightInPixels}px`, height: `${heightInPixels}px`,
top: `${topInPixels}px`, top: `${topInPixels}px`,
} }
// We loop through every 1 minutes between the start and end date and keep track of the number of records that overlap at a given time
// If the number of records exceeds 4, we hide the record and show a button to view more records
while (_startDate.isBefore(endDate)) {
const timeKey = _startDate.format('HH:mm')
if (!overlaps[timeKey]) {
overlaps[timeKey] = {
id: [],
overflow: false,
overflowCount: 0,
}
}
overlaps[timeKey].id.push(id)
// If the number of records exceeds 4, we hide the record mark the time as overflow
if (overlaps[timeKey].id.length > 4) {
overlaps[timeKey].overflow = true
style.display = 'none'
overlaps[timeKey].overflowCount += 1
}
_startDate = _startDate.add(1, 'minutes')
}
// This property is used to determine which side the record should be rounded. It can be top, bottom, both or none // 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 // We use the start and end date to determine the position of the record
let position = 'none' let position = 'none'
@ -330,41 +306,12 @@ const recordsAcrossAllRange = computed<{
} else if (fromCol) { } else if (fromCol) {
const { startDate, endDate } = calculateNewDates({ const { startDate, endDate } = calculateNewDates({
startDate: dayjs(record.row[fromCol.title!]), startDate: dayjs(record.row[fromCol.title!]),
endDate: dayjs(record.row[fromCol.title!]).add(1, 'hour'), endDate: dayjs(record.row[fromCol.title!]).add(1, 'hour').subtract(1, 'minute'),
scheduleStart, scheduleStart,
scheduleEnd, scheduleEnd,
}) })
const startHour = startDate.hour()
let style: Partial<CSSStyleDeclaration> = {} let style: Partial<CSSStyleDeclaration> = {}
let _startDate = startDate.clone()
// We loop through every minute between the start and end date and keep track of the number of records that overlap at a given time
while (_startDate.isBefore(endDate, 'minute')) {
const timeKey = _startDate.format('HH:mm')
if (!overlaps[timeKey]) {
overlaps[timeKey] = {
id: [],
overflow: false,
overflowCount: 0,
}
}
overlaps[timeKey].id.push(id)
// If the number of records exceeds 8, we hide the record and mark it as overflow
if (overlaps[timeKey].id.length > 8) {
overlaps[timeKey].overflow = true
overlaps[timeKey].overflowCount += 1
style = {
...style,
display: 'none',
}
}
_startDate = _startDate.add(1, 'minute')
}
// The top of the record is calculated based on the start hour // The top of the record is calculated based on the start hour
// Update such that it is also based on Minutes // Update such that it is also based on Minutes
@ -392,41 +339,15 @@ const recordsAcrossAllRange = computed<{
} }
} }
}) })
/*
recordsByRange.sort((a, b) => { recordsByRange.sort((a, b) => {
const fromColA = a.rowMeta.range?.fk_from_col const fromColA = a.rowMeta.range?.fk_from_col
const fromColB = b.rowMeta.range?.fk_from_col const fromColB = b.rowMeta.range?.fk_from_col
if (!fromColA || !fromColB) return 0 if (!fromColA || !fromColB) return 0
return dayjs(a.row[fromColA.title!]).isBefore(dayjs(b.row[fromColB.title!])) ? -1 : 1 return dayjs(a.row[fromColA.title!]).isBefore(dayjs(b.row[fromColB.title!])) ? -1 : 1
}) */
// We can't calculate the width & left of the records without knowing the number of records that overlap at a given time
// So we loop through the records again and calculate the width & left of the records based on the number of records that overlap at a given time
recordsByRange = recordsByRange.map((record) => {
// MaxOverlaps is the number of records that overlap at a given time
// overlapIndex is the index of the record in the list of records that overlap at a given time
let maxOverlaps = 1
let overlapIndex = 0
for (const minutes in overlaps) {
if (overlaps[minutes].id.includes(record.rowMeta.id!)) {
maxOverlaps = Math.max(maxOverlaps, overlaps[minutes].id.length - overlaps[minutes].overflowCount)
overlapIndex = Math.max(overlaps[minutes].id.indexOf(record.rowMeta.id!), overlapIndex)
}
}
const spacing = 0.25
const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps
const leftPerRecord = (widthPerRecord + spacing) * overlapIndex
record.rowMeta.style = {
...record.rowMeta.style,
left: `${leftPerRecord - 0.08}%`,
width: `calc(${widthPerRecord}%)`,
}
return record
}) })
// TODO: Rewrite the calculations for the style of the records for (const record of recordsByRange) {
/* for (const record of recordsByRange) {
const fromCol = record.rowMeta.range?.fk_from_col const fromCol = record.rowMeta.range?.fk_from_col
const toCol = record.rowMeta.range?.fk_to_col const toCol = record.rowMeta.range?.fk_to_col
@ -434,7 +355,7 @@ const recordsAcrossAllRange = computed<{
const { startDate, endDate } = calculateNewDates({ const { startDate, endDate } = calculateNewDates({
startDate: dayjs(record.row[fromCol.title!]), startDate: dayjs(record.row[fromCol.title!]),
endDate: toCol ? dayjs(record.row[toCol.title!]) : dayjs(record.row[fromCol.title!]).add(1, 'hour'), endDate: toCol ? dayjs(record.row[toCol.title!]) : dayjs(record.row[fromCol.title!]).add(1, 'hour').subtract(1, 'minute'),
scheduleStart, scheduleStart,
scheduleEnd, scheduleEnd,
}) })
@ -442,10 +363,16 @@ const recordsAcrossAllRange = computed<{
const gridTimes = getGridTimeSlots(startDate, endDate) const gridTimes = getGridTimeSlots(startDate, endDate)
for (let gridCounter = gridTimes.from; gridCounter <= gridTimes.to; gridCounter++) { for (let gridCounter = gridTimes.from; gridCounter <= gridTimes.to; gridCounter++) {
if (gridTimeMap.has(gridCounter)) { if (!gridTimeMap.has(gridCounter)) {
gridTimeMap.set(gridCounter, gridTimeMap.get(gridCounter) + 1) gridTimeMap.set(gridCounter, {
count: 1,
id: [record.rowMeta.id!],
})
} else { } else {
gridTimeMap.set(gridCounter, 1) gridTimeMap.set(gridCounter, {
count: gridTimeMap.get(gridCounter)!.count + 1,
id: [...gridTimeMap.get(gridCounter)!.id, record.rowMeta.id!],
})
} }
} }
@ -453,7 +380,7 @@ const recordsAcrossAllRange = computed<{
for (const column in columnArray) { for (const column in columnArray) {
if ( if (
hasSlotForRecord(record, columnArray[column], { hasSlotForRecord(columnArray[column], {
fromDate: startDate, fromDate: startDate,
toDate: endDate, toDate: endDate,
}) })
@ -468,47 +395,62 @@ const recordsAcrossAllRange = computed<{
columnArray.push([record]) columnArray.push([record])
} }
} }
for (const columnIndex in columnArray) { for (const columnIndex in columnArray) {
for (const record of columnArray[columnIndex]) { for (const record of columnArray[columnIndex]) {
const recordRange = record.rowMeta.range
const fromCol = recordRange?.fk_from_col
const toCol = recordRange?.fk_to_col
if (!fromCol) continue
const { startDate, endDate } = calculateNewDates({
startDate: dayjs(record.row[fromCol.title!]),
endDate: toCol ? dayjs(record.row[toCol.title!]) : dayjs(record.row[fromCol.title!]).add(1, 'hour'),
scheduleStart: dayjs(selectedDate.value).startOf('day'),
scheduleEnd: dayjs(selectedDate.value).endOf('day'),
})
record.rowMeta.numberOfOverlaps =
getMaxOfGrid(
{
fromDate: startDate,
toDate: endDate,
},
gridTimeMap,
) - 1
record.rowMeta.overLapIteration = parseInt(columnIndex) + 1 record.rowMeta.overLapIteration = parseInt(columnIndex) + 1
} }
} }
for (const record of recordsByRange) { for (const record of recordsByRange) {
record.rowMeta.numberOfOverlaps = getMaxOverlaps({ const numberOfOverlaps = getMaxOverlaps({
row: record, row: record,
rowArray: recordsByRange, gridTimeMap,
columnArray,
}) })
const width = 100 / columnArray.length record.rowMeta.numberOfOverlaps = numberOfOverlaps
const left = width * (record.rowMeta.overLapIteration - 1)
let width
let left = 100
let display = 'block'
if (numberOfOverlaps && numberOfOverlaps > 0) {
width = 100 / Math.min(numberOfOverlaps, 8)
if (record.rowMeta.overLapIteration! - 1 > 7) {
display = 'none'
gridTimeMap.forEach((value, key) => {
if (value.id.includes(record.rowMeta.id!)) {
if (!overlaps[key]) {
overlaps[key] = {
id: value.id,
overflow: true,
overflowCount: value.id.length,
}
} else {
overlaps[key].overflow = true
value.id.forEach((id) => {
if (!overlaps[key].id.includes(id)) {
overlaps[key].id.push(id)
}
})
}
}
})
} else {
left = width * (record.rowMeta.overLapIteration! - 1)
}
} else {
width = 100
left = 0
}
record.rowMeta.style = { record.rowMeta.style = {
...record.rowMeta.style, ...record.rowMeta.style,
display,
width: `${width.toFixed(2)}%`, width: `${width.toFixed(2)}%`,
left: `${left}%`, left: `${left.toFixed(2)}%`,
} }
} */ }
return { return {
count: overlaps, count: overlaps,
@ -802,7 +744,7 @@ const isOverflowAcrossHourRange = (hour: dayjs.Dayjs) => {
let overflowCount = 0 let overflowCount = 0
while (startOfHour.isBefore(endOfHour, 'minute')) { while (startOfHour.isBefore(endOfHour, 'minute')) {
const hourKey = startOfHour.format('HH:mm') const hourKey = startOfHour.hour() * 60 + startOfHour.minute()
if (recordsAcrossAllRange.value?.count?.[hourKey]?.overflow) { if (recordsAcrossAllRange.value?.count?.[hourKey]?.overflow) {
isOverflow = true isOverflow = true

Loading…
Cancel
Save