Skip to content

Commit 6f18d1e

Browse files
CGastrellenejbsimison
authored
Forms: Add response view actions (#45352)
* Add basic actions on the response view * Limit sidepanel height to match the list container's * Fix Gravatar from shrinking in a narrow space * Update modal header copy from "View response" to just "Response" --------- Co-authored-by: Enej Bajgoric <[email protected]> Co-authored-by: Mikael Korpela <[email protected]>
1 parent a6cf6bf commit 6f18d1e

File tree

5 files changed

+292
-16
lines changed

5 files changed

+292
-16
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Forms: add actions on dashboard inbox's single response view

projects/packages/forms/src/dashboard/components/gravatar/style.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.jp-forms__gravatar {
22
background-color: var(--jp-gray-5);
33
border-radius: 50%;
4+
flex-shrink: 0; // Prevent shrinking when Gravatar is inside flex containers
45
height: 48px;
56
overflow: hidden;
67
width: 48px;

projects/packages/forms/src/dashboard/inbox/dataviews/index.js

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { useView, defaultLayouts } from './views';
3737

3838
const EMPTY_ARRAY = [];
3939
const MOBILE_BREAKPOINT = 780;
40-
const getItemId = item => item.id.toString();
40+
const getItemId = item => item?.id?.toString() ?? '';
4141

4242
const formatFieldName = fieldName => {
4343
const match = fieldName.match( /^(\d+_)?(.*)/i );
@@ -207,16 +207,33 @@ export default function InboxView() {
207207
// set the sidePanelItem when we have data and selection.
208208
// We don't need to do this in `mobile`, because we don't render the side panel.
209209
if ( ! isMobile && !! data && !! selection.length ) {
210-
const firstValidSelection = selection.find( id =>
211-
data.some( record => getItemId( record ) === id )
212-
);
213-
const recordToShow = data?.find( record => getItemId( record ) === firstValidSelection );
210+
// Find the last (most recently selected) valid selection instead of the first
211+
const lastValidSelection = selection
212+
.slice()
213+
.reverse()
214+
.find( id => data.some( record => getItemId( record ) === id ) );
215+
const recordToShow = data?.find( record => getItemId( record ) === lastValidSelection );
214216
if ( ! sidePanelItem && recordToShow ) {
215217
setSidePanelItem( recordToShow );
216218
} else if ( !! sidePanelItem && ! recordToShow ) {
217219
// This case handles the case where we were having a side panel item
218220
// visible but the data have changed and the item is not there anymore.
219221
setSidePanelItem();
222+
} else if (
223+
!! sidePanelItem &&
224+
!! recordToShow &&
225+
getItemId( sidePanelItem ) === getItemId( recordToShow ) &&
226+
sidePanelItem !== recordToShow
227+
) {
228+
// Update side panel item if the data has been refreshed for the SAME item (e.g., after an action)
229+
// This ensures the side panel shows the latest version of the same entity
230+
setSidePanelItem( recordToShow );
231+
} else if (
232+
!! recordToShow &&
233+
( ! sidePanelItem || getItemId( sidePanelItem ) !== getItemId( recordToShow ) )
234+
) {
235+
// Set side panel item when selecting a different item
236+
setSidePanelItem( recordToShow );
220237
}
221238
}
222239
const paginationInfo = useMemo(
@@ -337,19 +354,30 @@ export default function InboxView() {
337354
setSidePanelItem={ setSidePanelItem }
338355
isLoadingData={ isLoadingData }
339356
isMobile={ isMobile }
357+
data={ data }
358+
onChangeSelection={ onChangeSelection }
359+
selection={ selection }
340360
/>
341361
</HStack>
342362
);
343363
}
344364

345-
const SingleResponse = ( { sidePanelItem, setSidePanelItem, isLoadingData, isMobile } ) => {
365+
const SingleResponse = ( {
366+
sidePanelItem,
367+
setSidePanelItem,
368+
isLoadingData,
369+
isMobile,
370+
data,
371+
onChangeSelection,
372+
selection,
373+
} ) => {
346374
const [ isChildModalOpen, setIsChildModalOpen ] = useState( false );
347375

348376
const onRequestClose = useCallback( () => {
349377
if ( ! isChildModalOpen ) {
350-
setSidePanelItem();
378+
onChangeSelection( [] );
351379
}
352-
}, [ setSidePanelItem, isChildModalOpen ] );
380+
}, [ onChangeSelection, isChildModalOpen ] );
353381

354382
const handleModalStateChange = useCallback(
355383
isOpen => {
@@ -358,22 +386,68 @@ const SingleResponse = ( { sidePanelItem, setSidePanelItem, isLoadingData, isMob
358386
[ setIsChildModalOpen ]
359387
);
360388

389+
const handleActionComplete = useCallback(
390+
actionedItemId => {
391+
// Remove only the actioned item from selection, keep the rest
392+
if ( actionedItemId && selection ) {
393+
const newSelection = selection.filter( id => id !== actionedItemId );
394+
onChangeSelection( newSelection );
395+
}
396+
},
397+
[ onChangeSelection, selection ]
398+
);
399+
400+
// Navigation logic
401+
const currentIndex =
402+
sidePanelItem && data
403+
? data.findIndex( item => getItemId( item ) === getItemId( sidePanelItem ) )
404+
: -1;
405+
const hasNext = currentIndex >= 0 && currentIndex < ( data?.length ?? 0 ) - 1;
406+
const hasPrevious = currentIndex > 0;
407+
408+
const handleNext = useCallback( () => {
409+
if ( hasNext && data && currentIndex >= 0 ) {
410+
const nextItem = data[ currentIndex + 1 ];
411+
if ( nextItem ) {
412+
setSidePanelItem( nextItem );
413+
onChangeSelection( [ getItemId( nextItem ) ] );
414+
}
415+
}
416+
}, [ hasNext, data, currentIndex, setSidePanelItem, onChangeSelection ] );
417+
418+
const handlePrevious = useCallback( () => {
419+
if ( hasPrevious && data && currentIndex >= 0 ) {
420+
const prevItem = data[ currentIndex - 1 ];
421+
if ( prevItem ) {
422+
setSidePanelItem( prevItem );
423+
onChangeSelection( [ getItemId( prevItem ) ] );
424+
}
425+
}
426+
}, [ hasPrevious, data, currentIndex, setSidePanelItem, onChangeSelection ] );
427+
361428
if ( ! sidePanelItem ) {
362429
return null;
363430
}
364431
const contents = (
365432
<InboxResponse
366433
response={ sidePanelItem }
367434
isLoading={ isLoadingData }
435+
isMobile={ isMobile }
368436
onModalStateChange={ handleModalStateChange }
437+
onClose={ onRequestClose }
438+
onNext={ handleNext }
439+
onPrevious={ handlePrevious }
440+
hasNext={ hasNext }
441+
hasPrevious={ hasPrevious }
442+
onActionComplete={ handleActionComplete }
369443
/>
370444
);
371445
if ( ! isMobile ) {
372446
return <div className="jp-forms__inbox__dataviews-response">{ contents }</div>;
373447
}
374448
return (
375449
<Modal
376-
title={ __( 'View response', 'jetpack-forms' ) }
450+
title={ __( 'Response', 'jetpack-forms' ) }
377451
size="medium"
378452
onRequestClose={ onRequestClose }
379453
>

0 commit comments

Comments
 (0)