{map(
- event =>
,
+ event => (
+
+ ),
events,
)}
@@ -54,6 +62,7 @@ Events.propTypes = {
handleClose: PropTypes.func.isRequired,
addEvent: PropTypes.func.isRequired,
classes: PropTypes.object,
+ history: PropTypes.object,
};
export default Events;
diff --git a/src/client/web/pages/Events/index.js b/src/client/web/pages/Events/index.js
index a1a4cb6..2a33e8c 100644
--- a/src/client/web/pages/Events/index.js
+++ b/src/client/web/pages/Events/index.js
@@ -1,4 +1,5 @@
import { connect } from 'react-redux';
+import { withRouter } from 'react-router';
import { compose, withStateHandlers } from 'recompose';
import injectSheet from 'react-jss';
import { getEvents } from '../../selectors/events';
@@ -29,6 +30,7 @@ const mapDispatchToProps = {
};
export default compose(
+ withRouter,
connect(mapStateToProps, mapDispatchToProps),
injectSheet(style),
withStateHandlers(
diff --git a/src/client/web/reducers/index.js b/src/client/web/reducers/index.js
index ef064c0..73830b9 100644
--- a/src/client/web/reducers/index.js
+++ b/src/client/web/reducers/index.js
@@ -1,8 +1,10 @@
import { combineReducers } from 'redux';
+import spendings from './spendings';
import events from './events';
import people from './people';
const reducer = combineReducers({
+ spendings,
events,
people,
});
diff --git a/src/client/web/reducers/spendings.js b/src/client/web/reducers/spendings.js
new file mode 100644
index 0000000..80c659e
--- /dev/null
+++ b/src/client/web/reducers/spendings.js
@@ -0,0 +1,47 @@
+import { append, findIndex, update, propEq, remove } from 'ramda';
+
+import {
+ SPENDINGS_LOADED,
+ SPENDING_ADDED,
+ SPENDING_UPDATED,
+ SPENDING_DELETED,
+} from '../actions/event';
+
+const initialState = {
+ spendings: [],
+};
+
+const spendings = (state = initialState, action) => {
+ switch (action.type) {
+ case SPENDINGS_LOADED:
+ return { ...state, spendings: action.payload.spendings };
+ case SPENDING_ADDED:
+ return {
+ ...state,
+ spendings: append(action.payload.spending, state.spendings),
+ };
+ case SPENDING_UPDATED:
+ return {
+ ...state,
+ spendings: update(
+ findIndex(propEq('id', action.payload.spending.id), state.spendings),
+ action.payload.spending,
+ state.spendings,
+ ),
+ };
+ case SPENDING_DELETED:
+ return {
+ ...state,
+ spendings: remove(
+ findIndex(propEq('id', action.payload.id), state.spendings),
+ 1,
+ state.spendings,
+ ),
+ };
+ default: {
+ return state;
+ }
+ }
+};
+
+export default spendings;
diff --git a/src/client/web/routes.js b/src/client/web/routes.js
index a2b6218..127357c 100644
--- a/src/client/web/routes.js
+++ b/src/client/web/routes.js
@@ -1,5 +1,6 @@
import { find, prop, values } from 'ramda';
import Events from './pages/Events';
+import Event from './pages/Event';
const routes = {
about: {
@@ -12,6 +13,11 @@ const routes = {
component: Events,
default: true,
},
+ event: {
+ exact: true,
+ path: '/event/:id',
+ component: Event,
+ },
};
export const defaultRoute = find(prop('default'), values(routes));
diff --git a/src/client/web/selectors/events.js b/src/client/web/selectors/events.js
index 3943766..d76d085 100644
--- a/src/client/web/selectors/events.js
+++ b/src/client/web/selectors/events.js
@@ -1 +1,7 @@
+import { createSelector } from 'reselect';
+import { find, propEq } from 'ramda';
+
export const getEvents = state => state.events.events;
+
+export const getEvent = id =>
+ createSelector([getEvents], events => find(propEq('id', id), events));
diff --git a/src/client/web/selectors/people.js b/src/client/web/selectors/people.js
index 266ef21..5a1b386 100644
--- a/src/client/web/selectors/people.js
+++ b/src/client/web/selectors/people.js
@@ -1,5 +1,6 @@
import { createSelector } from 'reselect';
import { find, propEq, map } from 'ramda';
+import { getEvent } from './events';
export const getPeople = state => state.people.people;
@@ -14,3 +15,9 @@ export const getSelectPeople = createSelector([getPeople], people =>
people,
),
);
+
+export const getAttendees = id =>
+ createSelector([getEvent(id), getPeople], (event, people) => {
+ if (event && people)
+ return map(id => find(propEq('id', id), people), event.attendeeIds);
+ });
diff --git a/src/client/web/selectors/spendings.js b/src/client/web/selectors/spendings.js
new file mode 100644
index 0000000..d60620c
--- /dev/null
+++ b/src/client/web/selectors/spendings.js
@@ -0,0 +1,6 @@
+import { filter, find, propEq } from 'ramda';
+
+export const getSpendings = eventId => state =>
+ filter(propEq('eventId', eventId), state.spendings.spendings);
+
+export const getSpending = (id, spendings) => find(propEq('id', id), spendings);
diff --git a/src/mock/api.vibes.js b/src/mock/api.vibes.js
index e53045b..1ab9874 100644
--- a/src/mock/api.vibes.js
+++ b/src/mock/api.vibes.js
@@ -1,6 +1,6 @@
const { vibe } = require('farso');
const faker = require('faker');
-const { findIndex, propEq, find, path } = require('ramda');
+const { findIndex, propEq, find, path, remove, filter } = require('ramda');
vibe.default(
'Main',
@@ -11,13 +11,12 @@ vibe.default(
api: {
clientId,
clientSecret,
- data: { events, people },
+ data: { events, people, spendings },
},
},
},
) => {
mock('events:list').reply([200, events]);
- mock('people:list').reply([200, people]);
mock('event:add').reply((req, res) => {
const id = faker.random.uuid();
const newEvent = {
@@ -43,5 +42,38 @@ vibe.default(
const { id } = req.params;
res.json(find(propEq('id', id), events));
});
+
+ mock('spendings:list').reply((req, res) =>
+ res.send(filter(spending => !spending.deleted, spendings)),
+ );
+ mock('spending:add').reply((req, res) => {
+ const id = faker.random.uuid();
+ const newSpending = {
+ id,
+ currency: 'EUR',
+ createdAt: new Date(),
+ ...req.body,
+ };
+ spendings.push(newSpending);
+ res.json(newSpending);
+ });
+ mock('spending:update').reply((req, res) => {
+ const updates = req.body;
+ const index = findIndex(propEq('id', req.params.id), spendings);
+ if (index === -1) return res.sendStatus(404);
+ const updatedSpending = { ...spendings[index], ...updates };
+ spendings[index] = updatedSpending;
+ res.json(updatedSpending);
+ });
+ mock('spending:delete').reply((req, res) => {
+ const updates = req.body;
+ const index = findIndex(propEq('id', req.params.id), spendings);
+ if (index === -1) return res.sendStatus(404);
+ const updatedSpending = { ...spendings[index], deleted: true };
+ spendings[index] = updatedSpending;
+ res.json(updatedSpending);
+ });
+
+ mock('people:list').reply([200, people]);
},
);
diff --git a/src/mock/data.js b/src/mock/data.js
index 1fb3def..cc86a07 100644
--- a/src/mock/data.js
+++ b/src/mock/data.js
@@ -4,30 +4,52 @@ const { times, map, uniq, compose, prop } = require('ramda');
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const getRandom = list => () => list[random(0, list.length - 1)];
+let i = 0;
const Person = () => ({
- id: faker.random.uuid(),
+ id: `${i++}`,
firstname: faker.name.firstName(),
lastname: faker.name.lastName(),
email: faker.internet.email(),
createdAt: faker.date.past(),
});
-const people = times(Person, 50);
+const people = times(Person, 10);
+i = 0;
const Event = () => ({
- id: faker.random.uuid(),
+ id: `${i++}`,
label: faker.lorem.words(),
attendeeIds: compose(uniq, map(prop('id')))(
- times(getRandom(people), random(0, 10)),
+ times(getRandom(people), random(1, 10)),
),
image: faker.image.image(),
currency: 'EUR',
createdAt: faker.date.past(),
});
-const events = times(Event, 10);
+const events = times(Event, 4);
+
+i = 0;
+const Spending = () => ({
+ id: `${i++}`,
+ eventId: prop('id', getRandom(events)()),
+ ownerId: prop('id', getRandom(people)()),
+ attendees: [
+ { id: i, personId: '1', key: '40' },
+ { id: i, personId: '2', key: '20' },
+ { id: i, personId: '3', key: '30' },
+ { id: i, personId: '4', key: '10' },
+ ],
+ label: faker.lorem.words(),
+ amount: random(10, 1000),
+ currency: 'EUR',
+ createdAt: faker.date.past(),
+});
+
+const spendings = times(Spending, 10);
module.exports = {
people,
events,
+ spendings,
};
diff --git a/src/mock/endpoints.js b/src/mock/endpoints.js
index 6a6a953..7d09666 100644
--- a/src/mock/endpoints.js
+++ b/src/mock/endpoints.js
@@ -4,4 +4,11 @@ endpoint('events:list', { uri: '/api/events', method: 'get' });
endpoint('event:add', { uri: '/api/events', method: 'post' });
endpoint('event:update', { uri: '/api/events/:id', method: 'patch' });
endpoint('event:get', { uri: '/api/events/:id', method: 'get' });
+
+endpoint('spendings:list', { uri: '/api/spendings', method: 'get' });
+endpoint('spending:add', { uri: '/api/spendings', method: 'post' });
+endpoint('spending:update', { uri: '/api/spendings/:id', method: 'patch' });
+endpoint('spending:get', { uri: '/api/spendings/:id', method: 'get' });
+endpoint('spending:delete', { uri: '/api/spendings/:id', method: 'delete' });
+
endpoint('people:list', { uri: '/api/people', method: 'get' });