/**
* External dependencies
*/
import { JetpackLogo } from '@automattic/jetpack-components';
import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis
import { DataViews } from '@wordpress/dataviews/wp';
import { dateI18n, getSettings as getDateSettings } from '@wordpress/date';
import { useCallback, useEffect, useMemo, useState } from '@wordpress/element';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useNavigate } from 'react-router';
/**
* Internal dependencies
*/
import useConfigValue from '../../hooks/use-config-value.ts';
import CreateFormButton from '../components/create-form-button/index.tsx';
import DataViewsHeaderRow from '../components/dataviews-header-row/index.tsx';
import { EmptyWrapper } from '../components/empty-responses/index.tsx';
import Page from '../components/page/index.tsx';
import useDeleteForm from '../hooks/use-delete-form.ts';
import useFormsData from '../hooks/use-forms-data.ts';
import { defaultLayouts, useView } from './views.ts';
import './style.scss';
import type { FormListItem } from '../hooks/use-forms-data.ts';
import type { Action, Operator } from '@wordpress/dataviews/wp';
/**
* Forms dashboard "Forms" route.
*
* @return {JSX.Element|null} The Forms list page, or null when redirecting.
*/
export default function FormsDashboardForms(): JSX.Element | null {
const navigate = useNavigate();
const isCentralFormManagementEnabled = useConfigValue( 'isCentralFormManagementEnabled' );
const isCentralFormManagementDisabled = isCentralFormManagementEnabled === false;
const dateSettings = getDateSettings();
const [ view, setView ] = useView();
const statusQuery = useMemo( () => {
const statusFilterValue = view.filters?.find( filter => filter.field === 'status' )?.value;
// Default: show all non-trash forms (matches WP core list behavior).
const nonTrashStatuses = 'publish,draft,pending,future,private';
if ( ! statusFilterValue ) {
return nonTrashStatuses;
}
if ( statusFilterValue === 'all' ) {
return nonTrashStatuses;
}
return statusFilterValue;
}, [ view.filters ] );
const isViewingTrash = useMemo( () => {
const statusFilterValue = view.filters?.find( filter => filter.field === 'status' )?.value;
return statusFilterValue === 'trash';
}, [ view.filters ] );
const { records, isLoading, totalItems, totalPages } = useFormsData(
view.page,
view.perPage,
view.search,
statusQuery
);
const {
isDeleting,
trashForms,
restoreForms,
isPermanentDeleteConfirmOpen,
openPermanentDeleteConfirm,
closePermanentDeleteConfirm,
confirmPermanentDelete,
} = useDeleteForm( {
view,
setView,
recordsLength: records?.length ?? 0,
statusQuery,
} );
const [ selection, setSelection ] = useState< string[] >( [] );
const [ pendingPermanentDeleteCount, setPendingPermanentDeleteCount ] = useState( 0 );
useEffect( () => {
if ( isCentralFormManagementDisabled ) {
navigate( '/responses', { replace: true } );
}
}, [ isCentralFormManagementDisabled, navigate ] );
// Selection is local (non-URL) state. Clear selection whenever the view changes (page/perPage/search/filters).
useEffect( () => {
setSelection( [] );
}, [ view.page, view.perPage, view.search, view.filters ] );
const onOpenPermanentDeleteConfirm = useCallback(
( items: FormListItem[] ) => {
setPendingPermanentDeleteCount( items?.length ?? 0 );
openPermanentDeleteConfirm( items );
},
[ openPermanentDeleteConfirm ]
);
const onClosePermanentDeleteConfirm = useCallback( () => {
setPendingPermanentDeleteCount( 0 );
closePermanentDeleteConfirm();
}, [ closePermanentDeleteConfirm ] );
const onConfirmPermanentDelete = useCallback( async () => {
setPendingPermanentDeleteCount( 0 );
try {
await confirmPermanentDelete();
} finally {
setSelection( [] );
}
}, [ confirmPermanentDelete ] );
const statusLabel = useCallback( ( status: string ) => {
switch ( status ) {
case 'publish':
return __( 'Published', 'jetpack-forms' );
case 'draft':
return __( 'Draft', 'jetpack-forms' );
case 'pending':
return __( 'Pending review', 'jetpack-forms' );
case 'future':
return __( 'Scheduled', 'jetpack-forms' );
case 'private':
return __( 'Private', 'jetpack-forms' );
default:
return status;
}
}, [] );
const fields = useMemo(
() => [
{
id: 'title',
label: __( 'Form name', 'jetpack-forms' ),
getValue: ( { item }: { item: FormListItem } ) => item.title,
render: ( { item }: { item: FormListItem } ) =>
item.title || __( '(no title)', 'jetpack-forms' ),
enableSorting: false,
enableHiding: false,
},
{
id: 'entries',
label: __( 'Entries', 'jetpack-forms' ),
getValue: ( { item }: { item: FormListItem } ) => item.entriesCount ?? 0,
render: ( { item }: { item: FormListItem } ) => item.entriesCount ?? 0,
enableSorting: false,
},
{
id: 'status',
label: __( 'Status', 'jetpack-forms' ),
getValue: ( { item }: { item: FormListItem } ) => item.status,
render: ( { item }: { item: FormListItem } ) => statusLabel( item.status ),
elements: [
{ label: __( 'All', 'jetpack-forms' ), value: 'all' },
{ label: __( 'Published', 'jetpack-forms' ), value: 'publish' },
{ label: __( 'Draft', 'jetpack-forms' ), value: 'draft' },
{ label: __( 'Pending review', 'jetpack-forms' ), value: 'pending' },
{ label: __( 'Scheduled', 'jetpack-forms' ), value: 'future' },
{ label: __( 'Private', 'jetpack-forms' ), value: 'private' },
{ label: __( 'Trash', 'jetpack-forms' ), value: 'trash' },
],
// Mark as primary so the filter UI (and its pill) is visible by default on load.
// DataViews expects `operators` to be typed as a known operator union; keep this narrowly typed.
filterBy: { operators: [ 'is' ] as Operator[], isPrimary: true },
enableSorting: false,
},
{
id: 'modified',
label: __( 'Last updated', 'jetpack-forms' ),
type: 'date' as const,
render: ( { item }: { item: FormListItem } ) =>
dateI18n( dateSettings.formats.datetime, item.modified ),
enableSorting: false,
},
],
[ dateSettings.formats.datetime, statusLabel ]
);
const actions = useMemo( () => {
const actionsList: Action< FormListItem >[] = [
{
id: 'view-responses',
isPrimary: false,
label: __( 'View responses', 'jetpack-forms' ),
supportsBulk: false,
callback( items: FormListItem[] ) {
const [ item ] = items;
if ( ! item ) {
return;
}
navigate( `/forms/${ item.id }/responses` );
},
},
{
id: 'edit-form',
isPrimary: false,
label: __( 'Edit', 'jetpack-forms' ),
supportsBulk: false,
async callback( items: FormListItem[] ) {
const [ item ] = items;
if ( ! item ) {
return;
}
const fallbackEditUrl = `post.php?post=${ item.id }&action=edit&post_type=jetpack_form`;
const editUrl = item.editUrl || fallbackEditUrl;
const url = new URL( editUrl, window.location.origin );
window.location.href = url.toString();
},
},
];
if ( isViewingTrash ) {
actionsList.push( {
id: 'restore-form',
isPrimary: false,
label: __( 'Restore', 'jetpack-forms' ),
supportsBulk: true,
async callback( items: FormListItem[] ) {
if ( isDeleting ) {
return;
}
try {
await restoreForms( items );
} finally {
setSelection( [] );
}
},
} );
actionsList.push( {
id: 'delete-form-permanently',
isPrimary: false,
label: __( 'Delete permanently', 'jetpack-forms' ),
supportsBulk: true,
async callback( items: FormListItem[] ) {
if ( isDeleting ) {
return;
}
if ( ! items?.length ) {
return;
}
onOpenPermanentDeleteConfirm( items );
},
} );
return actionsList;
}
actionsList.push( {
id: 'trash-form',
isPrimary: false,
label: __( 'Trash', 'jetpack-forms' ),
supportsBulk: true,
async callback( items: FormListItem[] ) {
if ( isDeleting ) {
return;
}
try {
await trashForms( items );
} finally {
setSelection( [] );
}
},
} );
return actionsList;
}, [
isDeleting,
isViewingTrash,
navigate,
onOpenPermanentDeleteConfirm,
restoreForms,
trashForms,
] );
const paginationInfo = useMemo(
() => ( {
totalItems: totalItems ?? 0,
totalPages: totalPages ?? 0,
} ),
[ totalItems, totalPages ]
);
const onChangeView = useCallback( newView => setView( newView ), [ setView ] );
const headerActions = useMemo( () => [
{ pendingPermanentDeleteCount === 1 ? __( 'This will permanently delete this form. This action cannot be undone.', 'jetpack-forms' ) : sprintf( /* translators: %d: number of forms */ _n( 'This will permanently delete %d form. This action cannot be undone.', 'This will permanently delete %d forms. This action cannot be undone.', pendingPermanentDeleteCount, 'jetpack-forms' ), pendingPermanentDeleteCount ) }