-
-
Notifications
You must be signed in to change notification settings - Fork 181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UI for bulk deleting projects #4404
Changes from all commits
9589fc4
22b662c
2414e11
139b479
3e870f6
bfec2f3
8f15732
5cdb441
2fcce88
ec542a1
dae49fd
4bf22f9
cf8826a
4b7bad2
74bc8ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
@use 'scss/sizes'; | ||
|
||
.promptContent { | ||
display: flex; | ||
flex-direction: column; | ||
gap: sizes.$x20; | ||
|
||
:global p { | ||
margin: 0; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import React, {useState} from 'react'; | ||
import {fetchPost} from 'js/api'; | ||
import {handleApiFail, notify} from 'js/utils'; | ||
import KoboPrompt from 'js/components/modals/koboPrompt'; | ||
import Checkbox from 'js/components/common/checkbox'; | ||
import styles from './bulkDeletePrompt.module.scss'; | ||
import customViewStore from 'js/projects/customViewStore'; | ||
import {searches} from 'js/searches'; | ||
|
||
type AssetsBulkAction = 'archive' | 'delete' | 'unarchive'; | ||
interface AssetsBulkResponse { | ||
detail: string; | ||
} | ||
|
||
interface BulkDeletePromptProps { | ||
assetUids: string[]; | ||
/** Being used by the parent component to close the prompt. */ | ||
onRequestClose: () => void; | ||
} | ||
|
||
export default function BulkDeletePrompt(props: BulkDeletePromptProps) { | ||
const [isDataChecked, setIsDataChecked] = useState(false); | ||
const [isFormChecked, setIsFormChecked] = useState(false); | ||
const [isRecoverChecked, setIsRecoverChecked] = useState(false); | ||
const [isConfirmDeletePending, setIsConfirmDeletePending] = useState(false); | ||
|
||
function onConfirmDelete() { | ||
setIsConfirmDeletePending(true); | ||
|
||
const payload: {asset_uids: string[]; action: AssetsBulkAction} = { | ||
asset_uids: props.assetUids, | ||
action: 'delete', | ||
}; | ||
|
||
fetchPost<AssetsBulkResponse>('/api/v2/assets/bulk/', {payload: payload}) | ||
.then((response) => { | ||
props.onRequestClose(); | ||
customViewStore.handleAssetsDeleted(props.assetUids); | ||
|
||
// Temporarily we do this hacky thing to update the sidebar list of | ||
// projects. After the Bookmarked Projects feature is done (see the | ||
// https://github.com/kobotoolbox/kpi/issues/4220 for history of | ||
// discussion and more details) we would remove this code. | ||
searches.forceRefreshFormsList(); | ||
|
||
notify(response.detail); | ||
}) | ||
.catch(handleApiFail); | ||
} | ||
|
||
return ( | ||
<KoboPrompt | ||
// This is always open, because parent is conditionally rendering this component | ||
isOpen | ||
onRequestClose={props.onRequestClose} | ||
title={t('Delete ##count## projects').replace( | ||
'##count##', | ||
String(props.assetUids.length) | ||
)} | ||
buttons={[ | ||
{ | ||
type: 'frame', | ||
color: 'storm', | ||
label: 'Cancel', | ||
onClick: props.onRequestClose, | ||
isDisabled: isConfirmDeletePending, | ||
}, | ||
{ | ||
type: 'full', | ||
color: 'red', | ||
label: 'Delete', | ||
onClick: onConfirmDelete, | ||
isDisabled: !isDataChecked || !isFormChecked || !isRecoverChecked, | ||
isPending: isConfirmDeletePending, | ||
}, | ||
]} | ||
> | ||
<div className={styles.promptContent}> | ||
<p> | ||
{t('You are about to permanently delete ##count## projects').replace( | ||
'##count##', | ||
String(props.assetUids.length) | ||
)} | ||
</p> | ||
|
||
<Checkbox | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not at all blocking, since it's an upstream issue, but the Checkbox component should probably have a focus state. When tab-navigating, you can't tell which the checkbox is the currently selected element. Would be a small accessibility improvement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed a small commit that adds focus styles for these :) |
||
checked={isDataChecked} | ||
onChange={setIsDataChecked} | ||
label={t('All data gathered for these projects will be deleted')} | ||
/> | ||
|
||
<Checkbox | ||
checked={isFormChecked} | ||
onChange={setIsFormChecked} | ||
label={t('Forms associated with these projects will be deleted')} | ||
/> | ||
|
||
<strong> | ||
<Checkbox | ||
checked={isRecoverChecked} | ||
onChange={setIsRecoverChecked} | ||
label={t( | ||
'I understand that if I delete these projects I will not be able to recover them' | ||
)} | ||
/> | ||
</strong> | ||
</div> | ||
</KoboPrompt> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React, {useState} from 'react'; | ||
import type {AssetResponse, ProjectViewAsset} from 'js/dataInterface'; | ||
import Button from 'js/components/common/button'; | ||
import actionsStyles from './projectActions.module.scss'; | ||
import BulkDeletePrompt from './bulkActions/bulkDeletePrompt'; | ||
|
||
interface ProjectBulkActionsProps { | ||
/** A list of selected assets for bulk operations. */ | ||
assets: Array<AssetResponse | ProjectViewAsset>; | ||
} | ||
|
||
export default function ProjectBulkActions(props: ProjectBulkActionsProps) { | ||
const [isDeletePromptOpen, setIsDeletePromptOpen] = useState(false); | ||
|
||
return ( | ||
<div className={actionsStyles.root}> | ||
<Button | ||
type='bare' | ||
color='storm' | ||
size='s' | ||
startIcon='trash' | ||
tooltip={t('Delete ##count## projects').replace( | ||
'##count##', | ||
String(props.assets.length) | ||
)} | ||
onClick={() => setIsDeletePromptOpen(true)} | ||
classNames={['right-tooltip']} | ||
/> | ||
|
||
{isDeletePromptOpen && ( | ||
<BulkDeletePrompt | ||
assetUids={props.assets.map((asset) => asset.uid)} | ||
onRequestClose={() => setIsDeletePromptOpen(false)} | ||
/> | ||
)} | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export namespace searches { | ||
const forceRefreshFormsList: () => void; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahhhh,
fetch()
error handling 😁