Refactor UI, Add ability to remove suggestions
This commit is contained in:
parent
d7adb27c10
commit
0400dd06b6
@ -19,7 +19,7 @@ var error_codes = map[int]string{
|
|||||||
5: "Attendee Exists",
|
5: "Attendee Exists",
|
||||||
6: "Suggestion Exists",
|
6: "Suggestion Exists",
|
||||||
7: "Attendee In Use",
|
7: "Attendee In Use",
|
||||||
// 5: "Connection Failed", //client-side error
|
8: "Suggestion Not Last",
|
||||||
999: "Internal Error",
|
999: "Internal Error",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,19 @@ function suggestionCreated(suggestion) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeSuggestion() {
|
||||||
|
return {
|
||||||
|
type: SuggestionConstants.REMOVE_SUGGESTION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function suggestionRemoved(suggestionId) {
|
||||||
|
return {
|
||||||
|
type: SuggestionConstants.SUGGESTION_REMOVED,
|
||||||
|
suggestionId: suggestionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fetchPopularSuggestions() {
|
function fetchPopularSuggestions() {
|
||||||
return {
|
return {
|
||||||
type: SuggestionConstants.FETCH_POPULAR_SUGGESTIONS
|
type: SuggestionConstants.FETCH_POPULAR_SUGGESTIONS
|
||||||
@ -129,8 +142,33 @@ function fetchPopular() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remove(suggestion) {
|
||||||
|
return function(dispatch) {
|
||||||
|
dispatch(removeSuggestion());
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "DELETE",
|
||||||
|
dataType: "json",
|
||||||
|
url: "suggestion/"+suggestion.SuggestionId+"/",
|
||||||
|
success: function(data, status, jqXHR) {
|
||||||
|
var e = new Error();
|
||||||
|
e.fromJSON(data);
|
||||||
|
if (e.isError()) {
|
||||||
|
ErrorActions.serverError(e);
|
||||||
|
} else {
|
||||||
|
dispatch(suggestionRemoved(suggestion.SuggestionId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(jqXHR, status, error) {
|
||||||
|
ErrorActions.ajaxError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fetchAll: fetchAll,
|
fetchAll: fetchAll,
|
||||||
create: create,
|
create: create,
|
||||||
|
remove: remove,
|
||||||
fetchPopular: fetchPopular
|
fetchPopular: fetchPopular
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ var ReactBootstrap = require('react-bootstrap');
|
|||||||
var Grid = ReactBootstrap.Grid;
|
var Grid = ReactBootstrap.Grid;
|
||||||
var Row = ReactBootstrap.Row;
|
var Row = ReactBootstrap.Row;
|
||||||
var Col = ReactBootstrap.Col;
|
var Col = ReactBootstrap.Col;
|
||||||
|
var FormGroup = ReactBootstrap.FormGroup;
|
||||||
var ControlLabel = ReactBootstrap.ControlLabel;
|
var ControlLabel = ReactBootstrap.ControlLabel;
|
||||||
var Button = ReactBootstrap.Button;
|
var Button = ReactBootstrap.Button;
|
||||||
|
|
||||||
@ -102,31 +103,38 @@ module.exports = React.createClass({
|
|||||||
var attendeeList = this.getAttendeeList();
|
var attendeeList = this.getAttendeeList();
|
||||||
var buttonDisabled = this.state.attendee == null || !this.state.suggestion;
|
var buttonDisabled = this.state.attendee == null || !this.state.suggestion;
|
||||||
return (
|
return (
|
||||||
<Grid><Row>
|
<Row>
|
||||||
<Col xs={4}>
|
|
||||||
<Combobox
|
|
||||||
value={this.state.attendee}
|
|
||||||
data={attendeeList}
|
|
||||||
valueField='AttendeeId'
|
|
||||||
textField='Name'
|
|
||||||
onChange={this.onChangeAttendee} />
|
|
||||||
</Col>
|
|
||||||
<Col xs={1}>
|
|
||||||
</Col>
|
|
||||||
<Col xs={4}>
|
<Col xs={4}>
|
||||||
|
<FormGroup controlId="newsuggestionrestaurantname">
|
||||||
|
<ControlLabel>Add Suggestion/Veto To:</ControlLabel>
|
||||||
<Combobox
|
<Combobox
|
||||||
value={this.state.suggestion}
|
value={this.state.suggestion}
|
||||||
data={this.unusedPopularSuggestions()}
|
data={this.unusedPopularSuggestions()}
|
||||||
valueField='RestaurantName'
|
valueField='RestaurantName'
|
||||||
textField='RestaurantName'
|
textField='RestaurantName'
|
||||||
onChange={this.onChangeSuggestion} />
|
onChange={this.onChangeSuggestion} />
|
||||||
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={1}>
|
<Col xs={1}></Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<FormGroup controlId="newsuggestionby">
|
||||||
|
<ControlLabel>Suggested By:</ControlLabel>
|
||||||
|
<Combobox
|
||||||
|
value={this.state.attendee}
|
||||||
|
data={attendeeList}
|
||||||
|
valueField='AttendeeId'
|
||||||
|
textField='Name'
|
||||||
|
onChange={this.onChangeAttendee} />
|
||||||
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col xs={1}></Col>
|
||||||
<Col xs={2}>
|
<Col xs={2}>
|
||||||
<Button bsStyle="success" disabled={buttonDisabled} onClick={this.onAddSuggestion}>Add Suggestion/Veto</Button>
|
<FormGroup controlId="addnewsuggestionbutton">
|
||||||
|
<ControlLabel><br /></ControlLabel>
|
||||||
|
<Button bsStyle="success" disabled={buttonDisabled} onClick={this.onAddSuggestion}>Add</Button>
|
||||||
|
</FormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
</Row></Grid>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,8 @@ var ControlLabel = ReactBootstrap.ControlLabel;
|
|||||||
var Grid = ReactBootstrap.Grid;
|
var Grid = ReactBootstrap.Grid;
|
||||||
var Row = ReactBootstrap.Row;
|
var Row = ReactBootstrap.Row;
|
||||||
var Col = ReactBootstrap.Col;
|
var Col = ReactBootstrap.Col;
|
||||||
|
var Panel = ReactBootstrap.Panel;
|
||||||
|
var Button = ReactBootstrap.Button;
|
||||||
|
|
||||||
var Multiselect = require('react-widgets').Multiselect;
|
var Multiselect = require('react-widgets').Multiselect;
|
||||||
|
|
||||||
@ -55,22 +57,47 @@ module.exports = React.createClass({
|
|||||||
var attendeeList = this.getAttendeeList();
|
var attendeeList = this.getAttendeeList();
|
||||||
|
|
||||||
var suggestionIds = Object.keys(this.props.suggestions);
|
var suggestionIds = Object.keys(this.props.suggestions);
|
||||||
suggestionIds.sort(function(a, b){return parseInt(a, 10) - parseInt(b, 10);});
|
suggestionIds.sort(function(a, b){return parseInt(b, 10) - parseInt(a, 10);});
|
||||||
var suggestions = [];
|
var suggestions = [];
|
||||||
for (var i in suggestionIds) {
|
for (var i in suggestionIds) {
|
||||||
var suggestion = this.props.suggestions[suggestionIds[i]];
|
var suggestion = this.props.suggestions[suggestionIds[i]];
|
||||||
|
var suggestorName = "Unknown";
|
||||||
|
if (this.props.attendees.hasOwnProperty(suggestion.AttendeeId))
|
||||||
|
suggestorName = this.props.attendees[suggestion.AttendeeId].Name;
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
var self = this;
|
||||||
|
var popFunction = function(){
|
||||||
|
return function(){self.props.removeSuggestion(suggestion);};
|
||||||
|
}();
|
||||||
|
suggestions.push((
|
||||||
|
<Row key={suggestion.SuggestionId}><Panel>
|
||||||
|
<Col xs={4}><ControlLabel>Current Suggestion:</ControlLabel></Col>
|
||||||
|
<Col xs={1}></Col>
|
||||||
|
<Col xs={4}>{suggestion.RestaurantName} (by {suggestorName})</Col>
|
||||||
|
<Col xs={1}></Col>
|
||||||
|
<Col xs={2}>
|
||||||
|
<Button bsStyle="danger" onClick={popFunction}>Pop</Button>
|
||||||
|
</Col>
|
||||||
|
</Panel></Row>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
suggestions.push((
|
suggestions.push((
|
||||||
<Row key={suggestion.SuggestionId}>
|
<Row key={suggestion.SuggestionId}>
|
||||||
<Col xs={4}>{this.props.attendees[suggestion.AttendeeId].Name}</Col>
|
<Col xs={4}><ControlLabel>{(suggestionIds.length - i).toString() + "."}</ControlLabel></Col>
|
||||||
<Col xs={1}></Col>
|
<Col xs={1}></Col>
|
||||||
<Col xs={4}>{suggestion.RestaurantName}</Col>
|
<Col xs={4}>{suggestion.RestaurantName} (by {suggestorName})</Col>
|
||||||
|
<Col xs={1}></Col>
|
||||||
|
<Col xs={2}>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div><form>
|
<div><form>
|
||||||
<Grid><Row><Col xs={12}>
|
<Grid fluid><Row><Col xs={12}>
|
||||||
<FormGroup controlId="attendees">
|
<FormGroup controlId="attendees">
|
||||||
<ControlLabel>Attendees</ControlLabel>
|
<ControlLabel>Attendees</ControlLabel>
|
||||||
<Multiselect
|
<Multiselect
|
||||||
@ -83,20 +110,13 @@ module.exports = React.createClass({
|
|||||||
onCreate={this.onCreateAttendee} />
|
onCreate={this.onCreateAttendee} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Col></Row>
|
</Col></Row>
|
||||||
<Row>
|
|
||||||
<Col xs={4}><ControlLabel>Suggested By:</ControlLabel></Col>
|
|
||||||
<Col xs={1}></Col>
|
|
||||||
<Col xs={4}><ControlLabel>Restaurant Name:</ControlLabel></Col>
|
|
||||||
</Row>
|
|
||||||
{suggestions}
|
|
||||||
</Grid>
|
|
||||||
<FormGroup controlId="newsuggestion">
|
|
||||||
<NewSuggestion
|
<NewSuggestion
|
||||||
createSuggestion={this.props.createSuggestion}
|
createSuggestion={this.props.createSuggestion}
|
||||||
attendees={this.props.attendees}
|
attendees={this.props.attendees}
|
||||||
suggestions={this.props.suggestions}
|
suggestions={this.props.suggestions}
|
||||||
popularSuggestions={this.props.popularSuggestions} />
|
popularSuggestions={this.props.popularSuggestions} />
|
||||||
</FormGroup>
|
{suggestions}
|
||||||
|
</Grid>
|
||||||
</form></div>
|
</form></div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ module.exports = keyMirror({
|
|||||||
SUGGESTIONS_FETCHED: null,
|
SUGGESTIONS_FETCHED: null,
|
||||||
CREATE_SUGGESTION: null,
|
CREATE_SUGGESTION: null,
|
||||||
SUGGESTION_CREATED: null,
|
SUGGESTION_CREATED: null,
|
||||||
|
REMOVE_SUGGESTION: null,
|
||||||
|
SUGGESTION_REMOVED: null,
|
||||||
FETCH_POPULAR_SUGGESTIONS: null,
|
FETCH_POPULAR_SUGGESTIONS: null,
|
||||||
POPULAR_SUGGESTIONS_FETCHED: null,
|
POPULAR_SUGGESTIONS_FETCHED: null,
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,8 @@ function mapDispatchToProps(dispatch) {
|
|||||||
return {
|
return {
|
||||||
createAttendee: function(attendee) {dispatch(AttendeeActions.create(attendee))},
|
createAttendee: function(attendee) {dispatch(AttendeeActions.create(attendee))},
|
||||||
removeAttendee: function(attendee) {dispatch(AttendeeActions.remove(attendee))},
|
removeAttendee: function(attendee) {dispatch(AttendeeActions.remove(attendee))},
|
||||||
createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))}
|
createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))},
|
||||||
|
removeSuggestion: function(suggestion) {dispatch(SuggestionActions.remove(suggestion))}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@ module.exports = function(state = {}, action) {
|
|||||||
[suggestion.SuggestionId]: suggestion
|
[suggestion.SuggestionId]: suggestion
|
||||||
});
|
});
|
||||||
return suggestions;
|
return suggestions;
|
||||||
|
case SuggestionConstants.SUGGESTION_REMOVED:
|
||||||
|
var suggestions = assign({}, state);
|
||||||
|
delete suggestions[action.suggestionId];
|
||||||
|
return suggestions;
|
||||||
case UserConstants.USER_LOGGEDOUT:
|
case UserConstants.USER_LOGGEDOUT:
|
||||||
return {};
|
return {};
|
||||||
default:
|
default:
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<html>
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Lunch</title>
|
<title>Lunch</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">
|
||||||
|
@ -6,7 +6,7 @@ div#content {
|
|||||||
width: 95%;
|
width: 95%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 20em;
|
min-width: 20em;
|
||||||
max-width: 100em;
|
max-width: 50em;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,3 +16,8 @@ div.tab-content {
|
|||||||
border-left: 1px solid #ddd;
|
border-left: 1px solid #ddd;
|
||||||
border-right: 1px solid #ddd;
|
border-right: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Make panels inside row line up with other rows by eliminating padding */
|
||||||
|
div.row div.panel div.panel-body {
|
||||||
|
padding: 15px 0 15px 0
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"gopkg.in/gorp.v1"
|
"gopkg.in/gorp.v1"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -52,6 +53,11 @@ func (aeu SuggestionExistsError) Error() string {
|
|||||||
return "Suggestion exists"
|
return "Suggestion exists"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SuggestionNotLastError struct{}
|
||||||
|
|
||||||
|
func (snle SuggestionNotLastError) Error() string {
|
||||||
|
return "Suggestion not last on the stack"
|
||||||
|
}
|
||||||
func (s *PopularSuggestion) Write(w http.ResponseWriter) error {
|
func (s *PopularSuggestion) Write(w http.ResponseWriter) error {
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
return enc.Encode(s)
|
return enc.Encode(s)
|
||||||
@ -82,6 +88,26 @@ func GetSuggestions(userid int64, date time.Time) (*[]*Suggestion, error) {
|
|||||||
return &suggestions, nil
|
return &suggestions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSuggestion(suggestionid int64, userid int64, date time.Time) (*Suggestion, error) {
|
||||||
|
var s Suggestion
|
||||||
|
|
||||||
|
err := DB.SelectOne(&s, "SELECT * from suggestions where UserId=? AND SuggestionId=? AND Date=?", userid, suggestionid, date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VetoedBy(transaction *gorp.Transaction, suggestion *Suggestion) (*[]*Suggestion, error) {
|
||||||
|
var suggestions []*Suggestion
|
||||||
|
|
||||||
|
_, err := transaction.Select(&suggestions, "SELECT * from suggestions WHERE UserId=? AND Date=? AND VetoingId=?", suggestion.UserId, suggestion.Date, suggestion.SuggestionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &suggestions, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetPopularSuggestions() (*[]*PopularSuggestion, error) {
|
func GetPopularSuggestions() (*[]*PopularSuggestion, error) {
|
||||||
var suggestions []*Suggestion
|
var suggestions []*Suggestion
|
||||||
suggestionMap := make(map[string]int64)
|
suggestionMap := make(map[string]int64)
|
||||||
@ -135,6 +161,42 @@ func InsertSuggestion(s *Suggestion) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteSuggestion(s *Suggestion) error {
|
||||||
|
transaction, err := DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure suggestion hasn't been vetoed
|
||||||
|
suggestions, err := VetoedBy(transaction, s)
|
||||||
|
if err != nil {
|
||||||
|
transaction.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(*suggestions) > 0 {
|
||||||
|
transaction.Rollback()
|
||||||
|
return SuggestionNotLastError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := transaction.Delete(s)
|
||||||
|
if err != nil {
|
||||||
|
transaction.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
transaction.Rollback()
|
||||||
|
return errors.New("Was going to delete more than one suggestion")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = transaction.Commit()
|
||||||
|
if err != nil {
|
||||||
|
transaction.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
|
func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := GetUserFromSession(r)
|
user, err := GetUserFromSession(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,8 +258,33 @@ func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Print(err)
|
log.Print(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if r.Method == "DELETE" {
|
||||||
|
suggestionid, err := GetURLID(r.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, 3 /* Invalid Request */)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestion, err := GetSuggestion(suggestionid, user.UserId, today)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, 3 /*Invalid Request*/)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DeleteSuggestion(suggestion)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(SuggestionNotLastError); ok {
|
||||||
|
WriteError(w, 8 /*Suggestion Not Last*/)
|
||||||
} else {
|
} else {
|
||||||
/* No PUT or DELETE */
|
WriteError(w, 999 /*Internal Error*/)
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSuccess(w)
|
||||||
|
} else {
|
||||||
|
/* No PUT */
|
||||||
WriteError(w, 3 /*Invalid Request*/)
|
WriteError(w, 3 /*Invalid Request*/)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user