Skip to content

Commit

Permalink
feat: user can record and view observation fields (#352)
Browse files Browse the repository at this point in the history
* chore: observation fields screens

* chore: translations

* chore: update navigation

* feat: view observation details in an observation

* chore: remove console.log

* chore: pr review
  • Loading branch information
ErikSin authored May 15, 2024
1 parent f8219a2 commit b330791
Show file tree
Hide file tree
Showing 14 changed files with 548 additions and 56 deletions.
16 changes: 16 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,18 @@
"description": "Title of observation screen showing (non-editable) view of observation with map and answered questions",
"message": "Observation"
},
"screens.ObservationDetails.done": {
"description": "Button text when all questions are complete",
"message": "Done"
},
"screens.ObservationDetails.nextQuestion": {
"description": "Button text to navigate to next question",
"message": "Next"
},
"screens.ObservationDetails.title": {
"description": "Title of observation details screen showing question number and total",
"message": "Question {current} of {total}"
},
"screens.ObservationEdit.BottomSheet.addLabel": {
"description": "Label above keyboard that expands into bottom sheet of options to add (photo, details etc)",
"message": "Add…"
Expand All @@ -491,6 +503,10 @@
"description": "Placeholder for description/notes field",
"message": "What is happening here?"
},
"screens.ObservationEdit.ObservationEditView.detailsButton": {
"description": "Button label to add details",
"message": "Add Details"
},
"screens.ObservationEdit.ObservationEditView.photoButton": {
"description": "Button label for adding photo",
"message": "Add Photo"
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/Navigation/Stack/AppScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
} from '../../screens/ManualGpsScreen';
import {HomeTabs} from '../Tab';
import {SaveTrackScreen} from '../../screens/SaveTrack/SaveTrackScreen';
import {ObservationFields} from '../../screens/ObservationFields';

export const TAB_BAR_HEIGHT = 70;

Expand Down Expand Up @@ -237,5 +238,6 @@ export const createDefaultScreenGroup = (
component={ManualGpsScreen}
options={createManualGpsNavigationOptions({intl})}
/>
<RootStack.Screen name="ObservationFields" component={ObservationFields} />
</RootStack.Group>
);
1 change: 0 additions & 1 deletion src/frontend/hooks/server/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ export const useFieldsQuery = () => {
if (!project) throw new Error('Project instance does not exist');
return project.field.getMany();
},
enabled: !!project,
});
};
63 changes: 38 additions & 25 deletions src/frontend/screens/Observation/FieldDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@
import * as React from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {MEDIUM_GREY, DARK_GREY, BLACK} from '../../lib/styles';
import {MEDIUM_GREY, DARK_GREY, BLACK, LIGHT_GREY} from '../../lib/styles';
import {
FormattedFieldProp,
FormattedFieldValue,
} from '../../sharedComponents/FormattedData';
import {Loading} from '../../sharedComponents/Loading';
import {Field} from '@mapeo/schema';
import {Field, Observation} from '@mapeo/schema';

export const FieldDetails = ({fields}: {fields: Field[]}) => {
export const FieldDetails = ({
fields,
observation,
}: {
fields: Field[];
observation: Observation;
}) => {
return (
<View>
{/* {fields.map((field, idx) => {
const value = getProp(observation.tags, field.key);
return (
<View
key={idx}
style={[styles.section, styles.optionalSection]}
>
<Text style={styles.fieldTitle}>
<FormattedFieldProp field={field} propName="label" />
</Text>
<Text
style={[
styles.fieldAnswer,
{ color: value === undefined ? MEDIUM_GREY : DARK_GREY },
]}
>
<FormattedFieldValue value={value} field={field} />
</Text>
</View>
);
})} */}
{fields.map(field => {
const value = observation.tags[field.tagKey];
return (
<View
key={field.docId}
style={[styles.section, styles.optionalSection]}>
<Text style={styles.fieldTitle}>
<FormattedFieldProp field={field} propName="label" />
</Text>
<Text
style={[
styles.fieldAnswer,
{color: value === undefined ? MEDIUM_GREY : DARK_GREY},
]}>
<FormattedFieldValue value={value} field={field} />
</Text>
</View>
);
})}
</View>
);
};
Expand All @@ -46,4 +50,13 @@ const styles = StyleSheet.create({
fontWeight: '700',
marginBottom: 10,
},
section: {
flex: 1,
marginHorizontal: 15,
paddingVertical: 15,
},
optionalSection: {
borderTopColor: LIGHT_GREY,
borderTopWidth: 1,
},
});
22 changes: 10 additions & 12 deletions src/frontend/screens/Observation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,20 @@ export const ObservationScreen: NativeNavigationComponent<'Observation'> = ({
}, [navigation, observationId]);

const {observation, preset} = useObservationWithPreset(observationId);
const fieldsQuery = useFieldsQuery();
const {data} = useFieldsQuery();

const defaultAcc: Field[] = [];
const fields = !fieldsQuery.data
? undefined
: preset.fieldIds.reduce((acc, pres) => {
const fieldToAdd = fieldsQuery.data.find(
field => field.tagKey === pres,
);
const fields = data
? preset.fieldIds.reduce((acc, pres) => {
const fieldToAdd = data.find(field => field.docId === pres);
if (!fieldToAdd) return acc;
return [...acc, fieldToAdd];
}, defaultAcc);
}, defaultAcc)
: [];

