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 {
// activeCalendarView,
selectedDate,
selectedTime,
formattedData,
@ -81,7 +80,7 @@ const calculateNewDates = ({
return { endDate, startDate }
}
/* const getGridTime = (date: dayjs.Dayjs, round = false) => {
const getGridTime = (date: dayjs.Dayjs, round = false) => {
const gridCalc = date.hour() * 60 + date.minute()
if (round) {
return Math.ceil(gridCalc)
@ -95,10 +94,9 @@ const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
from: getGridTime(from, false),
to: getGridTime(to, true) - 1,
}
} */
}
/* const hasSlotForRecord = (
record: Row,
const hasSlotForRecord = (
columnArray: Row[],
dates: {
fromDate: dayjs.Dayjs
@ -117,7 +115,9 @@ const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
const { startDate: columnFromDate, endDate: columnToDate } = calculateNewDates({
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'),
scheduleEnd: dayjs(selectedDate.value).endOf('day'),
})
@ -130,65 +130,60 @@ const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
}
}
return true
} */
/* const getMaxOfGrid = (
{
fromDate,
toDate,
}: {
fromDate: dayjs.Dayjs
toDate: dayjs.Dayjs
},
gridTimeMap: Map<number, number>,
) => {
let max = 0
const gridTimes = getGridTimeSlots(fromDate, toDate)
for (let gridCounter = gridTimes.from; gridCounter <= gridTimes.to; gridCounter++) {
if (gridTimeMap.has(gridCounter) && gridTimeMap.get(gridCounter) > max) {
max = gridTimeMap.get(gridCounter)
}
const getMaxOverlaps = ({
row,
gridTimeMap,
columnArray,
}: {
row: Row
gridTimeMap: Map<
number,
{
count: number
id: string[]
}
>
columnArray: Array<Array<Row>>
}) => {
const visited: Set<string> = new Set()
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 getMaxOverlaps = ({ row, rowArray }: { row: Row; rowArray: Row[] }) => {
let maxOverlaps = row.rowMeta.numberOfOverlaps
for (const record of rowArray) {
if (isOverlaps(row, record)) {
if (!record.rowMeta.numberOfOverlaps || !row.rowMeta.numberOfOverlaps) continue
if (record.rowMeta.numberOfOverlaps > row.rowMeta.numberOfOverlaps) {
maxOverlaps = record.rowMeta.numberOfOverlaps
const dfs = (id: string): number => {
visited.add(id)
let maxOverlaps = 1
const neighbors = graph.get(id)
if (neighbors) {
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
maxOverlaps = Math.min(Math.max(maxOverlaps, dfs(neighbor) + 1), columnArray.length)
}
}
}
return maxOverlaps
}
let maxOverlaps = 1
const id = row.rowMeta.id as string
if (graph.has(id)) {
maxOverlaps = dfs(id)
}
return maxOverlaps
} */
}
const recordsAcrossAllRange = computed<{
record: Row[]
@ -220,10 +215,16 @@ const recordsAcrossAllRange = computed<{
const perRecordHeight = 60
/* const columnArray: Array<Array<Row>> = [[]]
const gridTimeMap = new Map() */
const columnArray: Array<Array<Row>> = [[]]
const gridTimeMap = new Map<
number,
{
count: number
id: string[]
}
>()
let recordsByRange: Array<Row> = []
const recordsByRange: Array<Row> = []
calendarRange.value.forEach((range) => {
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
const heightInPixels = Math.max(endDate.diff(startDate, 'minute'), perRecordHeight)
const startHour = startDate.hour()
let _startDate = startDate.clone()
const style: Partial<CSSStyleDeclaration> = {
height: `${heightInPixels}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
// We use the start and end date to determine the position of the record
let position = 'none'
@ -330,41 +306,12 @@ const recordsAcrossAllRange = computed<{
} else if (fromCol) {
const { startDate, endDate } = calculateNewDates({
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,
scheduleEnd,
})
const startHour = startDate.hour()
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
// Update such that it is also based on Minutes
@ -392,41 +339,15 @@ const recordsAcrossAllRange = computed<{
}
}
})
/*
recordsByRange.sort((a, b) => {
const fromColA = a.rowMeta.range?.fk_from_col
const fromColB = b.rowMeta.range?.fk_from_col
if (!fromColA || !fromColB) return 0
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 toCol = record.rowMeta.range?.fk_to_col
@ -434,7 +355,7 @@ const recordsAcrossAllRange = computed<{
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'),
endDate: toCol ? dayjs(record.row[toCol.title!]) : dayjs(record.row[fromCol.title!]).add(1, 'hour').subtract(1, 'minute'),
scheduleStart,
scheduleEnd,
})
@ -442,10 +363,16 @@ const recordsAcrossAllRange = computed<{
const gridTimes = getGridTimeSlots(startDate, endDate)
for (let gridCounter = gridTimes.from; gridCounter <= gridTimes.to; gridCounter++) {
if (gridTimeMap.has(gridCounter)) {
gridTimeMap.set(gridCounter, gridTimeMap.get(gridCounter) + 1)
if (!gridTimeMap.has(gridCounter)) {
gridTimeMap.set(gridCounter, {
count: 1,
id: [record.rowMeta.id!],
})
} 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) {
if (
hasSlotForRecord(record, columnArray[column], {
hasSlotForRecord(columnArray[column], {
fromDate: startDate,
toDate: endDate,
})
@ -468,47 +395,62 @@ const recordsAcrossAllRange = computed<{
columnArray.push([record])
}
}
for (const columnIndex in columnArray) {
for (const columnIndex in columnArray) {
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
}
}
for (const record of recordsByRange) {
record.rowMeta.numberOfOverlaps = getMaxOverlaps({
const numberOfOverlaps = getMaxOverlaps({
row: record,
rowArray: recordsByRange,
gridTimeMap,
columnArray,
})
const width = 100 / columnArray.length
const left = width * (record.rowMeta.overLapIteration - 1)
record.rowMeta.numberOfOverlaps = numberOfOverlaps
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,
display,
width: `${width.toFixed(2)}%`,
left: `${left}%`,
left: `${left.toFixed(2)}%`,
}
} */
}
return {
count: overlaps,
@ -802,7 +744,7 @@ const isOverflowAcrossHourRange = (hour: dayjs.Dayjs) => {
let overflowCount = 0
while (startOfHour.isBefore(endOfHour, 'minute')) {
const hourKey = startOfHour.format('HH:mm')
const hourKey = startOfHour.hour() * 60 + startOfHour.minute()
if (recordsAcrossAllRange.value?.count?.[hourKey]?.overflow) {
isOverflow = true

Loading…
Cancel
Save