Skip to content

Commit

Permalink
Add event detail page and routes
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyen.the.vinh.bao committed Jun 5, 2019
1 parent cb71c0b commit 13891b2
Show file tree
Hide file tree
Showing 25 changed files with 19,032 additions and 22 deletions.
14,272 changes: 14,272 additions & 0 deletions client/package-lock.json

Large diffs are not rendered by default.

23 changes: 22 additions & 1 deletion client/src/app/actions/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const fetchEventById = eventId => async dispatch => {
type: actionTypes.GET_EVENTS_BEGIN,
});
try {
const { data } = await APIClient.getEventById(eventId);
const { data } = await APIClient.getE(eventId);
dispatch({
type: actionTypes.GET_EVENTS_SUCCESS,
payload: {
Expand All @@ -43,3 +43,24 @@ export const fetchEventById = eventId => async dispatch => {
});
}
};
export const updateEvent = newEvent => async dispatch => {
dispatch({
type: actionTypes.UPDATE_EVENT_BEGIN,
});
try {
const { data } = await APIClient.updateEvent(newEvent);
dispatch({
type: actionTypes.UPDATE_EVENT_SUCCESS,
payload: {
data,
},
});
} catch (error) {
dispatch({
type: actionTypes.UPDATE_EVENT_FAILURE,
payload: {
error,
},
});
}
};
37 changes: 37 additions & 0 deletions client/src/app/components/Student.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useState } from 'react';
import { Avatar, Card, Col, Checkbox } from 'antd';

const { Meta } = Card;
const Student = ({ item }) => {
const [value, setValue] = useState(item.isChecked);

const { fullName, email, isChecked } = item;

return (
<Col span={12} style={{ marginBottom: '1rem' }}>
<Card
title={
<div>
<Avatar
style={{ marginRight: '1rem' }}
shape="square"
size="large"
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
/>
{fullName}
</div>
}
extra={
<Checkbox
onChange={() => setValue(!value)}
checked={value}
/>
}
>
<Meta description={email || '[email protected]'} />
</Card>
</Col>
);
};

export default Student;
4 changes: 4 additions & 0 deletions client/src/app/constants/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const GET_EVENTS_BEGIN = 'GET_EVENTS_BEGIN';
export const GET_EVENTS_SUCCESS = 'GET_EVENTS_SUCCESS';
export const GET_EVENTS_FAILURE = 'GET_EVENTS_FAILURE';

export const UPDATE_EVENT_BEGIN = 'UPDATE_EVENT_BEGIN';
export const UPDATE_EVENT_SUCCESS = 'UPDATE_EVENT_SUCCESS';
export const UPDATE_EVENT_FAILURE = 'UPDATE_EVENT_FAILURE';

// UI ACTIONS CONSTANTS
export const CHANGE_PAGE = 'CHANGE_PAGE';
export const SET_HEADER_TITLE = 'SET_HEADER_TITLE';
32 changes: 32 additions & 0 deletions client/src/app/reducers/data/eventsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ export default (state = initialState, action) => {
isFetching: false,
};
}

case actionTypes.UPDATE_EVENT_BEGIN: {
return {
...state,
isFetching: true,
};
}
case actionTypes.UPDATE_EVENT_SUCCESS: {
const { data: newEvent } = action.payload;

return {
...state,
data: state.data.map(item => {
if (item._id === newEvent.id) {
item = newEvent;
}
return item;
}),
fetchError: null,
isFetching: false,
};
}
case actionTypes.UPDATE_EVENT_FAILURE: {
const { error: fetchError } = action.payload;

return {
...state,
fetchError,
isFetching: false,
};
}

