Skip to content

Commit

Permalink
Merge pull request #1767 from coopcycle/feature/incident
Browse files Browse the repository at this point in the history
Add incident support.
  • Loading branch information
r0xsh authored May 15, 2024
2 parents 7549831 + 8eae141 commit 0c8a049
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 40 deletions.
4 changes: 4 additions & 0 deletions src/components/TaskListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const styles = StyleSheet.create({
textDanger: {
color: redColor,
},
hasIncident: {
borderColor: yellowColor,
},
icon: {
fontSize: 18,
},
Expand Down Expand Up @@ -290,6 +293,7 @@ class TaskListItem extends Component {
</HStack>
) : null}
</VStack>
{task.hasIncidents && <Icon as={FontAwesome} name="exclamation-triangle" size="md" style={{ backgroundColor: yellowColor, color: redColor, marginRight: 12, borderRadius: 5 }} />}
<Icon as={FontAwesome} name="arrow-right" size="sm" />
</HStack>
</ItemTouchable>
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"COMPLETE_TASK": "Complete",
"VALIDATE": "Validate",
"MARK_FAILED": "Mark task as failed",
"REPORT_INCIDENT": "Report incident",
"SWIPE_TO_END": "Swipe right to end task, or to the left if there is a problem",
"WAITING_FOR_POS": "Waiting for location",
"PROBLEM_CONNECTING": "Problem connecting",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"FAILED": "Échec",
"VALIDATE": "Valider",
"MARK_FAILED": "Signaler un problème",
"REPORT_INCIDENT": "Signaler un incident",
"COMPLETE_TASK": "Terminer",
"SWIPE_TO_END": "Glissez vers la droite pour terminer, ou vers la gauche en cas de problème",
"WAITING_FOR_POS": "En attente de la position",
Expand Down
12 changes: 10 additions & 2 deletions src/navigation/home/FeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Checkbox, Column } from 'native-base';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { setSpinnerDelayEnabled } from '../../redux/App/actions';
import { selectIsSpinnerDelayEnabled } from '../../redux/App/selectors';
import { setSpinnerDelayEnabled, setIncidentEnabled } from '../../redux/App/actions';
import { selectIsSpinnerDelayEnabled, selectIsIncidentEnabled } from '../../redux/App/selectors';

export default function FeatureFlags() {
const isSpinnerDelayEnabled = useSelector(selectIsSpinnerDelayEnabled);
const isIncidentEnabled = useSelector(selectIsIncidentEnabled);

const { t } = useTranslation();

Expand All @@ -21,6 +22,13 @@ export default function FeatureFlags() {
defaultIsChecked={isSpinnerDelayEnabled}>
{t('FEATURE_FLAG_SPINNER_DELAY')}
</Checkbox>
<Checkbox
accessibilityLabel="configure incident"
onChange={checked => dispatch(setIncidentEnabled(checked))}
defaultIsChecked={isIncidentEnabled}
value="incident">
{t('FEATURE_FLAG_ENABLE_INCIDENT')}
</Checkbox>
</Column>
);
}
105 changes: 88 additions & 17 deletions src/navigation/task/Complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ import {
selectPictures,
selectSignatures,
} from '../../redux/Courier';
import { selectIsIncidentEnabled } from '../../redux/App/selectors';
import { greenColor, yellowColor } from '../../styles/common';
import { doneIconName, incidentIconName } from './styles/common';
import { reportIncident } from '../../redux/Courier/taskActions';

const DELETE_ICON_SIZE = 32;
const CONTENT_PADDING = 20;
Expand Down Expand Up @@ -128,11 +130,24 @@ class CompleteTask extends Component {
isContactNameModalVisible: false,
contactName: '',
isKeyboardVisible: false,
validateTaskAfterReport: false,
};
}

