Skip to content

Commit

Permalink
Polish CVR import and export UIs, specifically:
Browse files Browse the repository at this point in the history
- Remove references to singular CVR files, since CVR exports now
  consist of multiple files
- Make copy and UX more consistent and concise
- Don't allow closing saving/loading modals by clicking outside
  them
- Remove uses of deprecated Prose component and non-libs/ui Loading
  component
  • Loading branch information
arsalansufi committed Oct 6, 2023
1 parent 4fefc48 commit 056ef80
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export function ConfirmRemovingFileModal({
let singleFileRemoval = true;
switch (fileType) {
case ResultsFileType.CastVoteRecord: {
fileTypeName = 'CVRs';
const fileList = castVoteRecordFilesQuery.data;
singleFileRemoval = fileList.length <= 1;
fileTypeName = 'CVR Files';
mainContent = (
<React.Fragment>
<P>
Do you want to remove the {fileList.length} loaded CVR{' '}
{pluralize('files', fileList.length)}?
{pluralize('export', fileList.length)}?
</P>
<P>All reports will be unavailable without CVR data.</P>
</React.Fragment>
Expand All @@ -56,7 +56,7 @@ export function ConfirmRemovingFileModal({
<React.Fragment>
<P>
Do you want to remove the {fileList.length} loaded CVR{' '}
{pluralize('files', fileList.length)}
{pluralize('export', fileList.length)}
{hasManualData && ' and the manually entered data'}?
</P>
<P>All reports will be unavailable without CVR data.</P>
Expand All @@ -70,7 +70,7 @@ export function ConfirmRemovingFileModal({

return (
<Modal
centerContent
title={`Remove ${fileTypeName}`}
content={mainContent}
actions={
<React.Fragment>
Expand Down
81 changes: 47 additions & 34 deletions apps/admin/frontend/src/components/import_cvrfiles_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,11 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
if (currentState.state === 'duplicate') {
return (
<Modal
title="Duplicate File"
title="Duplicate Export"
content={
<P>
The selected file was ignored as a duplicate of a previously loaded
file.
The selected export was ignored as a duplicate of a previously
loaded export.
</P>
}
onOverlayClick={onClose}
Expand All @@ -266,25 +266,31 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
}

if (currentState.state === 'success') {
const { alreadyPresent, newlyAdded } = currentState.result;
const total = alreadyPresent + newlyAdded;
const content = (() => {
if (alreadyPresent > 0) {
if (total === 1) {
return <P>The 1 CVR in the selected export was previously loaded.</P>;
}
return (
<P>
Of the {format.count(total)} total CVRs in the selected export,{' '}
{format.count(alreadyPresent)}{' '}
{alreadyPresent === 1 ? 'was' : 'were'} previously loaded.
</P>
);
}
return <P>The CVRs in the selected export were successfully loaded.</P>;
})();
return (
<Modal
title={`${format.count(
currentState.result.newlyAdded
)} new CVRs Loaded`}
content={
currentState.result.alreadyPresent > 0 && (
<P>
Of the{' '}
{format.count(
currentState.result.newlyAdded +
currentState.result.alreadyPresent
)}{' '}
total CVRs in this file,{' '}
{format.count(currentState.result.alreadyPresent)} were previously
loaded.
</P>
)
title={
newlyAdded === 1
? '1 New CVR Loaded'
: `${format.count(newlyAdded)} New CVRs Loaded`
}
content={content}
onOverlayClick={onClose}
actions={<Button onPress={onClose}>Close</Button>}
/>
Expand All @@ -294,8 +300,7 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
if (
!castVoteRecordFilesQuery.isSuccess ||
!castVoteRecordFileModeQuery.isSuccess ||
!cvrFilesOnUsbQuery.isSuccess ||
currentState.state === 'loading'
!cvrFilesOnUsbQuery.isSuccess
) {
return (
<Modal
Expand All @@ -310,6 +315,10 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
);
}

if (currentState.state === 'loading') {
return <Modal content={<Loading>Loading CVRs</Loading>} />;
}

if (
usbDriveStatus.status === 'no_drive' ||
usbDriveStatus.status === 'ejected' ||
Expand All @@ -321,8 +330,7 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
content={
<P>
<UsbImage src="/assets/usb-drive.svg" alt="Insert USB Image" />
Please insert a USB drive in order to load CVR files from the
scanner.
Please insert a USB drive in order to load CVRs from a scanner.
</P>
}
onOverlayClick={onClose}
Expand All @@ -332,8 +340,9 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
<FileInputButton
data-testid="manual-input"
onChange={processCastVoteRecordFileFromFilePicker}
accept=".json"
>
Select Files
Select Export Manually
</FileInputButton>
)}
<Button onPress={onClose}>Cancel</Button>
Expand Down Expand Up @@ -410,29 +419,33 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
if (numberOfNewFiles === 0) {
instructionalText = fileModeLocked ? (
<React.Fragment>
There were no new {headerModeText} CVR files automatically found on
this USB drive. Save CVR files to this USB drive from the scanner.
Optionally, you may manually select files to load.
No new {headerModeText.toLowerCase()} CVR exports were automatically
found on this USB drive.
</React.Fragment>
) : (
'There were no new CVR files automatically found on this USB drive. Save CVR files to this USB drive from the scanner. Optionally, you may manually select files to load.'
<React.Fragment>
No new CVR exports were automatically found on this USB drive.
</React.Fragment>
);
} else if (fileModeLocked) {
instructionalText = (
<React.Fragment>
The following {headerModeText} CVR files were automatically found on
this USB drive. Previously loaded CVR entries will be ignored.
The following {headerModeText.toLowerCase()} CVR exports were
automatically found on this USB drive.
</React.Fragment>
);
} else {
instructionalText =
'The following CVR files were automatically found on this USB drive. Previously loaded CVR entries will be ignored.';
instructionalText = (
<React.Fragment>
The following CVR exports were automatically found on this USB drive.
</React.Fragment>
);
}

return (
<Modal
modalWidth={ModalWidth.Wide}
title={`Load ${headerModeText} CVR Files`}
title={`Load ${headerModeText} CVRs`}
content={
<Content>
<P>{instructionalText}</P>
Expand Down Expand Up @@ -462,7 +475,7 @@ export function ImportCvrFilesModal({ onClose }: Props): JSX.Element | null {
onChange={processCastVoteRecordFileFromFilePicker}
accept=".json"
>
Select File Manually…
Select Export Manually…
</FileInputButton>
<Button onPress={onClose}>Cancel</Button>
</React.Fragment>
Expand Down
26 changes: 14 additions & 12 deletions apps/admin/frontend/src/screens/tally_screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function TallyScreen(): JSX.Element | null {
const fileMode = castVoteRecordFileModeQuery.data;
const fileModeText =
fileMode === 'test'
? 'Currently tallying test ballots. Once you have completed L&A testing and are ready to start tallying official ballots remove all of the loaded CVR files before loading official ballot results.'
? 'Currently tallying test ballots. Once you have completed L&A testing and are ready to tally official ballots, remove the test ballot mode CVRs.'
: fileMode === 'official'
? 'Currently tallying official ballots.'
: '';
Expand All @@ -133,13 +133,15 @@ export function TallyScreen(): JSX.Element | null {
<H2>Cast Vote Record (CVR) Management</H2>
{fileModeText && <P>{fileModeText}</P>}
{isOfficialResults && (
<Button
variant="danger"
disabled={!hasAnyFiles}
onPress={() => beginConfirmRemoveFiles(ResultsFileType.All)}
>
Clear All Tallies and Results
</Button>
<P>
<Button
disabled={!hasAnyFiles}
onPress={() => beginConfirmRemoveFiles(ResultsFileType.All)}
variant="danger"
>
Clear All Tallies and Results
</Button>
</P>
)}

<P>
Expand All @@ -148,15 +150,15 @@ export function TallyScreen(): JSX.Element | null {
disabled={isOfficialResults}
onPress={() => setIsImportCvrModalOpen(true)}
>
Load CVR Files
Load CVRs
</Button>{' '}
<Button
disabled={fileMode === 'unlocked' || isOfficialResults}
onPress={() =>
beginConfirmRemoveFiles(ResultsFileType.CastVoteRecord)
}
>
Remove CVR Files
Remove CVRs
</Button>
</P>
{hasAnyFiles ? (
Expand Down Expand Up @@ -221,7 +223,7 @@ export function TallyScreen(): JSX.Element | null {
<tr>
<TD />
<TD as="th" narrow nowrap>
Total CVRs Count
Total CVR Count
</TD>
<TD as="th" narrow data-testid="total-cvr-count">
{format.count(
Expand All @@ -238,7 +240,7 @@ export function TallyScreen(): JSX.Element | null {
</Table>
) : (
<Caption>
<Icons.Info /> No CVR files loaded.
<Icons.Info /> No CVRs loaded.
</Caption>
)}
<H2>Manually Entered Results</H2>
Expand Down
23 changes: 10 additions & 13 deletions apps/central-scan/frontend/src/components/export_results_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import styled from 'styled-components';

import {
Button,
Loading,
Modal,
P,
UsbControllerButton,
Expand All @@ -12,7 +13,6 @@ import { isElectionManagerAuth } from '@votingworks/utils';

import { assert } from '@votingworks/basics';
import { AppContext } from '../contexts/app_context';
import { Loading } from './loading';
import { exportCastVoteRecordsToUsbDrive } from '../api';

function throwBadStatus(s: never): never {
Expand Down Expand Up @@ -81,12 +81,9 @@ export function ExportResultsModal({ onClose }: Props): JSX.Element {
if (usbDriveStatus === 'ejected') {
return (
<Modal
title="CVRs Saved"
title="USB Drive Ejected"
content={
<P>
USB drive successfully ejected, you may now take it to VxAdmin for
tabulation.
</P>
<P>You may now take the USB drive to VxAdmin for tabulation.</P>
}
onOverlayClick={onClose}
actions={<Button onPress={onClose}>Close</Button>}
Expand All @@ -98,8 +95,8 @@ export function ExportResultsModal({ onClose }: Props): JSX.Element {
title="CVRs Saved"
content={
<P>
CVR file saved successfully! You may now eject the USB drive and
take it to VxAdmin for tabulation.
You may now eject the USB drive and take it to VxAdmin for
tabulation.
</P>
}
onOverlayClick={onClose}
Expand All @@ -119,7 +116,7 @@ export function ExportResultsModal({ onClose }: Props): JSX.Element {
}

if (currentState === ModalState.SAVING) {
return <Modal content={<Loading />} onOverlayClick={onClose} />;
return <Modal content={<Loading>Saving CVRs</Loading>} />;
}

if (currentState !== ModalState.INIT) {
Expand All @@ -136,10 +133,10 @@ export function ExportResultsModal({ onClose }: Props): JSX.Element {
<Modal
title="No USB Drive Detected"
content={
<P>
<React.Fragment>
<UsbImage src="/assets/usb-drive.svg" alt="Insert USB Image" />
Please insert a USB drive in order to save CVRs.
</P>
<P>Please insert a USB drive in order to save CVRs.</P>
</React.Fragment>
}
onOverlayClick={onClose}
actions={<Button onPress={onClose}>Cancel</Button>}
Expand All @@ -161,7 +158,7 @@ export function ExportResultsModal({ onClose }: Props): JSX.Element {
content={
<React.Fragment>
<UsbImage src="/assets/usb-drive.svg" alt="Insert USB Image" />
<P>A CVR file will be saved to the mounted USB drive.</P>
<P>CVRs will be saved to the mounted USB drive.</P>
</React.Fragment>
}
onOverlayClick={onClose}
Expand Down
35 changes: 0 additions & 35 deletions apps/central-scan/frontend/src/components/loading.tsx

This file was deleted.

Loading

0 comments on commit 056ef80

Please sign in to comment.