default: {
return state;
}
Expand Down
1 change: 1 addition & 0 deletions client/src/app/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default combineReducers({
eventPage: combineReducers({
data: combineReducers({
events: eventsReducer,
// TODO: Create more studentsReducer for solve problem of nested state realtional.
}),
}),
classPage: combineReducers({
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/routes/class/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import StudentList from './containers/StudentList';
import { connect } from 'react-redux';
import { fetchStudents } from '../../actions/students';

// TODO: Change Class to display Calendar with events of specific classCode.
// TODO: Create another route & components to handle class management ( list student with attendance stastitics )
const Class = ({ students, fetchStudents, match, isFetching }) => {
useEffect(() => {
const { classId } = match.params;
Expand Down
25 changes: 25 additions & 0 deletions client/src/app/routes/event/components/CheckStatistic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { Statistic, Row, Col, Icon } from 'antd';

const CheckStatistic = ({ checkedCount, unCheckedCount, total }) => {
return (
<Row gutter={16}>
<Col span={12}>
<Statistic
title="Checked"
value={checkedCount}
prefix={<Icon type="check-circle" />}
/>
</Col>
<Col span={12}>
<Statistic
title="Unchecked"
value={unCheckedCount}
suffix={`/ ${total}`}
/>
</Col>
</Row>
);
};

export default CheckStatistic;
26 changes: 16 additions & 10 deletions client/src/app/routes/event/components/Description.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import React from 'react';
// import { Descriptions } from 'antd';
import { compose } from 'recompose';
import { Descriptions } from 'antd';
import getFormattedTime from 'app/helpers/formatTime';
import withMaybe from 'app/hoc/withMaybe';
const Description = ({ event }) => {
const { title, classCode, location, start, end } = event;
return (
// <Descriptions title="Description">
// <Descriptions.Item label="Course Name">{title}</Descriptions.Item>
// <Descriptions.Item label="ClassCode">{classCode}</Descriptions.Item>
// <Descriptions.Item label="Location">{location}</Descriptions.Item>
// <Descriptions.Item label="Start">{start}</Descriptions.Item>
// <Descriptions.Item label="End">{end}</Descriptions.Item>
// </Descriptions>
<h1>title</h1>
<Descriptions title="Description">
<Descriptions.Item label="Course Name">{title}</Descriptions.Item>
<Descriptions.Item label="ClassCode">{classCode}</Descriptions.Item>
<Descriptions.Item label="Location">{location}</Descriptions.Item>
<Descriptions.Item label="Start">
{getFormattedTime(start)}
</Descriptions.Item>
<Descriptions.Item label="End">
{getFormattedTime(end)}
</Descriptions.Item>
</Descriptions>
);
};
const nullConditionFn = props => !props.event;

export default withMaybe(Description);
export default withMaybe(nullConditionFn)(Description);
36 changes: 36 additions & 0 deletions client/src/app/routes/event/containers/CheckList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { Empty, Row } from 'antd';
import Student from 'app/components/Student';
import { Descriptions } from 'antd';
import withEither from 'app/hoc/withEither';
import Spinner from 'app/components/Spinner';
import withMaybe from 'app/hoc/withMaybe';
import { updateEvent } from 'app/actions/events';
const isLoadingConditionFn = props => props.isLoading;
const nullConditionFn = props => !props.students;
const isEmptyConditionFn = props => !props.students.length;

const withConditionalRenderings = compose(
withEither(isLoadingConditionFn, Spinner),
withMaybe(nullConditionFn),
withEither(isEmptyConditionFn, Empty),
);

const CheckList = ({ students }) => {
const renderList = () => {
return students.reverse().map(item => {
return <Student key={item._id} item={item} />;
});
};

return (
<Fragment>
<Descriptions title="Check Attendance" />
<Row gutter={16}>{renderList()}</Row>
</Fragment>
);
};

export default withConditionalRenderings(CheckList);
79 changes: 75 additions & 4 deletions client/src/app/routes/event/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,87 @@
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { fetchEventById } from '../../actions/events';
import { fetchEventById, updateEvent } from '../../actions/events';
import Description from './components/Description';
const Event = ({ event, fetchEventById, match, isFetching }) => {
import CheckList from './containers/CheckList';
import CheckStatistic from './components/CheckStatistic';
const Event = ({ event, fetchEventById, updateEvent, match, isFetching }) => {
useEffect(() => {
const { eventId } = match.params;
fetchEventById(eventId);
}, {});
// const handleOnStudentCheckToggle = student => {
// const newStudent = {
// ...student,
// isChecked: !student.isChecked,
// };
// const updatedStudents = event.students.map(student => {
// if (student._id === newStudent._id) {
// student = newStudent;
// }
// return student;
// });
// event.students = updatedStudents;

// updateEvent(event);
// };

const testEvent = {
eventId: '10129401294012',
classCode: 'CODE101',
title: 'Web Development',
location: 'ROOM:503',
start: Date.now(),
end: Date.now(),
students: [
{
_id: 21123421331234124,
fullName: 'Nguyen The Vinh Bao',
email: '[email protected]',
isChecked: false,
},
{
_id: 2112342134123124,
fullName: 'Nguyen Van A',
email: '[email protected]',
isChecked: true,
},
{
_id: 211234213412123214,
fullName: 'Nguyen The B',
email: '[email protected]',
isChecked: false,
},
{
_id: 211234213412212134,
fullName: 'Nguyen Vinh V',
email: '[email protected]',
isChecked: false,
},
],
};
const { students } = testEvent;

const checkedStudents = students.filter(student => student.isChecked);
const unCheckedStudents = students.filter(student => !student.isChecked);
return (
<div className="event-page">
<Description event={event} />
<div className="event-page__info">
<div className="event-page__desc">
<Description event={testEvent} />
</div>
<div className="event-page__stats">
<CheckStatistic
checkedCount={checkedStudents.length}
unCheckedCount={unCheckedStudents.length}
total={students.length}
/>
</div>
</div>

<CheckList
students={students}

/>
</div>
);
};
Expand All @@ -24,5 +95,5 @@ const mapStateToProps = state => {
};
export default connect(
mapStateToProps,
{ fetchEventById },
{ fetchEventById, updateEvent },
)(Event);
1 change: 0 additions & 1 deletion client/src/app/routes/home/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const Home = ({ events, fetchEvents, isFetching }) => {

const mapStateToProps = state => {
const homePageStore = state.homePage;
const commonUIStore = state.commonUI;

return {
events: homePageStore.data.events.data,
Expand Down
2 changes: 1 addition & 1 deletion client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store/index';
import jwt_decode from 'jwt-decode';
import './styles/main.scss';
import '@fortawesome/fontawesome-free/js/all';
import 'react-toastify/dist/ReactToastify.css';
import 'antd/dist/antd.css';
import './styles/main.scss';

import Layout from './app/layout/Layout';
import setAuthToken from './app/setAuthToken';
Expand Down
7 changes: 5 additions & 2 deletions client/src/services/APIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const endpoints = {
STUDENTS: 'students',
EVENTS: 'events',
};

const APIClient = {
BASE_URL: '/api',

Expand All @@ -29,9 +28,13 @@ const APIClient = {
let finalURL = `${this.BASE_URL}/${endpoints.EVENTS}`;
return axios.get(finalURL);
},
async getEventById(eventId) {
async getEvent(eventId) {
let finalURL = `${this.BASE_URL}/${endpoints.EVENTS}/${eventId}`;
return axios.get(finalURL);
},
async updateEvent(newEvent) {
let finalURL = `${this.BASE_URL}/${endpoints.EVENTS}/${newEvent._id}`;
return axios.put(finalURL, newEvent);
},
};
export { APIClient, endpoints };
1 change: 0 additions & 1 deletion client/src/styles/components/_all.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
@import 'logo';
@import 'content-box';
@import 'form-box';
@import 'calendar';
Loading

0 comments on commit 13891b2

Please sign in to comment.