Hook up team dropdowns and lists
This commit is contained in:
BIN
design/Deighton AR Design.sketch
Normal file
BIN
design/Deighton AR Design.sketch
Normal file
Binary file not shown.
@@ -24,11 +24,11 @@ export class ActivityRoutes {
|
||||
|
||||
listActivitys(req, res, next) {
|
||||
const Activity = this.db.Activity
|
||||
let limit = req.params.limit || 20
|
||||
let skip = req.params.skip || 0
|
||||
let partial = !!req.params.partial
|
||||
let branch = req.params.branch
|
||||
let query = {}
|
||||
const limit = req.query.limit || 20
|
||||
const skip = req.query.skip || 0
|
||||
const partial = !!req.query.partial
|
||||
const branch = req.query.branch
|
||||
const query = {}
|
||||
|
||||
if (branch) {
|
||||
query.branch = branch
|
||||
|
||||
@@ -22,19 +22,15 @@ export class TeamRoutes {
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteTeam)
|
||||
}
|
||||
|
||||
listTeams(req, res, next) {
|
||||
async listTeams(req, res, next) {
|
||||
const Team = this.db.Team
|
||||
let limit = req.params.limit || 20
|
||||
let skip = req.params.skip || 0
|
||||
let partial = !!req.params.partial
|
||||
let branch = req.params.branch
|
||||
let limit = req.query.limit || 20
|
||||
let skip = req.query.skip || 0
|
||||
let partial = !!req.query.partial
|
||||
let query = {}
|
||||
|
||||
if (branch) {
|
||||
query.branch = branch
|
||||
}
|
||||
|
||||
Team.count({}).then((total) => {
|
||||
try {
|
||||
const total = await Team.count({})
|
||||
let teams = []
|
||||
let cursor = Team.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
return doc.toClient(partial)
|
||||
@@ -52,93 +48,120 @@ export class TeamRoutes {
|
||||
})
|
||||
})
|
||||
cursor.on('error', (err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
throw err
|
||||
})
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createTeam(req, res, next) {
|
||||
if (!req.user.administrator) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
// Create a new Team template then assign it to a value in the req.body
|
||||
const Team = this.db.Team
|
||||
let team = new Team(req.body)
|
||||
|
||||
// Save the team (with promise) - If it doesnt, catch and throw error
|
||||
team.save().then((newTeam) => {
|
||||
res.json(newTeam.toClient())
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
}
|
||||
|
||||
updateTeam(req, res, next) {
|
||||
if (!req.user.administrator) {
|
||||
return next(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 Team = this.db.Team
|
||||
let teamUpdates = null
|
||||
|
||||
async createTeam(req, res, next) {
|
||||
try {
|
||||
teamUpdates = new Team(req.body)
|
||||
} catch (err) {
|
||||
return next(createError.BadRequest('Invalid data'))
|
||||
}
|
||||
if (!req.user.administrator) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
// Create a new Team template then assign it to a value in the req.body
|
||||
const Team = this.db.Team
|
||||
let team = new Team(req.body)
|
||||
|
||||
const newTeam = await team.save()
|
||||
|
||||
res.json(newTeam.toClient())
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateTeam(req, res, next) {
|
||||
try {
|
||||
if (!req.user.administrator) {
|
||||
throw 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 Team = this.db.Team
|
||||
let teamUpdates = null
|
||||
|
||||
try {
|
||||
teamUpdates = new Team(req.body)
|
||||
} catch (err) {
|
||||
throw createError.BadRequest('Invalid data')
|
||||
}
|
||||
|
||||
const foundTeam = await Team.findById(teamUpdates._id)
|
||||
|
||||
Team.findById(teamUpdates._id).then((foundTeam) => {
|
||||
if (!foundTeam) {
|
||||
return next(createError.NotFound(`Team with _id ${_id} was not found`))
|
||||
throw createError.NotFound(`Team with _id ${_id} was not found`)
|
||||
}
|
||||
foundTeam.merge(teamUpdates)
|
||||
return foundTeam.save()
|
||||
}).then((savedTeam) => {
|
||||
const savedTeam = await foundTeam.save()
|
||||
|
||||
res.json(savedTeam.toClient())
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTeam(req, res, next) {
|
||||
async getTeam(req, res, next) {
|
||||
const Team = this.db.Team
|
||||
const _id = req.params._id
|
||||
|
||||
Team.findById(_id).then((team) => {
|
||||
try {
|
||||
const team = await Team.findById(_id)
|
||||
|
||||
if (!team) {
|
||||
return next(createError.NotFound(`Team with _id ${_id} not found`))
|
||||
throw createError.NotFound(`Team with _id ${_id} not found`)
|
||||
}
|
||||
|
||||
res.json(team.toClient())
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteTeam(req, res, next) {
|
||||
if (!req.user.administrator) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
async deleteTeam(req, res, next) {
|
||||
try {
|
||||
if (!req.user.administrator) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const Team = this.db.Team
|
||||
const _id = req.params._id
|
||||
const Team = this.db.Team
|
||||
const _id = req.params._id
|
||||
|
||||
Team.remove({ _id }).then((team) => {
|
||||
if (!project) {
|
||||
return next(createError.NotFound(`Team with _id ${_id} not found`))
|
||||
const removedTeam = await Team.remove({ _id })
|
||||
|
||||
if (!removedTeam) {
|
||||
throw createError.NotFound(`Team 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,6 @@ export class UserRoutes {
|
||||
.get(passport.authenticate('bearer', { session: false }), this.getUser)
|
||||
.delete(passport.authenticate('bearer', { session: false }), this.deleteUser)
|
||||
|
||||
app.route('/users/set-image')
|
||||
.put(passport.authenticate('bearer', { session: false }), this.setImage)
|
||||
|
||||
app.route('/users/enter-room/:roomName')
|
||||
.put(passport.authenticate('bearer', { session: false }), this.enterRoom)
|
||||
|
||||
@@ -38,19 +35,27 @@ export class UserRoutes {
|
||||
.put(passport.authenticate('bearer', { session: false }), this.leaveRoom)
|
||||
}
|
||||
|
||||
listUsers(req, res, next) {
|
||||
async listUsers(req, res, next) {
|
||||
const User = this.db.User
|
||||
const limit = req.params.limit || 20
|
||||
const skip = req.params.skip || 0
|
||||
const limit = req.query.limit || 20
|
||||
const skip = req.query.skip || 0
|
||||
const partial = !!req.query.partial
|
||||
const isAdmin = !!req.user.administrator
|
||||
const team = req.query.team
|
||||
let query = {}
|
||||
|
||||
if (!isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
try {
|
||||
if (team) {
|
||||
query = { team }
|
||||
}
|
||||
|
||||
User.count({}).then((total) => {
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const total = await User.count({})
|
||||
let users = []
|
||||
let cursor = User.find({}).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
let cursor = User.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
return doc.toClient(req.user)
|
||||
})
|
||||
|
||||
@@ -66,56 +71,64 @@ export class UserRoutes {
|
||||
})
|
||||
})
|
||||
cursor.on('error', (err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
throw err
|
||||
})
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
}
|
||||
|
||||
getUser(req, res, next) {
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const isSelf = (_id === req.user._id)
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// User can see themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
User.findById(_id).then((user) => {
|
||||
if (!user) {
|
||||
return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`))
|
||||
}
|
||||
|
||||
res.json(user.toClient(req.user))
|
||||
}).catch((err) => {
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
createUser(req, res, next) {
|
||||
async getUser(req, res, next) {
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const isSelf = (_id === req.user._id)
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
if (!isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
try {
|
||||
// User can see themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const user = await User.findById(_id)
|
||||
|
||||
if (!user) {
|
||||
return Promise.reject(createError.NotFound(`User with _id ${_id} was not found`))
|
||||
}
|
||||
|
||||
res.json(user.toClient(req.user))
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createUser(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
try {
|
||||
if (!isAdmin) {
|
||||
throw new createError.Forbidden()
|
||||
}
|
||||
|
||||
let User = this.db.User
|
||||
let user = new User(req.body)
|
||||
|
||||
// Add email confirmation required token
|
||||
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||
|
||||
let User = this.db.User
|
||||
let user = new User(req.body)
|
||||
// Add email confirmation required token
|
||||
util.promisify(crypto.randomBytes)(32).then((buf) => {
|
||||
user.emailToken = {
|
||||
value: urlSafeBase64.encode(buf),
|
||||
created: new Date()
|
||||
}
|
||||
return user.save()
|
||||
}).then((savedUser) => {
|
||||
const savedUser = await user.save()
|
||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||
const senderFullName = `${req.user.firstName} ${req.user.lastName}`
|
||||
const siteUrl = url.parse(req.headers.referer)
|
||||
@@ -130,137 +143,70 @@ export class UserRoutes {
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
}
|
||||
|
||||
res.json(savedUser.toClient())
|
||||
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve()
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request('dar-email', 'sendEmail', msg)
|
||||
}
|
||||
} catch(err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateUser(req, res, next) {
|
||||
async updateUser(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
// Do this here because Mongoose will add it automatically otherwise
|
||||
if (!req.body._id) {
|
||||
return next(createError.BadRequest('No user _id given in body'))
|
||||
}
|
||||
|
||||
const isSelf = (req.body._id === req.user._id.toString())
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
const User = this.db.User
|
||||
let userUpdates = null
|
||||
|
||||
try {
|
||||
userUpdates = new User(req.body)
|
||||
} catch (err) {
|
||||
return next(createError.BadRequest('Invalid data'))
|
||||
}
|
||||
// Do this here because Mongoose will add it automatically otherwise
|
||||
if (!req.body._id) {
|
||||
throw createError.BadRequest('No user _id given in body')
|
||||
}
|
||||
|
||||
if (isSelf && !isAdmin) {
|
||||
return next(createError.BadRequest('Cannot modify own administrator level'))
|
||||
}
|
||||
const isSelf = (req.body._id === req.user._id.toString())
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
const User = this.db.User
|
||||
let userUpdates = null
|
||||
|
||||
try {
|
||||
userUpdates = new User(req.body)
|
||||
} catch (err) {
|
||||
throw createError.BadRequest('Invalid data')
|
||||
}
|
||||
|
||||
if (isSelf && !isAdmin) {
|
||||
throw createError.BadRequest('Cannot modify own administrator level')
|
||||
}
|
||||
|
||||
const foundUser = await User.findById(userUpdates._id)
|
||||
|
||||
User.findById(userUpdates._id).then((foundUser) => {
|
||||
if (!foundUser) {
|
||||
return Promise.reject(createError.NotFound(`User with _id ${user._id} was not found`))
|
||||
throw createError.NotFound(`User with _id ${user._id} was not found`)
|
||||
}
|
||||
|
||||
// We don't allow direct updates to the email field so remove it if present
|
||||
delete userUpdates.email
|
||||
|
||||
foundUser.merge(userUpdates)
|
||||
return foundUser.save()
|
||||
}).then((savedUser) => {
|
||||
const savedUser = await foundUser.save()
|
||||
|
||||
res.json(savedUser.toClient(req.user))
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
}
|
||||
|
||||
setImage(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
const { _id, imageId } = req.body
|
||||
|
||||
if (!_id || !imageId) {
|
||||
return next(createError.BadRequest('Must specify _id and imageId'))
|
||||
}
|
||||
|
||||
const isSelf = (_id === req.user._id.toString())
|
||||
|
||||
// User can change themselves, otherwise must be super user
|
||||
if (!isSelf && !isAdmin) {
|
||||
return next(new createError.Forbidden())
|
||||
}
|
||||
|
||||
const { bigSize = {}, smallSize = {} } = req.body
|
||||
|
||||
Promise.all([
|
||||
this.mq.request('dar-image', 'scaleImage', {
|
||||
newWidth: bigSize.width || 200,
|
||||
newHeight: bigSize.height || 200,
|
||||
scaleMode: 'aspectFill',
|
||||
inputAssetId: imageId,
|
||||
passback: {
|
||||
userId: _id,
|
||||
rooms: [ req.user._id ],
|
||||
routerName: 'userRoutes',
|
||||
funcName: 'completeSetBigImage'
|
||||
}
|
||||
}),
|
||||
this.mq.request('dar-image', 'scaleImage', {
|
||||
newWidth: smallSize.width || 25,
|
||||
newHeight: smallSize.height || 25,
|
||||
scaleMode: 'aspectFill',
|
||||
inputAssetId: imageId,
|
||||
passback: {
|
||||
userId: _id,
|
||||
rooms: [ req.user._id, 'users' ],
|
||||
routerName: 'userRoutes',
|
||||
funcName: 'completeSetSmallImage'
|
||||
}
|
||||
})
|
||||
]).then((correlationIds) => {
|
||||
res.json({ correlationIds })
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError('Unable to scale user images'))
|
||||
})
|
||||
}
|
||||
|
||||
completeSetBigImage(passback, error, data) {
|
||||
if (error || !passback.userId || !passback.rooms) {
|
||||
return
|
||||
}
|
||||
|
||||
const User = this.db.User
|
||||
|
||||
User.findByIdAndUpdate(passback.userId, { imageId: data.outputAssetId }).then((foundUser) => {
|
||||
if (foundUser) {
|
||||
this.ws.notify(passback.rooms, 'newProfileImage', { imageId: data.outputAssetId })
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.log.error(`Unable to notify [${passback.rooms.join(', ')}] of new image'`)
|
||||
})
|
||||
}
|
||||
|
||||
completeSetSmallImage(passback, error, data) {
|
||||
if (error || !passback.userId || !passback.rooms) {
|
||||
return
|
||||
}
|
||||
|
||||
const User = this.db.User
|
||||
|
||||
User.findByIdAndUpdate(passback.userId, { thumbnailImageId: data.outputAssetId }).then((foundUser) => {
|
||||
if (foundUser) {
|
||||
this.ws.notify(passback.rooms, 'newThumbnailImage', { imageId: data.outputAssetId })
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.log.error(`Unable to notify [${passback.rooms.join(', ')}] of new thumbnail image'`)
|
||||
})
|
||||
}
|
||||
|
||||
enterRoom(req, res, next) {
|
||||
@@ -273,19 +219,20 @@ export class UserRoutes {
|
||||
res.json({})
|
||||
}
|
||||
|
||||
deleteUser(req, res, next) {
|
||||
async deleteUser(req, res, next) {
|
||||
const isAdmin = req.user.administrator
|
||||
|
||||
if (!isAdmin) {
|
||||
return new createError.Forbidden()
|
||||
}
|
||||
try {
|
||||
if (!isAdmin) {
|
||||
throw createError.Forbidden()
|
||||
}
|
||||
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
let User = this.db.User
|
||||
const _id = req.params._id
|
||||
const deletedUser = await User.remove({ _id })
|
||||
|
||||
User.remove({ _id }).then((deletedUser) => {
|
||||
if (!deletedUser) {
|
||||
return next(createError.NotFound(`User with _id ${_id} was not found`))
|
||||
throw createError.NotFound(`User with _id ${_id} was not found`)
|
||||
}
|
||||
|
||||
const userFullName = `${deletedUser.firstName} ${deletedUser.lastName}`
|
||||
@@ -299,11 +246,17 @@ export class UserRoutes {
|
||||
supportEmail: this.supportEmail
|
||||
}
|
||||
}
|
||||
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve()
|
||||
}).then(() => {
|
||||
res.json({})
|
||||
}).catch((err) => {
|
||||
next(createError.InternalServerError(err.message))
|
||||
})
|
||||
|
||||
if (this.sendEmail) {
|
||||
await this.mq.request('dar-email', 'sendEmail', msg)
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof createError.HttpError) {
|
||||
next(err)
|
||||
} else {
|
||||
next(createError.InternalServerError(err.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,11 @@ export class WorkItemRoutes {
|
||||
|
||||
listWorkItems(req, res, next) {
|
||||
const WorkItem = this.db.WorkItem
|
||||
let limit = req.params.limit || 20
|
||||
let skip = req.params.skip || 0
|
||||
let partial = !!req.params.partial
|
||||
let branch = req.params.branch
|
||||
const limit = req.query.limit || 20
|
||||
const skip = req.query.skip || 0
|
||||
const partial = !!req.query.partial
|
||||
let query = {}
|
||||
|
||||
if (branch) {
|
||||
query.branch = branch
|
||||
}
|
||||
|
||||
WorkItem.count({}).then((total) => {
|
||||
let workItems = []
|
||||
let cursor = WorkItem.find(query).limit(limit).skip(skip).cursor().map((doc) => {
|
||||
|
||||
@@ -243,6 +243,9 @@ class API extends EventEmitter {
|
||||
listUsers() {
|
||||
return this.get('/users')
|
||||
}
|
||||
listUsersForTeam(teamId) {
|
||||
return this.get(`/users?team=${teamId}`)
|
||||
}
|
||||
createUser(user) {
|
||||
return this.post('/users', user)
|
||||
}
|
||||
@@ -280,7 +283,7 @@ class API extends EventEmitter {
|
||||
return this.post('/teams', team)
|
||||
}
|
||||
updateTeam(team) {
|
||||
this.put('/teams', team)
|
||||
return this.put('/teams', team)
|
||||
}
|
||||
deleteTeam(id) {
|
||||
return this.delete('/teams/' + id)
|
||||
|
||||
@@ -4,6 +4,7 @@ import autobind from 'autobind-decorator'
|
||||
import { Row, Column, BoundInput, BoundButton, List } from 'ui'
|
||||
import { FormBinder } from 'react-form-binder'
|
||||
import { sizeInfo } from 'ui/style'
|
||||
import { api } from 'src/API'
|
||||
|
||||
export class TeamForm extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -36,14 +37,23 @@ export class TeamForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.users = [
|
||||
{ id: 0, name: 'Meryll Streep' },
|
||||
{ id: 1, name: 'Scarlett Johansenn' },
|
||||
{ id: 2, name: 'John Lyon-Smith' },
|
||||
]
|
||||
|
||||
this.state = {
|
||||
binder: new FormBinder(props.team, TeamForm.bindings, this.props.onModifiedChanged)
|
||||
binder: new FormBinder(props.team, TeamForm.bindings, this.props.onModifiedChanged),
|
||||
users: []
|
||||
}
|
||||
|
||||
this.getUsersForTeam(props.team._id)
|
||||
}
|
||||
|
||||
@autobind
|
||||
getUsersForTeam(teamId) {
|
||||
if (teamId) {
|
||||
api.listUsersForTeam(teamId).then((list) => {
|
||||
list.items.sort((a, b) => (a.lastName.localeCompare(b.lastName)))
|
||||
this.setState({ users: list.items })
|
||||
}).catch(() => {
|
||||
this.setState({ users: [] })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +62,8 @@ export class TeamForm extends React.Component {
|
||||
this.setState({
|
||||
binder: new FormBinder(nextProps.team, TeamForm.bindings, nextProps.onModifiedChanged)
|
||||
})
|
||||
|
||||
this.getUsersForTeam(nextProps.team._id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +90,7 @@ export class TeamForm extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { binder } = this.state
|
||||
const { binder, users } = this.state
|
||||
|
||||
return (
|
||||
<form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='teamForm' onSubmit={this.handleSubmit}>
|
||||
@@ -93,10 +105,10 @@ export class TeamForm extends React.Component {
|
||||
<BoundInput label='Name' name='name' message='Must not be empty' binder={binder} />
|
||||
</Column.Item>
|
||||
<Column.Item height={300}>
|
||||
<List items={this.users} render={(item) => (
|
||||
<List.Item key={item.id}>
|
||||
<List items={users} render={(item) => (
|
||||
<List.Item key={item._id}>
|
||||
<List.Icon name='profile' size={sizeInfo.dropdownIconSize} />
|
||||
<List.Text>{item.name}</List.Text>
|
||||
<List.Text>{item.firstName + ' ' + item.lastName}</List.Text>
|
||||
</List.Item>
|
||||
)} />
|
||||
</Column.Item>
|
||||
|
||||
@@ -31,8 +31,8 @@ export class Teams extends Component {
|
||||
this.props.changeTitle('Teams')
|
||||
|
||||
api.listTeams().then((list) => {
|
||||
list.items.sort((teamA, teamB) => (teamA.lastName.localeCompare(teamB.lastName)))
|
||||
this.setState({ teams: list.items }) // TODO: <- Remove
|
||||
list.items.sort((teamA, teamB) => (teamA.name.localeCompare(teamB.name)))
|
||||
this.setState({ teams: list.items })
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
messageModal: {
|
||||
@@ -101,7 +101,7 @@ export class Teams extends Component {
|
||||
this.setState({
|
||||
waitModal: false,
|
||||
teams: this.state.teams.map((team) => (!team._id ? createdTeam : team)).sort((a, b) => (
|
||||
a.lastName < b.lastName ? -1 : a.lastName > b.lastName : 0
|
||||
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
||||
)),
|
||||
modified: false,
|
||||
selectedTeam: createdTeam
|
||||
|
||||
@@ -3,7 +3,9 @@ import PropTypes from 'prop-types'
|
||||
import autobind from 'autobind-decorator'
|
||||
import { regExpPattern } from 'regexp-pattern'
|
||||
import { api } from 'src/API'
|
||||
import { Row, Column, BoundInput, BoundButton, BoundCheckbox, BoundEmailIcon, DropdownList } from 'ui'
|
||||
import {
|
||||
Row, Column, BoundInput, BoundButton, BoundCheckbox, BoundEmailIcon, BoundDropdown,
|
||||
} from 'ui'
|
||||
import { FormBinder } from 'react-form-binder'
|
||||
import { sizeInfo } from 'ui/style'
|
||||
|
||||
@@ -40,6 +42,9 @@ export class UserForm extends React.Component {
|
||||
lastName: {
|
||||
isValid: (r, v) => (v !== '')
|
||||
},
|
||||
team: {
|
||||
isValid: true
|
||||
},
|
||||
administrator: {
|
||||
isValid: (r, v) => true,
|
||||
initValue: false,
|
||||
@@ -66,8 +71,21 @@ export class UserForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
binder: new FormBinder(this.props.user, UserForm.bindings, this.props.onModifiedChanged)
|
||||
binder: new FormBinder(props.user, UserForm.bindings, props.onModifiedChanged),
|
||||
teams: []
|
||||
}
|
||||
|
||||
this.getTeams()
|
||||
}
|
||||
|
||||
// TODO: This is not very efficient. Better to get the teams in User.js and pass them in
|
||||
// This however will always be up-to-date. Need to use the WebSocket to refresh.
|
||||
getTeams() {
|
||||
api.listTeams().then((list) => {
|
||||
this.setState({ teams: list.items.map((item) => ({ value: item._id, text: item.name, icon: 'team' })).sort((a, b) => (a.text.localeCompare(b.text))) })
|
||||
}).catch(() => {
|
||||
this.setState({ teams: [] })
|
||||
})
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
@@ -75,6 +93,8 @@ export class UserForm extends React.Component {
|
||||
this.setState({
|
||||
binder: new FormBinder(nextProps.user, UserForm.bindings, nextProps.onModifiedChanged)
|
||||
})
|
||||
|
||||
this.getTeams()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,17 +131,7 @@ export class UserForm extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { binder } = this.state
|
||||
const teams = [
|
||||
{ id: 1, name: 'Sign of the Times' },
|
||||
{ id: 2, name: 'Trash Monsters' },
|
||||
{ id: 3, name: 'The Bigger Picker Uppers' },
|
||||
{ id: 4, name: 'Carcass Masters' },
|
||||
{ id: 5, name: 'Dust Bunnies' },
|
||||
{ id: 6, name: 'Pavement Busters' },
|
||||
{ id: 7, name: 'Don\'t Hug That Tree' },
|
||||
{ id: 8, name: 'Broken Swingers' },
|
||||
]
|
||||
const { binder, teams } = this.state
|
||||
|
||||
return (
|
||||
<form style={{ width: '100%', height: '100%', overflow: 'scroll' }} id='userForm' onSubmit={this.handleSubmit}>
|
||||
@@ -155,12 +165,7 @@ export class UserForm extends React.Component {
|
||||
</Row>
|
||||
</Column.Item>
|
||||
<Column.Item>
|
||||
<DropdownList items={teams} render={(item) => (
|
||||
<DropdownList.Item key={item.id}>
|
||||
<DropdownList.Icon name='team' size={sizeInfo.dropdownIconSize} />
|
||||
<DropdownList.Text>{item.name}</DropdownList.Text>
|
||||
</DropdownList.Item>
|
||||
)} />
|
||||
<BoundDropdown name='team' label='Team' icon='team' items={teams} binder={binder} />
|
||||
</Column.Item>
|
||||
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||
<Column.Item minHeight={sizeInfo.buttonHeight}>
|
||||
|
||||
BIN
website/src/assets/icons/profile.png
Normal file
BIN
website/src/assets/icons/profile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,9 +1,17 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 129 129">
|
||||
<g>
|
||||
<g>
|
||||
<path d="m64.3,71.6c18,0 32.6-14.6 32.6-32.6s-14.6-32.5-32.6-32.5-32.6,14.6-32.6,32.5 14.6,32.6 32.6,32.6zm0-56.6c13.2,0 24,10.8 24,24s-10.8,24-24,24-24-10.8-24-24 10.8-24 24-24z"/>
|
||||
<path d="m7.9,122.5h113.2c2.4,0 4.3-1.9 4.3-4.3 0-22.5-18.3-40.9-40.9-40.9h-40c-22.5,0-40.9,18.3-40.9,40.9-1.33227e-15,2.4 1.9,4.3 4.3,4.3zm36.6-36.6h40c16.4,0 29.9,12.2 32,28h-104c2.1-15.7 15.6-28 32-28z"/>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="55px" height="52px" viewBox="0 0 55 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49.2 (51160) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>profile</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Home" transform="translate(-147.000000, -479.000000)">
|
||||
<g id="profile" transform="translate(147.000000, 479.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 1.7 KiB |
@@ -1,24 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="240px" height="240px" viewBox="0 0 240 240" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<!-- Generator: Sketch 49.2 (51160) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>users</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Login" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Home" transform="translate(-158.000000, -105.000000)" fill="#000000" fill-rule="nonzero">
|
||||
<g id="Home" transform="translate(-158.000000, -105.000000)">
|
||||
<g id="Users" transform="translate(122.000000, 94.000000)">
|
||||
<g id="users" transform="translate(36.000000, 11.000000)">
|
||||
<g id="profile" transform="translate(16.000000, 85.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Shape"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Shape"></path>
|
||||
<g id="profile" transform="translate(93.000000, 85.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<g id="profile-copy" transform="translate(170.000000, 85.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Shape"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Shape"></path>
|
||||
<g id="profile-copy" transform="translate(31.000000, 85.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<g id="profile-copy-2" transform="translate(93.000000, 85.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Shape"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Shape"></path>
|
||||
<g id="profile-copy-2" transform="translate(156.000000, 85.000000)">
|
||||
<path d="M28,29 C36.2822086,29 43,22.4961598 43,14.4777266 C43,6.45929339 36.2822086,0 28,0 C19.7177914,0 13,6.50384025 13,14.4777266 C13,22.4516129 19.7177914,29 28,29 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M28,3.78648233 C34.0736196,3.78648233 39.0429448,8.59754224 39.0429448,14.4777266 C39.0429448,20.3579109 34.0736196,25.1689708 28,25.1689708 C21.9263804,25.1689708 16.9570552,20.3579109 16.9570552,14.4777266 C16.9570552,8.59754224 21.9263804,3.78648233 28,3.78648233 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M1.94170772,52 L53.0582923,52 C54.1420361,52 55,51.159292 55,50.0973451 C55,40.1415929 46.7364532,32 36.5311987,32 L18.4688013,32 C8.30870279,32 0,40.0973451 0,50.0973451 C-6.01598683e-16,51.159292 0.857963875,52 1.94170772,52 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||
<path d="M18.4688013,35.8053097 L36.5311987,35.8053097 C43.9367816,35.8053097 50.0328407,41.2035398 50.9811166,48.1946903 L4.01888342,48.1946903 C4.96715928,41.2477876 11.0632184,35.8053097 18.4688013,35.8053097 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -41,9 +41,13 @@ export class BoundButton extends React.Component {
|
||||
const { name, text, submit, width, onClick } = this.props
|
||||
const { visible, disabled } = this.state
|
||||
|
||||
if (!visible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Button disabled={disabled} submit={submit} onClick={onClick}
|
||||
name={name} visible={visible} text={text} width={width} />
|
||||
name={name} text={text} width={width} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,12 @@ export class BoundCheckbox extends React.Component {
|
||||
const { name, label } = this.props
|
||||
const { visible, disabled, value } = this.state
|
||||
|
||||
if (!visible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={visible ? {} : { display: 'none' }}>
|
||||
<div>
|
||||
<Checkbox id={name} disabled={disabled} value={value} onChange={this.handleChange} />
|
||||
<label
|
||||
htmlFor={name}
|
||||
|
||||
75
website/src/ui/BoundDropdown.js
Normal file
75
website/src/ui/BoundDropdown.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Dropdown } from 'ui'
|
||||
import autobind from 'autobind-decorator'
|
||||
import { sizeInfo, fontInfo } from 'ui/style'
|
||||
|
||||
export class BoundDropdown extends React.Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
binder: PropTypes.object.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, text: PropTypes.string, icon: PropTypes.string })),
|
||||
icon: PropTypes.string,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = props.binder.getFieldState(props.name)
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleChange(item) {
|
||||
const { binder, name } = this.props
|
||||
const state = binder.getFieldState(name)
|
||||
|
||||
if (!state.readOnly && !state.disabled) {
|
||||
this.setState(binder.updateFieldValue(name, item.value))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.binder !== this.props.binder) {
|
||||
this.setState(nextProps.binder.getFieldState(nextProps.name))
|
||||
}
|
||||
|
||||
if (nextProps.items !== this.props.items) {
|
||||
this.forceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, label, icon, items } = this.props
|
||||
const { visible, disabled, value } = this.state
|
||||
|
||||
if (!visible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={name}
|
||||
style={{
|
||||
fontSize: fontInfo.size.medium,
|
||||
fontFamily: fontInfo.family,
|
||||
color: disabled ? fontInfo.color.dimmed : fontInfo.color.normal,
|
||||
}}>
|
||||
{label}
|
||||
<Dropdown
|
||||
items={items}
|
||||
value={value}
|
||||
emptyItem={{ value: null, text: '', icon: 'team' }}
|
||||
icon={icon}
|
||||
onChange={this.handleChange}
|
||||
render={(item) => (
|
||||
<Dropdown.Item key={item.value}>
|
||||
<Dropdown.Icon name={item.icon} size={sizeInfo.dropdownIconSize} />
|
||||
<Dropdown.Text>{item.text}</Dropdown.Text>
|
||||
</Dropdown.Item>
|
||||
)} />
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,16 @@ export class BoundInput extends React.Component {
|
||||
const { label, password, name, placeholder, message } = this.props
|
||||
const { visible, disabled, value, valid } = this.state
|
||||
|
||||
if (!visible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<Text>{label}</Text>
|
||||
<br />
|
||||
<Input value={value}
|
||||
visible={visible}
|
||||
visible
|
||||
disabled={disabled}
|
||||
password={password}
|
||||
name={name}
|
||||
|
||||
@@ -8,17 +8,37 @@ import upArrowImage from './images/up-arrow.svg'
|
||||
import autobind from 'autobind-decorator'
|
||||
|
||||
@Radium
|
||||
export class DropdownList extends Component {
|
||||
export class Dropdown extends Component {
|
||||
static propTypes = {
|
||||
items: PropTypes.array,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, text: PropTypes.string, icon: PropTypes.string })),
|
||||
value: PropTypes.string,
|
||||
render: PropTypes.func.isRequired,
|
||||
emptyItem: PropTypes.shape({ value: PropTypes.string, text: PropTypes.string, icon: PropTypes.string }),
|
||||
onChange: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
emptyItem: { value: null, text: '', icon: null },
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
open: false,
|
||||
selectedItem: (props.items && props.items.length > 0) ? props.items[0] : {},
|
||||
selectedItem: this.getSelectedFromValue(props.items, props.value)
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
getSelectedFromValue(items, value) {
|
||||
return items.find((item) => (item.value === value)) || this.props.emptyItem
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.value !== this.props.value || nextProps.items !== this.props.items) {
|
||||
this.setState({
|
||||
selectedItem: this.getSelectedFromValue(nextProps.items, nextProps.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +50,9 @@ export class DropdownList extends Component {
|
||||
@autobind
|
||||
handleItemClick(e, item) {
|
||||
this.setState({ selectedItem: item, open: false })
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(item)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -100,7 +123,7 @@ export class DropdownList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DropdownList.Item = Radium(class ListItem extends Component {
|
||||
Dropdown.Item = Radium(class ListItem extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
active: PropTypes.bool,
|
||||
@@ -124,7 +147,7 @@ DropdownList.Item = Radium(class ListItem extends Component {
|
||||
}
|
||||
})
|
||||
|
||||
DropdownList.Icon = Radium(class DropdownIcon extends Component {
|
||||
Dropdown.Icon = Radium(class DropdownIcon extends Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
@@ -149,7 +172,7 @@ DropdownList.Icon = Radium(class DropdownIcon extends Component {
|
||||
}
|
||||
})
|
||||
|
||||
DropdownList.Text = Radium(class DropdownText extends Component {
|
||||
Dropdown.Text = Radium(class DropdownText extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export { Text } from './Text'
|
||||
export { Link } from './Link'
|
||||
export { Icon } from './Icon'
|
||||
export { List } from './List'
|
||||
export { DropdownList } from './DropdownList'
|
||||
export { Dropdown } from './Dropdown'
|
||||
export { Modal } from './Modal'
|
||||
export { Dimmer } from './Dimmer'
|
||||
export { Loader } from './Loader'
|
||||
@@ -21,3 +21,4 @@ export { BoundButton } from './BoundButton'
|
||||
export { BoundCheckbox } from './BoundCheckbox'
|
||||
export { BoundInput } from './BoundInput'
|
||||
export { BoundEmailIcon } from './BoundEmailIcon'
|
||||
export { BoundDropdown } from './BoundDropdown'
|
||||
|
||||
Reference in New Issue
Block a user