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 (
"encoding/json"
"errors"
"log"
"net/http"
"strings"
@ -71,7 +72,7 @@ func GetAttendees(userid int64, date time.Time) (*[]*Attendee, error) {
func GetPopularAttendees() (*[]*PopularAttendee, error) {
var attendees []*Attendee
var attendeeMap map[string]int64
attendeeMap := make(map[string]int64)
popularAttendees := make([]*PopularAttendee, 0)
_, err := DB.Select(&attendees, "SELECT * from attendees")
@ -98,7 +99,7 @@ func InsertAttendee(a *Attendee) error {
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 {
transaction.Rollback()
return err
@ -123,6 +124,41 @@ func InsertAttendee(a *Attendee) error {
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) {
user, err := GetUserFromSession(r)
if err != nil {
@ -184,8 +220,29 @@ func AttendeeHandler(w http.ResponseWriter, r *http.Request) {
log.Print(err)
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 {
/* No PUT or DELETE */
/* No PUT */
WriteError(w, 3 /*Invalid Request*/)
return
}

View File

@ -2,8 +2,8 @@ var AttendeeConstants = require('../constants/AttendeeConstants');
var ErrorActions = require('./ErrorActions');
var models = require('../models.js');
var Attendee = models.Attendee;
var PopularAttendee = models.PopularAttendee;
var Error = models.Error;
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() {
return {
type: AttendeeConstants.FETCH_POPULAR_ATTENDEES
@ -114,8 +127,8 @@ function fetchPopular() {
if (e.isError()) {
ErrorActions.serverError(e);
} else {
dispatch(popularAttendeesFetched(data.attendees.map(function(json) {
var a = new Attendee();
dispatch(popularAttendeesFetched(data.popularattendees.map(function(json) {
var a = new PopularAttendee();
a.fromJSON(json);
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 = {
fetchAll: fetchAll,
create: create,
remove: remove,
fetchPopular: fetchPopular
};

View File

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

View File

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

View File

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

View File

@ -7,8 +7,9 @@ var Tab = ReactBootstrap.Tab;
var Modal = ReactBootstrap.Modal;
var TopBarContainer = require('../containers/TopBarContainer');
var NewUserForm = require('./NewUserForm');
var RecordLunchContainer = require('../containers/RecordLunchContainer');
var AccountSettingsModalContainer = require('../containers/AccountSettingsModalContainer');
var NewUserForm = require('./NewUserForm');
module.exports = React.createClass({
displayName: "LunchApp",
@ -62,7 +63,8 @@ module.exports = React.createClass({
if (this.props.user.isUser())
mainContent = (
<Tabs defaultActiveKey={1} id='mainNavigationTabs'>
<Tab title="New Entry" eventKey={1} >accounts
<Tab title="Record Lunch" eventKey={1} >
<RecordLunchContainer />
</Tab>
<Tab title="Statistics" eventKey={2} >stats will go here
</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,
CREATE_ATTENDEE: null,
ATTENDEE_CREATED: null,
REMOVE_ATTENDEE: null,
ATTENDEE_REMOVED: null,
FETCH_POPULAR_ATTENDEES: 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;
}
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() {
this.SuggestionId = -1;
this.VetoingId = -1;
@ -134,6 +161,33 @@ Suggestion.prototype.isSuggestion = function() {
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() {
this.ErrorId = -1;
this.ErrorString = "";
@ -167,6 +221,10 @@ module.exports = models = {
User: User,
Session: Session,
Error: Error,
Attendee: Attendee,
PopularAttendee: PopularAttendee,
Suggestion: Suggestion,
PopularSuggestion: PopularSuggestion,
// Constants
BogusPassword: "password"

View File

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

View File

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

View File

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

View File

@ -5,12 +5,14 @@ div#content {
display: block;
width: 95%;
height: 100%;
min-width: 75em;
min-width: 20em;
max-width: 100em;
margin: auto;
}
/* Keep the main windows sized to the full viewable height */
.fullheight {
height: 100%;
div.tab-content {
padding: 1em;
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) {
var suggestions []*Suggestion
var suggestionMap map[string]int64
suggestionMap := make(map[string]int64)
popularSuggestions := make([]*PopularSuggestion, 0)
_, err := DB.Select(&suggestions, "SELECT * from suggestions")