Add full ability to add/delete attendees

This commit is contained in:
Aaron Lindsay 2016-12-28 09:25:20 -05:00
parent 49893ffdb6
commit 350a30715a
15 changed files with 292 additions and 28 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"log" "log"
"net/http" "net/http"
"strings" "strings"
@ -71,7 +72,7 @@ func GetAttendees(userid int64, date time.Time) (*[]*Attendee, error) {
func GetPopularAttendees() (*[]*PopularAttendee, error) { func GetPopularAttendees() (*[]*PopularAttendee, error) {
var attendees []*Attendee var attendees []*Attendee
var attendeeMap map[string]int64 attendeeMap := make(map[string]int64)
popularAttendees := make([]*PopularAttendee, 0) popularAttendees := make([]*PopularAttendee, 0)
_, err := DB.Select(&attendees, "SELECT * from attendees") _, err := DB.Select(&attendees, "SELECT * from attendees")
@ -98,7 +99,7 @@ func InsertAttendee(a *Attendee) error {
return err return err
} }
existing, err := transaction.SelectInt("SELECT count(*) from users where Name=?", a.Name) existing, err := transaction.SelectInt("SELECT count(*) from attendees where UserId=? AND Name=? AND Date=?", a.UserId, a.Name, a.Date)
if err != nil { if err != nil {
transaction.Rollback() transaction.Rollback()
return err return err
@ -123,6 +124,41 @@ func InsertAttendee(a *Attendee) error {
return nil return nil
} }
func GetAttendee(attendeeid int64, userid int64, date time.Time) (*Attendee, error) {
var a Attendee
err := DB.SelectOne(&a, "SELECT * from attendees where UserId=? AND AttendeeId=? AND Date=?", userid, attendeeid, date)
if err != nil {
return nil, err
}
return &a, nil
}
func DeleteAttendee(a *Attendee) error {
transaction, err := DB.Begin()
if err != nil {
return err
}
count, err := transaction.Delete(a)
if err != nil {
transaction.Rollback()
return err
}
if count != 1 {
transaction.Rollback()
return errors.New("Was going to delete more than one attendee")
}
err = transaction.Commit()
if err != nil {
transaction.Rollback()
return err
}
return nil
}
func AttendeeHandler(w http.ResponseWriter, r *http.Request) { func AttendeeHandler(w http.ResponseWriter, r *http.Request) {
user, err := GetUserFromSession(r) user, err := GetUserFromSession(r)
if err != nil { if err != nil {
@ -184,8 +220,29 @@ func AttendeeHandler(w http.ResponseWriter, r *http.Request) {
log.Print(err) log.Print(err)
return return
} }
} else if r.Method == "DELETE" {
attendeeid, err := GetURLID(r.URL.Path)
if err != nil {
WriteError(w, 3 /* Invalid Request */)
return
}
attendee, err := GetAttendee(attendeeid, user.UserId, today)
if err != nil {
WriteError(w, 3 /*Invalid Request*/)
return
}
err = DeleteAttendee(attendee)
if err != nil {
WriteError(w, 999 /*Internal Error*/)
log.Print(err)
return
}
WriteSuccess(w)
} else { } else {
/* No PUT or DELETE */ /* No PUT */
WriteError(w, 3 /*Invalid Request*/) WriteError(w, 3 /*Invalid Request*/)
return return
} }

View File

@ -2,8 +2,8 @@ var AttendeeConstants = require('../constants/AttendeeConstants');
var ErrorActions = require('./ErrorActions'); var ErrorActions = require('./ErrorActions');
var models = require('../models.js');
var Attendee = models.Attendee; var Attendee = models.Attendee;
var PopularAttendee = models.PopularAttendee;
var Error = models.Error; var Error = models.Error;
function fetchAttendees() { function fetchAttendees() {
@ -32,6 +32,19 @@ function attendeeCreated(attendee) {
} }
} }
function removeAttendee() {
return {
type: AttendeeConstants.REMOVE_ATTENDEE
}
}
function attendeeRemoved(attendeeId) {
return {
type: AttendeeConstants.ATTENDEE_REMOVED,
attendeeId: attendeeId
}
}
function fetchPopularAttendees() { function fetchPopularAttendees() {
return { return {
type: AttendeeConstants.FETCH_POPULAR_ATTENDEES type: AttendeeConstants.FETCH_POPULAR_ATTENDEES
@ -114,8 +127,8 @@ function fetchPopular() {
if (e.isError()) { if (e.isError()) {
ErrorActions.serverError(e); ErrorActions.serverError(e);
} else { } else {
dispatch(popularAttendeesFetched(data.attendees.map(function(json) { dispatch(popularAttendeesFetched(data.popularattendees.map(function(json) {
var a = new Attendee(); var a = new PopularAttendee();
a.fromJSON(json); a.fromJSON(json);
return a; return a;
}))); })));
@ -128,8 +141,33 @@ function fetchPopular() {
}; };
} }
function remove(attendee) {
return function(dispatch) {
dispatch(removeAttendee());
$.ajax({
type: "DELETE",
dataType: "json",
url: "attendee/"+attendee.AttendeeId+"/",
success: function(data, status, jqXHR) {
var e = new Error();
e.fromJSON(data);
if (e.isError()) {
ErrorActions.serverError(e);
} else {
dispatch(attendeeRemoved(attendee.AttendeeId));
}
},
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
}; };

View File

@ -1,6 +1,6 @@
var ErrorConstants = require('../constants/ErrorConstants'); var ErrorConstants = require('../constants/ErrorConstants');
var models = require('../models.js'); var models = require('../models');
var Error = models.Error; var Error = models.Error;
function serverError(error) { function serverError(error) {

View File

@ -2,8 +2,9 @@ var SuggestionConstants = require('../constants/SuggestionConstants');
var ErrorActions = require('./ErrorActions'); var ErrorActions = require('./ErrorActions');
var models = require('../models.js'); var models = require('../models');
var Suggestion = models.Suggestion; var Suggestion = models.Suggestion;
var PopularSuggestion = models.PopularSuggestion;
var Error = models.Error; var Error = models.Error;
function fetchSuggestions() { function fetchSuggestions() {
@ -114,8 +115,8 @@ function fetchPopular() {
if (e.isError()) { if (e.isError()) {
ErrorActions.serverError(e); ErrorActions.serverError(e);
} else { } else {
dispatch(popularSuggestionsFetched(data.suggestions.map(function(json) { dispatch(popularSuggestionsFetched(data.popularsuggestions.map(function(json) {
var a = new Suggestion(); var a = new PopularSuggestion();
a.fromJSON(json); a.fromJSON(json);
return a; return a;
}))); })));

View File

@ -4,7 +4,7 @@ var AttendeeActions = require('./AttendeeActions');
var SuggestionActions = require('./SuggestionActions'); var SuggestionActions = require('./SuggestionActions');
var ErrorActions = require('./ErrorActions'); var ErrorActions = require('./ErrorActions');
var models = require('../models.js'); var models = require('../models');
var User = models.User; var User = models.User;
var Session = models.Session; var Session = models.Session;
var Error = models.Error; var Error = models.Error;

View File

@ -7,8 +7,9 @@ var Tab = ReactBootstrap.Tab;
var Modal = ReactBootstrap.Modal; var Modal = ReactBootstrap.Modal;
var TopBarContainer = require('../containers/TopBarContainer'); var TopBarContainer = require('../containers/TopBarContainer');
var NewUserForm = require('./NewUserForm'); var RecordLunchContainer = require('../containers/RecordLunchContainer');
var AccountSettingsModalContainer = require('../containers/AccountSettingsModalContainer'); var AccountSettingsModalContainer = require('../containers/AccountSettingsModalContainer');
var NewUserForm = require('./NewUserForm');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "LunchApp", displayName: "LunchApp",
@ -62,7 +63,8 @@ module.exports = React.createClass({
if (this.props.user.isUser()) if (this.props.user.isUser())
mainContent = ( mainContent = (
<Tabs defaultActiveKey={1} id='mainNavigationTabs'> <Tabs defaultActiveKey={1} id='mainNavigationTabs'>
<Tab title="New Entry" eventKey={1} >accounts <Tab title="Record Lunch" eventKey={1} >
<RecordLunchContainer />
</Tab> </Tab>
<Tab title="Statistics" eventKey={2} >stats will go here <Tab title="Statistics" eventKey={2} >stats will go here
</Tab> </Tab>

View File

@ -0,0 +1,71 @@
var React = require('react');
var ReactBootstrap = require('react-bootstrap');
var FormGroup = ReactBootstrap.FormGroup;
var ControlLabel = ReactBootstrap.ControlLabel;
var Multiselect = require('react-widgets').Multiselect;
var models = require('../models')
var Attendee = models.Attendee
module.exports = React.createClass({
displayName: "RecordLunch",
getInitialState: function() {
return {
};
},
getAttendeeList: function() {
var attendeeList = [];
for (var attendeeId in this.props.attendees) {
attendeeList.push(this.props.attendees[attendeeId]);
}
return attendeeList;
},
getAttendeeMap: function() {
var attendeeMap = {};
for (var attendeeId in this.props.attendees) {
var attendee = this.props.attendees[attendeeId];
attendeeMap[attendee.Name] = attendee;
}
return attendeeMap;
},
onChangeAttendees: function(attendees) {
var attendeeMap = this.getAttendeeMap();
for (var i in attendees) {
if (attendeeMap.hasOwnProperty(attendees[i].Name)) {
delete attendeeMap[attendees[i].Name];
} else {
var attendee = new Attendee();
attendee.Name = attendees[i].Name;
this.props.createAttendee(attendee);
}
}
for (var i in attendeeMap) {
this.props.removeAttendee(attendeeMap[i]);
}
},
onCreateAttendee: function(attendeeName) {
var attendee = new Attendee();
attendee.Name = attendeeName;
this.props.createAttendee(attendee);
},
render: function() {
var attendeeList = this.getAttendeeList();
return (
<div><form>
<FormGroup>
<ControlLabel>Attendees</ControlLabel>
<Multiselect
value={attendeeList}
data={this.props.popularAttendees}
valueField='Name'
textField='Name'
messages={{'createNew': "(create new attendee)"}}
onChange={this.onChangeAttendees}
onCreate={this.onCreateAttendee} />
</FormGroup>
</form></div>
);
}
});

View File

@ -5,6 +5,8 @@ module.exports = keyMirror({
ATTENDEES_FETCHED: null, ATTENDEES_FETCHED: null,
CREATE_ATTENDEE: null, CREATE_ATTENDEE: null,
ATTENDEE_CREATED: null, ATTENDEE_CREATED: null,
REMOVE_ATTENDEE: null,
ATTENDEE_REMOVED: null,
FETCH_POPULAR_ATTENDEES: null, FETCH_POPULAR_ATTENDEES: null,
POPULAR_ATTENDEES_FETCHED: null POPULAR_ATTENDEES_FETCHED: null
}); });

View File

@ -0,0 +1,29 @@
var connect = require('react-redux').connect;
var UserActions = require('../actions/UserActions');
var AttendeeActions = require('../actions/AttendeeActions');
var SuggestionActions = require('../actions/SuggestionActions');
var RecordLunch = require('../components/RecordLunch');
function mapStateToProps(state) {
return {
attendees: state.attendees,
popularAttendees: state.popularAttendees,
suggestions: state.suggestions,
popularSuggestions: state.popularSuggestions
}
}
function mapDispatchToProps(dispatch) {
return {
createAttendee: function(attendee) {dispatch(AttendeeActions.create(attendee))},
removeAttendee: function(attendee) {dispatch(AttendeeActions.remove(attendee))},
createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))}
}
}
module.exports = connect(
mapStateToProps,
mapDispatchToProps
)(RecordLunch)

View File

@ -101,6 +101,33 @@ Attendee.prototype.isAttendee = function() {
this.Name != empty_attendee.Name; this.Name != empty_attendee.Name;
} }
function PopularAttendee() {
this.Name = "";
this.Popularity = 0;
}
PopularAttendee.prototype.toJSON = function() {
var json_obj = {};
json_obj.Name = this.Name;
json_obj.Popularity = this.Popularity;
return JSON.stringify(json_obj);
}
PopularAttendee.prototype.fromJSON = function(json_input) {
var json_obj = getJSONObj(json_input);
if (json_obj.hasOwnProperty("Popularity"))
this.Popularity = json_obj.Popularity;
if (json_obj.hasOwnProperty("Name"))
this.Name = json_obj.Name;
}
PopularAttendee.prototype.isPopularAttendee = function() {
var empty_attendee = new PopularAttendee();
return this.Popularity != empty_attendee.Popularity ||
this.Name != empty_attendee.Name;
}
function Suggestion() { function Suggestion() {
this.SuggestionId = -1; this.SuggestionId = -1;
this.VetoingId = -1; this.VetoingId = -1;
@ -134,6 +161,33 @@ Suggestion.prototype.isSuggestion = function() {
this.RestaurantName != empty_suggestion.RestaurantName; this.RestaurantName != empty_suggestion.RestaurantName;
} }
function PopularSuggestion() {
this.RestaurantName = "";
this.Popularity = 0;
}
PopularSuggestion.prototype.toJSON = function() {
var json_obj = {};
json_obj.RestaurantName = this.RestaurantName;
json_obj.Popularity = this.Popularity;
return JSON.stringify(json_obj);
}
PopularSuggestion.prototype.fromJSON = function(json_input) {
var json_obj = getJSONObj(json_input);
if (json_obj.hasOwnProperty("RestaurantName"))
this.RestaurantName = json_obj.RestaurantName;
if (json_obj.hasOwnProperty("Popularity"))
this.Popularity = json_obj.Popularity;
}
PopularSuggestion.prototype.isPopularSuggestion = function() {
var empty_suggestion = new PopularSuggestion();
return this.RestaurantName != empty_suggestion.RestaurantName &&
this.Popularity != empty_suggestion.Popularity;
}
function Error() { function Error() {
this.ErrorId = -1; this.ErrorId = -1;
this.ErrorString = ""; this.ErrorString = "";
@ -167,6 +221,10 @@ module.exports = models = {
User: User, User: User,
Session: Session, Session: Session,
Error: Error, Error: Error,
Attendee: Attendee,
PopularAttendee: PopularAttendee,
Suggestion: Suggestion,
PopularSuggestion: PopularSuggestion,
// Constants // Constants
BogusPassword: "password" BogusPassword: "password"

View File

@ -18,6 +18,10 @@ module.exports = function(state = {}, action) {
[attendee.AttendeeId]: attendee [attendee.AttendeeId]: attendee
}); });
return attendees; return attendees;
case AttendeeConstants.ATTENDEE_REMOVED:
var attendees = assign({}, state);
delete attendees[action.attendeeId];
return attendees;
case UserConstants.USER_LOGGEDOUT: case UserConstants.USER_LOGGEDOUT:
return {}; return {};
default: default:

View File

@ -3,17 +3,17 @@ var assign = require('object-assign');
var AttendeeConstants = require('../constants/AttendeeConstants'); var AttendeeConstants = require('../constants/AttendeeConstants');
var UserConstants = require('../constants/UserConstants'); var UserConstants = require('../constants/UserConstants');
module.exports = function(state = {}, action) { module.exports = function(state = [], action) {
switch (action.type) { switch (action.type) {
case AttendeeConstants.POPULAR_ATTENDEES_FETCHED: case AttendeeConstants.POPULAR_ATTENDEES_FETCHED:
var attendees = {}; var attendees = [];
for (var i = 0; i < action.attendees.length; i++) { for (var i = 0; i < action.attendees.length; i++) {
var attendee = action.attendees[i]; attendees.push(action.attendees[i]);
attendees[attendee.AttendeeId] = attendee;
} }
attendees.sort(function(a, b){return a.Popularity - b.Popularity});
return attendees; return attendees;
case UserConstants.USER_LOGGEDOUT: case UserConstants.USER_LOGGEDOUT:
return {}; return [];
default: default:
return state; return state;
} }

View File

@ -3,17 +3,17 @@ var assign = require('object-assign');
var SuggestionConstants = require('../constants/SuggestionConstants'); var SuggestionConstants = require('../constants/SuggestionConstants');
var UserConstants = require('../constants/UserConstants'); var UserConstants = require('../constants/UserConstants');
module.exports = function(state = {}, action) { module.exports = function(state = [], action) {
switch (action.type) { switch (action.type) {
case SuggestionConstants.POPULAR_SUGGESTIONS_FETCHED: case SuggestionConstants.POPULAR_SUGGESTIONS_FETCHED:
var suggestions = {}; var suggestions = [];
for (var i = 0; i < action.suggestions.length; i++) { for (var i = 0; i < action.suggestions.length; i++) {
var suggestion = action.suggestions[i]; suggestions.push(action.suggestions[i]);
suggestions[suggestion.SuggestionId] = suggestion;
} }
suggestions.sort(function(a, b){return a.Popularity - b.Popularity});
return suggestions; return suggestions;
case UserConstants.USER_LOGGEDOUT: case UserConstants.USER_LOGGEDOUT:
return {}; return [];
default: default:
return state; return state;
} }

View File

@ -5,12 +5,14 @@ div#content {
display: block; display: block;
width: 95%; width: 95%;
height: 100%; height: 100%;
min-width: 75em; min-width: 20em;
max-width: 100em; max-width: 100em;
margin: auto; margin: auto;
} }
/* Keep the main windows sized to the full viewable height */ div.tab-content {
.fullheight { padding: 1em;
height: 100%; border-bottom: 1px solid #ddd;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
} }

View File

@ -73,7 +73,7 @@ func GetSuggestions(userid int64, date time.Time) (*[]*Suggestion, error) {
func GetPopularSuggestions() (*[]*PopularSuggestion, error) { func GetPopularSuggestions() (*[]*PopularSuggestion, error) {
var suggestions []*Suggestion var suggestions []*Suggestion
var suggestionMap map[string]int64 suggestionMap := make(map[string]int64)
popularSuggestions := make([]*PopularSuggestion, 0) popularSuggestions := make([]*PopularSuggestion, 0)
_, err := DB.Select(&suggestions, "SELECT * from suggestions") _, err := DB.Select(&suggestions, "SELECT * from suggestions")