Browse Source

feat(nc-gui): rewrite date time

pull/7611/head
DarkPhoenix2704 9 months ago
parent
commit
a12d81dce9
  1. 14
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  2. 30
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  3. 101
      packages/nc-gui/components/smartsheet/calendar/VRecordCard.vue
  4. 203
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue

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

@ -501,23 +501,23 @@ const dragStart = (event: MouseEvent, record: Row) => {
</NcButton> </NcButton>
</div> </div>
<div class="absolute inset-0 pointer-events-none"> <div class="absolute inset-0 pointer-events-none">
<div class="relative !ml-[50px]"> <div class="relative !ml-[60px]">
<div <div
v-for="(record, rowIndex) in recordsAcrossAllRange" v-for="(record, rowIndex) in recordsAcrossAllRange"
:key="rowIndex" :key="rowIndex"
:data-unique-id="record.rowMeta.id" :data-unique-id="record.rowMeta.id"
:style="record.rowMeta.style" :style="record.rowMeta.style"
class="absolute draggable-record cursor-pointer pointer-events-auto" class="absolute draggable-record group cursor-pointer pointer-events-auto"
@mousedown="dragStart($event, record)" @mousedown="dragStart($event, record)"
@dragover.prevent @dragover.prevent
> >
<LazySmartsheetRow :row="record"> <LazySmartsheetRow :row="record">
<LazySmartsheetCalendarRecordCard <LazySmartsheetCalendarVRecordCard
:date="dayjs(record.row[record.rowMeta.range!.fk_from_col.title!]).format('HH:mm')" :date="dayjs(record.row![record.rowMeta!.range!.fk_from_col.title!]).format('HH:mm')"
:name="record.row[displayField!.title!]" :name="record.row![displayField!.title!]"
:position="record.rowMeta.position" :position="record.rowMeta!.position"
:record="record" :record="record"
:resize="false" :resize="true"
color="blue" color="blue"
size="auto" size="auto"
/> />

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

@ -341,7 +341,7 @@ const onDrag = (event: MouseEvent) => {
endDate = newStartDate.clone() endDate = newStartDate.clone()
} }
newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD') newRow.row[toCol.title!] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
} }
formattedData.value = formattedData.value.map((r) => { formattedData.value = formattedData.value.map((r) => {
@ -378,7 +378,7 @@ const onResize = (event: MouseEvent) => {
if (resizeDirection.value === 'right') { if (resizeDirection.value === 'right') {
let newEndDate = dates.value[week] ? dayjs(dates.value[week][day]).endOf('day') : null let newEndDate = dates.value[week] ? dayjs(dates.value[week][day]).endOf('day') : null
const updateProperty = [toCol.title] const updateProperty = [toCol!.title!]
if (dayjs(newEndDate).isBefore(ogStartDate)) { if (dayjs(newEndDate).isBefore(ogStartDate)) {
newEndDate = dayjs(ogStartDate).clone().endOf('day') newEndDate = dayjs(ogStartDate).clone().endOf('day')
@ -390,14 +390,14 @@ const onResize = (event: MouseEvent) => {
...resizeRecord.value, ...resizeRecord.value,
row: { row: {
...resizeRecord.value.row, ...resizeRecord.value.row,
[toCol.title]: dayjs(newEndDate).format('YYYY-MM-DD HH:mm:ssZ'), [toCol!.title!]: dayjs(newEndDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
formattedData.value = formattedData.value.map((r) => { formattedData.value = formattedData.value.map((r) => {
const pk = extractPkFromRow(r.row, meta.value.columns) const pk = extractPkFromRow(r.row, meta.value!.columns!)
if (pk === extractPkFromRow(newRow.row, meta.value.columns)) { if (pk === extractPkFromRow(newRow.row, meta.value!.columns!)) {
return newRow return newRow
} }
return r return r
@ -405,7 +405,7 @@ const onResize = (event: MouseEvent) => {
useDebouncedRowUpdate(newRow, updateProperty, false) useDebouncedRowUpdate(newRow, updateProperty, false)
} else if (resizeDirection.value === 'left') { } else if (resizeDirection.value === 'left') {
let newStartDate = dates.value[week] ? dayjs(dates.value[week][day]) : null let newStartDate = dates.value[week] ? dayjs(dates.value[week][day]) : null
const updateProperty = [fromCol.title] const updateProperty = [fromCol!.title!]
if (dayjs(newStartDate).isAfter(ogEndDate)) { if (dayjs(newStartDate).isAfter(ogEndDate)) {
newStartDate = dayjs(ogEndDate).clone() newStartDate = dayjs(ogEndDate).clone()
@ -416,14 +416,14 @@ const onResize = (event: MouseEvent) => {
...resizeRecord.value, ...resizeRecord.value,
row: { row: {
...resizeRecord.value.row, ...resizeRecord.value.row,
[fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'), [fromCol!.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
formattedData.value = formattedData.value.map((r) => { formattedData.value = formattedData.value.map((r) => {
const pk = extractPkFromRow(r.row, meta.value.columns) const pk = extractPkFromRow(r.row, meta.value!.columns!)
if (pk === extractPkFromRow(newRow.row, meta.value.columns)) { if (pk === extractPkFromRow(newRow.row, meta.value!.columns!)) {
return newRow return newRow
} }
return r return r
@ -482,15 +482,15 @@ const stopDrag = (event: MouseEvent) => {
...dragRecord.value, ...dragRecord.value,
row: { row: {
...dragRecord.value.row, ...dragRecord.value.row,
[fromCol.title]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'), [fromCol!.title!]: dayjs(newStartDate).format('YYYY-MM-DD HH:mm:ssZ'),
}, },
} }
const updateProperty = [fromCol.title] const updateProperty = [fromCol!.title!]
if (toCol) { if (toCol) {
const fromDate = dragRecord.value.row[fromCol.title] ? dayjs(dragRecord.value.row[fromCol.title]) : null const fromDate = dragRecord.value.row[fromCol!.title!] ? dayjs(dragRecord.value.row[fromCol!.title!]) : null
const toDate = dragRecord.value.row[toCol.title] ? dayjs(dragRecord.value.row[toCol.title]) : null const toDate = dragRecord.value.row[toCol!.title!] ? dayjs(dragRecord.value.row[toCol!.title!]) : null
if (fromDate && toDate) { if (fromDate && toDate) {
endDate = dayjs(newStartDate).add(toDate.diff(fromDate, 'day'), 'day') endDate = dayjs(newStartDate).add(toDate.diff(fromDate, 'day'), 'day')
@ -503,9 +503,9 @@ const stopDrag = (event: MouseEvent) => {
} }
dragRecord.value = null dragRecord.value = null
newRow.row[toCol.title] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ') newRow.row[toCol!.title!] = dayjs(endDate).format('YYYY-MM-DD HH:mm:ssZ')
updateProperty.push(toCol.title) updateProperty.push(toCol!.title!)
} }
if (!newRow) return if (!newRow) return

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

@ -0,0 +1,101 @@
<script lang="ts" setup>
interface Props {
record: Record<string, string>
name: string
date?: string
color?: string
resize?: boolean
selected?: boolean
showDate?: boolean
position?: 'topRounded' | 'bottomRounded' | 'rounded' | 'none'
}
withDefaults(defineProps<Props>(), {
name: '',
date: '',
resize: true,
selected: false,
color: 'blue',
size: 'small',
showDate: true,
position: 'rounded',
})
const emit = defineEmits(['resize-start'])
</script>
<template>
<div
v-if="(position === 'topRounded' || position === 'rounded') && resize"
class="absolute flex items-center justify-center w-full -mt-4 h-7.1 hidden z-1 resize !group-hover:(flex rounded-lg)"
>
<NcButton
:class="{
'!flex border-2 rounded-lg border-brand-500': selected,
}"
class="!group-hover:(border-brand-500) !border-2 cursor-ns-resize"
size="xsmall"
type="secondary"
@mousedown.stop="emit('resize-start', 'left', $event, record)"
>
<component :is="iconMap.drag" class="text-gray-400"></component>
</NcButton>
</div>
<div
:class="{
'rounded-t-lg mt-3': position === 'topRounded',
'rounded-b-lg mb-3': position === 'bottomRounded',
'rounded-lg': position === 'rounded',
'rounded-none': 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',
'group-hover:(border-brand-500)': resize,
'border-brand-500': selected,
}"
class="relative h-full border-2 border-white"
>
<div class="h-full absolute py-2">
<div
:class="{
'bg-maroon-500': color === 'maroon',
'bg-blue-500': color === 'blue',
'bg-green-500': color === 'green',
'bg-yellow-500': color === 'yellow',
'bg-pink-500': color === 'pink',
'bg-purple-500': color === 'purple',
}"
class="block h-full min-h-5 ml-1 w-1 rounded mr-2"
></div>
</div>
<div v-if="position === 'topRounded' || position === 'none'" class="absolute inset-x-0 mx-0">....</div>
<div class="ml-3 mt-2 pr-3 text-ellipsis overflow-hidden w-full h-6 absolute">
<span class="text-sm text-gray-800">{{ name }}</span>
<span v-if="showDate" class="text-xs ml-1 text-gray-600">{{ date }}</span>
</div>
<div v-if="position === 'bottomRounded' || position === 'none'" class="absolute inset-x-0 mx-0">....</div>
</div>
<div
v-if="(position === 'bottomRounded' || position === 'rounded') && resize"
class="absolute items-center justify-center w-full hidden h-7.1 -mt-4 !group-hover:(flex rounded-lg)"
>
<NcButton
:class="{
'!flex border-2 rounded-lg z-1 cursor-ns-resize border-brand-500': selected,
}"
class="!group-hover:(border-brand-500) !border-2"
size="xsmall"
type="secondary"
@mousedown.stop="emit('resize-start', 'right', $event, record)"
>
<component :is="iconMap.drag" class="text-gray-400"></component>
</NcButton>
</div>
</template>
<style lang="scss" scoped></style>

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

@ -30,9 +30,9 @@ const datesHours = computed(() => {
.minute(0) .minute(0)
.second(0) .second(0)
.millisecond(0) .millisecond(0)
.year(startOfWeek.getFullYear() || dayjs().year()) .year(startOfWeek.getFullYear())
.month(startOfWeek.getMonth() || dayjs().month()) .month(startOfWeek.getMonth())
.date(startOfWeek.getDate() || dayjs().date()), .date(startOfWeek.getDate()),
) )
} }
datesHours.push(hours) datesHours.push(hours)
@ -47,40 +47,7 @@ function getRandomNumbers() {
return randomValues.join('') return randomValues.join('')
} }
const findFirstSuitableColumn = (recordsInDay: any, startDayIndex: number, spanDays: number) => { const recordsAcrossAllRange = computed(() => {
let column = 0
while (true) {
let isColumnSuitable = true
for (let i = 0; i < spanDays; i++) {
const dayIndex = startDayIndex + i
if (!recordsInDay[dayIndex]) {
recordsInDay[dayIndex] = {}
}
if (recordsInDay[dayIndex][column]) {
isColumnSuitable = false
break
}
}
if (isColumnSuitable) {
return column
}
column++
}
}
const recordsAcrossAllRange = computed<{
records: Array<Row>
count: {
[key: string]: {
[key: string]: {
overflow: boolean
count: number
overflowCount: number
}
}
}
}>(() => {
if (!formattedData.value || !calendarRange.value || !container.value) return [] if (!formattedData.value || !calendarRange.value || !container.value) return []
const { scrollHeight } = container.value const { scrollHeight } = container.value
@ -93,28 +60,22 @@ const recordsAcrossAllRange = computed<{
const perRecordHeight = 40 const perRecordHeight = 40
const spaceBetweenRecords = 5 const overlaps: {
const recordsInDayHour: {
[key: string]: { [key: string]: {
[key: string]: { [key: string]: Array<string>
overflow: boolean
count: number
overflowCount: number
}
} }
} = {} } = {}
if (!calendarRange.value) return [] if (!calendarRange.value) return []
const recordsToDisplay: Array<Row> = [] let recordsToDisplay: Array<Row> = []
calendarRange.value.forEach((range) => { calendarRange.value.forEach((range) => {
const fromCol = range.fk_from_col const fromCol = range.fk_from_col
const toCol = range.fk_to_col const toCol = range.fk_to_col
const sortedFormattedData = [...formattedData.value].filter((record) => { const sortedFormattedData = [...formattedData.value].filter((record) => {
const fromDate = record.row[fromCol.title!] ? dayjs(record.row[fromCol.title!]) : null const fromDate = record.row[fromCol!.title!] ? dayjs(record.row[fromCol!.title!]) : null
if (fromCol && toCol) { if (fromCol && toCol) {
const fromDate = record.row[fromCol.title!] ? dayjs(record.row[fromCol.title!]) : null const fromDate = record.row[fromCol.title!] ? dayjs(record.row[fromCol.title!]) : null
@ -133,45 +94,31 @@ const recordsAcrossAllRange = computed<{
if (!startDate) return if (!startDate) return
const dateKey = startDate?.format('YYYY-MM-DD') const dateKey = startDate?.format('YYYY-MM-DD')
const hourKey = startDate?.format('HH:mm') const hourKey = startDate?.format('HH:mm')
const id = record.rowMeta.id ?? getRandomNumbers()
if (dateKey && hourKey) { if (dateKey && hourKey) {
if (!recordsInDayHour[dateKey]) { if (!overlaps[dateKey]) {
recordsInDayHour[dateKey] = {} overlaps[dateKey] = {}
} }
if (!recordsInDayHour[dateKey][hourKey]) { if (!overlaps[dateKey][hourKey]) {
recordsInDayHour[dateKey][hourKey] = { overlaps[dateKey][hourKey] = []
overflow: false,
count: 0,
overflowCount: 0,
}
} }
recordsInDayHour[dateKey][hourKey].count++ overlaps[dateKey][hourKey].push(id)
} }
const id = record.rowMeta.id ?? getRandomNumbers() let dayIndex = dayjs(dateKey).day() - 1
if (dayIndex === -1) {
dayIndex = 6
}
const dayIndex = dayjs(dateKey).day() - 1
const hourIndex = datesHours.value[dayIndex].findIndex((h) => h.format('HH:mm') === hourKey) const hourIndex = datesHours.value[dayIndex].findIndex((h) => h.format('HH:mm') === hourKey)
const style: Partial<CSSStyleDeclaration> = { const style: Partial<CSSStyleDeclaration> = {
left: `${dayIndex * perWidth}px`,
top: `${hourIndex * perHeight}px`, top: `${hourIndex * perHeight}px`,
height: `${perHeight - 30}px`,
} }
const recordIndex = recordsInDayHour[dateKey][hourKey].count
const top = hourIndex * perHeight + (recordIndex - 1) * (perRecordHeight + spaceBetweenRecords)
const heightRequired = perRecordHeight * recordIndex + spaceBetweenRecords
if (heightRequired + 20 > perHeight) {
style.display = 'none'
recordsInDayHour[dateKey][hourKey].overflow = true
recordsInDayHour[dateKey][hourKey].overflowCount++
} else {
style.height = `${perRecordHeight}px`
style.top = `${top}px`
}
recordsToDisplay.push({ recordsToDisplay.push({
...record, ...record,
rowMeta: { rowMeta: {
@ -210,65 +157,52 @@ const recordsAcrossAllRange = computed<{
while (hour.isSameOrBefore(recordEnd, 'hour')) { while (hour.isSameOrBefore(recordEnd, 'hour')) {
const hourKey = hour.format('HH:mm') const hourKey = hour.format('HH:mm')
if (!recordsInDayHour[dateKey]) { if (!overlaps[dateKey]) {
recordsInDayHour[dateKey] = {} overlaps[dateKey] = {}
} }
if (!recordsInDayHour[dateKey][hourKey]) { if (!overlaps[dateKey][hourKey]) {
recordsInDayHour[dateKey][hourKey] = { overlaps[dateKey][hourKey] = []
overflow: false,
count: 0,
overflowCount: 0,
}
} }
recordsInDayHour[dateKey][hourKey].count++ overlaps[dateKey][hourKey].push(id)
hour = hour.add(1, 'hour') hour = hour.add(1, 'hour')
} }
let dayIndex = recordStart.day() let dayIndex = recordStart.day() - 1
if (dayIndex === -1) { if (dayIndex === -1) {
dayIndex = 7 dayIndex = 6
}
let maxRecordCount = 0
for (let i = 0; i < (datesHours.value[dayIndex - 1] ?? []).length; i++) {
const hourKey = datesHours.value[dayIndex - 1][i].format('HH:mm')
if (recordsInDayHour[dateKey]?.[hourKey]?.count > maxRecordCount) {
maxRecordCount = recordsInDayHour[dateKey][hourKey].count
}
} }
const startHourIndex = Math.max( const startHourIndex = Math.max(
(datesHours.value[dayIndex - 1] ?? []).findIndex((h) => h.format('HH:mm') === recordStart.format('HH:mm')), (datesHours.value[dayIndex] ?? []).findIndex((h) => h.format('HH:mm') === recordStart.format('HH:mm')),
0, 0,
) )
const endHourIndex = Math.max( const endHourIndex = Math.max(
(datesHours.value[dayIndex - 1] ?? []).findIndex( (datesHours.value[dayIndex] ?? []).findIndex((h) => h.format('HH:mm') === recordEnd?.startOf('hour').format('HH:mm')),
(h) => h.format('HH:mm') === recordEnd?.startOf('hour').format('HH:mm'),
),
0, 0,
) )
console.log( let _startHourIndex = startHourIndex
record.row[displayField.value.title],
recordEnd?.startOf('hour').format('HH:mm'),
(datesHours.value[dayIndex - 1] ?? []).findIndex(
(h) => h.format('HH:mm') === recordEnd?.startOf('hour').format('HH:mm'),
),
)
const spanHours = endHourIndex - startHourIndex + 1 while (_startHourIndex <= endHourIndex) {
const hourKey = datesHours.value[dayIndex][_startHourIndex].format('HH:mm')
if (!overlaps[dateKey]) {
overlaps[dateKey] = {}
}
if (!overlaps[dateKey][hourKey]) {
overlaps[dateKey][hourKey] = []
}
overlaps[dateKey][hourKey].push(id)
_startHourIndex++
}
const left = (dayIndex - 1) * perWidth const spanHours = endHourIndex - startHourIndex + 1
const top = startHourIndex * perRecordHeight const top = startHourIndex * perRecordHeight
const height = (endHourIndex - startHourIndex + 1) * perHeight - spanHours - 5 const height = (endHourIndex - startHourIndex + 1) * perHeight - spanHours - 5
const style: Partial<CSSStyleDeclaration> = { const style: Partial<CSSStyleDeclaration> = {
left: `${left}px`,
top: `${top}px`, top: `${top}px`,
height: `${height}px`, height: `${height}px`,
} }
@ -288,13 +222,40 @@ const recordsAcrossAllRange = computed<{
} }
}) })
console.log(recordsInDayHour) recordsToDisplay = recordsToDisplay.map((record) => {
let maxOverlaps = 1
let overlapIndex = 0
for (const days in overlaps) {
for (const hours in overlaps[days]) {
if (overlaps[days][hours].includes(record.rowMeta.id!)) {
maxOverlaps = Math.max(maxOverlaps, overlaps[days][hours].length)
overlapIndex = Math.max(overlaps[days][hours].indexOf(record.rowMeta.id!), overlapIndex)
}
}
}
let dayIndex = dayjs(record.row![record.rowMeta!.range!.fk_from_col.title!]).day() - 1
if (dayIndex === -1) {
dayIndex = 6
}
const spacing = 1
const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps / 7
const leftPerRecord = (widthPerRecord + spacing) * overlapIndex
record.rowMeta.style = {
...record.rowMeta.style,
left: `calc(${dayIndex * perWidth}px + ${leftPerRecord}%)`,
width: `calc(${widthPerRecord}%)`,
}
return record
})
console.log(overlaps)
}) })
return { return recordsToDisplay
records: recordsToDisplay,
count: recordsInDayHour,
}
}) })
const dragElement = ref<HTMLElement | null>(null) const dragElement = ref<HTMLElement | null>(null)
@ -604,34 +565,26 @@ const dropEvent = (event: DragEvent) => {
<span v-if="date[0].day() === selectedDateRange.start?.getDay()" class="absolute left-1"> <span v-if="date[0].day() === selectedDateRange.start?.getDay()" class="absolute left-1">
{{ hour.format('h A') }} {{ hour.format('h A') }}
</span> </span>
<div
v-if="recordsAcrossAllRange.count?.[dayjs(hour).format('YYYY-MM-DD')]?.[dayjs(hour).format('HH:mm')]?.overflow"
class="text-xs absolute bottom-2 text-center inset-x-0 !z-[90] text-gray-500"
>
+
{{ recordsAcrossAllRange.count[dayjs(hour).format('YYYY-MM-DD')]?.[dayjs(hour).format('HH:mm')]?.overflowCount }}
more
</div>
</div> </div>
</div> </div>
<div class="absolute pointer-events-none inset-0 !mt-[20px]"> <div class="absolute pointer-events-none inset-0 !mt-[20px]">
<div <div
v-for="(record, rowIndex) in recordsAcrossAllRange.records" v-for="(record, rowIndex) in recordsAcrossAllRange"
:key="rowIndex" :key="rowIndex"
:data-unique-id="record.rowMeta!.id" :data-unique-id="record.rowMeta!.id"
:style="record.rowMeta!.style" :style="record.rowMeta!.style"
class="absolute draggable-record w-1/7 cursor-pointer pointer-events-auto" class="absolute draggable-record w-1/7 group cursor-pointer pointer-events-auto"
@mousedown="dragStart($event, record)" @mousedown="dragStart($event, record)"
@dragover.prevent @dragover.prevent
> >
<LazySmartsheetRow :row="record"> <LazySmartsheetRow :row="record">
<LazySmartsheetCalendarRecordCard <LazySmartsheetCalendarVRecordCard
:date="dayjs(record.row![record.rowMeta!.range!.fk_from_col.title!]).format('HH:mm')" :date="dayjs(record.row![record.rowMeta!.range!.fk_from_col.title!]).format('HH:mm')"
:name="record.row![displayField!.title!]" :name="record.row![displayField!.title!]"
:position="record.rowMeta!.position" :position="record.rowMeta!.position"
:record="record" :record="record"
:resize="false" :resize="true"
color="blue" color="blue"
size="auto" size="auto"
/> />

Loading…
Cancel
Save