Hooked up Work Item screen to API
This commit is contained in:
Binary file not shown.
@@ -248,9 +248,6 @@ class API extends EventEmitter {
|
|||||||
deleteUser(_id) {
|
deleteUser(_id) {
|
||||||
return this.delete('/users/' + _id)
|
return this.delete('/users/' + _id)
|
||||||
}
|
}
|
||||||
setUserImage(details) {
|
|
||||||
return this.put('/users/set-image', details)
|
|
||||||
}
|
|
||||||
enterRoom(roomName) {
|
enterRoom(roomName) {
|
||||||
return this.put('/users/enter-room/' + (roomName || ''))
|
return this.put('/users/enter-room/' + (roomName || ''))
|
||||||
}
|
}
|
||||||
@@ -258,6 +255,22 @@ class API extends EventEmitter {
|
|||||||
return this.put('/users/leave-room')
|
return this.put('/users/leave-room')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWorkItem(_id) {
|
||||||
|
return this.get('/workitems/' + _id)
|
||||||
|
}
|
||||||
|
listWorkItems() {
|
||||||
|
return this.get('/workitems')
|
||||||
|
}
|
||||||
|
createWorkItem(workItem) {
|
||||||
|
return this.post('/workitems', workItem)
|
||||||
|
}
|
||||||
|
updateWorkItem(workItem) {
|
||||||
|
return this.put('/workitems', workItem)
|
||||||
|
}
|
||||||
|
deleteWorkItem(_id) {
|
||||||
|
return this.delete('/workitems/' + _id)
|
||||||
|
}
|
||||||
|
|
||||||
upload(file, progressCallback) {
|
upload(file, progressCallback) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const chunkSize = 32 * 1024
|
const chunkSize = 32 * 1024
|
||||||
|
|||||||
@@ -86,7 +86,10 @@ export class Home extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Header title='Work Item Map' leftButton={{ icon: 'logout', onPress: this.handleLogoutPress }} rightButton={{ icon: 'glasses', onPress: this.handleGlassesPress }} />
|
<Header
|
||||||
|
title='Work Item Map'
|
||||||
|
leftButton={{ icon: 'logout', onPress: this.handleLogoutPress }}
|
||||||
|
rightButton={{ icon: 'glasses', onPress: this.handleGlassesPress }} />
|
||||||
<MapView
|
<MapView
|
||||||
ref={ref => { this.map = ref }}
|
ref={ref => { this.map = ref }}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ export class MessageModal extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isVisible={open}>
|
<Modal isVisible={open}>
|
||||||
<View style={{ alignSelf: 'center', padding: 5, backgroundColor: '#FFFFFF', flexDirection: 'row' }}>
|
<View style={{ flexDirection: 'row', width: '100%', padding: 5, backgroundColor: '#FFFFFF' }}>
|
||||||
<Icon name={icon} size={130} margin={3} />
|
<Icon name={icon} size={100} margin={3} />
|
||||||
<View style={{ marginLeft: 20, marginRight: 20, flexGrow: 1, flexDirection: 'column' }}>
|
<View style={{ flexGrow: 1, flexBasis: 0, flexDirection: 'column', flexWrap: 'wrap', marginLeft: 20, marginRight: 20}}>
|
||||||
<Text style={{ marginTop: 5, fontSize: 18, alignSelf: 'center' }}>{message}</Text>
|
<Text style={{ marginTop: 5, fontSize: 18 }}>{message}</Text>
|
||||||
<Text style={{ marginTop: 20, alignSelf: 'center' }}>{detail}</Text>
|
<Text style={{ marginTop: 20 }}>{detail}</Text>
|
||||||
<TouchableOpacity onPress={this.handleButtonPress}
|
<TouchableOpacity onPress={this.handleButtonPress}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: 'center',
|
alignSelf: 'flex-end',
|
||||||
backgroundColor: 'blue',
|
backgroundColor: 'blue',
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
|
|||||||
@@ -21,11 +21,15 @@ import {
|
|||||||
PhotoButton,
|
PhotoButton,
|
||||||
BoundOptionStrip,
|
BoundOptionStrip,
|
||||||
} from '../ui'
|
} from '../ui'
|
||||||
|
import { MessageModal } from '../Modal'
|
||||||
import autobind from 'autobind-decorator'
|
import autobind from 'autobind-decorator'
|
||||||
import { ifIphoneX, isIphoneX } from 'react-native-iphone-x-helper'
|
import { ifIphoneX, isIphoneX } from 'react-native-iphone-x-helper'
|
||||||
|
import KeyboardSpacer from 'react-native-keyboard-spacer'
|
||||||
|
import { api } from '../API'
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
flexDirection: 'column',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
backgroundColor: '#DDDDDD',
|
backgroundColor: '#DDDDDD',
|
||||||
},
|
},
|
||||||
@@ -47,11 +51,23 @@ const styles = StyleSheet.create({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const workItemOptions = [
|
const workItemOptions = [
|
||||||
{ value: 'workOrder', text: 'Work Order' },
|
{ value: 'order', text: 'Work Order' },
|
||||||
{ value: 'inspection', text: 'Inspection' },
|
{ value: 'inspection', text: 'Inspection' },
|
||||||
{ value: 'complaint', text: 'Complaint' }
|
{ value: 'complaint', text: 'Complaint' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const latLngToString = (lat, lng) => (`${Math.abs(lng).toFixed(4)}°${lng > 0 ? 'S' : 'N'}, ${Math.abs(lat).toFixed(4)}°${lat > 0 ? 'W' : 'E'}`)
|
||||||
|
const latLngStringToPoint = (str) => {
|
||||||
|
const parts = str.split(', ')
|
||||||
|
return {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [
|
||||||
|
new Number(parts[0].substring(0, parts[0].length - 2)),
|
||||||
|
new Number(parts[1].substring(0, parts[1].length - 2)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class WorkItem extends React.Component {
|
export class WorkItem extends React.Component {
|
||||||
static bindings = {
|
static bindings = {
|
||||||
header: {
|
header: {
|
||||||
@@ -67,6 +83,8 @@ export class WorkItem extends React.Component {
|
|||||||
},
|
},
|
||||||
workItemType: {
|
workItemType: {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
|
initValue: 'order',
|
||||||
|
alwaysGet: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,28 +109,51 @@ export class WorkItem extends React.Component {
|
|||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleDonePress() {
|
handleDonePress() {
|
||||||
|
const { binder } = this.state
|
||||||
|
let obj = binder.getModifiedFieldValues()
|
||||||
|
|
||||||
|
obj.location = latLngStringToPoint(obj.location)
|
||||||
|
|
||||||
|
if (!obj._id) {
|
||||||
|
api.createWorkItem(obj).then((workItem) => {
|
||||||
|
this.handleBackPress()
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({ messageModal: {
|
||||||
|
icon: 'hand',
|
||||||
|
message: 'Unable to create work item',
|
||||||
|
detail: error.message,
|
||||||
|
}})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
api.updateWorkItem(obj).then((workItem) => {
|
||||||
|
this.handleBackPress()
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({ messageModal: {
|
||||||
|
icon: 'hand',
|
||||||
|
message: 'Unable to update work item',
|
||||||
|
detail: error.message,
|
||||||
|
}})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleMessageDismiss() {
|
||||||
|
this.setState({ messageModal: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleRegionChange(region) {
|
handleRegionChange(region) {
|
||||||
const { binder } = this.state
|
const { binder } = this.state
|
||||||
// NOTE: For some reason, object destructuring does not work here :(
|
|
||||||
const lon = region.longitude
|
|
||||||
const lat = region.latitude
|
|
||||||
|
|
||||||
this.setState(binder.updateFieldValue('location',
|
this.setState(binder.updateFieldValue('location', latLngToString(region.latitude, region.longitude)))
|
||||||
`${Math.abs(lon).toFixed(4)}°${lon > 0 ? 'S' : 'N'}, ${Math.abs(lat).toFixed(4)}°${lat > 0 ? 'W' : 'E'}`))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { binder } = this.state
|
const { binder, messageModal } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<View style={{ flex: 1 }}>
|
||||||
style={{ width: '100%', height: '100%' }}
|
|
||||||
behavior='padding'
|
|
||||||
keyboardVerticalOffset={Platform.select({ios: 0, android: -220})}>
|
|
||||||
<BoundHeader
|
<BoundHeader
|
||||||
binder={binder}
|
binder={binder}
|
||||||
name='header'
|
name='header'
|
||||||
@@ -123,6 +164,9 @@ export class WorkItem extends React.Component {
|
|||||||
<View style={styles.panel}>
|
<View style={styles.panel}>
|
||||||
<BoundOptionStrip binder={binder} name='workItemType' label='Work Item Type:' options={workItemOptions} />
|
<BoundOptionStrip binder={binder} name='workItemType' label='Work Item Type:' options={workItemOptions} />
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.panel}>
|
||||||
|
<BoundInput binder={binder} name='details' lines={4} label='Details:' message='You must supply details for the work item' />
|
||||||
|
</View>
|
||||||
<View style={styles.panel}>
|
<View style={styles.panel}>
|
||||||
<MapView
|
<MapView
|
||||||
style={{
|
style={{
|
||||||
@@ -152,12 +196,16 @@ export class WorkItem extends React.Component {
|
|||||||
<PhotoButton />
|
<PhotoButton />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.panel}>
|
|
||||||
<BoundInput binder={binder} name='details' lines={4} label='Details:' message='You must supply details for the work item' />
|
|
||||||
</View>
|
|
||||||
{ isIphoneX ? <View style={{ height: 30, width: '100%' }} /> : null }
|
{ isIphoneX ? <View style={{ height: 30, width: '100%' }} /> : null }
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
<MessageModal
|
||||||
|
open={!!messageModal}
|
||||||
|
icon={messageModal ? messageModal.icon : ''}
|
||||||
|
message={messageModal ? messageModal.message : ''}
|
||||||
|
detail={messageModal ? messageModal.detail : ''}
|
||||||
|
onDismiss={messageModal && this.handleMessageDismiss} />
|
||||||
|
<KeyboardSpacer />
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,16 @@ export class WorkItemRoutes {
|
|||||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteWorkItem)
|
.delete(passport.authenticate('bearer', { session: false }), this.deleteWorkItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
listWorkItems(req, res, next) {
|
async listWorkItems(req, res, next) {
|
||||||
const WorkItem = this.db.WorkItem
|
try {
|
||||||
const limit = req.query.limit || 20
|
const WorkItem = this.db.WorkItem
|
||||||
const skip = req.query.skip || 0
|
const limit = req.query.limit || 20
|
||||||
const partial = !!req.query.partial
|
const skip = req.query.skip || 0
|
||||||
let query = {}
|
const partial = !!req.query.partial
|
||||||
|
let query = {}
|
||||||
|
|
||||||
|
const total = await WorkItem.count({})
|
||||||
|
|
||||||
WorkItem.count({}).then((total) => {
|
|
||||||
let workItems = []
|
let workItems = []
|
||||||
let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||||
return doc.toClient(partial)
|
return doc.toClient(partial)
|
||||||
@@ -47,102 +49,128 @@ export class WorkItemRoutes {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
cursor.on('error', (err) => {
|
cursor.on('error', (err) => {
|
||||||
next(createError.InternalServerError(err.message))
|
throw createError.InternalServerError(err.message)
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createWorkItem(req, res, next) {
|
async createWorkItem(req, res, next) {
|
||||||
const role = req.user.role
|
|
||||||
|
|
||||||
// If user's role is not Executive or Administrator, return an error
|
|
||||||
if (role !== 'executive' && role !== 'administrator') {
|
|
||||||
return next(new createError.Forbidden())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new WorkItem template then assign it to a value in the req.body
|
|
||||||
const WorkItem = this.db.WorkItem
|
|
||||||
let workItem = new WorkItem(req.body)
|
|
||||||
|
|
||||||
// Save the workItem (with promise) - If it doesnt, catch and throw error
|
|
||||||
workItem.save().then((newWorkItem) => {
|
|
||||||
res.json(newWorkItem.toClient())
|
|
||||||
}).catch((err) => {
|
|
||||||
next(createError.InternalServerError(err.message))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
updateWorkItem(req, res, next) {
|
|
||||||
const role = req.user.role
|
|
||||||
|
|
||||||
// If user's role is not Executive or Administrator, return an error
|
|
||||||
if (role !== 'executive' && role !== 'administrator') {
|
|
||||||
return new createError.Forbidden()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do this here because Mongoose will add it automatically otherwise
|
|
||||||
if (!req.body._id) {
|
|
||||||
return next(createError.BadRequest('No _id given in body'))
|
|
||||||
}
|
|
||||||
|
|
||||||
let WorkItem = this.db.WorkItem
|
|
||||||
let workItemUpdates = null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
workItemUpdates = new WorkItem(req.body)
|
const isAdmin = req.user.administrator
|
||||||
} catch (err) {
|
|
||||||
return next(createError.BadRequest('Invalid data'))
|
if (!isAdmin) {
|
||||||
}
|
return new createError.Forbidden()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new WorkItem template then assign it to a value in the req.body
|
||||||
|
const WorkItem = this.db.WorkItem
|
||||||
|
let workItem = new WorkItem(req.body)
|
||||||
|
|
||||||
|
// Save the workItem (with promise) - If it doesnt, catch and throw error
|
||||||
|
const newWorkItem = await workItem.save()
|
||||||
|
|
||||||
|
res.json(newWorkItem.toClient())
|
||||||
|
} catch(err) {
|
||||||
|
if (err instanceof createError.HttpError) {
|
||||||
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateWorkItem(req, res, next) {
|
||||||
|
try {
|
||||||
|
const isAdmin = req.user.administrator
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
return new createError.Forbidden()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this here because Mongoose will add it automatically otherwise
|
||||||
|
if (!req.body._id) {
|
||||||
|
throw createError.BadRequest('No _id given in body')
|
||||||
|
}
|
||||||
|
|
||||||
|
let WorkItem = this.db.WorkItem
|
||||||
|
let workItemUpdates = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
workItemUpdates = new WorkItem(req.body)
|
||||||
|
} catch (err) {
|
||||||
|
throw createError.BadRequest('Invalid data')
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundWorkItem = await WorkItem.findById(workItemUpdates._id)
|
||||||
|
|
||||||
WorkItem.findById(workItemUpdates._id).then((foundWorkItem) => {
|
|
||||||
if (!foundWorkItem) {
|
if (!foundWorkItem) {
|
||||||
return next(createError.NotFound(`WorkItem with _id ${_id} was not found`))
|
return next(createError.NotFound(`WorkItem with _id ${_id} was not found`))
|
||||||
}
|
}
|
||||||
|
|
||||||
foundWorkItem.merge(workItemUpdates)
|
foundWorkItem.merge(workItemUpdates)
|
||||||
return foundWorkItem.save()
|
|
||||||
}).then((savedWorkItem) => {
|
const savedWorkItem = await foundWorkItem.save()
|
||||||
|
|
||||||
res.json(savedWorkItem.toClient())
|
res.json(savedWorkItem.toClient())
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getWorkItem(req, res, next) {
|
async getWorkItem(req, res, next) {
|
||||||
const WorkItem = this.db.WorkItem
|
try {
|
||||||
const _id = req.params._id
|
const WorkItem = this.db.WorkItem
|
||||||
|
const _id = req.params._id
|
||||||
|
const workItem = await WorkItem.findById(_id)
|
||||||
|
|
||||||
WorkItem.findById(_id).then((workItem) => {
|
|
||||||
if (!workItem) {
|
if (!workItem) {
|
||||||
return next(createError.NotFound(`WorkItem with _id ${_id} not found`))
|
throw createError.NotFound(`WorkItem with _id ${_id} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(workItem.toClient())
|
res.json(workItem.toClient())
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteWorkItem(req, res, next) {
|
async deleteWorkItem(req, res, next) {
|
||||||
const role = req.user.role
|
try {
|
||||||
|
const isAdmin = req.user.administrator
|
||||||
|
|
||||||
// If user's role is not Executive or Administrator, return an error
|
if (!isAdmin) {
|
||||||
if (role !== 'executive' && role !== 'administrator') {
|
return new createError.Forbidden()
|
||||||
return new createError.Forbidden()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const WorkItem = this.db.WorkItem
|
const WorkItem = this.db.WorkItem
|
||||||
const _id = req.params._id
|
const _id = req.params._id
|
||||||
|
const workItem = await WorkItem.remove({ _id })
|
||||||
|
|
||||||
WorkItem.remove({ _id }).then((workItem) => {
|
|
||||||
if (!workItem) {
|
if (!workItem) {
|
||||||
return next(createError.NotFound(`WorkItem with _id ${_id} not found`))
|
throw createError.NotFound(`WorkItem with _id ${_id} not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({})
|
res.json({})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export let workItemSchema = new Schema({
|
|||||||
message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
|
message: 'enum validator failed for path `{PATH}` with value `{VALUE}`'
|
||||||
}},
|
}},
|
||||||
number: Number,
|
number: Number,
|
||||||
loc: {
|
location: {
|
||||||
type: { type: String },
|
type: { type: String },
|
||||||
coordinates: [Number],
|
coordinates: [Number],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user