+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
+
+
+ {{ $t('activity.expandRecord') }}
+
+
+
{{ $t('activity.deleteRow') }}
-
+
-
+
-
+
-
-
-
-
+
+
+
-
diff --git a/packages/nc-gui/components/smartsheet/Kanban.vue b/packages/nc-gui/components/smartsheet/Kanban.vue
index c2f1fb691a..5f888edebb 100644
--- a/packages/nc-gui/components/smartsheet/Kanban.vue
+++ b/packages/nc-gui/components/smartsheet/Kanban.vue
@@ -1,5 +1,7 @@
-
-
- e.target.classList.add('grabbing')"
- @end="(e) => e.target.classList.remove('grabbing')"
- @change="onMoveStack($event)"
- >
-
-
+
@@ -243,17 +259,25 @@ watch(
@@ -271,75 +296,87 @@ watch(
-
-
+
+
+
-
+
+
+
@@ -261,7 +285,8 @@ watch(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+ -
-
-
-
+
+ e.target.classList.add('grabbing')"
+ @end="(e) => e.target.classList.remove('grabbing')"
+ @change="onMoveStack($event)"
+ >
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+
+
+
+
-
+
+
+
+
+
+
- {{ stack.title ?? 'uncategorized' }}
-
-
-
-
-
-
+
+ handleSubmitRenameOrNewStack(loadMeta, payload, stackIdx)"
+ />
+
+ {
- selectedStackTitle = stack.title
- openNewRecordFormHook.trigger(stack.title)
+ if (stack.title !== null && hasEditPermission && !isPublic && !isLocked) {
+ isRenameOrNewStack = stack
+ }
}
"
>
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('activity.addNewRecord') }}
-
-
-
-
- {{ $t('activity.kanban.collapseStack') }}
-
-
-
- {{ $t('activity.kanban.deleteStack') }}
-
-
-
- e.target.classList.add('grabbing')"
- @end="(e) => e.target.classList.remove('grabbing')"
- @change="onMove($event, stack.title)"
- >
-
-
+
+
+
+
+
+
+ {
+ selectedStackTitle = stack.title
+ openNewRecordFormHook.trigger(stack.title)
+ }
+ "
+ >
+
+ {
+ isRenameOrNewStack = stack
+ }
+ "
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
-
+
+
+ {{ stack.title ?? 'Uncategorized' }}
+
+
+ {{ stack.title ?? 'Uncategorized' }}
+
+
+
+
+
+
+
+ {{ $t('activity.addNewRecord') }}
+
+
+
+ {{ $t('activity.kanban.renameStack') }}
+
+
+
+ {{ $t('activity.kanban.collapseStack') }}
+
+
+
+ {{ $t('activity.kanban.collapseAll') }}
+
+
+
+ {{ $t('activity.kanban.expandAll') }}
+
+
+
+ {{ $t('activity.kanban.deleteStack') }}
+
+
+
+ e.target.classList.add('grabbing')"
+ @end="(e) => e.target.classList.remove('grabbing')"
+ @change="onMove($event, stack.title)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -729,17 +1120,20 @@ const getRowId = (row: RowType) => {
:row="expandedFormRow"
:state="expandedFormRowState"
:meta="meta"
+ :load-row="!isPublic"
:view="view"
+ @cancel="removeRowFromUncategorizedStack"
/>
@@ -763,12 +1157,16 @@ const getRowId = (row: RowType) => {
// override ant design style
.a-layout,
.ant-layout-header,
+.ant-layout-footer {
+ @apply !bg-white;
+}
.ant-layout-content {
- @apply !bg-gray-100;
+ background-color: unset;
}
-
-.ant-layout-header {
- @apply !h-[30px] !leading-[30px] !px-[5px] !my-[10px];
+.ant-layout-header,
+.ant-layout-footer {
+ @apply p-2 text-sm;
+ height: unset !important;
}
.nc-kanban-collapsed-stack {
@@ -782,8 +1180,7 @@ const getRowId = (row: RowType) => {
}
.ant-carousel.gallery-carousel :deep(.slick-dots) {
- @apply !w-auto absolute h-auto bottom-[-15px] absolute h-auto;
- height: auto;
+ @apply !w-full max-w-[calc(100%_-_36%)] absolute left-0 right-0 bottom-[-18px] h-6 overflow-x-auto nc-scrollbar-thin !mx-auto;
}
.ant-carousel.gallery-carousel :deep(.slick-dots li div > div) {
@@ -809,4 +1206,123 @@ const getRowId = (row: RowType) => {
:deep(.slick-slide) {
@apply !pointer-events-none;
}
+
+:deep(.ant-card) {
+ @apply transition-shadow duration-0.3s;
+
+ box-shadow: 0px 2px 4px -2px rgba(0, 0, 0, 0.06), 0px 4px 4px -2px rgba(0, 0, 0, 0.02);
+
+ &:hover {
+ box-shadow: 0px 12px 16px -4px rgba(0, 0, 0, 0.1), 0px 4px 6px -2px rgba(0, 0, 0, 0.06);
+
+ .nc-action-icon {
+ @apply invisible;
+ }
+ }
+}
+
+.nc-card-display-value-wrapper {
+ @apply my-0 text-base leading-8 text-gray-800;
+
+ .nc-cell,
+ .nc-virtual-cell {
+ @apply text-base leading-6;
+
+ :deep(.nc-cell-field),
+ :deep(input),
+ :deep(textarea),
+ :deep(.nc-cell-field-link) {
+ @apply !text-base leading-6 text-gray-800;
+ }
+ }
+}
+
+.nc-card-col-header {
+ :deep(.nc-cell-icon),
+ :deep(.nc-virtual-cell-icon) {
+ @apply ml-0 !w-3.5 !h-3.5;
+ }
+}
+
+:deep(.nc-cell),
+:deep(.nc-virtual-cell) {
+ @apply text-small leading-[18px];
+
+ .nc-cell-field,
+ input,
+ textarea,
+ .nc-cell-field-link {
+ @apply !text-small !leading-[18px];
+ }
+}
+:deep(.nc-cell) {
+ &.nc-cell-longtext {
+ .long-text-wrapper {
+ @apply min-h-1;
+ .nc-readonly-rich-text-wrapper {
+ @apply !min-h-1;
+ }
+ .nc-rich-text {
+ @apply pl-0;
+ .tiptap.ProseMirror {
+ @apply -ml-1 min-h-1;
+ }
+ }
+ }
+ }
+ &.nc-cell-checkbox {
+ @apply children:pl-0;
+ }
+ &.nc-cell-singleselect .nc-cell-field > div {
+ @apply flex items-center;
+ }
+ &.nc-cell-multiselect .nc-cell-field > div {
+ @apply h-5;
+ }
+ &.nc-cell-email,
+ &.nc-cell-phonenumber {
+ @apply flex items-center;
+ }
+
+ &.nc-cell-email,
+ &.nc-cell-phonenumber,
+ &.nc-cell-url {
+ .nc-cell-field-link {
+ @apply py-0;
+ }
+ }
+}
+
+:deep(.nc-virtual-cell) {
+ .nc-links-wrapper {
+ @apply py-0 children:min-h-4;
+ }
+ &.nc-virtual-cell-linktoanotherrecord {
+ .chips-wrapper {
+ @apply min-h-4 !children:min-h-4;
+ .chip.group {
+ @apply my-0;
+ }
+ }
+ }
+ &.nc-virtual-cell-lookup {
+ .nc-lookup-cell {
+ @apply !h-5.5;
+
+ .nc-cell-lookup-scroll {
+ @apply py-0 h-auto;
+ }
+ }
+ }
+ &.nc-virtual-cell-formula {
+ .nc-cell-field {
+ @apply py-0;
+ }
+ }
+
+ &.nc-virtual-cell-qrcode,
+ &.nc-virtual-cell-barcode {
+ @apply children:justify-start;
+ }
+}
diff --git a/packages/nc-gui/components/smartsheet/Map.vue b/packages/nc-gui/components/smartsheet/Map.vue
index b366fc3cf7..b4479dca6f 100644
--- a/packages/nc-gui/components/smartsheet/Map.vue
+++ b/packages/nc-gui/components/smartsheet/Map.vue
@@ -51,7 +51,8 @@ const getMapCenterLocalStorageKey = (viewId: string) => `mapView.${viewId}.cente
const expandForm = (row: Row, state?: Record) => {
const rowId = extractPkFromRow(row.row, meta.value!.columns!)
- if (rowId) {
+
+ if (rowId && !isPublic.value) {
router.push({
query: {
...route.query,
@@ -236,6 +237,7 @@ const count = computed(() => paginationData.value.totalRows)
v-if="expandedFormRow && expandedFormDlg"
v-model="expandedFormDlg"
:row="expandedFormRow"
+ :load-row="!isPublic"
:state="expandedFormRowState"
:meta="meta"
:view="view"
@@ -245,9 +247,11 @@ const count = computed(() => paginationData.value.totalRows)
diff --git a/packages/nc-gui/components/smartsheet/SharedMapMarkerPopup.vue b/packages/nc-gui/components/smartsheet/SharedMapMarkerPopup.vue
index 2b1dc7e4f2..ca081e18fd 100644
--- a/packages/nc-gui/components/smartsheet/SharedMapMarkerPopup.vue
+++ b/packages/nc-gui/components/smartsheet/SharedMapMarkerPopup.vue
@@ -15,13 +15,6 @@ const { loadData } = useViewData(meta, view)
provide(IsFormInj, ref(false))
provide(IsGridInj, ref(false))
-const isRowEmpty = (record: any, col: any) => {
- const val = record.row[col.title]
- if (!val) return true
-
- return Array.isArray(val) && val.length === 0
-}
-
reloadViewDataHook?.on(async () => {
await loadData()
})
diff --git a/packages/nc-gui/components/smartsheet/Toolbar.vue b/packages/nc-gui/components/smartsheet/Toolbar.vue
index 7fbcdede43..b550838b5f 100644
--- a/packages/nc-gui/components/smartsheet/Toolbar.vue
+++ b/packages/nc-gui/components/smartsheet/Toolbar.vue
@@ -1,8 +1,6 @@
-
-
+
+
@@ -65,8 +61,6 @@ const { allowCSVDownload } = useSharedView()
-
-
diff --git a/packages/nc-gui/components/smartsheet/VirtualCell.vue b/packages/nc-gui/components/smartsheet/VirtualCell.vue
index a531a7a83b..08307376f6 100644
--- a/packages/nc-gui/components/smartsheet/VirtualCell.vue
+++ b/packages/nc-gui/components/smartsheet/VirtualCell.vue
@@ -40,10 +40,13 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
+
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
+
-
-
-
-
-
-
+
-
-
-
-
-
- -
+ {
+ selectedStackTitle = stack.title
+ openNewRecordFormHook.trigger(stack.title)
+ }
+ "
+ >
+
+
+
+
+
+
+ {{ $t('general.empty') }} {{ $t('general.stack').toLowerCase() }}
+
+
+ {{ $t('title.looksLikeThisStackIsEmpty') }}
+
+
+
+
+
+ {{ $t('activity.newRecord') }}
-
-
+
+ {
+ selectedStackTitle = stack.title
+ openNewRecordFormHook.trigger(stack.title)
+ }
+ "
+ >
+
+
+
+
+
+
+
+
+ {{ $t('activity.newRecord') }}
-
-
+
+ {{ formattedData.get(stack.title)!.length }}/{{ countByStack.get(stack.title) ?? 0 }}
+ {{ countByStack.get(stack.title) !== 1 ? $t('objects.records') : $t('objects.record') }}
+
+
+
-
+
-
+
+
+
+
+
+
+
+
-
+
+
+
+
+ {{ stack.title ?? 'Uncategorized' }}
+
+
+ {{ stack.title ?? 'Uncategorized' }}
+
+
+
+
+
+
-
+
+
+
+
-
+
+
+
+
+
+
+ {{ formattedData.get(stack.title)!.length }}
+ {{ countByStack.get(stack.title) !== 1 ? $t('objects.records') : $t('objects.record') }}
+
-
-
- {{ formattedData.get(stack.title)!.length }} / {{ countByStack.get(stack.title) ?? 0 }}
- {{ countByStack.get(stack.title) !== 1 ? $t('objects.records') : $t('objects.record') }}
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
- {
- selectedStackTitle = stack.title
- openNewRecordFormHook.trigger(stack.title)
- }
- "
- >
- Add Record
+
+
+
+
-
- {
+ if (!compareStack(addNewStackObj, isRenameOrNewStack)) {
+ isRenameOrNewStack = addNewStackObj
+ }
+ }
+ "
+ >
+
+
+
+ {{ $t('general.new') }} {{ $t('general.stack').toLowerCase() }}
-
-
-
-
-
-
-
-
+
+
-
- {{ stack.title ?? 'uncategorized' }}
-
+
+ handleSubmitRenameOrNewStack(loadMeta, undefined)"
+ />
+
-
- {{ formattedData.get(stack.title)!.length }} / {{ countByStack.get(stack.title) }}
- {{ countByStack.get(stack.title) !== 1 ? $t('objects.records') : $t('objects.record') }}
+
+
+
{{ $t('activity.expandRecord') }}
-
-
-
-
-
+
+
+
+
{{ $t('activity.deleteRecord') }}
-
-
+
+
-
+
-
diff --git a/packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue b/packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
index 463ea3dc11..5701131615 100644
--- a/packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
+++ b/packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
@@ -26,6 +26,8 @@ const meta = inject(MetaInj, ref())
const fields = inject(FieldsInj, ref())
+const isPublic = inject(IsPublicInj, ref(false))
+
const { fields: _fields } = useViewColumnsOrThrow()
const fieldStyles = computed(() => {
@@ -56,6 +58,29 @@ const hours = computed(() => {
return hours
})
+const currTime = ref(dayjs())
+
+const overlayTop = computed(() => {
+ const perRecordHeight = 52
+
+ const minutes = currTime.value.minute() + currTime.value.hour() * 60
+
+ const top = (perRecordHeight / 60) * minutes
+
+ return top
+})
+
+onMounted(() => {
+ const intervalId = setInterval(() => {
+ currTime.value = dayjs()
+ }, 10000) // 10000 ms = 10 seconds
+
+ // Clean up the interval when the component is unmounted
+ onUnmounted(() => {
+ clearInterval(intervalId)
+ })
+})
+
const calculateNewDates = useMemoize(
({
endDate,
@@ -166,12 +191,21 @@ const getMaxOverlaps = ({
return maxOverlaps
}
- let maxOverlaps = 1
const id = row.rowMeta.id as string
if (graph.has(id)) {
- maxOverlaps = dfs(id)
+ dfs(id)
}
- return maxOverlaps
+
+ const overlapIterations: Array = []
+
+ columnArray
+ .flat()
+ .filter((record) => visited.has(record.rowMeta.id!))
+ .forEach((record) => {
+ overlapIterations.push(record.rowMeta.overLapIteration!)
+ })
+
+ return Math.max(...overlapIterations)
}
const recordsAcrossAllRange = computed<{
@@ -246,8 +280,8 @@ const recordsAcrossAllRange = computed<{
const heightInPixels = Math.max(endDate.diff(startDate, 'minute'), perRecordHeight)
const style: Partial = {
- height: `${heightInPixels - 8}px`,
- top: `${topInPixels + 4}px`,
+ height: `${heightInPixels - 2}px`,
+ 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
@@ -298,8 +332,8 @@ const recordsAcrossAllRange = computed<{
const heightInPixels = Math.max((endDate.diff(startDate, 'minute') / 60) * 52, perRecordHeight)
style = {
...style,
- top: `${topInPixels + 4}px`,
- height: `${heightInPixels - 8}px`,
+ top: `${topInPixels + 1}px`,
+ height: `${heightInPixels - 2}px`,
}
recordsByRange.push({
@@ -681,13 +715,13 @@ const stopDrag = (event: MouseEvent) => {
}
const dragStart = (event: MouseEvent, record: Row) => {
- if (!isUIAllowed('dataEdit')) return
let target = event.target as HTMLElement
isDragging.value = false
// We use a timeout to determine if the user is dragging or clicking on the record
dragTimeout.value = setTimeout(() => {
+ if (!isUIAllowed('dataEdit')) return
isDragging.value = true
while (!target.classList.contains('draggable-record')) {
target = target.parentElement as HTMLElement
@@ -868,6 +902,24 @@ watch(
data-testid="nc-calendar-day-view"
@drop="dropEvent"
>
+
@@ -944,12 +996,12 @@ watch(
{
+ return viewMetaProperties.value?.hide_weekend ? 5 : 7
+})
+
const days = computed(() => {
+ let days = []
+
if (isMondayFirst.value) {
- return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+ days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
} else {
- return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
+ days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
}
+
+ if (maxVisibleDays.value === 5) {
+ days = days.filter((day) => day !== 'Sat' && day !== 'Sun')
+ }
+
+ return days
})
const calendarGridContainer = ref()
@@ -42,6 +55,14 @@ const isDayInPagedMonth = (date: dayjs.Dayjs) => {
return date.month() === selectedMonth.value.month()
}
+const getDayIndex = (date: dayjs.Dayjs) => {
+ let dayIndex = date.day() - 1
+ if (dayIndex === -1) {
+ dayIndex = 6
+ }
+ return dayIndex
+}
+
const dragElement = ref(null)
const draggingId = ref(null)
@@ -115,7 +136,7 @@ const recordsToDisplay = computed<{
}>(() => {
if (!dates.value || !calendarRange.value) return []
- const perWidth = gridContainerWidth.value / 7
+ const perWidth = gridContainerWidth.value / maxVisibleDays.value
const perHeight = gridContainerHeight.value / dates.value.length
const perRecordHeight = 24
@@ -176,6 +197,12 @@ const recordsToDisplay = computed<{
width: `${perWidth}px`,
}
+ if (maxVisibleDays.value === 5) {
+ if (dayIndex === 5 || dayIndex === 6) {
+ style.display = 'none'
+ }
+ }
+
// Number of records in that day
const recordIndex = recordsInDay[dateKey].count
@@ -364,12 +391,15 @@ const calculateNewRow = (event: MouseEvent, updateSideBar?: boolean, skipChangeC
if (!fromCol) return { newRow: null, updateProperty: [] }
const week = Math.floor(percentY * dates.value.length)
- const day = Math.floor(percentX * 7)
+ const day = Math.floor(percentX * maxVisibleDays.value)
let newStartDate = dates.value[week] ? dayjs(dates.value[week][day]) : null
if (!newStartDate) return { newRow: null, updateProperty: [] }
- const fromDate = dayjs(dragRecord.value.row[fromCol.title!])
+ let fromDate = dayjs(dragRecord.value.row[fromCol.title!])
+ if (!fromDate.isValid()) {
+ fromDate = dayjs()
+ }
newStartDate = newStartDate.add(fromDate.hour(), 'hour').add(fromDate.minute(), 'minute').add(fromDate.second(), 'second')
@@ -461,7 +491,7 @@ const onResize = (event: MouseEvent) => {
const toCol = resizeRecord.value.rowMeta.range?.fk_to_col
const week = Math.floor(percentY * dates.value.length)
- const day = Math.floor(percentX * 7)
+ const day = Math.floor(percentX * maxVisibleDays.value)
let updateProperty: string[] = []
let newRow: Row
@@ -567,11 +597,12 @@ const stopDrag = (event: MouseEvent) => {
}
const dragStart = (event: MouseEvent, record: Row) => {
- if (!isUIAllowed('dataEdit') || resizeInProgress.value || !record.rowMeta.id) return
+ if (resizeInProgress.value || !record.rowMeta.id) return
let target = event.target as HTMLElement
isDragging.value = false
dragTimeout.value = setTimeout(() => {
+ if (!isUIAllowed('dataEdit')) return
isDragging.value = true
while (!target.classList.contains('draggable-record')) {
@@ -670,11 +701,17 @@ const addRecord = (date: dayjs.Dayjs) => {
+
+
+
+ {{ dayjs().format('hh:mm A') }}
+
+
+
+
-
+
{{ day }}
@@ -690,50 +727,61 @@ const addRecord = (date: dayjs.Dayjs) => {
style="height: calc(100% - 1.59rem)"
@drop="dropEvent"
>
-
-
-
-
-
-
-
+
+
- + {{ recordsToDisplay.count[dayjs(day).format('YYYY-MM-DD')]?.overflowCount }}
-
-
+
+
-
+ + {{ recordsToDisplay.count[dayjs(day).format('YYYY-MM-DD')]?.overflowCount }}
-
- {{ day.format('DD') }}
-
-
+
-
-
-
-
- Select date field to add
-
+
+
+
+
+
+
+
+ Select date field to add
+ {
const record = {
row: {
@@ -743,25 +791,25 @@ const addRecord = (date: dayjs.Dayjs) => {
emit('newRecord', record)
}
"
- >
-
-
-
-
-
+ {
const record = {
row: {
@@ -771,35 +819,36 @@ const addRecord = (date: dayjs.Dayjs) => {
emit('newRecord', record)
}
"
+ >
+
+
+
+ {{ day.format('DD') }}
+
+
+
-
- {{ range.fk_from_col!.title }}
-
-
+
+ {{ range.fk_from_col!.title }}
+
+
+
+
+
+ {{ dayjs(day).format('DD') }}
+
+ {{ dayjs(day).format('DD') }}
-
-
@@ -825,7 +874,6 @@ const addRecord = (date: dayjs.Dayjs) => {
:resize="!!record.rowMeta.range?.fk_to_col && isUIAllowed('dataEdit')"
:selected="dragRecord?.rowMeta?.id === record.rowMeta.id || resizeRecord?.rowMeta?.id === record.rowMeta.id"
@resize-start="onResizeStart"
- @dblclick.stop="emit('expandRecord', record)"
>
@@ -857,4 +905,8 @@ const addRecord = (date: dayjs.Dayjs) => {
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */
}
+
+.grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+}
diff --git a/packages/nc-gui/components/smartsheet/calendar/SideMenu.vue b/packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
index 35330889e1..cdaa03a3ba 100644
--- a/packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
+++ b/packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
@@ -9,6 +9,10 @@ const props = defineProps<{
const emit = defineEmits(['expandRecord', 'newRecord'])
+interface Attachment {
+ url: string
+}
+
const INFINITY_SCROLL_THRESHOLD = 100
const { isUIAllowed } = useRoles()
@@ -21,6 +25,10 @@ const { height } = useWindowSize()
const meta = inject(MetaInj, ref())
+const { fields } = useViewColumnsOrThrow()
+
+const { getPossibleAttachmentSrc } = useAttachment()
+
const { t } = useI18n()
const {
@@ -45,6 +53,23 @@ const {
const sideBarListRef = ref(null)
+const coverImageColumns: any = computed(() => {
+ if (!fields.value || !meta.value?.columns) return
+ return meta.value.columns.find((c) => c.uidt === UITypes.Attachment && fields.value?.find((f) => f.fk_column_id === c.id).show)
+})
+
+const attachments = (record: any): Attachment[] => {
+ const col = coverImageColumns.value
+ try {
+ if (col?.title && record.row[col.title]) {
+ return typeof record.row[col.title] === 'string' ? JSON.parse(record.row[col.title]) : record.row[col.title]
+ }
+ return []
+ } catch (e) {
+ return []
+ }
+}
+
const pushToArray = (arr: Array, record: Row, range) => {
arr.push({
...record,
@@ -381,9 +406,14 @@ onClickOutside(searchRef, toggleSearch)
>
{{ $t('objects.records') }}
-
+
-
+
{{ option.label }}
@@ -429,7 +459,7 @@ onClickOutside(searchRef, toggleSearch)
v-if="!showSearch"
data-testid="nc-calendar-sidebar-search-btn"
size="small"
- class="!h-7"
+ class="!h-7 !rounded-md"
type="secondary"
@click="clickSearch"
>
@@ -444,7 +474,7 @@ onClickOutside(searchRef, toggleSearch)
v-if="isUIAllowed('dataEdit') && props.visible"
v-e="['c:calendar:calendar-sidemenu-new-record-btn']"
data-testid="nc-calendar-side-menu-new-btn"
- class="!h-7"
+ class="!h-7 !rounded-md"
size="small"
type="secondary"
@click="newRecord"
@@ -497,8 +527,8 @@ onClickOutside(searchRef, toggleSearch)
:from-date="
record.rowMeta.range?.fk_from_col
? calDataType === UITypes.Date
- ? dayjs(record.row[record.rowMeta.range.fk_from_col.title!]).format('DD MMM')
- : dayjs(record.row[record.rowMeta.range.fk_from_col.title!]).format('DD MMM • HH:mm A')
+ ? dayjs(record.row[record.rowMeta.range.fk_from_col.title!]).format('D MMM')
+ : dayjs(record.row[record.rowMeta.range.fk_from_col.title!]).format('D MMM • h:mm a')
: null
"
:invalid="
@@ -521,6 +551,50 @@ onClickOutside(searchRef, toggleSearch)
@dragstart="dragStart($event, record)"
@dragover.prevent
>
+
+
+
+
+
+
+
+
@@ -582,4 +656,20 @@ onClickOutside(searchRef, toggleSearch)
-
+
diff --git a/packages/nc-gui/components/smartsheet/calendar/SideRecordCard.vue b/packages/nc-gui/components/smartsheet/calendar/SideRecordCard.vue
index bdbd9344b3..3a5b4e1394 100644
--- a/packages/nc-gui/components/smartsheet/calendar/SideRecordCard.vue
+++ b/packages/nc-gui/components/smartsheet/calendar/SideRecordCard.vue
@@ -16,17 +16,7 @@ const props = withDefaults(defineProps(), {
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(null)
@@ -41,12 +53,17 @@ const getFieldStyle = (field: ColumnType) => {
// Calculate the dates of the week
const weekDates = computed(() => {
let startOfWeek = dayjs(selectedDateRange.value.start)
- const endOfWeek = dayjs(selectedDateRange.value.end)
+ let endOfWeek = dayjs(selectedDateRange.value.end)
+
+ if (maxVisibleDays.value === 5) {
+ endOfWeek = endOfWeek.subtract(2, 'day')
+ }
const datesArray = []
while (startOfWeek.isBefore(endOfWeek) || startOfWeek.isSame(endOfWeek, 'day')) {
datesArray.push(dayjs(startOfWeek))
startOfWeek = startOfWeek.add(1, 'day')
}
+
return datesArray
})
@@ -111,7 +128,7 @@ const calendarData = computed(() => {
}
const recordsInRange: Array = []
- const perDayWidth = containerWidth.value / 7
+ const perDayWidth = containerWidth.value / maxVisibleDays.value
calendarRange.value.forEach((range) => {
const fromCol = range.fk_from_col
@@ -284,7 +301,7 @@ const onResize = (event: MouseEvent) => {
const ogEndDate = dayjs(resizeRecord.value.row[toCol.title!])
const ogStartDate = dayjs(resizeRecord.value.row[fromCol.title!])
- const day = Math.floor(percentX * 7)
+ const day = Math.floor(percentX * maxVisibleDays.value)
let updateProperty: string[] = []
let updateRecord: Row
@@ -370,7 +387,7 @@ const calculateNewRow = (event: MouseEvent, updateSideBarData?: boolean) => {
// Calculate the day index based on the percentage of the width
// The day index is a number between 0 and 6
- const day = Math.floor(percentX * 7)
+ const day = Math.floor(percentX * maxVisibleDays.value)
// Calculate the new start date based on the day index by adding the day index to the start date of the selected date range
const newStartDate = dayjs(selectedDateRange.value.start).add(day, 'day')
@@ -467,13 +484,13 @@ const stopDrag = (event: MouseEvent) => {
}
const dragStart = (event: MouseEvent, record: Row) => {
- if (!isUIAllowed('dataEdit')) return
if (resizeInProgress.value) return
let target = event.target as HTMLElement
isDragging.value = false
dragTimeout.value = setTimeout(() => {
+ if (!isUIAllowed('dataEdit')) return
isDragging.value = true
while (!target.classList.contains('draggable-record')) {
target = target.parentElement as HTMLElement
@@ -557,8 +574,10 @@ const addRecord = (date: dayjs.Dayjs) => {
:key="weekIndex"
:class="{
'!border-brand-500 !border-b-gray-200': dayjs(date).isSame(selectedDate, 'day'),
+ 'w-1/5': maxVisibleDays === 5,
+ 'w-1/7': maxVisibleDays === 7,
}"
- class="w-1/7 cursor-pointer text-center text-[10px] font-semibold leading-4 flex items-center justify-center uppercase text-gray-500 w-full py-1 border-gray-200 border-l-gray-50 border-t-gray-50 last:border-r-0 border-1 bg-gray-50"
+ class="cursor-pointer text-center text-[10px] font-semibold leading-4 flex items-center justify-center uppercase text-gray-500 w-full py-1 border-gray-200 border-l-gray-50 border-t-gray-50 last:border-r-0 border-1 bg-gray-50"
@click="selectDate(date)"
@dblclick="addRecord(date)"
>
@@ -572,8 +591,10 @@ const addRecord = (date: dayjs.Dayjs) => {
:class="{
'!border-1 !border-t-0 border-brand-500': dayjs(date).isSame(selectedDate, 'day'),
'!bg-gray-50': date.get('day') === 0 || date.get('day') === 6,
+ 'w-1/5': maxVisibleDays === 5,
+ 'w-1/7': maxVisibleDays === 7,
}"
- class="flex cursor-pointer flex-col border-r-1 min-h-[100vh] last:border-r-0 items-center w-1/7"
+ class="flex cursor-pointer flex-col border-r-1 min-h-[100vh] last:border-r-0 items-center"
data-testid="nc-calendar-week-day"
@click="selectDate(date)"
@dblclick="addRecord(date)"
diff --git a/packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue b/packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
index 9005928fd7..248156e071 100644
--- a/packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
+++ b/packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
@@ -11,6 +11,7 @@ const {
formattedSideBarData,
calendarRange,
displayField,
+ viewMetaProperties,
selectedTime,
updateRowProperty,
sideBarFilterOption,
@@ -25,6 +26,8 @@ const scrollContainer = ref(null)
const { width: containerWidth } = useElementSize(container)
+const isPublic = inject(IsPublicInj, ref(false))
+
const { isUIAllowed } = useRoles()
const meta = inject(MetaInj, ref())
@@ -47,6 +50,50 @@ const fieldStyles = computed(() => {
)
})
+const getDayIndex = (date: dayjs.Dayjs) => {
+ let dayIndex = date.day() - 1
+ if (dayIndex === -1) {
+ dayIndex = 6
+ }
+ return dayIndex
+}
+
+const maxVisibleDays = computed(() => {
+ return viewMetaProperties.value?.hide_weekend ? 5 : 7
+})
+
+const currTime = ref(dayjs())
+
+const overlayStyle = computed(() => {
+ if (!containerWidth.value)
+ return {
+ top: 0,
+ left: 0,
+ }
+
+ const left = (containerWidth.value / maxVisibleDays.value) * getDayIndex(currTime.value)
+ const minutes = currTime.value.hour() * 60 + currTime.value.minute()
+
+ const top = (52 / 60) * minutes
+
+ return {
+ width: `${containerWidth.value / maxVisibleDays.value}px`,
+ top: `${top}px`,
+ left: `${left}px`,
+ }
+})
+
+onMounted(() => {
+ const intervalId = setInterval(() => {
+ currTime.value = dayjs()
+ }, 10000) // 10000 ms = 10 seconds
+
+ // Clean up the interval when the component is unmounted
+ onUnmounted(() => {
+ clearInterval(intervalId)
+ })
+})
+
const getFieldStyle = (field: ColumnType) => {
return fieldStyles.value.get(field.id)
}
@@ -85,7 +132,11 @@ const calculateNewDates = useMemoize(
const datesHours = computed(() => {
const datesHours: Array> = []
let startOfWeek = dayjs(selectedDateRange.value.start) ?? dayjs().startOf('week')
- const endOfWeek = dayjs(selectedDateRange.value.end) ?? dayjs().endOf('week')
+ let endOfWeek = dayjs(selectedDateRange.value.end) ?? dayjs().endOf('week')
+
+ if (maxVisibleDays.value === 5) {
+ endOfWeek = endOfWeek.subtract(2, 'day')
+ }
while (startOfWeek.isSameOrBefore(endOfWeek)) {
const hours: Array = []
@@ -107,14 +158,6 @@ const datesHours = computed(() => {
return datesHours
})
-const getDayIndex = (date: dayjs.Dayjs) => {
- let dayIndex = date.day() - 1
- if (dayIndex === -1) {
- dayIndex = 6
- }
- return dayIndex
-}
-
const getGridTime = (date: dayjs.Dayjs, round = false) => {
const gridCalc = date.hour() * 60 + date.minute()
if (round) {
@@ -201,8 +244,20 @@ const getMaxOverlaps = ({
let maxOverlaps = 1
if (graph.has(id)) {
- maxOverlaps = dfs(id)
+ dfs(id)
}
+
+ const overlapIterations: Array = []
+
+ columnArray[dayIndex]
+ .flat()
+ .filter((record) => visited.has(record.rowMeta.id!))
+ .forEach((record) => {
+ overlapIterations.push(record.rowMeta.overLapIteration!)
+ })
+
+ maxOverlaps = Math.max(...overlapIterations)
+
return { maxOverlaps, dayIndex, overlapIndex }
}
@@ -224,11 +279,15 @@ const recordsAcrossAllRange = computed<{
records: [],
gridTimeMap: new Map(),
}
- const perWidth = containerWidth.value / 7
+ const perWidth = containerWidth.value / maxVisibleDays.value
const perHeight = 52
const scheduleStart = dayjs(selectedDateRange.value.start).startOf('day')
- const scheduleEnd = dayjs(selectedDateRange.value.end).endOf('day')
+ let scheduleEnd = dayjs(selectedDateRange.value.end).endOf('day')
+
+ if (maxVisibleDays.value === 5) {
+ scheduleEnd = scheduleEnd.subtract(2, 'day')
+ }
const columnArray: Array>> = [[[]]]
const gridTimeMap = new Map<
@@ -489,17 +548,25 @@ const recordsAcrossAllRange = computed<{
})
const dayIndex = record.rowMeta.dayIndex ?? tDayIndex
+
+ let display = 'block'
+
+ if (maxVisibleDays.value === 5) {
+ if (dayIndex === 5 || dayIndex === 6) {
+ display = 'none'
+ }
+ }
+
record.rowMeta.numberOfOverlaps = maxOverlaps
let width = 0
let left = 100
const majorLeft = dayIndex * perWidth
- let display = 'block'
if (record.rowMeta.overLapIteration! - 1 > 2) {
display = 'none'
} else {
- width = 100 / Math.min(maxOverlaps, 3) / 7
+ width = 100 / Math.min(maxOverlaps, 3) / maxVisibleDays.value
left = width * (overlapIndex - 1)
}
record.rowMeta.style = {
@@ -563,7 +630,7 @@ const onResize = (event: MouseEvent) => {
const ogEndDate = dayjs(resizeRecord.value.row[toCol.title!])
const ogStartDate = dayjs(resizeRecord.value.row[fromCol.title!])
- const day = Math.floor(percentX * 7)
+ const day = Math.floor(percentX * maxVisibleDays.value)
const hour = Math.floor(percentY * 23)
const minutes = Math.round((percentY * 24 * 60) % 60)
@@ -656,7 +723,7 @@ const calculateNewRow = (
if (!fromCol) return { newRow: null, updatedProperty: [] }
- const day = Math.max(0, Math.min(6, Math.floor(percentX * 7)))
+ const day = Math.max(0, Math.min(6, Math.floor(percentX * maxVisibleDays.value)))
const hour = Math.max(0, Math.min(23, Math.floor(percentY * 24)))
const minutes = Math.round(((percentY * 24 * 60) % 60) / 15) * 15
@@ -762,13 +829,13 @@ const stopDrag = (event: MouseEvent) => {
}
const dragStart = (event: MouseEvent, record: Row) => {
- if (!isUIAllowed('dataEdit')) return
if (resizeInProgress.value) return
let target = event.target as HTMLElement
isDragging.value = false
dragTimeout.value = setTimeout(() => {
+ if (!isUIAllowed('dataEdit')) return
isDragging.value = true
while (!target.classList.contains('draggable-record')) {
target = target.parentElement as HTMLElement
@@ -877,14 +944,32 @@ watch(
data-testid="nc-calendar-week-view"
@drop="dropEvent"
>
+
+
diff --git a/packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue b/packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
index f896139bf8..8071b26605 100644
--- a/packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
+++ b/packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
@@ -5,8 +5,20 @@ import type { Row } from '~/lib/types'
const emits = defineEmits(['expandRecord', 'newRecord'])
-const { selectedDateRange, formattedData, formattedSideBarData, calendarRange, selectedDate, displayField, updateRowProperty } =
- useCalendarViewStoreOrThrow()
+const {
+ selectedDateRange,
+ formattedData,
+ formattedSideBarData,
+ calendarRange,
+ selectedDate,
+ displayField,
+ updateRowProperty,
+ viewMetaProperties,
+} = useCalendarViewStoreOrThrow()
+
+const maxVisibleDays = computed(() => {
+ return viewMetaProperties.value?.hide_weekend ? 5 : 7
+})
const container = ref
-
+
- {{ fromDate }} {{ toDate ? ` - ${toDate}` : '' }}
+ {{ fromDate }} {{ toDate ? ` - ${toDate}` : '' }}
+
+
+
+ {{ dayjs().format('hh:mm A') }}
+
+
+
+
{{ dayjs(date[0]).format('DD ddd') }}
@@ -899,15 +984,27 @@ watch(
-
+
-
+
{
if (width.value > 1250) {
size.value = 'medium'
cols.value = 4
- } else if (width.value > 850) {
+ } else if (width.value > 950) {
size.value = 'medium'
cols.value = 3
} else if (width.value > 680) {
diff --git a/packages/nc-gui/components/smartsheet/calendar/index.vue b/packages/nc-gui/components/smartsheet/calendar/index.vue
index eb1dbb1a51..bc08f9e44b 100644
--- a/packages/nc-gui/components/smartsheet/calendar/index.vue
+++ b/packages/nc-gui/components/smartsheet/calendar/index.vue
@@ -8,11 +8,13 @@ const meta = inject(MetaInj, ref())
const view = inject(ActiveViewInj, ref())
+const { isMobileMode } = useGlobal()
+
const reloadViewMetaHook = inject(ReloadViewMetaHookInj)
const reloadViewDataHook = inject(ReloadViewDataHookInj)
-const { isMobileMode } = useGlobal()
+const isPublic = inject(IsPublicInj, ref(false))
provide(IsFormInj, ref(false))
@@ -45,14 +47,15 @@ const expandedFormOnRowIdDlg = computed({
get() {
return !!route.query.rowId
},
- set(val) {
- if (!val)
+ set(value) {
+ if (!value) {
router.push({
query: {
...route.query,
rowId: undefined,
},
})
+ }
},
})
@@ -64,7 +67,10 @@ const expandedFormRowState = ref>()
const expandRecord = (row: RowType, state?: Record) => {
const rowId = extractPkFromRow(row.row, meta.value!.columns!)
- if (rowId) {
+
+ expandedFormRowState.value = state
+
+ if (rowId && !isPublic.value) {
router.push({
query: {
...route.query,
@@ -73,12 +79,12 @@ const expandRecord = (row: RowType, state?: Record) => {
})
} else {
expandedFormRow.value = row
- expandedFormRowState.value = state
expandedFormDlg.value = true
}
}
const newRecord = (row: RowType) => {
+ if (isPublic.value) return
$e('c:calendar:new-record', activeCalendarView.value)
expandRecord({
row: {
@@ -114,77 +120,97 @@ reloadViewDataHook?.on(async (params: void | { shouldShowLoading?: boolean }) =>
-
-
-
-
+
+
+
-
-
+
diff --git a/packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue b/packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
index 31528ad2a4..312bdc437e 100644
--- a/packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
@@ -32,7 +32,7 @@ vModel.value.au = !!vModel.value.au */
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
+ {{ $t('general.available') }}
{{ $t('title.inDesktop') }} +
+ {{ $t('title.inDesktop') }} +
+ {{ $t('msg.calendarViewNotSupportedOnMobile') }}
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('activity.noRange') }}
-
-
+
+
+
+
+
+
+
+ {{ $t('activity.noRange') }}
+
+
+
+
@@ -72,7 +72,11 @@ vModel.value.au = !!vModel.value.au */
-
+
+
+
+
+
{{ type }}
@@ -85,19 +89,14 @@ vModel.value.au = !!vModel.value.au */
-
+
diff --git a/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue b/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue
index 4be9ed854f..e1085cffaa 100644
--- a/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue
@@ -1,7 +1,6 @@
-
-
-
-
-
+
diff --git a/packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue b/packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
index c1b3826587..25226c799a 100644
--- a/packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
@@ -46,6 +46,8 @@ const picked = computed({
},
})
+const isOpenColorPicker = ref(false)
+
// set default value
vModel.value.meta = {
icon: {
@@ -73,26 +75,19 @@ watch(
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
- {{ $t('msg.validColumnsForBarCode') }}
-
-
-
-
-
-
+
+
-
-
-
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ option.title }}
+
+
+
+
+ {{ $t('msg.validColumnsForBarCode') }}
+
+
+
+
+
+
-
-
-
+
-
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue b/packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
index 392841ab63..ac8f36956e 100644
--- a/packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
@@ -86,6 +86,8 @@ currencyLocales().then((locales) => {
:disabled="isMoney && isPg"
dropdown-class-name="nc-dropdown-currency-cell-locale"
>
+
+
@@ -115,6 +117,8 @@ currencyLocales().then((locales) => {
:disabled="isMoney && isPg"
dropdown-class-name="nc-dropdown-currency-cell-code"
>
+
+
+
+
+
- vModel.meta.color=el"
- />
+
+
+
+
+
+
+
-
+ vModel.meta.color=el"
+ >
+
+
+
{{ currencyCode }}
diff --git a/packages/nc-gui/components/smartsheet/column/DateOptions.vue b/packages/nc-gui/components/smartsheet/column/DateOptions.vue
index de079c405e..c8037c2d4a 100644
--- a/packages/nc-gui/components/smartsheet/column/DateOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/DateOptions.vue
@@ -16,15 +16,18 @@ if (!vModel.value.meta?.date_format) {
-
+
+
+
+
-
+
{{ format }}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
- {{ format }}
-
-
-
- {{ format }}
-
-
-
+
+
+ 12 Hrs
+ 24 Hrs
+
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/DecimalOptions.vue b/packages/nc-gui/components/smartsheet/column/DecimalOptions.vue
index de8db503af..11adec8061 100644
--- a/packages/nc-gui/components/smartsheet/column/DecimalOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/DecimalOptions.vue
@@ -22,12 +22,12 @@ const precisionFormatsDisplay = {
const vModel = useVModel(props, 'value', emit)
-onMounted(() => {
- if (!vModel.value.meta?.precision) {
- if (!vModel.value.meta) vModel.value.meta = {}
- vModel.value.meta.precision = precisionFormats[0]
- }
-})
+// set default value
+vModel.value.meta = {
+ precision: precisionFormats[0],
+ isLocaleString: false,
+ ...(vModel.value.meta || {}),
+}
// update datatype precision when precision is less than the new value
// avoid downgrading precision if the new value is less than the current precision
@@ -45,6 +45,9 @@ const onPrecisionChange = (value: number) => {
dropdown-class-name="nc-dropdown-decimal-format"
@change="onPrecisionChange"
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ format }}
+
+
+
+ {{ format }}
+
+
+
{{ (precisionFormatsDisplay as any)[format] }}
@@ -58,4 +61,12 @@ const onPrecisionChange = (value: number) => {
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/DefaultValue.vue b/packages/nc-gui/components/smartsheet/column/DefaultValue.vue
index c5cde4b971..74ea13d5cf 100644
--- a/packages/nc-gui/components/smartsheet/column/DefaultValue.vue
+++ b/packages/nc-gui/components/smartsheet/column/DefaultValue.vue
@@ -1,14 +1,17 @@
-
+
+
+
+ {{ $t('labels.showThousandsSeparator') }}
+ {{ $t('placeholder.defaultValue') }}
-
-
- {{ $t('labels.durationInfo') }}
-
-
-
-
+
+
+
-
+
-
-
+
+
+
+
+ {{ $t('general.set') }} {{ $t('placeholder.defaultValue').toLowerCase() }}
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/DurationOptions.vue b/packages/nc-gui/components/smartsheet/column/DurationOptions.vue
index a53fe5f0cc..3674fbbbe7 100644
--- a/packages/nc-gui/components/smartsheet/column/DurationOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/DurationOptions.vue
@@ -11,7 +11,7 @@ const durationOptionList =
durationOptions.map((o) => ({
...o,
// h:mm:ss (e.g. 3:45, 1:23:40)
- title: `${o.title} ${o.example}`,
+ title: `${o.title}`,
})) || []
// set default value
@@ -24,14 +24,12 @@ vModel.value.meta = {
+
+ {{ $t('placeholder.defaultValue') }}
+
+
+
+
+
+
{{ duration.title }}
{{ duration.title }}
diff --git a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
index 928d140027..303020633d 100644
--- a/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
+++ b/packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
@@ -1,7 +1,8 @@
-
-
tableExplorerColumns?: ColumnType[]
fromTableExplorer?: boolean
+ isColumnValid?: (value: Partial) => boolean
}
const props = defineProps()
@@ -16,13 +17,9 @@ const emit = defineEmits(['submit', 'cancel', 'mounted'])
const meta = inject(MetaInj, ref())
-const column = toRef(props, 'column')
+const { column, preload, tableExplorerColumns, fromTableExplorer, isColumnValid } = toRefs(props)
-const preload = toRef(props, 'preload')
-
-const tableExplorerColumns = toRef(props, 'tableExplorerColumns')
-
-useProvideColumnCreateStore(meta, column, tableExplorerColumns)
+useProvideColumnCreateStore(meta, column, tableExplorerColumns, fromTableExplorer, isColumnValid)
diff --git a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
index 5a6e1db3da..fdeae9a644 100644
--- a/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
@@ -283,27 +283,42 @@ setAdditionalValidations({
onMounted(() => {
jsep.plugins.register(jsepCurlyHook)
})
+
+const suggestionPreviewLeft = ref('-left-85')
+
+watch(sugListRef, () => {
+ nextTick(() => {
+ setTimeout(() => {
+ const fieldModal = document.querySelector('.nc-dropdown-edit-column.active') as HTMLDivElement
+
+ if (fieldModal && fieldModal.getBoundingClientRect().left < 364) {
+ suggestionPreviewLeft.value = '-right-85'
+ }
+ }, 500)
+ })
+})
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue b/packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue
index 62c188f169..50d77f2a68 100644
--- a/packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue
+++ b/packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue
@@ -8,6 +8,7 @@ interface Props {
preload?: Partial
-
-
-
-
-
+
+
+
+
+
-
-
- {{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
+
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ opt.name }}
+ {{ UITypesName[opt.name] }}
({{ $t('general.deprecated') }})
-
+
+
-
+ {{
+ `${$t('msg.acceptOnlyValid', {
+ type:
+ formState.uidt === UITypes.URL
+ ? `${UITypesName[formState.uidt as UITypes]}s`
+ : `${UITypesName[formState.uidt as UITypes]}s`.toLowerCase(),
+ })}`
+ }}
+
+
-
-
-
+
-
-
-
-
- Set as Unique
-
-
- {{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
-
-
-
-
-
-
-
+
+
-
+
+
+
Set as Unique
+
-
-
- {{ $t('general.cancel') }}
-
-
-
-
- {{ $t('general.save') }} {{ columnLabel }}
- {{ $t('general.saving') }} {{ columnLabel }}
-
+ {{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('general.cancel') }}
+
+
+
+
+ {{ submitBtnLabel.label }}
+
+ {{ submitBtnLabel.loadingLabel }}
+
+
+
+
-
-
+
@@ -463,4 +618,21 @@ const loadListData = async ($state: any) => {
width: calc(100% + 5px);
display: block;
}
+
+:deep(.nc-select-col-option-select-option) {
+ @apply !truncate;
+
+ &:not(.nc-kanban-stack-input):not(:focus):hover {
+ @apply !border-transparent;
+ }
+
+ &:not(.nc-kanban-stack-input):not(:focus) {
+ @apply !border-transparent;
+ }
+
+ &:focus,
+ &:focus-visible {
+ @apply !border-[var(--ant-primary-color-hover)];
+ }
+}
diff --git a/packages/nc-gui/components/smartsheet/column/SpecificDBTypeOptions.vue b/packages/nc-gui/components/smartsheet/column/SpecificDBTypeOptions.vue
index 04d85e1bdb..5fe9d800cb 100644
--- a/packages/nc-gui/components/smartsheet/column/SpecificDBTypeOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/SpecificDBTypeOptions.vue
@@ -1,3 +1,3 @@
-
+
diff --git a/packages/nc-gui/components/smartsheet/column/TimeOptions.vue b/packages/nc-gui/components/smartsheet/column/TimeOptions.vue
new file mode 100644
index 0000000000..91a2fd7fa2
--- /dev/null
+++ b/packages/nc-gui/components/smartsheet/column/TimeOptions.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
-
+
{{ suggestionPreviewed.text }}
-
+
-
-
- {{ suggestionPreviewed.description }}
+
+
{{ suggestionPreviewed.description }}
Syntax
{{ suggestionPreviewed.syntax }}
@@ -322,16 +337,16 @@ onMounted(() => {
{{ example }}
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue b/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
new file mode 100644
index 0000000000..a15e86302d
--- /dev/null
+++ b/packages/nc-gui/components/smartsheet/column/UITypesOptionsWithSearch.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+ 12 Hrs
+ 24 Hrs
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/column/UserOptions.vue b/packages/nc-gui/components/smartsheet/column/UserOptions.vue
index 9210b51040..4235bb120e 100644
--- a/packages/nc-gui/components/smartsheet/column/UserOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/UserOptions.vue
@@ -14,7 +14,7 @@ const initialIsMulti = ref()
const validators = {}
-const { setAdditionalValidations } = useColumnCreateStoreOrThrow()
+const { setAdditionalValidations, updateFieldName } = useColumnCreateStoreOrThrow()
setAdditionalValidations({
...validators,
@@ -31,34 +31,35 @@ onMounted(() => {
initialIsMulti.value = vModel.value.meta.is_multi
})
-const updateIsMulti = (e) => {
- vModel.value.meta.is_multi = e.target.checked
+const updateIsMulti = (isChecked: boolean) => {
+ vModel.value.meta.is_multi = isChecked
if (!vModel.value.meta.is_multi) {
vModel.value.cdf = vModel.value.cdf?.split(',')[0] || null
}
+ updateFieldName()
}
-
+
+
+
+
+
+
+
+
+
+ {{ options.length ? $t('title.noResultsMatchedYourSearch') : 'The list is empty' }}
+
+
+
+
+
+
+
+ {{ UITypesName[option.name] }}
+ ({{ $t('general.deprecated') }})
+
- ', comment: 'API token' }],
+ headers: [
+ {
+ name: 'xc-token',
+ value: `CREATE_YOUR_API_TOKEN_FROM ${location.origin + location.pathname}#/account/tokens`,
+ comment: 'API token',
+ },
+ ],
url: apiUrl.value,
queryString: [
...Object.entries(queryParams.value || {}).map(([name, value]) => {
@@ -95,7 +101,7 @@ const code = computed(() => {
const api = new Api({
baseURL: "${(appInfo.value && appInfo.value.ncSiteUrl) || '/'}",
headers: {
- "xc-token": ""
+ "xc-token": "CREATE_YOUR_API_TOKEN_FROM ${location.origin + location.pathname}#/account/tokens"
}
})
diff --git a/packages/nc-gui/components/smartsheet/details/Fields.vue b/packages/nc-gui/components/smartsheet/details/Fields.vue
index d023c0275e..24b1eb783f 100644
--- a/packages/nc-gui/components/smartsheet/details/Fields.vue
+++ b/packages/nc-gui/components/smartsheet/details/Fields.vue
@@ -1,10 +1,11 @@
@@ -955,7 +1039,7 @@ watch(
{{ $t('labels.multiField.deletedField') }}
()
const commentInputRef = ref()
-const editRef = ref()
+const comment = ref('')
+
+const { copy } = useClipboard()
+
+const route = useRoute()
+
+const { dashboardUrl } = useDashboard()
const { user, appInfo } = useGlobal()
@@ -33,6 +38,8 @@ const tab = ref<'comments' | 'audits'>('comments')
const { isUIAllowed } = useRoles()
+const router = useRouter()
+
const hasEditPermission = computed(() => isUIAllowed('commentEdit'))
const editComment = ref()
@@ -66,10 +73,16 @@ async function onEditComment() {
isCommentMode.value = true
- await updateComment(editComment.value.id!, {
- comment: editComment.value?.comment,
+ const tempCom = {
+ ...editCommentValue.value,
+ }
+
+ isEditing.value = false
+ editCommentValue.value = undefined
+ await updateComment(tempCom.id!, {
+ comment: tempCom.comment,
})
- onStopEdit()
+ loadComments()
}
function onCancel() {
@@ -93,6 +106,9 @@ onKeyStroke('Enter', (event) => {
function editComments(comment: CommentType) {
editComment.value = comment
isEditing.value = true
+ nextTick(() => {
+ scrollToComment(comment.id)
+ })
}
const value = computed({
@@ -114,18 +130,44 @@ function scrollComments() {
}
}
-const isSaving = ref(false)
-
const saveComment = async () => {
- if (isSaving.value) return
+ if (!comment.value.trim()) return
+
+ while (comment.value.endsWith('
') || comment.value.endsWith('\n')) { + if (comment.value.endsWith('
')) { + comment.value = comment.value.slice(0, -6) + } else { + comment.value = comment.value.slice(0, -2) + } + } isCommentMode.value = true isSaving.value = true + // Optimistic Insert + + comments.value = [ + ...comments.value, + { + id: `temp-${new Date().getTime()}`, + comment: comment.value, + created_at: new Date().toISOString(), + created_by: user.value?.id, + created_by_email: user.value?.email, + created_display_name: user.value?.display_name ?? '', + }, + ] + + const tempCom = comment.value + comment.value = '' + + commentInputRef?.value?.setEditorContent('', true) + await nextTick(() => { + scrollComments() + }) try { - await _saveComment() + await _saveComment(tempCom) await nextTick(() => { - commentInputRef?.value?.setEditorContent('', true) isExpandedFormCommentMode.value = true }) scrollComments() @@ -136,13 +178,69 @@ const saveComment = async () => { } } +function scrollToComment(commentId: string) { + const commentEl = document.querySelector(`.${commentId}`) + if (commentEl) { + commentEl.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }) + } +} + watch(commentsWrapperEl, () => { setTimeout(() => { nextTick(() => { - scrollComments() + const query = router.currentRoute.value.query + const commentId = query.commentId + if (commentId) { + router.push({ + query: { + rowId: query.rowId, + }, + }) + scrollToComment(commentId as string) + } else { + scrollComments() + } }) }, 100) }) + +const timesAgo = (comment: CommentType) => { + return comment.created_at !== comment.updated_at ? `Edited ${timeAgo(comment.updated_at!)}` : timeAgo(comment.created_at!) +} + +const createdBy = ( + comment: CommentType & { + created_display_name?: string + }, +) => { + if (comment.created_by === user.value?.id) { + return 'You' + } else if (comment.created_display_name?.trim()) { + return comment.created_by_email || 'Shared source' + } else if (comment.created_by_email) { + return comment.created_by_email + } else { + return 'Shared source' + } +} + +const getUserRole = (email: string) => { + const user = baseUsers.value.find((user) => user.email === email) + if (!user) return ProjectRoles.NO_ACCESS + + return user.roles || ProjectRoles.NO_ACCESS +} + +const editedAt = (comment: CommentType) => { + if (comment.updated_at !== comment.created_at && comment.updated_at) { + const str = timeAgo(comment.updated_at).replace(' ', '_') + return `[(edited)](a~~~###~~~Edited_${str}) ` + } + return '' +} @@ -172,77 +270,71 @@ watch(commentsWrapperEl, () => {
-
- Allow adding multiple users
-
-
-
-
- Notify users with base access when they're added
-
-
-
- Changing from multiple mode to single will retain only first user in each cell!!!
-
+
+
+
+
+
+
+
+ Alert
+ Changing from multiple mode to single will retain only first user in each cell!
+
diff --git a/packages/nc-gui/components/smartsheet/details/Api.vue b/packages/nc-gui/components/smartsheet/details/Api.vue
index 6b5ca12e5f..99691c809c 100644
--- a/packages/nc-gui/components/smartsheet/details/Api.vue
+++ b/packages/nc-gui/components/smartsheet/details/Api.vue
@@ -7,7 +7,7 @@ const { t } = useI18n()
const baseStore = useBase()
const { base } = storeToRefs(baseStore)
-const { appInfo, token } = useGlobal()
+const { appInfo } = useGlobal()
const meta = inject(MetaInj, ref())
@@ -73,7 +73,13 @@ const snippet = computed(
() =>
new HTTPSnippet({
method: 'GET',
- headers: [{ name: 'xc-token', value: '
+
+
+
+ Allow adding multiple users
+
+
+
+
+ Notify users with base access when they're added
+ ') || comment.value.endsWith('\n')) { + if (comment.value.endsWith('
')) { + comment.value = comment.value.slice(0, -6) + } else { + comment.value = comment.value.slice(0, -2) + } + } isCommentMode.value = true isSaving.value = true + // Optimistic Insert + + comments.value = [ + ...comments.value, + { + id: `temp-${new Date().getTime()}`, + comment: comment.value, + created_at: new Date().toISOString(), + created_by: user.value?.id, + created_by_email: user.value?.email, + created_display_name: user.value?.display_name ?? '', + }, + ] + + const tempCom = comment.value + comment.value = '' + + commentInputRef?.value?.setEditorContent('', true) + await nextTick(() => { + scrollComments() + }) try { - await _saveComment() + await _saveComment(tempCom) await nextTick(() => { - commentInputRef?.value?.setEditorContent('', true) isExpandedFormCommentMode.value = true }) scrollComments() @@ -136,13 +178,69 @@ const saveComment = async () => { } } +function scrollToComment(commentId: string) { + const commentEl = document.querySelector(`.${commentId}`) + if (commentEl) { + commentEl.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }) + } +} + watch(commentsWrapperEl, () => { setTimeout(() => { nextTick(() => { - scrollComments() + const query = router.currentRoute.value.query + const commentId = query.commentId + if (commentId) { + router.push({ + query: { + rowId: query.rowId, + }, + }) + scrollToComment(commentId as string) + } else { + scrollComments() + } }) }, 100) }) + +const timesAgo = (comment: CommentType) => { + return comment.created_at !== comment.updated_at ? `Edited ${timeAgo(comment.updated_at!)}` : timeAgo(comment.created_at!) +} + +const createdBy = ( + comment: CommentType & { + created_display_name?: string + }, +) => { + if (comment.created_by === user.value?.id) { + return 'You' + } else if (comment.created_display_name?.trim()) { + return comment.created_by_email || 'Shared source' + } else if (comment.created_by_email) { + return comment.created_by_email + } else { + return 'Shared source' + } +} + +const getUserRole = (email: string) => { + const user = baseUsers.value.find((user) => user.email === email) + if (!user) return ProjectRoles.NO_ACCESS + + return user.roles || ProjectRoles.NO_ACCESS +} + +const editedAt = (comment: CommentType) => { + if (comment.updated_at !== comment.created_at && comment.updated_at) { + const str = timeAgo(comment.updated_at).replace(' ', '_') + return `[(edited)](a~~~###~~~Edited_${str}) ` + } + return '' +} @@ -172,77 +270,71 @@ watch(commentsWrapperEl, () => {
{{ $t('activity.startCommenting') }}
-
+
-
-
-
-
-
-
-
- {{ comment.created_display_name?.trim() || comment.created_by_email || 'Shared source' }}
-
-
- {{
- comment.created_by === user?.id
- ? 'You'
- : comment.created_display_name?.trim() || comment.created_by_email || 'Shared source'
- }}
-
-
-
{
@keydown.enter.exact.prevent="onEditComment"
/>
-
- {{
- comment.created_at !== comment.updated_at
- ? `Edited ${timeAgo(comment.updated_at)}`
- : timeAgo(comment.created_at)
- }}
-
+
+
+
+
+
-
+
+
+
+ {{ comment.created_display_name?.trim() || comment.created_by_email || 'Shared source' }}
+
+
+ {{ createdBy(comment) }}
+
+
+
+
-
-
+ {{ timesAgo(comment) }}
-
-
-
-
-
-
-
-
+
+
-
- {{ $t('general.edit') }}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
{
{{ $t('general.delete') }}
-
-
-
+
+
+
+
+
+
+
+ {{ $t('general.edit') }}
+
+
+
+ {{ $t('general.copy') }} URL
+
+
+
+
+
+
+
+ Click to resolve
+
+
+
+ {{ `Resolved by ${comment.resolved_display_name}` }}
+
+
+
+
+
+ Resolved
+
+
+
+
{
{
-
- Coming soon
-
+ {{ $t('title.comingSoon') }}
@@ -357,14 +474,7 @@ watch(commentsWrapperEl, () => {
{{ audit.display_name?.trim() || audit.user || 'Shared source' }}
-
+
{{ audit.display_name?.trim() || audit.user || 'Shared source' }}
@@ -449,10 +559,15 @@ watch(commentsWrapperEl, () => {
box-shadow: none;
&:focus,
&:focus-within {
- @apply min-h-16;
+ @apply min-h-16 !bg-white border-brand-500;
+ box-shadow: 0px 0px 0px 2px rgba(51, 102, 255, 0.24);
}
&::placeholder {
@apply !text-gray-400;
}
}
+
+:deep(.expanded-form-comment-edit-input .nc-comment-rich-editor) {
+ @apply !pl-2 bg-white;
+}
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/RichComment.vue b/packages/nc-gui/components/smartsheet/expanded-form/RichComment.vue
index f9b63173bc..9f0f93e32e 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/RichComment.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/RichComment.vue
@@ -60,6 +60,8 @@ const vModel = useVModel(props, 'value', emits, { defaultValue: '' })
const tiptapExtensions = [
StarterKit.configure({
heading: false,
+ codeBlock: false,
+ code: false,
}),
Underline,
Link,
@@ -82,6 +84,7 @@ const editor = useEditor({
onFocus: () => {
isFocused.value = true
emits('focus')
+ onFocusWrapper()
},
onBlur: (e) => {
if (
@@ -122,6 +125,7 @@ const setEditorContent = (contentMd: any, focusEndOfDoc?: boolean) => {
const onFocusWrapper = () => {
if (!props.readOnly && !keys.shift.value) {
editor.value?.chain().focus().run()
+ setEditorContent(vModel.value, true)
}
}
@@ -236,6 +240,7 @@ defineExpose({
v-if="editor"
ref="richTextLinkOptionRef"
:editor="editor"
+ :is-comment="true"
:is-form-field="true"
@blur="isFocused = false"
/>
@@ -243,7 +248,11 @@ defineExpose({
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/RichTextOptions.vue b/packages/nc-gui/components/smartsheet/expanded-form/RichTextOptions.vue
index 4238e7fea4..ea3535ad47 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/RichTextOptions.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/RichTextOptions.vue
@@ -1,6 +1,5 @@
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue
index 6c0dac31e7..c7dafb86d6 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue
@@ -29,6 +29,7 @@ interface Props {
newRecordHeader?: string
skipReload?: boolean
newRecordSubmitBtnText?: string
+ expandForm?: (row: Row) => void
}
const props = defineProps()
@@ -63,13 +64,13 @@ const isFirstRow = toRef(props, 'firstRow')
const route = useRoute()
-const router = useRouter()
-
const isPublic = inject(IsPublicInj, ref(false))
// to check if a expanded form which is not yet saved exist or not
const isUnsavedFormExist = ref(false)
+const isUnsavedDuplicatedRecordExist = ref(false)
+
const isRecordLinkCopied = ref(false)
const { isUIAllowed } = useRoles()
@@ -89,6 +90,11 @@ const { isExpandedFormCommentMode } = storeToRefs(useConfigStore())
// override cell click hook to avoid unexpected behavior at form fields
provide(CellClickHookInj, undefined)
+const loadingEmit = (event: 'update:modelValue' | 'cancel' | 'next' | 'prev' | 'createdRecord') => {
+ emits(event)
+ isLoading.value = true
+}
+
const fields = computedInject(FieldsInj, (_fields) => {
if (props.useMetaFields) {
return (meta.value.columns ?? []).filter((col) => !isSystemColumn(col))
@@ -126,7 +132,6 @@ const {
isNew,
loadRow: _loadRow,
primaryKey,
- saveRowAndStay,
row: _row,
save: _save,
loadComments,
@@ -174,6 +179,7 @@ const onClose = () => {
const onDuplicateRow = () => {
duplicatingRowInProgress.value = true
isUnsavedFormExist.value = true
+ isUnsavedDuplicatedRecordExist.value = true
const oldRow = { ..._row.value.row }
delete oldRow.ncRecordId
const newRow = Object.assign(
@@ -222,6 +228,17 @@ const save = async () => {
if (props.closeAfterSave) {
isExpanded.value = false
+ } else {
+ if (isUnsavedDuplicatedRecordExist.value) {
+ const newRowId = extractPkFromRow(_row.value.row, meta.value.columns as ColumnType[])
+ if (newRowId !== rowId.value) {
+ props?.expandForm?.(_row.value)
+ }
+
+ setTimeout(() => {
+ isUnsavedDuplicatedRecordExist.value = false
+ }, 500)
+ }
}
emits('createdRecord', _row.value.row)
@@ -242,7 +259,7 @@ const isCloseModalOpen = ref(false)
const discardPreventModal = () => {
// when user click on next or previous button
if (isPreventChangeModalOpen.value) {
- emits('next')
+ loadingEmit('next')
if (_row.value?.rowMeta?.new) emits('cancel')
isPreventChangeModalOpen.value = false
}
@@ -261,7 +278,7 @@ const onNext = async () => {
isPreventChangeModalOpen.value = true
return
}
- emits('next')
+ loadingEmit('next')
}
const copyRecordUrl = async () => {
@@ -280,7 +297,7 @@ const saveChanges = async () => {
if (isPreventChangeModalOpen.value) {
isUnsavedFormExist.value = false
await save()
- emits('next')
+ loadingEmit('next')
isPreventChangeModalOpen.value = false
}
if (isCloseModalOpen.value) {
@@ -303,14 +320,21 @@ provide(ReloadRowDataHookInj, reloadHook)
if (isKanban.value) {
// adding column titles to changedColumns if they are preset
- for (const [k, v] of Object.entries(_row.value.row)) {
- if (v) {
- changedColumns.value.add(k)
+ if (_row.value.rowMeta.new) {
+ for (const [k, v] of Object.entries(_row.value.row)) {
+ if (v) {
+ changedColumns.value.add(k)
+ }
}
}
}
provide(IsExpandedFormOpenInj, isExpanded)
+const triggerRowLoad = async (rowId?: string) => {
+ await Promise.allSettled([loadComments(rowId), loadAudits(rowId), _loadRow(rowId)])
+ isLoading.value = false
+}
+
const cellWrapperEl = ref()
onMounted(async () => {
@@ -318,22 +342,15 @@ onMounted(async () => {
isLoading.value = true
const focusFirstCell = !isExpandedFormCommentMode.value
+ let isTriggered = false
- if (props.loadRow) {
- await _loadRow()
- await Promise.all([loadComments(), loadAudits()])
- }
-
- if (props.rowId) {
- try {
- await _loadRow(props.rowId)
- await Promise.all([loadComments(), loadAudits()])
- } catch (e: any) {
- if (e.response?.status === 404) {
- message.error(t('msg.noRecordFound'))
- router.replace({ query: {} })
- } else throw e
- }
+ if (props.loadRow && !props.rowId) {
+ await triggerRowLoad()
+ isTriggered = true
+ } else if (props.rowId && props.loadRow && !isTriggered) {
+ await triggerRowLoad(props.rowId)
+ } else {
+ _row.value = props.row
}
isLoading.value = false
@@ -365,7 +382,7 @@ useActiveKeyupListener(
if (!e.altKey) return
if (e.key === 'ArrowLeft') {
e.stopPropagation()
- emits('prev')
+ loadingEmit('prev')
} else if (e.key === 'ArrowRight') {
e.stopPropagation()
onNext()
@@ -385,9 +402,6 @@ useActiveKeyupListener(
await save()
reloadHook?.trigger(null)
}
- if (!saveRowAndStay.value) {
- onClose()
- }
} catch (e: any) {
if (isNew.value) {
message.error(`Add row failed: ${await extractSdkResponseErrorMsg(e)}`)
@@ -401,7 +415,7 @@ useActiveKeyupListener(
;(document.activeElement as HTMLInputElement)?.blur?.()
if (changedColumns.value.size > 0) {
- await Modal.confirm({
+ Modal.confirm({
title: t('msg.saveChanges'),
okText: t('general.save'),
cancelText: t('labels.discard'),
@@ -415,7 +429,7 @@ useActiveKeyupListener(
},
})
} else if (isNew.value) {
- await Modal.confirm({
+ Modal.confirm({
title: 'Do you want to save the record?',
okText: t('general.save'),
cancelText: t('labels.discard'),
@@ -448,7 +462,10 @@ const onDeleteRowClick = () => {
const onConfirmDeleteRowClick = async () => {
showDeleteRowModal.value = false
- await deleteRowById(primaryKey.value)
+ // Close expanded form
+ isExpanded.value = false
+
+ await deleteRowById(primaryKey.value || undefined)
message.success(t('msg.rowDeleted'))
await reloadViewDataTrigger.trigger({
shouldShowLoading: false,
@@ -458,8 +475,7 @@ const onConfirmDeleteRowClick = async () => {
}
watch(rowId, async (nRow) => {
- await _loadRow(nRow)
- await Promise.all([loadComments(), loadAudits()])
+ await triggerRowLoad(nRow)
})
const showRightSections = computed(() => {
@@ -483,6 +499,9 @@ const onIsExpandedUpdate = (v: boolean) => {
if (changedColumns.value.size === 0 && !isUnsavedFormExist.value) {
isExpanded.value = v
+ if (isKanban.value) {
+ emits('cancel')
+ }
} else if (!v && isUIAllowed('dataEdit')) {
preventModalStatus.value = true
} else {
@@ -548,13 +567,13 @@ export default {
:closable="false"
:footer="null"
:visible="isExpanded"
- :width="commentsDrawer && isUIAllowed('commentList') ? 'min(80vw,1280px)' : 'min(80vw,1280px)'"
+ :width="commentsDrawer && isUIAllowed('commentList') ? 'min(80vw,1280px)' : 'min(70vw,768px)'"
class="nc-drawer-expanded-form"
:size="isMobileMode ? 'medium' : 'small'"
v-bind="modalProps"
@update:visible="onIsExpandedUpdate"
>
-
-
+.nc-grid-add-new-row {
+ :deep(.ant-btn.ant-dropdown-trigger.ant-btn-icon-only) {
+ @apply !flex items-center justify-center;
+ }
+}
+
diff --git a/packages/nc-gui/components/smartsheet/grid/index.vue b/packages/nc-gui/components/smartsheet/grid/index.vue
index 4589d106c7..288fef994c 100644
--- a/packages/nc-gui/components/smartsheet/grid/index.vue
+++ b/packages/nc-gui/components/smartsheet/grid/index.vue
@@ -79,6 +79,8 @@ provide(IsCalendarInj, ref(false))
provide(RowHeightInj, rowHeight)
+const isPublic = inject(IsPublicInj, ref(false))
+
// reload table data reload hook as fallback to rowdatareload
provide(ReloadRowDataHookInj, reloadViewDataHook)
@@ -86,10 +88,8 @@ const skipRowRemovalOnCancel = ref(false)
function expandForm(row: Row, state?: Record, fromToolbar = false) {
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
-
- if (rowId) {
- expandedFormRowState.value = state
-
+ expandedFormRowState.value = state
+ if (rowId && !isPublic.value) {
router.push({
query: {
...routeQuery.value,
@@ -98,7 +98,6 @@ function expandForm(row: Row, state?: Record, fromToolbar = false)
})
} else {
expandedFormRow.value = row
- expandedFormRowState.value = state
expandedFormDlg.value = true
skipRowRemovalOnCancel.value = !fromToolbar
}
@@ -238,6 +237,7 @@ const goToPreviousRow = () => {
{
diff --git a/packages/nc-gui/components/smartsheet/header/Cell.vue b/packages/nc-gui/components/smartsheet/header/Cell.vue
index 15e4547207..cfffcd3e81 100644
--- a/packages/nc-gui/components/smartsheet/header/Cell.vue
+++ b/packages/nc-gui/components/smartsheet/header/Cell.vue
@@ -30,8 +30,6 @@ const isExpandedBulkUpdateForm = inject(IsExpandedBulkUpdateFormOpenInj, ref(fal
const isDropDownOpen = ref(false)
-const isKanban = inject(IsKanbanInj, ref(false))
-
const column = toRef(props, 'column')
const { isUIAllowed } = useRoles()
@@ -100,9 +98,8 @@ const onClick = (e: Event) => {
class="flex items-center w-full text-xs text-gray-500 font-weight-medium group"
:class="{
'h-full': column,
- '!text-gray-400': isKanban,
'flex-col !items-start justify-center pt-0.5': isExpandedForm && !isMobileMode && !isExpandedBulkUpdateForm,
- 'cursor-pointer hover:bg-gray-100':
+ 'nc-cell-expanded-form-header cursor-pointer hover:bg-gray-100':
isExpandedForm && !isMobileMode && isUIAllowed('fieldEdit') && !isExpandedBulkUpdateForm,
'bg-gray-100': isExpandedForm && !isExpandedBulkUpdateForm ? editColumnDropdown || isDropDownOpen : false,
}"
@@ -191,7 +188,7 @@ const onClick = (e: Event) => {
class="h-full"
:trigger="['click']"
:placement="isExpandedForm && !isExpandedBulkUpdateForm ? 'bottomLeft' : 'bottomRight'"
- overlay-class-name="nc-dropdown-edit-column"
+ :overlay-class-name="`nc-dropdown-edit-column ${editColumnDropdown ? 'active' : ''}`"
>
diff --git a/packages/nc-gui/components/smartsheet/header/Menu.vue b/packages/nc-gui/components/smartsheet/header/Menu.vue
index 3fa673c310..c7661b1606 100644
--- a/packages/nc-gui/components/smartsheet/header/Menu.vue
+++ b/packages/nc-gui/components/smartsheet/header/Menu.vue
@@ -1,6 +1,6 @@
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/toolbar/Calendar/ActiveView.vue b/packages/nc-gui/components/smartsheet/toolbar/Calendar/ActiveView.vue
index 7ec69daa3e..b7f38bc849 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/Calendar/ActiveView.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/Calendar/ActiveView.vue
@@ -1,9 +1,11 @@
-
+
{{ activeCalendarView }}
diff --git a/packages/nc-gui/components/smartsheet/toolbar/Calendar/Header.vue b/packages/nc-gui/components/smartsheet/toolbar/Calendar/Header.vue
index f601d08df8..8a873311ec 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/Calendar/Header.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/Calendar/Header.vue
@@ -35,7 +35,7 @@ const headerText = computed(() => {
{{ $t('labels.previous') }}
{
'w-29': activeCalendarView === 'day',
'w-38': activeCalendarView === 'week',
}"
- class="!h-6 !bg-gray-100 !border-0"
+ class="!h-6 prev-next-btn !bg-gray-100 !border-0"
full-width
size="small"
type="secondary"
@@ -108,7 +108,7 @@ const headerText = computed(() => {
{{ $t('labels.next') }}
{
.nc-cal-toolbar-header {
@apply !h-6 !w-6;
}
+
+.prev-next-btn {
+ @apply !hover:bg-gray-200;
+}
diff --git a/packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue b/packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue
index ff6115b91f..d611949cf5 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/Calendar/Mode.vue
@@ -62,7 +62,9 @@ watch(activeCalendarView, () => {
{{ option }}
- {{ option }}
+
+ {{ option }}
+
viewMetaProperties.value?.hide_weekend ?? false,
+ set: (newValue) => {
+ updateCalendarMeta({
+ meta: {
+ hide_weekend: newValue,
+ },
+ })
+ },
+})
+
watch(
() => activeView.value?.id,
async (newVal, oldVal) => {
@@ -135,29 +147,6 @@ const saveCalendarRange = async (range: CalendarRangeType, value?) => {
+
-
-
+
-
+
@@ -572,7 +591,7 @@ export default {
class="nc-prev-arrow !w-7 !h-7 !text-gray-500 !disabled:text-gray-300"
type="text"
size="xsmall"
- @click="$emit('prev')"
+ @click="loadingEmit('prev')"
>
@@ -601,16 +620,6 @@ export default {
'xs:max-w-[calc(100%_-_82px)]': !isNew,
}"
>
-
-
+
@@ -721,13 +728,13 @@ export default {
-
-
-
-
- {{ meta.title }}
-
- {{ meta.title }}
-
-
{{ newRecordSubmitBtnText ?? 'Save Record' }}
-
+ {{ isRecordLinkCopied ? $t('labels.copiedRecordURL') : $t('labels.copyRecordURL') }}
+
-
-
- {{ isRecordLinkCopied ? $t('labels.copiedRecordURL') : $t('labels.copyRecordURL') }}
-
-
+
+
+
+
+
+
-
+
{{ $t('labels.copyRecordURL') }}
@@ -915,7 +924,7 @@ export default {
-
+
+
@@ -1005,6 +1015,9 @@ export default {
.nc-expanded-cell-header > :first-child {
@apply !text-md pl-2 xs:(pl-0 -ml-0.5);
}
+.nc-expanded-cell-header:not(.nc-cell-expanded-form-header) > :first-child {
+ @apply pl-0;
+}
.nc-drawer-expanded-form .nc-modal {
@apply !p-0;
@@ -1016,19 +1029,34 @@ export default {
@apply !xs:(h-full);
}
-:deep(.ant-select-selection-item) {
- @apply !xs:(mt-1.75 ml-1);
-}
-
.nc-data-cell {
@apply !rounded-lg;
transition: all 0.3s;
- &:hover {
- @apply !border-1 !border-brand-400;
+
+ &:not(.nc-readonly-div-data-cell):not(.nc-system-field) {
+ box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08);
+ }
+ &:not(:focus-within):hover:not(.nc-readonly-div-data-cell):not(.nc-system-field) {
+ @apply !border-1;
+ box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24);
+ }
+
+ &.nc-readonly-div-data-cell,
+ &.nc-system-field {
+ @apply !border-gray-200;
+
+ .nc-cell,
+ .nc-virtual-cell {
+ @apply text-gray-400;
+ }
+ }
+ &.nc-readonly-div-data-cell:focus-within,
+ &.nc-system-field:focus-within {
+ @apply !border-gray-200;
}
- &:focus-within {
- box-shadow: 0px 0px 0px 2px rgba(51, 102, 255, 0.24) !important;
+ &:focus-within:not(.nc-readonly-div-data-cell):not(.nc-system-field) {
+ @apply !shadow-selected;
}
}
.nc-data-cell:focus-within {
diff --git a/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue b/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
index 730ac4e96c..0d41a9f44a 100644
--- a/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
+++ b/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue
@@ -136,6 +136,14 @@ const pagination = computed(() => {
extraStyle: `margin-left: ${scrollBump.value}px;`,
}
})
+
+async function deleteSelectedRowsWrapper() {
+ if (!deleteSelectedRows) return
+
+ await deleteSelectedRows()
+ // reload table data
+ await reloadTableData({ shouldShowLoading: true })
+}
@@ -151,7 +159,7 @@ const pagination = computed(() => {
:expand-form="props.expandForm"
:row-height="rowHeight"
:delete-row="deleteRow"
- :delete-selected-rows="deleteSelectedRows"
+ :delete-selected-rows="deleteSelectedRowsWrapper"
:delete-range-of-rows="deleteRangeOfRows"
:update-or-save-row="updateOrSaveRow"
:remove-row-if-new="removeRowIfNew"
diff --git a/packages/nc-gui/components/smartsheet/grid/Table.vue b/packages/nc-gui/components/smartsheet/grid/Table.vue
index 00bdb44efe..3371fb33c3 100644
--- a/packages/nc-gui/components/smartsheet/grid/Table.vue
+++ b/packages/nc-gui/components/smartsheet/grid/Table.vue
@@ -234,6 +234,9 @@ const isKeyDown = ref(false)
async function clearCell(ctx: { row: number; col: number } | null, skipUpdate = false) {
if (!ctx || !hasEditPermission.value || (!isLinksOrLTAR(fields.value[ctx.col]) && isVirtualCol(fields.value[ctx.col]))) return
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ if (colMeta.value[ctx.col].isReadonly) return
+
const rowObj = dataRef.value[ctx.row]
const columnObj = fields.value[ctx.col]
@@ -424,15 +427,23 @@ const cellMeta = computed(() => {
})
})
+const isReadonly = (col: ColumnType) => {
+ return (
+ isSystemColumn(col) ||
+ isLookup(col) ||
+ isRollup(col) ||
+ isFormula(col) ||
+ isVirtualCol(col) ||
+ isCreatedOrLastModifiedTimeCol(col) ||
+ isCreatedOrLastModifiedByCol(col)
+ )
+}
+
const colMeta = computed(() => {
return fields.value.map((col) => {
return {
- isLookup: isLookup(col),
- isRollup: isRollup(col),
- isFormula: isFormula(col),
- isCreatedOrLastModifiedTimeCol: isCreatedOrLastModifiedTimeCol(col),
- isCreatedOrLastModifiedByCol: isCreatedOrLastModifiedByCol(col),
isVirtualCol: isVirtualCol(col),
+ isReadonly: isReadonly(col),
}
})
})
@@ -932,6 +943,9 @@ async function clearSelectedRangeOfCells() {
continue
}
+ // skip readonly columns
+ if (isReadonly(col)) continue
+
row.row[col.title] = null
props.push(col.title)
}
@@ -1251,6 +1265,16 @@ const refreshFillHandle = () => {
})
}
+const selectedReadonly = computed(
+ () =>
+ // if all the selected columns are not readonly
+ (selectedRange.isEmpty() && activeCell.col && colMeta.value[activeCell.col].isReadonly) ||
+ (!selectedRange.isEmpty() &&
+ Array.from({ length: selectedRange.end.col - selectedRange.start.col + 1 }).every(
+ (_, i) => colMeta.value[selectedRange.start.col + i].isReadonly,
+ )),
+)
+
const showFillHandle = computed(
() =>
!readOnly.value &&
@@ -1259,16 +1283,10 @@ const showFillHandle = computed(
!dataRef.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new &&
activeCell.col !== null &&
fields.value[activeCell.col] &&
- !(
- isLookup(fields.value[activeCell.col]) ||
- isRollup(fields.value[activeCell.col]) ||
- isFormula(fields.value[activeCell.col]) ||
- isCreatedOrLastModifiedTimeCol(fields.value[activeCell.col]) ||
- isCreatedOrLastModifiedByCol(fields.value[activeCell.col])
- ) &&
!isViewDataLoading.value &&
!isPaginationLoading.value &&
- dataRef.value.length,
+ dataRef.value.length &&
+ !selectedReadonly.value,
)
watch(
@@ -1660,7 +1678,7 @@ onKeyStroke('ArrowDown', onDown)
@@ -2242,7 +2246,7 @@ onKeyStroke('ArrowDown', onDown)
(isLinksOrLTAR(fields[contextMenuTarget.col]) || !cellMeta[0]?.[contextMenuTarget.col].isVirtualCol)
"
class="nc-base-menu-item"
- :disabled="isSystemColumn(fields[contextMenuTarget.col])"
+ :disabled="selectedReadonly"
data-testid="context-menu-item-clear"
@click="clearCell(contextMenuTarget)"
>
@@ -2256,7 +2260,7 @@ onKeyStroke('ArrowDown', onDown)
@@ -2307,8 +2311,8 @@ onKeyStroke('ArrowDown', onDown)
-
Date field is required!
+ {
-
+
diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
index 2b908e6578..985ad6080a 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
@@ -1,5 +1,5 @@
-
-
-
-
-
-
- {{ `${$t('activity.calendar')} ${$t('activity.viewSettings')}` }}
-
-
-
- Go to Docs
-
- {
-->
+
+
+
+ {{ $t('activity.hideWeekends') }}
+
+
+
+
diff --git a/packages/nc-gui/components/smartsheet/toolbar/Calendar/Today.vue b/packages/nc-gui/components/smartsheet/toolbar/Calendar/Today.vue
index e9197e5d26..81c131911a 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/Calendar/Today.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/Calendar/Today.vue
@@ -24,7 +24,7 @@ const goToToday = () => {
-
-
+
+
{{ $t('activity.addFilterGroup') }}
-
+
@@ -459,29 +575,35 @@ watchEffect(() => {
+
@@ -443,13 +559,13 @@ watchEffect(() => {
-
+
+
-
-
+
+
- {{ $t('labels.where') }}
+ {{
+ $t('labels.where')
+ }}
{
{
-
+ {
class="nc-filter-field-select min-w-32 max-w-32 max-h-8"
:columns="fieldsToFilter"
:disabled="filter.readOnly"
+ :meta="meta"
@click.stop
@change="selectFilterField(filter, i)"
/>
{
+
-
- updateFilterValue(value, filter, i)"
- @click.stop
- />
-
-
-
+
+
+
+ updateFilterValue(value, filter, i)"
+ @click.stop
+ />
+
+
+
+
+
+
+
{{ $t('labels.where') }}
+
+
-