shouldComponentUpdate(nextProps, nextState, nextContext) {
return this.state.failureReason !== nextState.failureReason;
shouldComponentUpdate(nextProps, nextState) {
if (this.state.failureReason !== nextState.failureReason) return true;

if (
this.state.validateTaskAfterReport !== nextState.validateTaskAfterReport
)
return true;

if (this.props.signatures.length !== nextProps.signatures.length)
return true;

if (this.props.pictures.length !== nextProps.pictures.length) return true;

return false;
}

markTaskDone() {
Expand All @@ -142,7 +157,6 @@ class CompleteTask extends Component {

if (tasks && tasks.length) {
this.props.markTasksDone(
this.props.httpClient,
tasks,
notes,
() => {
Expand All @@ -155,7 +169,6 @@ class CompleteTask extends Component {
);
} else {
this.props.markTaskDone(
this.props.httpClient,
task,
notes,
() => {
Expand All @@ -176,10 +189,9 @@ class CompleteTask extends Component {
const { notes, failureReason } = this.state;

this.props.markTaskFailed(
this.props.httpClient,
task,
failureReason,
notes,
failureReason,
() => {
// Make sure to use merge = true, so that it doesn't break
// when navigating to DispatchTaskList
Expand All @@ -192,6 +204,27 @@ class CompleteTask extends Component {
);
}

reportIncident() {
const task = this.props.route.params?.task;
const { notes, failureReason } = this.state;

this.props.reportIncident(
task,
notes,
failureReason,
() => {
if (this.state.validateTaskAfterReport) {
this.markTaskDone();
} else {
this.props.navigation.navigate({
name: this.props.route.params?.navigateAfter,
merge: true,
});
}
},
);
}

onSwipeComplete() {
this.setState({ isContactNameModalVisible: false });
}
Expand Down Expand Up @@ -245,9 +278,12 @@ class CompleteTask extends Component {
}

multipleTasksLabel(tasks) {
return tasks.reduce((label, task, idx) => {
return `${label}${idx !== 0 ? ',' : ''} #${task.id}`;
}, `${this.props.t('COMPLETE_TASKS')}: `);
return tasks.reduce(
(label, task, idx) => {
return `${label}${idx !== 0 ? ',' : ''} #${task.id}`;
},
`${this.props.t('COMPLETE_TASKS')}: `,
);
}

isDropoff() {
Expand Down Expand Up @@ -276,10 +312,12 @@ class CompleteTask extends Component {
const footerBgColor = success ? greenColor : yellowColor;
const footerText = success
? this.props.t('VALIDATE')
: this.props.t('MARK_FAILED');
: this.props.t('REPORT_INCIDENT');
const onPress = success
? this.markTaskDone.bind(this)
: this.markTaskFailed.bind(this);
: this.props.isIncidentEnabled
? this.reportIncident.bind(this)
: this.markTaskFailed.bind(this);

const contactName = this.resolveContactName();

Expand Down Expand Up @@ -355,6 +393,34 @@ class CompleteTask extends Component {
totalLines={2}
onChangeText={text => this.setState({ notes: text })}
/>
{!success && this.props.isIncidentEnabled && (
<FormControl p="3">
<Button
bg={
this.state.validateTaskAfterReport
? greenColor
: undefined
}
onPress={() =>
this.setState({
validateTaskAfterReport:
!this.state.validateTaskAfterReport,
})
}
variant={
this.state.validateTaskAfterReport
? 'solid'
: 'outline'
}
endIcon={
this.state.validateTaskAfterReport ? (
<Icon as={FontAwesome} name="check" size="sm" />
) : undefined
}>
Validate the task after reporting
</Button>
</FormControl>
)}
</FormControl>
<View>
<ScrollView style={{ height: '50%' }}>
Expand Down Expand Up @@ -504,19 +570,24 @@ function mapStateToProps(state) {
taskCompleteError: selectIsTaskCompleteFailure(state),
signatures: selectSignatures(state),
pictures: selectPictures(state),
isIncidentEnabled: selectIsIncidentEnabled(state),
};
}

function mapDispatchToProps(dispatch) {
return {
markTaskFailed: (client, task, notes, reason, onSuccess, contactName) =>
markTaskFailed: (task, notes, reason, onSuccess, contactName) =>
dispatch(
markTaskFailed(task, notes, reason, onSuccess, contactName),
),
markTaskDone: (task, notes, onSuccess, contactName) =>
dispatch(markTaskDone(task, notes, onSuccess, contactName)),
markTasksDone: (tasks, notes, onSuccess, contactName) =>
dispatch(markTasksDone(tasks, notes, onSuccess, contactName)),
reportIncident: (task, notes, failureReasonCode, onSuccess) =>
dispatch(
markTaskFailed(client, task, notes, reason, onSuccess, contactName),
reportIncident(task, notes, failureReasonCode, onSuccess),
),
markTaskDone: (client, task, notes, onSuccess, contactName) =>
dispatch(markTaskDone(client, task, notes, onSuccess, contactName)),
markTasksDone: (client, tasks, notes, onSuccess, contactName) =>
dispatch(markTasksDone(client, tasks, notes, onSuccess, contactName)),
deleteSignatureAt: index => dispatch(deleteSignatureAt(index)),
deletePictureAt: index => dispatch(deletePictureAt(index)),
};
Expand Down
2 changes: 2 additions & 0 deletions src/redux/App/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const LOAD_PRIVACY_POLICY_SUCCESS = '@app/LOAD_PRIVACY_POLICY_SUCCESS';
export const LOAD_PRIVACY_POLICY_FAILURE = '@app/LOAD_PRIVACY_POLICY_FAILURE';

export const SET_SPINNER_DELAY_ENABLED = '@app/SET_IS_SPINNER_DELAY_ENABLED';
export const SET_INCIDENT_ENABLED = '@app/SET_IS_INCIDENT_ENABLED';

/*
* Action Creators
Expand Down Expand Up @@ -201,6 +202,7 @@ const registrationErrors = createAction(REGISTRATION_ERRORS);
const loginByEmailErrors = createAction(LOGIN_BY_EMAIL_ERRORS);

export const setSpinnerDelayEnabled = createAction(SET_SPINNER_DELAY_ENABLED);
export const setIncidentEnabled = createAction(SET_INCIDENT_ENABLED);

function setBaseURL(baseURL) {
return (dispatch, getState) => {
Expand Down
8 changes: 8 additions & 0 deletions src/redux/App/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
SET_SERVERS,
SET_SETTINGS,
SET_SPINNER_DELAY_ENABLED,
SET_INCIDENT_ENABLED,
SET_USER,
} from './actions';

Expand Down Expand Up @@ -98,6 +99,7 @@ const initialState = {
termsAndConditionsText: '',
privacyPolicyText: '',
isSpinnerDelayEnabled: true,
isIncidentEnabled: false,
};

export default (state = initialState, action = {}) => {
Expand Down Expand Up @@ -393,6 +395,12 @@ export default (state = initialState, action = {}) => {
...state,
isSpinnerDelayEnabled: action.payload,
};

case SET_INCIDENT_ENABLED:
return {
...state,
isIncidentEnabled: action.payload,
};
}

return state;
Expand Down
3 changes: 3 additions & 0 deletions src/redux/App/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,6 @@ export const selectServersWithoutRepeats = createSelector(

export const selectIsSpinnerDelayEnabled = state =>
state.app.isSpinnerDelayEnabled ?? true;

export const selectIsIncidentEnabled = state =>
state.app.isIncidentEnabled ?? false;
10 changes: 5 additions & 5 deletions src/redux/Courier/__tests__/taskActions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ describe('Redux | Tasks | Actions', () => {
});

// Make sure to return the promise
return store.dispatch(markTaskDone(client, task, notes)).then(() => {
return store.dispatch(markTaskDone(task, notes)).then(() => {
const actions = store.getActions();

expect(actions).toContainEqual(markTaskDoneRequest(task));
Expand Down Expand Up @@ -272,7 +272,7 @@ describe('Redux | Tasks | Actions', () => {
});

// Make sure to return the promise
return store.dispatch(markTaskDone(client, task, notes)).then(() => {
return store.dispatch(markTaskDone(task, notes)).then(() => {
const actions = store.getActions();

expect(actions).toContainEqual(markTaskDoneRequest(task));
Expand Down Expand Up @@ -312,7 +312,7 @@ describe('Redux | Tasks | Actions', () => {
});

// Make sure to return the promise
return store.dispatch(markTaskDone(client, task, notes)).then(() => {
return store.dispatch(markTaskDone(task, notes)).then(() => {
const actions = store.getActions();

expect(actions).toContainEqual(markTaskDoneRequest(task));
Expand Down Expand Up @@ -349,7 +349,7 @@ describe('Redux | Tasks | Actions', () => {

// Make sure to return the promise
return store
.dispatch(markTaskFailed(client, task, notes, reason))
.dispatch(markTaskFailed(task, notes, reason))
.then(() => {
const actions = store.getActions();

Expand Down Expand Up @@ -391,7 +391,7 @@ describe('Redux | Tasks | Actions', () => {

// Make sure to return the promise
return store
.dispatch(markTaskFailed(client, task, notes, reason))
.dispatch(markTaskFailed(task, notes, reason))
.then(() => {
const actions = store.getActions();

Expand Down
Loading

0 comments on commit 0c8a049

Please sign in to comment.