Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions client/components/ManageTestQueue/index.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState } from 'react';
import styled from '@emotion/styled';
import PropTypes from 'prop-types';
import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus';
import DisclosureComponent from '../common/DisclosureComponent';
import ManageAtVersions from '@components/ManageTestQueue/ManageAtVersions';
import AddTestPlans from '@components/ManageTestQueue/AddTestPlans';
import { AtPropType, TestPlanVersionPropType } from '../common/proptypes';
import ManageRequiredReportsDisclosure from '../common/ManageRequiredReportsDisclosure';
import styled from '@emotion/styled';

export const DisclosureContainer = styled.div`
// Following directives are related to the ManageTestQueue component
Expand Down Expand Up @@ -91,7 +92,6 @@ export const DisclosureContainer = styled.div`
flex-wrap: wrap;
column-gap: 1rem;
row-gap: 0.75rem;

select {
width: inherit;
@media (max-width: 767px) {
Expand All @@ -100,6 +100,14 @@ export const DisclosureContainer = styled.div`
}
}

.disclosure-row-controls {
display: grid;
grid-auto-flow: column;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 1rem;
align-items: end;
}

.disclosure-form-label {
font-weight: bold;
font-size: 1rem;
Expand All @@ -109,23 +117,27 @@ export const DisclosureContainer = styled.div`
const ManageTestQueue = ({
ats = [],
testPlanVersions = [],
triggerUpdate = () => {}
triggerUpdate = () => {},
browsers = []
}) => {
const { loadingMessage } = useTriggerLoad();

const [showManageATs, setShowManageATs] = useState(false);
const [showAddTestPlans, setShowAddTestPlans] = useState(false);
const [showManageReqReports, setShowManageReqReports] = useState(false);

const onManageAtsClick = () => setShowManageATs(!showManageATs);
const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans);
const onManageReqReportsClick = () =>
setShowManageReqReports(!showManageReqReports);

return (
<LoadingStatus message={loadingMessage}>
<DisclosureComponent
componentId="manage-test-queue"
title={[
'Manage Assistive Technology Versions',
'Add Test Plans to the Test Queue'
'Add Test Plans to the Test Queue',
'Manage Required Reports'
]}
disclosureContainerView={[
<ManageAtVersions
Expand All @@ -138,10 +150,20 @@ const ManageTestQueue = ({
ats={ats}
testPlanVersions={testPlanVersions}
triggerUpdate={triggerUpdate}
/>,
<ManageRequiredReportsDisclosure
key="ManageRequiredReportsDisclosure"
ats={ats}
triggerUpdate={triggerUpdate}
browsers={browsers}
/>
]}
onClick={[onManageAtsClick, onAddTestPlansClick]}
expanded={[showManageATs, showAddTestPlans]}
onClick={[
onManageAtsClick,
onAddTestPlansClick,
onManageReqReportsClick
]}
expanded={[showManageATs, showAddTestPlans, showManageReqReports]}
stacked
/>
</LoadingStatus>
Expand All @@ -151,7 +173,9 @@ const ManageTestQueue = ({
ManageTestQueue.propTypes = {
ats: PropTypes.arrayOf(AtPropType).isRequired,
testPlanVersions: PropTypes.arrayOf(TestPlanVersionPropType),
triggerUpdate: PropTypes.func
triggerUpdate: PropTypes.func,
browsers: PropTypes.array,
enableManageRequiredReports: PropTypes.bool
};

export default ManageTestQueue;
50 changes: 50 additions & 0 deletions client/components/ManageTestQueue/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,53 @@ export const DELETE_AT_VERSION_MUTATION = gql`
}
}
`;

export const CREATE_REQUIRED_REPORT_MUTATION = gql`
mutation CreateRequiredReport(
$atId: ID!
$browserId: ID!
$phase: RequiredReportPhase!
) {
requiredReport(atId: $atId, browserId: $browserId, phase: $phase) {
createRequiredReport {
atId
browserId
phase
}
}
}
`;

export const UPDATE_REQUIRED_REPORT_MUTATION = gql`
mutation UpdateRequiredReport(
$atId: ID!
$browserId: ID!
$phase: RequiredReportPhase!
$updateAtId: ID!
$updateBrowserId: ID!
) {
requiredReport(atId: $atId, browserId: $browserId, phase: $phase) {
updateRequiredReport(atId: $updateAtId, browserId: $updateBrowserId) {
atId
browserId
phase
}
}
}
`;

export const DELETE_REQUIRED_REPORT_MUTATION = gql`
mutation DeleteRequiredReport(
$atId: ID!
$browserId: ID!
$phase: RequiredReportPhase!
) {
requiredReport(atId: $atId, browserId: $browserId, phase: $phase) {
deleteRequiredReport {
atId
browserId
phase
}
}
}
`;
2 changes: 2 additions & 0 deletions client/components/TestQueue/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ const TestQueue = () => {
ats={data.ats}
testPlanVersions={testPlanVersions}
triggerUpdate={refetch}
enableManageRequiredReports={true}
browsers={data.browsers}
/>
)}

Expand Down
9 changes: 9 additions & 0 deletions client/components/TestQueue/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ export const TEST_QUEUE_PAGE_QUERY = gql`
browsers {
...BrowserFields
}
candidateBrowsers {
...BrowserFields
}
recommendedBrowsers {
...BrowserFields
}
}
browsers {
...BrowserFields
}
testPlans(testPlanVersionPhases: [DRAFT, CANDIDATE, RECOMMENDED]) {
...TestPlanFields
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Button, Dropdown, Form } from 'react-bootstrap';
import styled from '@emotion/styled';
import { AtPropType } from '../proptypes';

const CustomToggleButton = styled.button`
background-color: transparent;
width: 100%;
height: 38px;
text-align: center;

border: none;
margin: 0;
padding: 0;
display: block;

.icon-container {
background-color: red;
float: right;
margin-top: 2px;
margin-right: 3px;
}
.icon-chevron {
font-size: 0.8rem;
}
`;

const CustomToggleP = styled.p`
border: 1px solid #ced4da;
border-radius: 0.375rem;
background-color: #fff;
padding: 2px;
width: 100%;
height: 38px;
cursor: default;
display: inline-block;
`;

const CustomToggleSpan = styled.span`
background-image: url('data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%23343a40%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e');
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
float: left;
margin-top: 2px;
white-space: nowrap;
background-color: ${props =>
props.phaseLabel === 'Select a Phase'
? '#fff'
: props.phaseLabel === 'Candidate'
? '#ff6c00'
: props.phaseLabel === 'Recommended'
? '#8441de'
: 'black'};
border-radius: 14px;
padding: 2px 32px 2px 14px;
text-align: left;
width: 100%;
font-size: 1rem;
font-weight: 400;
color: ${props => (props.phaseLabel === 'Select a Phase' ? 'black' : '#fff')};
`;

const CustomMenu = React.forwardRef(({ children, className }, ref) => {
const value = '';

return (
<div ref={ref} className={className}>
<ul>
{React.Children.toArray(children).filter(
child =>
!value || child.props.children.toLowerCase().startsWith(value)
)}
</ul>
</div>
);
});

// You can learn everything about this component here: https://react-bootstrap.netlify.app/docs/components/dropdowns#custom-dropdown-components
const CustomToggle = React.forwardRef(({ children, onClick }, ref) => (
<CustomToggleButton
ref={ref}
onClick={e => {
e.preventDefault();
onClick(e);
}}
>
<CustomToggleP>
<CustomToggleSpan phaseLabel={children}>{children}</CustomToggleSpan>
</CustomToggleP>
</CustomToggleButton>
));

const FormGroup = ({ label, children }) => (
<Form.Group className="form-group">
<Form.Label className="disclosure-form-label">{label}</Form.Label>
{children}
</Form.Group>
);

export const CreateRequiredReportForm = ({ ats, handleCreate }) => {
const [formState, setFormState] = useState({
phase: '',
at: '',
browser: ''
});

const handleInputChange = (field, value) => {
setFormState(prev => ({ ...prev, [field]: value }));
};

const handleSubmit = async () => {
await handleCreate({
atId: formState.at,
browserId: formState.browser,
phase: formState.phase.toUpperCase()
});
setFormState({ phase: '', at: '', browser: '' });
};

return (
<div className="disclosure-row-controls">
<FormGroup label="Phase">
<Dropdown>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components">
{formState.phase || 'Select a Phase'}
</Dropdown.Toggle>
<Dropdown.Menu className="drop-down-div" as={CustomMenu}>
{['Candidate', 'Recommended'].map(phase => (
<Dropdown.Item
key={phase}
className="phase-option"
onClick={() => handleInputChange('phase', phase)}
>
{phase}
</Dropdown.Item>
))}
</Dropdown.Menu>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This custom menu might be a bit much for just this as I'm not seeing a major advantage to highlighting the selected option's color. It also feels a bit out of place given this and the connected disclosures' options. This feels like a case where simplicity may be better, with a native <select>.

Copy link
Contributor Author

@stalgiag stalgiag Sep 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another case where I agree but didn't want to deviate from the pattern that was previously used. I knew that you and Alex were pairing on the code so I didn't want to be too presumptuous about what was discardable. I assume this custom menu was being used to replicate the original mockups as closely as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 43df566

</Dropdown>
</FormGroup>
<FormGroup label="Assistive Technology">
<Form.Select
value={formState.at}
onChange={e => handleInputChange('at', e.target.value)}
required
>
<option value="" disabled>
Select an Assistive Technology
</option>
{ats.map(item => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</Form.Select>
</FormGroup>
<FormGroup label="Browser">
<Form.Select
value={formState.browser}
onChange={e => handleInputChange('browser', e.target.value)}
required
>
<option value="" disabled>
Select a Browser
</option>
{ats
.find(at => at.id === formState.at)
?.browsers.map(item => (
<option key={`${item.name}-${item.id}`} value={item.id}>
{item.name}
</option>
))}
</Form.Select>
</FormGroup>
<FormGroup>
<Button
disabled={!formState.phase || !formState.at || !formState.browser}
onClick={handleSubmit}
>
Add Required Reports
</Button>
</FormGroup>
</div>
);
};

FormGroup.propTypes = {
label: PropTypes.string,
children: PropTypes.node.isRequired
};

CustomToggle.propTypes = {
children: PropTypes.string,
onClick: PropTypes.func
};

CustomMenu.propTypes = {
children: PropTypes.array,
className: PropTypes.string
};

CreateRequiredReportForm.propTypes = {
ats: PropTypes.arrayOf(AtPropType).isRequired,
handleCreate: PropTypes.func.isRequired
};

CreateRequiredReportForm.propTypes = {};
Loading