diff --git a/design/Deighton AR Design.sketch b/design/Deighton AR Design.sketch index dd63102..b669cee 100644 Binary files a/design/Deighton AR Design.sketch and b/design/Deighton AR Design.sketch differ diff --git a/mobile/src/API.js b/mobile/src/API.js index 2009db2..cce99b5 100644 --- a/mobile/src/API.js +++ b/mobile/src/API.js @@ -248,9 +248,6 @@ class API extends EventEmitter { deleteUser(_id) { return this.delete('/users/' + _id) } - setUserImage(details) { - return this.put('/users/set-image', details) - } enterRoom(roomName) { return this.put('/users/enter-room/' + (roomName || '')) } @@ -258,6 +255,22 @@ class API extends EventEmitter { 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) { return new Promise((resolve, reject) => { const chunkSize = 32 * 1024 diff --git a/mobile/src/Home/Home.js b/mobile/src/Home/Home.js index 56290b8..3901cc4 100644 --- a/mobile/src/Home/Home.js +++ b/mobile/src/Home/Home.js @@ -86,7 +86,10 @@ export class Home extends React.Component { render() { return ( -
+
{ this.map = ref }} style={{ diff --git a/mobile/src/Modal/MessageModal.js b/mobile/src/Modal/MessageModal.js index 1225f29..3431147 100644 --- a/mobile/src/Modal/MessageModal.js +++ b/mobile/src/Modal/MessageModal.js @@ -35,14 +35,14 @@ export class MessageModal extends Component { return ( - - - - {message} - {detail} + + + + {message} + {detail} (`${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 { static bindings = { header: { @@ -67,6 +83,8 @@ export class WorkItem extends React.Component { }, workItemType: { isValid: true, + initValue: 'order', + alwaysGet: true, } } @@ -91,28 +109,51 @@ export class WorkItem extends React.Component { @autobind 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 handleRegionChange(region) { 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', - `${Math.abs(lon).toFixed(4)}°${lon > 0 ? 'S' : 'N'}, ${Math.abs(lat).toFixed(4)}°${lat > 0 ? 'W' : 'E'}`)) + this.setState(binder.updateFieldValue('location', latLngToString(region.latitude, region.longitude))) } render() { - const { binder } = this.state + const { binder, messageModal } = this.state return ( - + + + + - - - { isIphoneX ? : null } - + + + ) } } diff --git a/server/src/api/routes/WorkItemRoutes.js b/server/src/api/routes/WorkItemRoutes.js index f26dc75..8d06829 100644 --- a/server/src/api/routes/WorkItemRoutes.js +++ b/server/src/api/routes/WorkItemRoutes.js @@ -22,14 +22,16 @@ export class WorkItemRoutes { .delete(passport.authenticate('bearer', { session: false }), this.deleteWorkItem) } - listWorkItems(req, res, next) { - const WorkItem = this.db.WorkItem - const limit = req.query.limit || 20 - const skip = req.query.skip || 0 - const partial = !!req.query.partial - let query = {} + async listWorkItems(req, res, next) { + try { + const WorkItem = this.db.WorkItem + const limit = req.query.limit || 20 + const skip = req.query.skip || 0 + const partial = !!req.query.partial + let query = {} + + const total = await WorkItem.count({}) - WorkItem.count({}).then((total) => { let workItems = [] let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => { return doc.toClient(partial) @@ -47,102 +49,128 @@ export class WorkItemRoutes { }) }) cursor.on('error', (err) => { - next(createError.InternalServerError(err.message)) + throw createError.InternalServerError(err.message) }) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - 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 + async createWorkItem(req, res, next) { try { - workItemUpdates = new WorkItem(req.body) - } catch (err) { - return next(createError.BadRequest('Invalid data')) - } + const isAdmin = req.user.administrator + + 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) { return next(createError.NotFound(`WorkItem with _id ${_id} was not found`)) } + foundWorkItem.merge(workItemUpdates) - return foundWorkItem.save() - }).then((savedWorkItem) => { + + const savedWorkItem = await foundWorkItem.save() + res.json(savedWorkItem.toClient()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - getWorkItem(req, res, next) { - const WorkItem = this.db.WorkItem - const _id = req.params._id + async getWorkItem(req, res, next) { + try { + const WorkItem = this.db.WorkItem + const _id = req.params._id + const workItem = await WorkItem.findById(_id) - WorkItem.findById(_id).then((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()) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } - deleteWorkItem(req, res, next) { - const role = req.user.role + async deleteWorkItem(req, res, next) { + try { + const isAdmin = req.user.administrator - // If user's role is not Executive or Administrator, return an error - if (role !== 'executive' && role !== 'administrator') { - return new createError.Forbidden() - } + if (!isAdmin) { + return new createError.Forbidden() + } - const WorkItem = this.db.WorkItem - const _id = req.params._id + const WorkItem = this.db.WorkItem + const _id = req.params._id + const workItem = await WorkItem.remove({ _id }) - WorkItem.remove({ _id }).then((workItem) => { if (!workItem) { - return next(createError.NotFound(`WorkItem with _id ${_id} not found`)) + throw createError.NotFound(`WorkItem with _id ${_id} not found`) } res.json({}) - }).catch((err) => { - next(createError.InternalServerError(err.message)) - }) + } catch(err) { + if (err instanceof createError.HttpError) { + next(err) + } else { + next(createError.InternalServerError(err.message)) + } + } } } diff --git a/server/src/database/schemas/workItem.js b/server/src/database/schemas/workItem.js index 3f7b934..84f0fe8 100644 --- a/server/src/database/schemas/workItem.js +++ b/server/src/database/schemas/workItem.js @@ -8,7 +8,7 @@ export let workItemSchema = new Schema({ message: 'enum validator failed for path `{PATH}` with value `{VALUE}`' }}, number: Number, - loc: { + location: { type: { type: String }, coordinates: [Number], },