const deviceId = '';
const {lat, lon, createdBy} = observation;
const isMine = deviceId === createdBy;
// Currently only show photo attachments
const photos = [];

return (
<ScrollView
Expand Down Expand Up @@ -96,8 +92,10 @@ export const ObservationScreen: NativeNavigationComponent<'Observation'> = ({
/>
)} */}
</View>
{fields && fields.length > 0 && <FieldDetails fields={fields} />}
<View style={styles.divider}></View>
{fields.length > 0 && (
<FieldDetails observation={observation} fields={fields} />
)}
<View style={styles.divider} />
<ButtonFields isMine={isMine} observationId={observationId} />
</>
</ScrollView>
Expand Down
32 changes: 20 additions & 12 deletions src/frontend/screens/ObservationEdit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {PresetView} from './PresetView';
import {useBottomSheetModal} from '../../sharedComponents/BottomSheetModal';
import {ErrorModal} from '../../sharedComponents/ErrorModal';
import {SaveButton} from './SaveButton';
import {DetailsIcon} from '../../sharedComponents/icons';
import {useDraftObservation} from '../../hooks/useDraftObservation';

const m = defineMessages({
editTitle: {
Expand All @@ -29,6 +31,11 @@ const m = defineMessages({
defaultMessage: 'Add Photo',
description: 'Button label for adding photo',
},
detailsButton: {
id: 'screens.ObservationEdit.ObservationEditView.detailsButton',
defaultMessage: 'Add Details',
description: 'Button label to add details',
},
});

export const ObservationEdit: NativeNavigationComponent<'ObservationEdit'> & {
Expand All @@ -37,7 +44,8 @@ export const ObservationEdit: NativeNavigationComponent<'ObservationEdit'> & {
const observationId = usePersistedDraftObservation(
store => store.observationId,
);

const {usePreset} = useDraftObservation();
const preset = usePreset();
const isNew = !observationId;
const {formatMessage: t} = useIntl();
const {openSheet, sheetRef, isOpen, closeSheet} = useBottomSheetModal({
Expand All @@ -56,9 +64,9 @@ export const ObservationEdit: NativeNavigationComponent<'ObservationEdit'> & {
navigation.navigate('AddPhoto');
}, [navigation]);

// const handleDetailsPress = React.useCallback(() => {
// navigation.navigate('ObservationDetails', {question: 1});
// }, [navigation]);
const handleDetailsPress = React.useCallback(() => {
navigation.navigate('ObservationFields', {question: 1});
}, [navigation]);

const bottomSheetItems = [
{
Expand All @@ -67,14 +75,14 @@ export const ObservationEdit: NativeNavigationComponent<'ObservationEdit'> & {
onPress: handleCameraPress,
},
];
// if (preset && preset.fields && preset.fields.length) {
// // Only show the option to add details if preset fields are defined.
// bottomSheetItems.push({
// icon: <DetailsIcon />,
// label: t(m.detailsButton),
// onPress: handleDetailsPress,
// });
// }
if (preset?.fieldIds.length) {
// Only show the option to add details if preset fields are defined.
bottomSheetItems.push({
icon: <DetailsIcon />,
label: t(m.detailsButton),
onPress: handleDetailsPress,
});
}

return (
<View style={styles.container}>
Expand Down
26 changes: 26 additions & 0 deletions src/frontend/screens/ObservationFields/Question.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';

import {SelectOne} from './SelectOne';
import {SelectMultiple} from './SelectMultiple';
import {TextArea} from './TextArea';
import {Field} from '@mapeo/schema';
import {
SelectMultipleField,
SelectOneField,
} from '../../sharedTypes/PresetTypes';

export type QuestionProps = {
field: Field;
};

export const Question = ({field}: QuestionProps) => {
if (field.type === 'selectOne' && Array.isArray(field.options)) {
return <SelectOne field={field as SelectOneField} />;
}

if (field.type === 'selectMultiple' && Array.isArray(field.options)) {
return <SelectMultiple field={field as SelectMultipleField} />;
}

return <TextArea field={field} />;
};
40 changes: 40 additions & 0 deletions src/frontend/screens/ObservationFields/QuestionLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import {View, StyleSheet} from 'react-native';
import {FormattedFieldProp} from '../../sharedComponents/FormattedData';
import {Text} from '../../sharedComponents/Text';
import {Field} from '@mapeo/schema';

interface Props {
field: Field;
}

export const QuestionLabel = ({field}: Props) => {
const hint = <FormattedFieldProp field={field} propName="placeholder" />;
return (
<View style={styles.labelContainer}>
<Text style={styles.label}>
<FormattedFieldProp field={field} propName="label" />
</Text>
{hint ? <Text style={styles.hint}>{hint}</Text> : null}
</View>
);
};

const styles = StyleSheet.create({
labelContainer: {
flex: 0,
padding: 20,
borderBottomWidth: 2,
borderColor: '#F3F3F3',
},
label: {
fontSize: 20,
color: 'black',
fontWeight: '700',
},
hint: {
fontSize: 16,
color: '#666666',
fontWeight: '500',
},
});
Loading

0 comments on commit b330791

Please sign in to comment.