Browse Source

fix(nc-gui): date time major rewrite

pull/7753/head
DarkPhoenix2704 9 months ago
parent
commit
6c65a1a452
  1. 298
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  2. 4
      packages/nc-gui/composables/useCalendarViewStore.ts
  3. 4
      packages/nc-gui/lib/types.ts

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

@ -52,6 +52,143 @@ const hours = computed(() => {
return hours return hours
}) })
const calculateNewDates = ({
endDate,
startDate,
scheduleStart,
scheduleEnd,
}: {
endDate: dayjs.Dayjs
startDate: dayjs.Dayjs
scheduleStart: dayjs.Dayjs
scheduleEnd: dayjs.Dayjs
}) => {
// If there is no end date, we add 15 minutes to the start date and use that as the end date
if (!endDate.isValid()) {
endDate = startDate.clone().add(15, 'minutes')
}
// If the start date is before the opened date, we use the schedule start as the start date
// This is to ensure the generated style of the record is not outside the bounds of the calendar
if (startDate.isBefore(scheduleStart)) {
startDate = scheduleStart
}
// If the end date is after the schedule end, we use the schedule end as the end date
// This is to ensure the generated style of the record is not outside the bounds of the calendar
if (endDate.isAfter(scheduleEnd)) {
endDate = scheduleEnd
}
return { endDate, startDate }
}
const getGridTime = (date: dayjs.Dayjs, round = false) => {
const gridCalc = date.hour() * 60 + date.minute()
if (round) {
return Math.ceil(gridCalc)
} else {
return Math.floor(gridCalc)
}
}
const getGridTimeSlots = (from: dayjs.Dayjs, to: dayjs.Dayjs) => {
return {
from: getGridTime(from, false),
to: getGridTime(to, true) - 1,
}
}
const hasSlotForRecord = (
record: Row,
columnArray: Row[],
dates: {
fromDate: dayjs.Dayjs
toDate: dayjs.Dayjs
},
) => {
const { fromDate, toDate } = dates
if (!fromDate || !toDate) return false
for (const column of columnArray) {
const columnFromCol = column.rowMeta.range.fk_from_col
const columnToCol = column.rowMeta.range.fk_to_col
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'),
scheduleStart: dayjs(selectedDate.value).startOf('day'),
scheduleEnd: dayjs(selectedDate.value).endOf('day'),
})
if (
fromDate.isBetween(columnFromDate, columnToDate, null, '[]') ||
toDate.isBetween(columnFromDate, columnToDate, null, '[]')
) {
return false
}
}
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)
}
}
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) {
maxOverlaps = record.rowMeta.numberOfOverlaps
}
}
}
return maxOverlaps
}
const recordsAcrossAllRange = computed<{ const recordsAcrossAllRange = computed<{
record: Row[] record: Row[]
count: { count: {
@ -82,7 +219,10 @@ const recordsAcrossAllRange = computed<{
const perRecordHeight = 80 const perRecordHeight = 80
let recordsByRange: Array<Row> = [] const columnArray: Array<Array<Row>> = [[]]
const gridTimeMap = new Map()
const recordsByRange: Array<Row> = []
calendarRange.value.forEach((range) => { calendarRange.value.forEach((range) => {
const fromCol = range.fk_from_col const fromCol = range.fk_from_col
@ -107,34 +247,19 @@ const recordsAcrossAllRange = computed<{
.sort((a, b) => { .sort((a, b) => {
const aDate = dayjs(a.row[fromCol!.title!]) const aDate = dayjs(a.row[fromCol!.title!])
const bDate = dayjs(b.row[fromCol!.title!]) const bDate = dayjs(b.row[fromCol!.title!])
return aDate.isBefore(bDate) ? -1 : 1 return aDate.isBefore(bDate) ? 1 : -1
}) })
// If there is a start and end column, we calculate the top and height of the record based on the start and end date
if (fromCol && endCol) {
for (const record of sortedFormattedData) { for (const record of sortedFormattedData) {
// We use this id during the drag and drop operation and to keep track of the number of records that overlap at a given time const id = generateRandomNumber()
const id = record.rowMeta?.id ?? generateRandomNumber()
let startDate = dayjs(record.row[fromCol.title!])
let endDate = dayjs(record.row[endCol.title!])
// If there is no end date, we add 30 minutes to the start date and use that as the end date
if (!endDate.isValid()) {
endDate = startDate.clone().add(30, 'minutes')
}
// If the start date is before the opened date, we use the schedule start as the start date
// This is to ensure the generated style of the record is not outside the bounds of the calendar
if (startDate.isBefore(scheduleStart)) {
startDate = scheduleStart
}
// If the end date is after the schedule end, we use the schedule end as the end date
// This is to ensure the generated style of the record is not outside the bounds of the calendar
if (endDate.isAfter(scheduleEnd)) {
endDate = scheduleEnd
}
if (fromCol && endCol) {
const { endDate, startDate } = calculateNewDates({
endDate: dayjs(record.row[endCol.title!]),
startDate: dayjs(record.row[fromCol.title!]),
scheduleStart,
scheduleEnd,
})
// The top of the record is calculated based on the start hour and minute // The top of the record is calculated based on the start hour and minute
const topInPixels = (startDate.hour() + startDate.minute() / 60) * 80 const topInPixels = (startDate.hour() + startDate.minute() / 60) * 80
@ -143,7 +268,6 @@ const recordsAcrossAllRange = computed<{
const heightInPixels = Math.max((endDate.diff(startDate, 'minute') / 60) * 80, perRecordHeight) const heightInPixels = Math.max((endDate.diff(startDate, 'minute') / 60) * 80, perRecordHeight)
const startHour = startDate.hour() const startHour = startDate.hour()
let _startDate = startDate.clone() let _startDate = startDate.clone()
const style: Partial<CSSStyleDeclaration> = { const style: Partial<CSSStyleDeclaration> = {
@ -202,18 +326,13 @@ const recordsAcrossAllRange = computed<{
range: range as any, range: range as any,
}, },
}) })
}
} else if (fromCol) { } else if (fromCol) {
for (const record of sortedFormattedData) { const { startDate, endDate } = calculateNewDates({
const id = record.rowMeta?.id ?? generateRandomNumber() startDate: dayjs(record.row[fromCol.title!]),
endDate: dayjs(record.row[fromCol.title!]).add(1, 'hour'),
const startDate = dayjs(record.row[fromCol.title!]) scheduleStart,
scheduleEnd,
let endDate = dayjs(record.row[fromCol.title!]).add(1, 'hour').add(1, 'minute') })
if (endDate.isAfter(scheduleEnd)) {
endDate = scheduleEnd
}
const startHour = startDate.hour() const startHour = startDate.hour()
@ -242,6 +361,7 @@ const recordsAcrossAllRange = computed<{
display: 'none', display: 'none',
} }
} }
_startDate = _startDate.add(1, 'minute') _startDate = _startDate.add(1, 'minute')
} }
@ -277,32 +397,98 @@ const recordsAcrossAllRange = computed<{
} }
}) })
// We can't calculate the width & left of the records without knowing the number of records that overlap at a given time recordsByRange.sort((a, b) => {
// 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 const fromColA = a.rowMeta.range?.fk_from_col
recordsByRange = recordsByRange.map((record) => { const fromColB = b.rowMeta.range?.fk_from_col
// MaxOverlaps is the number of records that overlap at a given time if (!fromColA || !fromColB) return 0
// overlapIndex is the index of the record in the list of records that overlap at a given time return dayjs(a.row[fromColA.title!]).isBefore(dayjs(b.row[fromColB.title!])) ? -1 : 1
let maxOverlaps = 1 })
let overlapIndex = 0
for (const minutes in overlaps) { for (const record of recordsByRange) {
if (overlaps[minutes].id.includes(record.rowMeta.id!)) { const fromCol = record.rowMeta.range?.fk_from_col
maxOverlaps = Math.max(maxOverlaps, overlaps[minutes].id.length - overlaps[minutes].overflowCount) const toCol = record.rowMeta.range?.fk_to_col
overlapIndex = Math.max(overlaps[minutes].id.indexOf(record.rowMeta.id!), overlapIndex)
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,
scheduleEnd,
})
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)
} else {
gridTimeMap.set(gridCounter, 1)
} }
} }
const spacing = 0.25 let foundAColumn = false
const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps
const leftPerRecord = (widthPerRecord + spacing) * overlapIndex for (const column in columnArray) {
if (
hasSlotForRecord(record, columnArray[column], {
fromDate: startDate,
toDate: endDate,
})
) {
columnArray[column].push(record)
foundAColumn = true
break
}
}
if (!foundAColumn) {
columnArray.push([record])
}
}
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
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({
row: record,
rowArray: recordsByRange,
})
const width = 100 / columnArray.length
const left = width * (record.rowMeta.overLapIteration - 1)
record.rowMeta.style = { record.rowMeta.style = {
...record.rowMeta.style, ...record.rowMeta.style,
left: `${leftPerRecord - 0.08}%`, width: `${width.toFixed(2)}%`,
width: `calc(${widthPerRecord}%)`, left: `${left}%`,
} }
return record }
})
console.log(columnArray)
return { return {
count: overlaps, count: overlaps,
@ -783,7 +969,7 @@ const newRecord = (hour: dayjs.Dayjs) => {
v-if="!isRowEmpty(record, displayField!)" v-if="!isRowEmpty(record, displayField!)"
v-model="record.row[displayField!.title!]" v-model="record.row[displayField!.title!]"
:bold="getFieldStyle(displayField!).bold" :bold="getFieldStyle(displayField!).bold"
:column="displayField" :column="displayField!"
:italic="getFieldStyle(displayField!).italic" :italic="getFieldStyle(displayField!).italic"
:underline="getFieldStyle(displayField!).underline" :underline="getFieldStyle(displayField!).underline"
/> />

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

@ -111,8 +111,8 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const calendarRange = ref< const calendarRange = ref<
Array<{ Array<{
fk_from_col: ColumnType | null fk_from_col: ColumnType
fk_to_col: ColumnType | null fk_to_col?: ColumnType | null
id: string id: string
}> }>
>([]) >([])

4
packages/nc-gui/lib/types.ts

@ -75,6 +75,10 @@ interface Row {
id?: string id?: string
position?: string position?: string
dayIndex?: number dayIndex?: number
overLapIteration?: number
numberOfOverlaps?: number
minutes?: number
} }
} }

Loading…
Cancel
Save