diff --git a/packages/nc-gui/components/smartsheet/grid/ExpandedFormPanel.vue b/packages/nc-gui/components/smartsheet/grid/ExpandedFormPanel.vue new file mode 100644 index 0000000000..3e30dbed0d --- /dev/null +++ b/packages/nc-gui/components/smartsheet/grid/ExpandedFormPanel.vue @@ -0,0 +1,7 @@ + + + diff --git a/packages/nc-gui/components/smartsheet/grid/ExpandedFormPanelActivity.vue b/packages/nc-gui/components/smartsheet/grid/ExpandedFormPanelActivity.vue new file mode 100644 index 0000000000..df4aaf4192 --- /dev/null +++ b/packages/nc-gui/components/smartsheet/grid/ExpandedFormPanelActivity.vue @@ -0,0 +1,7 @@ + + + diff --git a/packages/nc-gui/components/smartsheet/grid/index.vue b/packages/nc-gui/components/smartsheet/grid/index.vue index 32f0571c15..b507964a76 100644 --- a/packages/nc-gui/components/smartsheet/grid/index.vue +++ b/packages/nc-gui/components/smartsheet/grid/index.vue @@ -20,10 +20,17 @@ const { xWhere, eventBus, isExternalSource } = useSmartsheetStoreOrThrow() const { t } = useI18n() +const { isMobileMode } = useGlobal() + const { isFeatureEnabled } = useBetaFeatureToggle() const { blockExternalSourceRecordVisibility, showUpgradeToSeeMoreRecordsModal } = useEeConfig() +// --- Expanded form panel (right-side slide-in) --- +const expandedFormPanelStore = useProvideExpandedFormPanel() + +const { isOpen: isExpandedFormPanelOpen, rowNavigator: expandedFormPanelRowNavigator } = expandedFormPanelStore + const bulkUpdateDlg = ref(false) const routeQuery = computed(() => route.value.query as Record) @@ -191,6 +198,29 @@ const skipRowRemovalOnCancel = ref(false) function expandForm(row: Row, state?: Record, fromToolbar = false, path: Array = []) { const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[]) + + // EE desktop: open the right-side panel instead of modal + if (isEeUI && !isMobileMode.value && !isPublic.value && rowId) { + expandedFormPanelStore.openPanel(row, undefined, state) + + // Update route for deep-linking + const routeParams = { + query: { + ...routeQuery.value, + rowId, + path: ncIsEmptyArray(path) ? undefined : path.join('-'), + expand: undefined, + }, + } + if (routeQuery.value.expand) { + router.replace(routeParams) + } else { + router.push(routeParams) + } + return + } + + // Fallback: existing modal behavior (CE, mobile, public, new rows) expandedFormRowState.value = state if (rowId && !isPublic.value) { expandedFormRow.value = undefined @@ -204,11 +234,9 @@ function expandForm(row: Row, state?: Record, fromToolbar = false, colId: undefined, cellMode: undefined, path: ncIsEmptyArray(path) ? undefined : path.join('-'), - // Remove expand from query to avoid triggering the expanded form on closing the dialog expand: undefined, }, } - // if expand is true, replace the route to avoid adding a new history entry if (routeQuery.value.expand) { router.replace(routeParams) } else { @@ -233,6 +261,8 @@ defineExpose({ const expandedFormOnRowIdDlg = computed({ get() { if (!routeQuery.value.rowId) return false + // When the side panel is open, don't trigger the modal + if (isExpandedFormPanelOpen.value) return false // When ?colId points at a SmartText column the SmartText panel claims // the URL — expanded record dialog stays closed. const colId = routeQuery.value.colId @@ -243,7 +273,11 @@ const expandedFormOnRowIdDlg = computed({ return true }, set(val) { - if (!val) + if (!val) { + // Close panel if it's open + if (isExpandedFormPanelOpen.value) { + expandedFormPanelStore.closePanel() + } router.push({ query: { ...routeQuery.value, @@ -251,9 +285,20 @@ const expandedFormOnRowIdDlg = computed({ rowId: undefined, }, }) + } }, }) +// Close panel when route rowId is cleared (e.g. browser back) +watch( + () => routeQuery.value.rowId, + (newRowId) => { + if (!newRowId && isExpandedFormPanelOpen.value) { + expandedFormPanelStore.closePanel() + } + }, +) + const addRowExpandOnClose = (row: Row) => { if (!skipRowRemovalOnCancel.value) { eventBus.emit(SmartsheetStoreEvents.CLEAR_NEW_ROW, row) @@ -306,6 +351,31 @@ const updateViewWidth = () => { const isInfiniteScrollingEnabled = computed(() => isFeatureEnabled(FEATURE_FLAG.INFINITE_SCROLLING)) +// Wire row navigator for the expanded form panel +expandedFormPanelRowNavigator.value = { + getRow: (index: number) => { + if (isInfiniteScrollingEnabled.value) { + const row = cachedRows.value.get(index) + if (!row) return null + const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[]) + if (!rowId) return null + return { rowId, row } + } else { + const row = pData.value[index] + if (!row) return null + const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[]) + if (!rowId) return null + return { rowId, row } + } + }, + totalRows: () => { + if (isInfiniteScrollingEnabled.value) { + return totalRows.value ?? 0 + } + return pData.value.length + }, +} + const isCanvasTableEnabled = computed(() => !ncIsPlaywright()) const isCanvasGroupByTableEnabled = computed( @@ -622,6 +692,9 @@ watch([() => view.value?.id, () => meta.value?.columns], async () => { + + + diff --git a/packages/nc-gui/composables/useExpandedFormPanel.ts b/packages/nc-gui/composables/useExpandedFormPanel.ts new file mode 100644 index 0000000000..4e0ba16324 --- /dev/null +++ b/packages/nc-gui/composables/useExpandedFormPanel.ts @@ -0,0 +1,57 @@ +/** + * CE stub for expanded form panel composable. + * EE override at ee/composables/useExpandedFormPanel.ts provides the real implementation. + */ + +const [useProvideExpandedFormPanel, useExpandedFormPanel] = useInjectionState(() => { + const isOpen = ref(false) + const activeRowId = ref(null) + const activeRowIndex = ref(null) + const isFullscreen = ref(false) + const panelWidth = ref(420) + const isLoading = ref(false) + const activityExpanded = ref(false) + const activeActivityTab = ref<'comments' | 'audits'>('comments') + + const hasPrev = computed(() => false) + const hasNext = computed(() => false) + const activeDisplayValue = computed(() => null) + + const rowNavigator = ref(null) + + const openPanel = (_row: Row, _rowIndex?: number, _state?: Record) => {} + const closePanel = () => {} + const setFullscreen = (_val: boolean) => {} + const navigatePrev = () => {} + const navigateNext = () => {} + const toggleActivity = (_tab?: 'comments' | 'audits') => {} + + return { + isOpen, + activeRowId, + activeRowIndex, + isFullscreen, + panelWidth, + isLoading, + activityExpanded, + activeActivityTab, + hasPrev, + hasNext, + activeDisplayValue, + rowNavigator, + openPanel, + closePanel, + setFullscreen, + navigatePrev, + navigateNext, + toggleActivity, + } +}, 'expanded-form-panel-store') + +export { useProvideExpandedFormPanel, useExpandedFormPanel } + +export function useExpandedFormPanelOrThrow() { + const store = useExpandedFormPanel() + if (!store) throw new Error('useExpandedFormPanel must be used within a provider') + return store +}