Enhance Dashboard Components with Loading State and Styling Improvements

- Updated ListViewEditButtons to disable actions during initial loading, improving user experience.
- Refactored ListViewTabs to adjust icon styles based on loading state and removed unnecessary imports for cleaner code.
- Modified ObjectListViewContext to use viewId instead of view reference for better clarity and consistency.
- Updated TableStateContext to reflect changes in view parameter naming, enhancing code maintainability.
- Adjusted App.css for improved styling of segmented navigation items and loading indicators.
This commit is contained in:
Tom Butcher 2026-09-02 21:26:39 +01:00
parent 96b809b9d8
commit 73de7ac8ee
6 changed files with 82 additions and 76 deletions

View File

@ -2647,6 +2647,7 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
.segmented-nav-item-icon { .segmented-nav-item-icon {
padding-inline: 8px; padding-inline: 8px;
min-width: 32px;
} }
.segmented-nav-item-icon-contents { .segmented-nav-item-icon-contents {
@ -2667,7 +2668,12 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
.segmented-nav-item-selected, .segmented-nav-item-selected,
.segmented-nav-item:not(.segmented-nav-item-disabled):hover { .segmented-nav-item:not(.segmented-nav-item-disabled):hover {
background: var(--color-button-background); background: var(--color-button-background);
border: 1px solid var(--color-button-border); border: 1px solid
color-mix(
in srgb,
var(--color-button-border) 40%,
var(--color-button-background)
);
} }
.segmented-nav-item-disabled { .segmented-nav-item-disabled {

View File

@ -4,12 +4,14 @@ import EditButtons from './EditButtons'
import { useObjectListView } from '../context/ObjectListViewContext' import { useObjectListView } from '../context/ObjectListViewContext'
const ListViewEditButtons = ({ showStartingDivider = false }) => { const ListViewEditButtons = ({ showStartingDivider = false }) => {
const { isEditing, saving, saveEdits, cancelEdits, startEditing } = const {
useObjectListView() isEditing,
initialLoading,
if (!isEditing) { saving,
return null saveEdits,
} cancelEdits,
startEditing
} = useObjectListView()
return ( return (
<Flex align='center' gap='small'> <Flex align='center' gap='small'>
@ -24,6 +26,7 @@ const ListViewEditButtons = ({ showStartingDivider = false }) => {
cancelEditing={cancelEdits} cancelEditing={cancelEdits}
startEditing={startEditing} startEditing={startEditing}
formValid={true} formValid={true}
disabled={initialLoading}
loading={saving} loading={saving}
requirePermission={false} requirePermission={false}
/> />

View File

@ -7,7 +7,6 @@ import {
Button, Button,
Flex, Flex,
Typography, Typography,
Spin,
Divider, Divider,
theme theme
} from 'antd' } from 'antd'
@ -18,7 +17,6 @@ import { getModelByName } from '../../../database/ObjectModels'
import { useObjectListView } from '../context/ObjectListViewContext' import { useObjectListView } from '../context/ObjectListViewContext'
import classNames from 'classnames' import classNames from 'classnames'
import PlusIcon from '../../Icons/PlusIcon' import PlusIcon from '../../Icons/PlusIcon'
import EditIcon from '../../Icons/EditIcon'
import BinIcon from '../../Icons/BinIcon' import BinIcon from '../../Icons/BinIcon'
import ScrollBox from './ScrollBox' import ScrollBox from './ScrollBox'
import ColorSelector from './ColorSelector' import ColorSelector from './ColorSelector'
@ -154,14 +152,16 @@ const ViewTabLabel = ({
className='list-view-tabs-label list-view-tabs-label-editing' className='list-view-tabs-label list-view-tabs-label-editing'
onFocusCapture={() => onSelect?.()} onFocusCapture={() => onSelect?.()}
> >
<ColorSelector <div style={{ marginRight: 2 }}>
size='small' <ColorSelector
showText={false} size='small'
required showText={false}
value={view.color || '#3498DB'} required
onChange={(color) => onDraftChange({ color })} value={view.color || '#3498DB'}
onClick={(event) => event.stopPropagation()} onChange={(color) => onDraftChange({ color })}
/> onClick={(event) => event.stopPropagation()}
/>
</div>
<ViewTabNameInput <ViewTabNameInput
value={view.name} value={view.name}
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
@ -196,7 +196,6 @@ const ListViewTabs = () => {
activeTabKey, activeTabKey,
initialLoading, initialLoading,
isEditing, isEditing,
startEditing,
selectTab, selectTab,
handleTabEdit, handleTabEdit,
updateDraftView, updateDraftView,
@ -207,10 +206,19 @@ const ListViewTabs = () => {
const model = getModelByName(objectType) const model = getModelByName(objectType)
const options = useMemo(() => { const options = useMemo(() => {
const Icon = model?.icon const Icon = initialLoading ? LoadingOutlined : model?.icon
const styles = initialLoading
? { fontSize: 12, color: 'var(--color-button-border)' }
: { fontSize: 14 }
const allOption = { const allOption = {
value: ALL_TAB_KEY, value: ALL_TAB_KEY,
icon: Icon ? <Icon style={{ fontSize: 14 }} /> : null icon: Icon ? (
<Icon
style={styles}
color={'secondary'}
spin={initialLoading || undefined}
/>
) : null
} }
const viewOptions = views.map((view) => ({ const viewOptions = views.map((view) => ({
@ -242,6 +250,7 @@ const ListViewTabs = () => {
ALL_TAB_KEY, ALL_TAB_KEY,
activeTabKey, activeTabKey,
handleTabEdit, handleTabEdit,
initialLoading,
isEditing, isEditing,
model?.icon, model?.icon,
selectTab, selectTab,
@ -256,51 +265,40 @@ const ListViewTabs = () => {
style={{ minHeight: 0, width: '100%', minWidth: 0 }} style={{ minHeight: 0, width: '100%', minWidth: 0 }}
> >
<Divider type='vertical' style={{ margin: '0 4px', height: '18px' }} /> <Divider type='vertical' style={{ margin: '0 4px', height: '18px' }} />
<Spin
spinning={initialLoading === true} <Flex
wrapperClassName='list-view-tabs-spin' align='center'
indicator={<LoadingOutlined spin />} gap={4}
className={classNames('list-view-tabs', {
'list-view-tabs-editing': isEditing
})}
style={{ flex: 1, minWidth: 0 }}
> >
<Flex <ScrollBox className='list-view-tabs-segmented-wrap' horizontal>
align='center' <Flex
gap={4} align='center'
className={classNames('list-view-tabs', { gap={8}
'list-view-tabs-editing': isEditing className='list-view-tabs-segmented-inner'
})} >
style={{ flex: 1, minWidth: 0 }} <SegmentedNav
> className='list-view-tabs-segmented'
<ScrollBox className='list-view-tabs-segmented-wrap' horizontal> value={activeTabKey}
<Flex options={options}
align='center' onChange={selectTab}
gap={8} onReorder={isEditing ? reorderDraftViews : undefined}
className='list-view-tabs-segmented-inner' />
> {isEditing && (
<SegmentedNav
className='list-view-tabs-segmented'
value={activeTabKey}
options={options}
onChange={selectTab}
onReorder={isEditing ? reorderDraftViews : undefined}
/>
<Button <Button
type='text' type='text'
size='small' size='small'
className='list-view-tabs-add-button' className='list-view-tabs-add-button'
onClick={ onClick={() => handleTabEdit(null, 'add')}
isEditing ? () => handleTabEdit(null, 'add') : startEditing icon={<PlusIcon style={{ fontSize: 14, marginTop: 3 }} />}
}
icon={
isEditing ? (
<PlusIcon style={{ fontSize: 14, marginTop: 3 }} />
) : (
<EditIcon style={{ fontSize: 12, marginBottom: 5 }} />
)
}
/> />
</Flex> )}
</ScrollBox> </Flex>
</Flex> </ScrollBox>
</Spin> </Flex>
</Flex> </Flex>
) )
} }

View File

@ -37,15 +37,12 @@ const DEFAULT_VIEW = {
// eslint-disable-next-line react-refresh/only-export-components // eslint-disable-next-line react-refresh/only-export-components
export const getObjectViewKey = (view) => export const getObjectViewKey = (view) =>
view?._reference || view?._id || null view?._id != null ? String(view._id) : null
const resolveViewFromRef = (views, viewRef) => { const resolveViewFromId = (views, viewId) => {
if (!viewRef) return null if (!viewId) return null
return ( return (
views.find( views.find((view) => String(view._id) === String(viewId)) || null
(view) =>
view._reference === viewRef || String(view._id) === String(viewRef)
) || null
) )
} }
@ -206,15 +203,15 @@ export const ObjectListViewProvider = ({
useEffect(() => { useEffect(() => {
if (initialLoading || urlSyncedRef.current) return if (initialLoading || urlSyncedRef.current) return
const viewRef = getViewFromUrl() const viewId = getViewFromUrl()
if (!viewRef) { if (!viewId) {
urlSyncedRef.current = true urlSyncedRef.current = true
return return
} }
const matched = const matched =
resolveViewFromRef(views, viewRef) || resolveViewFromId(views, viewId) ||
resolveViewFromRef(draftViews, viewRef) resolveViewFromId(draftViews, viewId)
if (matched) { if (matched) {
setActiveTabKey(String(matched._id)) setActiveTabKey(String(matched._id))
@ -222,7 +219,7 @@ export const ObjectListViewProvider = ({
return return
} }
// Wait until views have loaded before deciding the URL reference is invalid // Wait until views have loaded before deciding the URL viewId is invalid
if (views.length === 0) return if (views.length === 0) return
if (!isEditing) { if (!isEditing) {

View File

@ -12,7 +12,7 @@ const TableStateContext = createContext()
export const FILTER_URL_PARAM = 'filter' export const FILTER_URL_PARAM = 'filter'
export const SORT_URL_PARAM = 'sort' export const SORT_URL_PARAM = 'sort'
export const VIEW_URL_PARAM = 'view' export const VIEW_URL_PARAM = 'viewId'
const getSessionFilterKey = (scope) => `tableState:${scope}:filter` const getSessionFilterKey = (scope) => `tableState:${scope}:filter`
const getSessionSortKey = (scope) => `tableState:${scope}:sort` const getSessionSortKey = (scope) => `tableState:${scope}:sort`
@ -137,10 +137,12 @@ export const TableStateProvider = ({ children }) => {
) )
const persistView = useCallback( const persistView = useCallback(
(viewRef) => { (viewId) => {
const next = new URLSearchParams(searchParams) const next = new URLSearchParams(searchParams)
if (viewRef) { // Drop legacy `view` param that previously stored `_reference`
next.set(VIEW_URL_PARAM, viewRef) next.delete('view')
if (viewId) {
next.set(VIEW_URL_PARAM, String(viewId))
} else { } else {
next.delete(VIEW_URL_PARAM) next.delete(VIEW_URL_PARAM)
} }

View File

@ -182,7 +182,7 @@ export const ThemeProvider = ({ children }) => {
) )
root.style.setProperty( root.style.setProperty(
'--color-list-view-tabs-border', '--color-list-view-tabs-border',
isDarkMode ? '#2a2a2a' : '#E7E7E7' isDarkMode ? '#1f1f1f' : '#E7E7E7'
) )
root.style.setProperty( root.style.setProperty(
'--color-button-background', '--color-button-background',