Skip to content

Commit

Permalink
[JN-616] Consistent page styling (#602)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewBemis authored Oct 26, 2023
1 parent 03c8073 commit 13cc7a0
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 185 deletions.
10 changes: 6 additions & 4 deletions ui-admin/src/components/forms/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ export type TextInputProps = Omit<JSX.IntrinsicElements['input'], 'onChange'> &
infoContent?: React.ReactNode,
description?: string
required?: boolean
label: string
label?: string
labelClassname?: string,
placeholder?: string,
onChange?: (value: string) => void
}

/** A text input with label and description. */
export const TextInput = (props: TextInputProps) => {
const { infoContent, description, required, label, labelClassname, ...inputProps } = props
const { infoContent, description, required, label, labelClassname, placeholder, ...inputProps } = props
const { className, disabled, id, value, onChange } = inputProps

const generatedId = useId()
Expand All @@ -22,13 +23,13 @@ export const TextInput = (props: TextInputProps) => {

return (
<>
<label
{label && <label
className={classNames('form-label', labelClassname)}
htmlFor={inputId}
>
{label}
{required && <span className="text-danger">*</span>}
</label>
</label>}
{infoContent &&
//@ts-ignore
<InfoPopup content={infoContent}/>}
Expand All @@ -39,6 +40,7 @@ export const TextInput = (props: TextInputProps) => {
aria-disabled={disabled}
className={classNames('form-control', { disabled }, className, { 'is-invalid': required && value === '' })}
disabled={undefined}
placeholder={placeholder}
id={inputId}
// Allow value to be undefined without triggering a React warning about uncontrolled input.
value={value ?? ''}
Expand Down
40 changes: 23 additions & 17 deletions ui-admin/src/portal/MailingListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import {
SortingState,
useReactTable
} from '@tanstack/react-table'
import { basicTableLayout, IndeterminateCheckbox } from 'util/tableUtils'
import { basicTableLayout, IndeterminateCheckbox, renderEmptyMessage, RowVisibilityCount } from 'util/tableUtils'
import { currentIsoDate, instantToDateString, instantToDefaultString } from 'util/timeUtils'
import { Button } from 'components/forms/Button'
import { escapeCsvValue, saveBlobAsDownload } from 'util/downloadUtils'
import { failureNotification, successNotification } from '../util/notifications'
import { Store } from 'react-notifications-component'
import Modal from 'react-bootstrap/Modal'
import { useLoadingEffect } from '../api/api-utils'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faDownload, faTrash } from '@fortawesome/free-solid-svg-icons'
import { renderPageHeader } from 'util/pageUtils'


/** show the mailing list in table */
Expand Down Expand Up @@ -108,26 +111,29 @@ export default function MailingListView({ portalContext, portalEnv }:
setShowDeleteConfirm(false)
}

return <div className="container p-3">
<h1 className="h4">Mailing list </h1>
return <div className="container-fluid px-4 py-2">
{ renderPageHeader('Mailing List') }
<LoadingSpinner isLoading={isLoading}>
<div className="d-flex align-items-center">
<div>
{numSelected} of {table.getPreFilteredRowModel().rows.length} selected
<div className="d-flex align-items-center justify-content-between">
<div className="d-flex">
<RowVisibilityCount table={table}/>
</div>
<div className="d-flex">
<Button onClick={download}
variant="light" className="border m-1" disabled={!numSelected}
tooltip={numSelected ? 'Download selected contacts' : 'You must select contacts to download'}>
<FontAwesomeIcon icon={faDownload} className="fa-lg"/> Download
</Button>
<Button onClick={() => setShowDeleteConfirm(!showDeleteConfirm)}
variant="light" className="border m-1" disabled={!numSelected}
tooltip={numSelected ? 'Remove selected contacts' : 'You must select contacts to remove'}>
<FontAwesomeIcon icon={faTrash} className="fa-lg"/> Remove
</Button>
</div>
<Button onClick={download}
variant="secondary" disabled={!numSelected}
tooltip={numSelected ? 'Download selected contacts' : 'you must select contacts to download'}>
Download
</Button>
<Button onClick={() => setShowDeleteConfirm(!showDeleteConfirm)}
variant="secondary" disabled={!numSelected} className="ms-auto"
tooltip={numSelected ? 'Remove selected contacts' : 'you must select contacts to remove'}>
Remove
</Button>
</div>

{basicTableLayout(table)}
{ basicTableLayout(table) }
{ renderEmptyMessage(contacts, 'No contacts') }
{ showDeleteConfirm && <Modal show={true} onHide={() => setShowDeleteConfirm(false)}>
<Modal.Body>
<div>Do you want to delete the <strong>{ numSelected }</strong> selected entries?</div>
Expand Down
5 changes: 3 additions & 2 deletions ui-admin/src/portal/PortalEnvConfigView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ const PortalEnvConfigView = ({ portalContext, portalEnv }: PortalEnvConfigViewPr
reloadPortal(portal.shortcode)
}, { setIsLoading })
}
return <form className="bg-white p-3">
<h2 className="h4">Website configuration ({portalContext.portal.name})</h2>

return <form className="bg-white">
<h4>Website configuration ({portalContext.portal.name})</h4>
<p>Configure the accessibility of the landing page shown to all visitors, and sitewide properties</p>
<div>
<label className="form-label">
Expand Down
14 changes: 8 additions & 6 deletions ui-admin/src/study/StudyContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
import ArchiveSurveyModal from './surveys/ArchiveSurveyModal'
import DeleteSurveyModal from './surveys/DeleteSurveyModal'
import { StudyEnvironmentSurvey } from '@juniper/ui-core'
import { renderPageHeader } from 'util/pageUtils'

/** renders the main configuration page for a study environment */
function StudyContent({ studyEnvContext }: {studyEnvContext: StudyEnvContextT}) {
const { currentEnv } = studyEnvContext
const contentHeaderStyle = {
padding: '1em 0 0 1em',
paddingTop: '1em',
borderBottom: '1px solid #f6f6f6'
}

Expand All @@ -30,14 +31,15 @@ function StudyContent({ studyEnvContext }: {studyEnvContext: StudyEnvContextT})
currentEnv.configuredConsents
.sort((a, b) => a.consentOrder - b.consentOrder)

return <div className="container row">
<div className="col-12 p-3">
return <div className="container-fluid px-4 py-2">
{ renderPageHeader('Forms & Surveys') }
<div className="col-12">
{ currentEnv.studyEnvironmentConfig.initialized && <ul className="list-unstyled">
<li className="mb-3 bg-white">
<div style={contentHeaderStyle}>
<h6>Pre-enrollment questionnaire</h6>
</div>
<div className="flex-grow-1 p-3">
<div className="flex-grow-1 pt-3">
{ preEnrollSurvey && <ul className="list-unstyled"><li>
<Link to={`preEnroll/${preEnrollSurvey.stableId}?readOnly=${isReadOnlyEnv}`}>
{preEnrollSurvey.name} <span className="detail">v{preEnrollSurvey.version}</span>
Expand All @@ -49,7 +51,7 @@ function StudyContent({ studyEnvContext }: {studyEnvContext: StudyEnvContextT})
<div style={contentHeaderStyle}>
<h6>Consent forms</h6>
</div>
<div className="flex-grow-1 p-3">
<div className="flex-grow-1 pt-3">
<ul className="list-unstyled">
{ currentEnv.configuredConsents.map((config, index) => {
const consentForm = config.consentForm
Expand All @@ -66,7 +68,7 @@ function StudyContent({ studyEnvContext }: {studyEnvContext: StudyEnvContextT})
<div style={contentHeaderStyle}>
<h6>Surveys</h6>
</div>
<div className="flex-grow-1 p-3">
<div className="flex-grow-1 pt-3">
<ul className="list-unstyled">
{ currentEnv.configuredSurveys.map((surveyConfig, index) => {
const survey = surveyConfig.survey
Expand Down
7 changes: 5 additions & 2 deletions ui-admin/src/study/StudySettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ import Api from 'api/api'
import { Store } from 'react-notifications-component'
import { successNotification } from 'util/notifications'
import LoadingSpinner from 'util/LoadingSpinner'
import { renderPageHeader } from 'util/pageUtils'

/** shows settings for both a study and its containing portal */
export default function StudySettings({ studyEnvContext, portalContext }:
{studyEnvContext: StudyEnvContextT, portalContext: LoadedPortalContextT}) {
const portalEnv = portalContext.portal.portalEnvironments
.find(env =>
env.environmentName === studyEnvContext.currentEnv.environmentName) as PortalEnvironment
return <div className="ps-4">

return <div className="container-fluid px-4 py-2">
{ renderPageHeader('Site Settings') }
<StudyEnvConfigView studyEnvContext={studyEnvContext} portalContext={portalContext}/>
<PortalEnvConfigView portalEnv={portalEnv} portalContext={portalContext}/>
</div>
Expand All @@ -45,7 +48,7 @@ export function StudyEnvConfigView({ studyEnvContext, portalContext }:
}, { setIsLoading })
}

return <form className="bg-white p-3 mb-5" onSubmit={e => e.preventDefault()}>
return <form className="bg-white mb-5" onSubmit={e => e.preventDefault()}>
<h2 className="h4">{studyEnvContext.study.name} study configuration</h2>
<p>Configure whether participants can access study content, such as surveys and consents.</p>
<div>
Expand Down
17 changes: 10 additions & 7 deletions ui-admin/src/study/adminTasks/AdminTaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
SortingState,
useReactTable
} from '@tanstack/react-table'
import { basicTableLayout } from 'util/tableUtils'
import { basicTableLayout, renderEmptyMessage } from 'util/tableUtils'
import { instantToDateString, instantToDefaultString } from 'util/timeUtils'
import { paramsFromContext, StudyEnvContextT, StudyEnvParams } from '../StudyEnvironmentRouter'
import { useAdminUserContext } from 'providers/AdminUserProvider'
Expand All @@ -21,6 +21,7 @@ import { faCheck, faEdit } from '@fortawesome/free-solid-svg-icons'
import { useUser } from 'user/UserProvider'
import { IconButton } from '../../components/forms/Button'
import { AdminTaskEditModal } from './AdminTaskEditor'
import { renderPageHeader } from 'util/pageUtils'


/** show the lists of the user's tasks and all tasks */
Expand Down Expand Up @@ -92,11 +93,13 @@ export default function AdminTaskList({ studyEnvContext }: {studyEnvContext: Stu
}
}

return <div className="container p-3">
return <div className="container-fluid px-4 py-2">
{ renderPageHeader('Tasks') }
<LoadingSpinner isLoading={isLoading}>
<MyTaskList studyEnvContext={studyEnvContext} taskData={taskData}/>
<h2 className="h4 mt-5">All tasks</h2>
{basicTableLayout(allTasksTable)}
<h3 className="h4 mt-5">All tasks</h3>
{ basicTableLayout(allTasksTable) }
{ renderEmptyMessage(taskData.tasks, 'No tasks') }
</LoadingSpinner>
{ (showEditModal && selectedTask) && <AdminTaskEditModal task={selectedTask} users={users}
onDismiss={onDoneEditing} studyEnvContext={studyEnvContext}/> }
Expand Down Expand Up @@ -136,9 +139,9 @@ taskData: AdminTaskListDto}) => {
getSortedRowModel: getSortedRowModel()
})
return <>
<h2 className="h4">My tasks</h2>
{ !!myTasks.length && basicTableLayout(table)}
{ !myTasks.length && <div className="text-muted fst-italic mb-3">None</div> }
<h3 className="h4 mt-3">My tasks</h3>
{ basicTableLayout(table) }
{ renderEmptyMessage(myTasks, 'No tasks') }
</>
}

Expand Down
55 changes: 30 additions & 25 deletions ui-admin/src/study/kits/KitEnrolleeSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@ import {

import Api, { Enrollee } from 'api/api'
import { StudyEnvContextT } from 'study/StudyEnvironmentRouter'
import { basicTableLayout, checkboxColumnCell, ColumnVisibilityControl, IndeterminateCheckbox } from 'util/tableUtils'
import {
basicTableLayout,
checkboxColumnCell,
ColumnVisibilityControl,
IndeterminateCheckbox, renderEmptyMessage,
RowVisibilityCount
} from 'util/tableUtils'
import LoadingSpinner from 'util/LoadingSpinner'
import { instantToDateString } from 'util/timeUtils'
import RequestKitsModal from './RequestKitsModal'
import { useLoadingEffect } from 'api/api-utils'
import { enrolleeKitRequestPath } from '../participants/enrolleeView/EnrolleeView'
import { Button } from 'components/forms/Button'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPaperPlane } from '@fortawesome/free-solid-svg-icons'

type EnrolleeRow = Enrollee & {
taskCompletionStatus: Record<string, boolean>
Expand Down Expand Up @@ -165,30 +174,26 @@ export default function KitEnrolleeSelection({ studyEnvContext }: { studyEnvCont
getFilteredRowModel: getFilteredRowModel()
})

return <>
<LoadingSpinner isLoading={isLoading}>
<div className="d-flex align-items-center justify-content-between py-3">
<div className="d-flex align-items-center">
<span className="me-2">
{numSelected} of{' '}
{table.getPreFilteredRowModel().rows.length} selected
</span>
<span className="me-2">
<button className="btn btn-secondary"
onClick={() => { setShowRequestKitModal(true) }}
title={enableActionButtons ? 'Send sample collection kit' : 'Select at least one participant'}
aria-disabled={!enableActionButtons}>Send sample collection kit</button>
</span>
{ showRequestKitModal && <RequestKitsModal
studyEnvContext={studyEnvContext}
onDismiss={() => setShowRequestKitModal(false)}
enrolleeShortcodes={enrolleesSelected}
onSubmit={onSubmit}/> }
</div>
return <LoadingSpinner isLoading={isLoading}>
<div className="d-flex align-items-center justify-content-between">
<div className="d-flex align-items-center">
<RowVisibilityCount table={table}/>
</div>
<div className="d-flex">
<Button onClick={() => { setShowRequestKitModal(true) }}
variant="light" className="border m-1" disabled={!enableActionButtons}
tooltip={enableActionButtons ? 'Send sample collection kit' : 'Select at least one participant'}>
<FontAwesomeIcon icon={faPaperPlane} className="fa-lg"/> Send sample collection kit
</Button>
<ColumnVisibilityControl table={table}/>
{ showRequestKitModal && <RequestKitsModal
studyEnvContext={studyEnvContext}
onDismiss={() => setShowRequestKitModal(false)}
enrolleeShortcodes={enrolleesSelected}
onSubmit={onSubmit}/> }
</div>
{ basicTableLayout(table, { filterable: true }) }
{ enrollees.length === 0 && <span className="text-muted fst-italic">No participants</span> }
</LoadingSpinner>
</>
</div>
{ basicTableLayout(table, { filterable: true }) }
{ renderEmptyMessage(enrollees, 'No participants') }
</LoadingSpinner>
}
9 changes: 5 additions & 4 deletions ui-admin/src/study/kits/KitList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import Api, { KitRequest } from 'api/api'
import { StudyEnvContextT } from 'study/StudyEnvironmentRouter'
import LoadingSpinner from 'util/LoadingSpinner'
import { basicTableLayout, ColumnVisibilityControl } from 'util/tableUtils'
import { basicTableLayout, ColumnVisibilityControl, renderEmptyMessage } from 'util/tableUtils'
import { instantToDateString, isoToInstant } from 'util/timeUtils'
import { doApiLoad, useLoadingEffect } from 'api/api-utils'
import { enrolleeKitRequestPath } from '../participants/enrolleeView/EnrolleeView'
Expand Down Expand Up @@ -134,8 +134,8 @@ export default function KitList({ studyEnvContext }: { studyEnvContext: StudyEnv
}

return <LoadingSpinner isLoading={isLoading}>
<div className="container p-0 mt-2">
<div className="d-flex w-100 align-items-center" style={{ backgroundColor: '#ccc' }}>
<div className="container-fluid p-0 mt-2">
<div className="d-flex w-100 align-items-center mb-2" style={{ backgroundColor: '#ccc' }}>
{ statusTabs.map(tab => {
const kits = kitsByStatus[tab.status] || []
return <NavLink key={tab.key} to={tab.key} style={tabLinkStyle}>
Expand Down Expand Up @@ -248,6 +248,7 @@ function KitListView({ studyEnvContext, tab, kits, initialColumnVisibility }: {
<div className="d-flex align-items-center justify-content-between">
<ColumnVisibilityControl table={table}/>
</div>
{basicTableLayout(table, { filterable: true })}
{ basicTableLayout(table, { filterable: true }) }
{ renderEmptyMessage(kits, `No kits with status ${tab}`) }
</>
}
8 changes: 4 additions & 4 deletions ui-admin/src/study/kits/KitsRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { studyKitsPath } from 'portal/PortalRouter'
import { StudyEnvContextT } from 'study/StudyEnvironmentRouter'
import KitEnrolleeSelection from './KitEnrolleeSelection'
import KitList from './KitList'
import { renderPageHeader } from 'util/pageUtils'

/** Router for kit management screens. */
export default function KitsRouter({ studyEnvContext }: {studyEnvContext: StudyEnvContextT}) {
Expand All @@ -23,9 +24,9 @@ export default function KitsRouter({ studyEnvContext }: {studyEnvContext: StudyE
<NavBreadcrumb value={studyEnvContext.currentEnvPath}>
<Link to={studyKitsPath(portalShortcode, studyShortcode, environmentName)}>kits</Link>
</NavBreadcrumb>
<div className="container">
<div className="d-flex w-100" style={{ backgroundColor: '#ccc' }}>

<div className="container-fluid px-4 py-2">
{ renderPageHeader('Kits') }
<div className="d-flex w-100 mb-2" style={{ backgroundColor: '#ccc' }}>
<NavLink to="eligible" style={tabLinkStyle}>
<div className="py-3 px-5">
Eligible for kit
Expand All @@ -43,6 +44,5 @@ export default function KitsRouter({ studyEnvContext }: {studyEnvContext: StudyE
<Route path="requested/*" element={<KitList studyEnvContext={studyEnvContext}/>}/>
</Routes>
</div>

</>
}
Loading

0 comments on commit 13cc7a0

Please sign in to comment.