├── mobile
├── .watchmanconfig
├── .gitignore
├── app.json
├── images
│ ├── REWmEe.jpg
│ └── blur-bg.jpg
├── .babelrc
├── App.test.js
├── package.json
├── components
│ ├── MainContent.js
│ └── LoginForm.js
├── App.js
├── .flowconfig
└── README.md
├── src
├── server
│ ├── .babelrc
│ ├── graphql
│ │ ├── staff.js
│ │ ├── conversation_messages.js
│ │ ├── conversation.js
│ │ ├── entities.js
│ │ └── schema.js
│ ├── jwtConfig.js
│ ├── views
│ │ ├── index.pug
│ │ ├── error.pug
│ │ └── layout.pug
│ ├── models-knex
│ │ ├── entities.js
│ │ ├── conversations.js
│ │ ├── conversation_messages.js
│ │ └── conversation_participants.js
│ ├── public
│ │ └── stylesheets
│ │ │ ├── style.sass
│ │ │ ├── style.css
│ │ │ └── style.css.map
│ ├── db
│ │ └── knex.js
│ ├── routes
│ │ ├── graphql.js
│ │ ├── customer.js
│ │ ├── index1.js
│ │ ├── index2.js
│ │ ├── test.js
│ │ ├── department.js
│ │ ├── authentication.js
│ │ ├── staff.js
│ │ └── index.js
│ ├── bookshelf.js
│ ├── .sequelizerc
│ ├── utils
│ │ ├── chattoken.js
│ │ ├── chatnew.js
│ │ └── chat.js
│ ├── sequelize
│ │ ├── models
│ │ │ ├── entities.js
│ │ │ ├── conversations.js
│ │ │ ├── conversations_messages.js
│ │ │ ├── conversations_participants.js
│ │ │ └── index.js
│ │ ├── config
│ │ │ └── config.json
│ │ ├── migrations
│ │ │ ├── 20170924185022-create-conversations.js
│ │ │ ├── 20170924190713-create-conversations-participants.js
│ │ │ ├── 20170924185717-create-conversations-messages.js
│ │ │ └── 20170924182938-create-entities.js
│ │ └── seeders
│ │ │ └── 20170924202122-demo-entities.js
│ ├── migrations_temp
│ │ ├── 20170831101624_increment_table.js
│ │ └── 20170913112234_leave_application.js
│ ├── seeds
│ │ ├── 01-department.js
│ │ ├── 03-customer.js
│ │ └── 02-staff.js
│ ├── seeds1
│ │ └── 04-conversations.js
│ ├── knexfile.js
│ ├── .eslintrc.json
│ ├── migrations
│ │ ├── 20170831073944_entities.js
│ │ └── 20170910074905_conversations.js
│ ├── package.json
│ ├── app.js
│ └── bin
│ │ └── start
├── images
│ ├── REWmEe.jpg
│ └── blur-bg.jpg
├── actions
│ ├── config.js
│ ├── leaveapplication.js
│ ├── types.js
│ ├── staffInformation.js
│ ├── department.js
│ └── userAuthentication.js
├── rootReducer.js
├── App.test.js
├── components
│ ├── customers
│ │ ├── CustomerNew.js
│ │ ├── CustomerList.js
│ │ └── Customer.js
│ ├── humanresource
│ │ ├── PayAdvice.js
│ │ ├── NewDepartmentForm.js
│ │ ├── NewLeaveApplication.js
│ │ ├── AllowanceAndBenefits.js
│ │ ├── LeaveApplicationDetails.js
│ │ ├── DepartmentDetails.js
│ │ ├── LeaveApplication.js
│ │ ├── Department.js
│ │ ├── NewStaffForm.js
│ │ ├── StaffList.js
│ │ └── StaffDetails.js
│ ├── users
│ │ ├── ChangePassword.js
│ │ ├── UserRoles.js
│ │ └── LoginForm.js
│ ├── helpers
│ │ ├── Pagination.js
│ │ ├── PageNotFound.js
│ │ └── ComposeEmail.js
│ ├── navigations
│ │ ├── RightMenu.js
│ │ ├── Accordion.js
│ │ ├── MainContent.js
│ │ ├── TopNavigation.js
│ │ └── LeftMenu.js
│ ├── dashboard
│ │ └── Graph.js
│ ├── Dashboard.js
│ ├── communications
│ │ └── MainChat.js
│ └── tests
│ │ └── TryOverlay.js
├── utils
│ ├── setAuthorizationToken.js
│ └── validateLogin.js
├── AppInit.js
├── App.css
├── reducers
│ ├── auth.js
│ └── humanresource.js
├── index.scss
├── index.js
├── App.js
├── logo.svg
├── index.css
├── registerServiceWorker.js
└── pagination.css
├── .gitignore
├── public
├── REWmEe.jpg
├── favicon.ico
├── manifest.json
├── index.html
└── flexboxgrid.min.css
├── .yo-rc.json
├── package.json
└── README.md
/mobile/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | npm-debug.*
4 |
--------------------------------------------------------------------------------
/src/server/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/mobile/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "sdkVersion": "20.0.0"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/server/graphql/staff.js:
--------------------------------------------------------------------------------
1 | const Staff = `
2 | type Staff {
3 |
4 | }
5 | `
6 |
--------------------------------------------------------------------------------
/src/server/jwtConfig.js:
--------------------------------------------------------------------------------
1 | export default {
2 | jwtSecret: 'justputyourownsecretkey'
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | mobile/node_modules
4 | src/server/node_modules
5 |
6 |
--------------------------------------------------------------------------------
/public/REWmEe.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passionate-dev219/ERP_System/HEAD/public/REWmEe.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passionate-dev219/ERP_System/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/images/REWmEe.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passionate-dev219/ERP_System/HEAD/src/images/REWmEe.jpg
--------------------------------------------------------------------------------
/src/images/blur-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passionate-dev219/ERP_System/HEAD/src/images/blur-bg.jpg
--------------------------------------------------------------------------------
/mobile/images/REWmEe.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passionate-dev219/ERP_System/HEAD/mobile/images/REWmEe.jpg
--------------------------------------------------------------------------------
/src/server/views/index.pug:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= title
5 | p Welcome to #{title}
6 |
--------------------------------------------------------------------------------
/mobile/images/blur-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passionate-dev219/ERP_System/HEAD/mobile/images/blur-bg.jpg
--------------------------------------------------------------------------------
/src/server/views/error.pug:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-express-es6": {
3 | "promptValues": {
4 | "viewEngine": "pug",
5 | "cssPreprocessor": "sass"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/server/models-knex/entities.js:
--------------------------------------------------------------------------------
1 | import bookshelf from '../bookshelf';
2 |
3 | export default bookshelf.Model.extend({
4 | tableName: 'entities'
5 | })
6 |
--------------------------------------------------------------------------------
/src/server/public/stylesheets/style.sass:
--------------------------------------------------------------------------------
1 | body
2 | padding: 50px
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif
4 |
5 | a
6 | color: #00B7FF
7 |
--------------------------------------------------------------------------------
/src/server/models-knex/conversations.js:
--------------------------------------------------------------------------------
1 | import bookshelf from '../bookshelf';
2 |
3 | export default bookshelf.Model.extend({
4 | tableName: 'conversations'
5 | })
6 |
--------------------------------------------------------------------------------
/mobile/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-expo"],
3 | "env": {
4 | "development": {
5 | "plugins": ["transform-react-jsx-source"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/server/views/layout.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='/stylesheets/style.css')
6 | body
7 | block content
8 |
--------------------------------------------------------------------------------
/src/server/models-knex/conversation_messages.js:
--------------------------------------------------------------------------------
1 | import bookshelf from '../bookshelf';
2 |
3 | export default bookshelf.Model.extend({
4 | tableName: 'conversation_messages'
5 | })
6 |
--------------------------------------------------------------------------------
/src/server/db/knex.js:
--------------------------------------------------------------------------------
1 | var environment = process.env.NODE_ENV || 'development';
2 | var config = require('../knexfile.js')[environment];
3 |
4 | module.exports = require('knex')(config);
5 |
--------------------------------------------------------------------------------
/src/server/models-knex/conversation_participants.js:
--------------------------------------------------------------------------------
1 | import bookshelf from '../bookshelf';
2 |
3 | export default bookshelf.Model.extend({
4 | tableName: 'conversation_participants'
5 | })
6 |
--------------------------------------------------------------------------------
/src/actions/config.js:
--------------------------------------------------------------------------------
1 | let port = window.location.port;
2 |
3 | if (port !== '')
4 | port = ':' + 3003;
5 |
6 | export const apiServer = window.location.protocol + '//' + window.location.hostname + port;
7 |
--------------------------------------------------------------------------------
/src/server/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; }
4 |
5 | a {
6 | color: #00B7FF; }
7 |
8 | /*# sourceMappingURL=style.css.map */
--------------------------------------------------------------------------------
/src/actions/leaveapplication.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | const apiServer = 'http://localhost:3003';
3 |
4 | function getLeaveApplicationDetails(leavenum) {
5 | return axios.get(apiServer + `/api/leave/$leavenum`)
6 | }
7 |
--------------------------------------------------------------------------------
/src/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import auth from './reducers/auth';
3 | import humanresource from './reducers/humanresource';
4 |
5 | export default combineReducers({
6 | auth, humanresource
7 | })
8 |
--------------------------------------------------------------------------------
/src/server/routes/graphql.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | // import {
3 | // graphiqlExpress,
4 | // } from 'graphql-server-express';
5 |
6 | const router = express.Router();
7 |
8 |
9 |
10 | export default router;
11 |
--------------------------------------------------------------------------------
/src/server/routes/customer.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 |
3 | const router = express.Router();
4 |
5 | router.get('/api/list', (req, res) => {
6 | res.status(200).json({'title': 'taitel'})
7 | });
8 |
9 | export default router;
10 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/server/bookshelf.js:
--------------------------------------------------------------------------------
1 | import knex from 'knex';
2 | import bookshelf from 'bookshelf';
3 | import knexConfig from './knexfile';
4 |
5 | var bookshelf1 = bookshelf(knex(knexConfig.development));
6 | bookshelf1.plugin('pagination');
7 | export default bookshelf1;
8 |
--------------------------------------------------------------------------------
/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const SET_CURRENT_USER = 'SET_CURRENT_USER';
2 | export const SET_DISPLAYED_STAFF = 'SET_DISPLAYED_STAFF';
3 | export const SET_DISPLAYED_DEPARTMENT = 'SET_DISPLAYED_DEPARTMENT';
4 | export const SET_SELECTED_STAFF = 'SET_SELECTED_STAFF';
5 |
--------------------------------------------------------------------------------
/src/components/customers/CustomerNew.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class CustomerNew extends Component {
4 | render() {
5 | return(
6 |
7 | New Customer
8 |
9 | )
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mobile/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from './App';
3 |
4 | import renderer from 'react-test-renderer';
5 |
6 | it('renders without crashing', () => {
7 | const rendered = renderer.create( ).toJSON();
8 | expect(rendered).toBeTruthy();
9 | });
10 |
--------------------------------------------------------------------------------
/src/server/public/stylesheets/style.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "file": "style.css",
4 | "sources": [
5 | "style.sass"
6 | ],
7 | "mappings": "AAAA,AAAA,IAAI,CAAC;EACH,OAAO,EAAE,IAAK;EACd,IAAI,EAAE,kDAAmD,GAAG;;AAE9D,AAAA,CAAC,CAAC;EACA,KAAK,EAAE,OAAQ,GAAG",
8 | "names": []
9 | }
--------------------------------------------------------------------------------
/src/components/humanresource/PayAdvice.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class PayAdvice extends Component {
4 | render() {
5 | return(
6 |
7 | Pay Advice
8 |
9 | )
10 | }
11 | }
12 |
13 | export default PayAdvice;
14 |
--------------------------------------------------------------------------------
/src/components/humanresource/NewDepartmentForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class NewDepartmentForm extends Component {
4 | render() {
5 | return(
6 |
7 | New Department
8 |
9 | )
10 | }
11 | }
12 |
13 | export default NewDepartmentForm;
14 |
--------------------------------------------------------------------------------
/src/components/humanresource/NewLeaveApplication.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class NewLeaveApplication extends Component {
4 | render() {
5 | return(
6 |
7 | New Leave
8 |
9 | )
10 | }
11 | }
12 |
13 | export default NewLeaveApplication;
14 |
--------------------------------------------------------------------------------
/src/components/humanresource/AllowanceAndBenefits.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class AllowanceAndBenefits extends Component {
4 | render() {
5 | return(
6 |
7 | Allowance And Benefits
8 |
9 | )
10 | }
11 | }
12 |
13 | export default AllowanceAndBenefits;
14 |
--------------------------------------------------------------------------------
/src/components/humanresource/LeaveApplicationDetails.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class LeaveApplicationDetails extends Component {
4 | render() {
5 | return(
6 |
7 | Leave Details
8 |
9 | )
10 | }
11 | }
12 |
13 | export default LeaveApplicationDetails;
14 |
--------------------------------------------------------------------------------
/src/server/.sequelizerc:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | "config": path.resolve('./sequelize/config', 'config.json'),
5 | "models-path": path.resolve('./sequelize/models'),
6 | "seeders-path": path.resolve('./sequelize/seeders'),
7 | "migrations-path": path.resolve('./sequelize/migrations')
8 | };
9 |
--------------------------------------------------------------------------------
/src/server/utils/chattoken.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken';
2 | import jwtConfig from '../jwtConfig';
3 |
4 | export default (jwtData) => {
5 | if (jwtData) {
6 | return jwt.verify(jwtData, jwtConfig.jwtSecret);
7 | } else {
8 | return {
9 | stats: false,
10 | errors: 'Invalid token'
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/setAuthorizationToken.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | // import isEmpty from 'lodash';
3 |
4 | export default function setAuthorizationToken(token) {
5 | if (token) {
6 | axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
7 | } else {
8 | delete axios.defaults.headers.common['Authorization'];
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/server/sequelize/models/entities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var Entities = sequelize.define('entities', {
4 | id: DataTypes.INTEGER
5 | }, {
6 | classMethods: {
7 | associate: function(models) {
8 | // associations can be defined here
9 | }
10 | }
11 | });
12 | return Entities;
13 | };
14 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/server/sequelize/models/conversations.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var conversations = sequelize.define('conversations', {
4 | conversationtype: DataTypes.INTEGER
5 | }, {
6 | classMethods: {
7 | associate: function(models) {
8 | // associations can be defined here
9 | }
10 | }
11 | });
12 | return conversations;
13 | };
14 |
--------------------------------------------------------------------------------
/src/AppInit.js:
--------------------------------------------------------------------------------
1 | import jwtDecode from 'jwt-decode';
2 | import { setCurrentUser } from './actions/userAuthentication';
3 | import { setDisplayedStaff } from './actions/staffInformation';
4 |
5 | if (localStorage.jwtToken) {
6 | setAuthorizationToken(localStorage.jwtToken);
7 | store.dispatch(setCurrentUser(jwtDecode(localStorage.jwtToken)));
8 | store.dispatch(setDisplayedStaff(localStorage.staffList));
9 | }
10 |
--------------------------------------------------------------------------------
/src/server/graphql/conversation_messages.js:
--------------------------------------------------------------------------------
1 | import Conversation from './conversation';
2 |
3 | const typeDefs = `
4 | type Conversation_Messages {
5 | id: ID!
6 | entity_ID: Conversation!
7 | message: String
8 | created_at: Date!
9 | updated_at: Date!
10 | }
11 |
12 | type Query {
13 | conversation_messages: [Conversation_Messages]
14 | }
15 | `;
16 |
17 | export default typeDefs;
18 |
--------------------------------------------------------------------------------
/src/server/sequelize/models/conversations_messages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var conversations_messages = sequelize.define('conversation_messages', {
4 | conversation_id: DataTypes.INTEGER
5 | }, {
6 | classMethods: {
7 | associate: function(models) {
8 | // associations can be defined here
9 | }
10 | }
11 | });
12 | return conversations_messages;
13 | };
14 |
--------------------------------------------------------------------------------
/src/server/migrations_temp/20170831101624_increment_table.js:
--------------------------------------------------------------------------------
1 |
2 | exports.up = function(knex, Promise) {
3 | return knex.schema.createTable('increment_table', (table) => {
4 | table.increments();
5 | table.string('the_prefix');
6 | table.string('entity_name');
7 | table.integer('current_number');
8 | })
9 | };
10 |
11 | exports.down = function(knex, Promise) {
12 | return knex.schema.dropTable('increment_table');
13 | };
14 |
--------------------------------------------------------------------------------
/src/server/sequelize/models/conversations_participants.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = (sequelize, DataTypes) => {
3 | var conversations_participants = sequelize.define('conversation_participants', {
4 | conversation_id: DataTypes.INTEGER
5 | }, {
6 | classMethods: {
7 | associate: function(models) {
8 | // associations can be defined here
9 | }
10 | }
11 | });
12 | return conversations_participants;
13 | };
14 |
--------------------------------------------------------------------------------
/src/utils/validateLogin.js:
--------------------------------------------------------------------------------
1 | const validator = require('validator');
2 |
3 | function validateLogin(email = '', password = '') {
4 | let errors;
5 |
6 | if (email === '') {
7 | errors.email = 'Email cannot be empty';
8 | }
9 | if (!validator.isEmail(email)) {
10 | errors.email = 'Invalid email format';
11 | }
12 | if (password === '') {
13 | errors.password = 'Password cannot be empty';
14 | }
15 |
16 | return errors;
17 | }
18 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 |
2 | .App {
3 | text-align: center;
4 | }
5 |
6 | .App-logo {
7 | animation: App-logo-spin infinite 20s linear;
8 | height: 80px;
9 | }
10 |
11 | .App-header {
12 | background-color: #222;
13 | height: 150px;
14 | padding: 20px;
15 | color: white;
16 | }
17 |
18 | .App-intro {
19 | font-size: large;
20 | }
21 |
22 | @keyframes App-logo-spin {
23 | from { transform: rotate(0deg); }
24 | to { transform: rotate(360deg); }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/customers/CustomerList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | // Button, Dialog, Intent, Hotkey, Hotkeys, HotkeysTarget
4 | } from "@blueprintjs/core";
5 |
6 | export default class CustomerList extends Component {
7 | render() {
8 | return(
9 |
10 | Customer List
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/users/ChangePassword.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class ChangePassword extends Component {
4 | state = {
5 | dialogTitle: 'Change Password',
6 | isOpen: false
7 | }
8 |
9 | onChange = (e) => {
10 | this.setState({
11 | [e.target.name]: e.target.value
12 | })
13 | }
14 | render() {
15 | return(
16 |
17 |
18 |
19 | )
20 | }
21 | }
22 |
23 | export default ChangePassword;
24 |
--------------------------------------------------------------------------------
/src/reducers/auth.js:
--------------------------------------------------------------------------------
1 | import { SET_CURRENT_USER } from '../actions/types';
2 | import isEmpty from 'lodash/isEmpty';
3 |
4 | const initialState = {
5 | isAuthenticated: false,
6 | user: {}
7 | };
8 |
9 | export default (state = initialState, action = {}) => {
10 | switch (action.type) {
11 | case SET_CURRENT_USER:
12 | return {
13 | isAuthenticated: !isEmpty(action.user),
14 | user: action.user
15 | }
16 | default:return state;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/customers/Customer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Switch, Route } from 'react-router-dom';
3 | import CustomerList from './CustomerList';
4 | import CustomerNew from './CustomerNew';
5 |
6 | export default class Customer extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/server/routes/index1.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import schema from '../graphql/schema';
3 | import bodyParser from 'body-parser';
4 | import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
5 |
6 | const router = express.Router();
7 |
8 | /* GET index page. */
9 | router.get('/', (req, res, next) => {
10 | res.render('index', {
11 | title: 'Express'
12 | });
13 | });
14 |
15 | router.use('/graphql', bodyParser.json(), graphqlExpress({ schema }));
16 | router.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));
17 |
18 | export default router;
19 |
--------------------------------------------------------------------------------
/src/server/sequelize/config/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "development": {
3 | "username": "iqbal",
4 | "password": "iqbal007",
5 | "database": "bisnew",
6 | "host": "127.0.0.1",
7 | "dialect": "postgres"
8 | },
9 | "test": {
10 | "username": "root",
11 | "password": null,
12 | "database": "database_test",
13 | "host": "127.0.0.1",
14 | "dialect": "postgres"
15 | },
16 | "production": {
17 | "username": "root",
18 | "password": null,
19 | "database": "database_production",
20 | "host": "127.0.0.1",
21 | "dialect": "postgres"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reducers/humanresource.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_DISPLAYED_STAFF,
3 | SET_SELECTED_STAFF,
4 | SET_DISPLAYED_DEPARTMENT
5 | }
6 | from '../actions/types';
7 |
8 | export default (state = [], action = {}) => {
9 | switch (action.type) {
10 | case SET_DISPLAYED_STAFF:
11 | return {
12 | staffList: action.staffList
13 | }
14 | case SET_SELECTED_STAFF:
15 | return {
16 | selectedStaff: action.staff
17 | }
18 | case SET_DISPLAYED_DEPARTMENT:
19 | return {
20 | departmentList: action.departmentList
21 | }
22 | default: return state;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/server/seeds/01-department.js:
--------------------------------------------------------------------------------
1 | const faker = require('faker');
2 | const bcrypt = require('bcrypt');
3 |
4 | var createDepartment = (knex, id) => {
5 | return knex('entities').insert({
6 | fullname: faker.commerce.department(),
7 | email: faker.internet.email(),
8 | isdepartment: true
9 | })
10 | }
11 |
12 | exports.seed = function(knex, Promise) {
13 | return knex('entities').del()
14 | .then(function () {
15 | let records = []
16 |
17 | for (i = 0; i < 7; i++) {
18 | records.push(createDepartment(knex, i));
19 | }
20 | return Promise.all(records);
21 | })
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/users/UserRoles.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class UserRoles extends Component {
4 | render() {
5 | return(
6 |
7 | User Roles
8 |
9 |
10 |
11 |
12 | Type
13 | Number of Users
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 | }
22 |
23 | export default UserRoles;
24 |
--------------------------------------------------------------------------------
/src/server/seeds1/04-conversations.js:
--------------------------------------------------------------------------------
1 | var getStaff = (knex) => {
2 | return knex('entities')
3 | .select('id')
4 | .pluck('id')
5 | .where('isstaff', true)
6 | .then(staffList => {
7 | return staffList
8 | })
9 | .catch(err => {
10 | console.log('error: ' + err);
11 | })
12 | }
13 |
14 | var createConversation = (knex) => {
15 | return knex('conversations').insert({})
16 | .returning('id')
17 | .then(id => {
18 |
19 | })
20 | }
21 |
22 | exports.seed = function(knex, Promise) {
23 | // Deletes ALL existing entries
24 | let staffList = getStaff(knex);
25 | console.log(staffList);
26 | };
27 |
--------------------------------------------------------------------------------
/src/server/graphql/conversation.js:
--------------------------------------------------------------------------------
1 | import Entities from './entities';
2 |
3 | const Conversations = `
4 | type Conversations {
5 | conversation_type: Int!
6 | }
7 |
8 | type ConversationMessages {
9 | id: Int!
10 | conversation_id: Conversations
11 | message: String
12 | entity_id: Entities
13 | }
14 |
15 | type Participants {
16 | id: Int!
17 | conversation_id: Conversations
18 | entity_id: Entities
19 | isadmin: Boolean
20 | }
21 |
22 | type Query {
23 | conversation(conversation_type: Int!): Conversations
24 | getConversation: String
25 | }
26 | `
27 |
28 | export default () => [Conversations, Entities];
29 |
--------------------------------------------------------------------------------
/src/server/graphql/entities.js:
--------------------------------------------------------------------------------
1 | const Entities = `
2 | scalar Date
3 |
4 | type Entities {
5 | id: Int!
6 | parent_id: Int
7 | email: String
8 | fullname: String
9 | firstname: String
10 | lastname: String
11 | designation: String
12 | department_id: Int
13 | address1: String
14 | address2: String
15 | postcode: String
16 | city: String
17 | state: String
18 | country: String
19 | isstaff: Boolean
20 | isdepartment: Boolean
21 | iscustomer: Boolean
22 | issupllier: Boolean
23 | isfixedasset: Boolean
24 | isonline: Boolean
25 | isallowedtologin: Boolean
26 | createdAt: Date
27 | updatedAt: Date
28 | }
29 | `
30 | export default Entities;
31 |
--------------------------------------------------------------------------------
/src/actions/staffInformation.js:
--------------------------------------------------------------------------------
1 | import { SET_DISPLAYED_STAFF } from './types';
2 | import axios from 'axios';
3 | import { apiServer } from './config';
4 |
5 | export function putDisplayedStaff(staffList) {
6 | return {
7 | type: SET_DISPLAYED_STAFF,
8 | staffList
9 | };
10 | }
11 |
12 | export function setDisplayedStaff(pageNum = 0) {
13 | return dispatch => {
14 | return axios.get(apiServer + '/api/staff/' + pageNum)
15 | .then((response) => {
16 | localStorage.setItem("staffList", response.data)
17 | dispatch(putDisplayedStaff(response.data));
18 | })
19 | }
20 | }
21 |
22 | export function getStaffById(staffId) {
23 | return axios.get(apiServer + '/api/staffdetails/' + staffId)
24 | }
25 |
--------------------------------------------------------------------------------
/src/server/routes/index2.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Validator from 'validator';
4 | import knex from '../db/knex';
5 | import bcrypt from 'bcrypt';
6 | import Entities from '../models-knex/entities';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 | const router = express.Router();
10 | const staff = require('./staff')(router);
11 | const department = require('./department')(router);
12 | const test = require('./test')(router);
13 | const authentication = require('./authentication')(router);
14 |
15 | router.get('/', (req, res, next) => {
16 | res.status(200).json({
17 | 'string': 'It works!'
18 | })
19 | });
20 |
21 | export default router;
22 |
--------------------------------------------------------------------------------
/src/actions/department.js:
--------------------------------------------------------------------------------
1 | import { SET_DISPLAYED_DEPARTMENT} from './types';
2 | import axios from 'axios';
3 | import { apiServer } from './config';
4 |
5 | export function putDisplayedDepartment(departmentList) {
6 | return {
7 | type: SET_DISPLAYED_DEPARTMENT,
8 | departmentList
9 | };
10 | }
11 |
12 | export function setDisplayedDepartment(pageNum = 1) {
13 | // console.log('here');
14 | return dispatch => {
15 | return axios.get(apiServer + '/api/department/' + pageNum)
16 | .then((response) => {
17 | localStorage.setItem("departmentList", response.data)
18 | dispatch(putDisplayedDepartment(response.data));
19 | })
20 | }
21 | }
22 |
23 | export function getDepartmentById(departmentId) {
24 | return axios.get(apiServer + '/api/departmentdetails/' + departmentId)
25 | }
26 |
--------------------------------------------------------------------------------
/src/server/routes/test.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Validator from 'validator';
4 | import knex from '../db/knex';
5 | import bcrypt from 'bcrypt';
6 | // import Entities from '../models-knex/entities';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 |
10 | module.exports = (router) => {
11 | router.get('/api/test/cm', (req, res, next) => {
12 | knex('conversation_messages')
13 | // .table('conversation_participants')
14 | .distinct('conversation_id')
15 | .max("id")
16 | .select(knex.raw('jsonb_agg(entity_id) as entities, jsonb_agg(message) as messages'))
17 | .groupBy("conversation_id")
18 | .debug()
19 | .then(results => {
20 | res.status(200).json(results);
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/src/server/seeds/03-customer.js:
--------------------------------------------------------------------------------
1 | const faker = require('faker');
2 | const bcrypt = require('bcrypt');
3 |
4 | var createCustomer = (knex, id) => {
5 | return knex('entities').insert({
6 | fullname: faker.company.companyName(),
7 | password_digest: bcrypt.hashSync('password', 10),
8 | email: faker.internet.email(),
9 | address1: faker.address.streetName(),
10 | address2: faker.address.streetAddress(),
11 | postcode: faker.address.zipCode(),
12 | city: faker.address.city(),
13 | state: faker.address.state(),
14 | country: faker.address.country(),
15 | iscustomer: true
16 | })
17 | }
18 |
19 | exports.seed = function(knex, Promise) {
20 | let records = [];
21 |
22 | for (let i = 0; i < 100; i++) {
23 | records.push(createCustomer(knex, i))
24 | }
25 |
26 | return Promise.all(records)
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/helpers/Pagination.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | class Pagination extends Component {
5 | render() {
6 | const { theElements } = this.props
7 | return(
8 |
9 | {
10 | theElements.map((element, key) => {
11 | return (
12 |
15 | this.props.reRender(key+1)} style={{width:'100%', minHeight:'100%'}}>
16 | {element.display}
17 |
18 |
19 | )
20 | })
21 | }
22 |
23 | )
24 | }
25 | }
26 |
27 | export default Pagination;
28 |
--------------------------------------------------------------------------------
/src/server/sequelize/migrations/20170924185022-create-conversations.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('conversations', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | conversationtype: {
12 | type: Sequelize.INTEGER
13 | },
14 | createdAt: {
15 | allowNull: false,
16 | type: Sequelize.DATE,
17 | defaultValue: Sequelize.fn('NOW')
18 | },
19 | updatedAt: {
20 | allowNull: false,
21 | type: Sequelize.DATE,
22 | defaultValue: Sequelize.fn('NOW')
23 | }
24 | });
25 | },
26 | down: (queryInterface, Sequelize) => {
27 | return queryInterface.dropTable('conversations');
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/mobile/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mobile",
3 | "version": "0.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "babel-plugin-import": "^1.4.0",
7 | "jest-expo": "~20.0.0",
8 | "react-native-scripts": "1.3.1",
9 | "react-test-renderer": "16.0.0-alpha.12"
10 | },
11 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
12 | "scripts": {
13 | "start": "react-native-scripts start",
14 | "eject": "react-native-scripts eject",
15 | "android": "react-native-scripts android",
16 | "ios": "react-native-scripts ios",
17 | "test": "node node_modules/jest/bin/jest.js --watch"
18 | },
19 | "jest": {
20 | "preset": "jest-expo"
21 | },
22 | "dependencies": {
23 | "antd-mobile": "^1.6.5",
24 | "expo": "^20.0.0",
25 | "react": "16.0.0-alpha.12",
26 | "react-native": "^0.47.0",
27 | "react-native-elements": "^0.16.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/server/migrations_temp/20170913112234_leave_application.js:
--------------------------------------------------------------------------------
1 |
2 | exports.up = function(knex, Promise) {
3 | return knex.schema.createTable('leave_application', table => {
4 | table.increments();
5 | table.integer('applicant_id')
6 | .unsigned()
7 | .references('entities.id');
8 | table.integer('leave_type').unsigned();
9 | table.string('applicant_remarks');
10 | table.string('date_from').notNullable().defaultTo(knex.raw('now()'));
11 | table.string('date_to').notNullable().defaultTo(knex.raw('now()'));
12 | table.integer('reviewed_by')
13 | .unsigned()
14 | .references('entities_id');
15 | table.integer('approval_status');
16 | table.string('approval_remarks');
17 | table.timestamp('created_at').notNullable().defaultTo(knex.raw('now()'));
18 | table.timestamp('updated_at').notNullable().defaultTo(knex.raw('now()'));
19 | })
20 | };
21 |
22 | exports.down = function(knex, Promise) {
23 | return knex.schema.dropTable('leave_application');
24 | };
25 |
--------------------------------------------------------------------------------
/mobile/components/MainContent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { StyleSheet, Text, View, Image } from 'react-native';
3 |
4 | class MainContent extends Component {
5 | render() {
6 | return(
7 |
8 | Open up App.js to start working on your app!
9 | Changes you make will automatically reload.
10 | Shake your phone to open the developer menu.
11 | {/* */}
18 |
19 | )
20 | }
21 | }
22 |
23 | const styles = StyleSheet.create({
24 | container: {
25 | flex: 1,
26 | backgroundColor: 'rgba(0,0,0,0)',
27 | alignItems: 'center',
28 | justifyContent: 'center'
29 | }
30 | })
31 |
32 | export default MainContent;
33 |
--------------------------------------------------------------------------------
/src/server/graphql/schema.js:
--------------------------------------------------------------------------------
1 | import {
2 | makeExecutableSchema
3 | } from 'graphql-tools';
4 | import Conversations from './conversation';
5 |
6 | const resolvers = {
7 | String: () => 'It works',
8 | Query: {
9 | conversation: (root, args) => {
10 | return {
11 | conversation_type: args.conversation_type
12 | }
13 | }
14 | }
15 | }
16 |
17 | const typeDefs = `
18 | type Conversations {
19 | conversation_type: Int!
20 | }
21 |
22 | type ConversationMessages {
23 | id: Int!
24 | conversation_id: Conversations
25 | message: String
26 | entity_id: Entities
27 | }
28 |
29 | type Participants {
30 | id: Int!
31 | conversation_id: Conversations
32 | entity_id: Entities
33 | isadmin: Boolean
34 | }
35 |
36 | type Query {
37 | conversation(conversation_type: Int!): Conversations
38 | getConversation: String
39 | }
40 | `
41 | const schema = makeExecutableSchema({
42 | typeDefs: [Conversations],
43 | resolvers
44 | });
45 |
46 | export default schema;
47 |
--------------------------------------------------------------------------------
/src/server/routes/department.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Validator from 'validator';
4 | import knex from '../db/knex';
5 | import bcrypt from 'bcrypt';
6 | import Entities from '../models-knex/entities';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 |
10 | module.exports = (router) => {
11 | router.get('/api/department/:pageNum', (req, res, next) => {
12 | knex('entities')
13 | .select('id', 'fullname')
14 | .where('isdepartment', true)
15 | .then(departments => {
16 | console.log(departments);
17 | res.status(200).json({
18 | departments
19 | })
20 | })
21 | })
22 |
23 | router.get('/api/departmentdetails/:departmentId', (req, res, next) => {
24 | Entities.where({
25 | isdepartment: true,
26 | id: req.params.departmentId
27 | }).fetch()
28 | .then((results) => {
29 | res.status(200).send(results)
30 | })
31 | .catch((err) => {
32 | res.status(401).send(err)
33 | })
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/src/server/knexfile.js:
--------------------------------------------------------------------------------
1 | // Update with your config settings.
2 |
3 | module.exports = {
4 |
5 | development: {
6 | client: 'postgresql',
7 | connection: {
8 | database: 'bis',
9 | user: 'postgreuser',
10 | password: 'password'
11 | },
12 | pool: {
13 | min: 2,
14 | max: 10
15 | },
16 | migrations: {
17 | tableName: 'knex_migrations'
18 | }
19 | },
20 |
21 | staging: {
22 | client: 'postgresql',
23 | connection: {
24 | database: 'my_db',
25 | user: 'username',
26 | password: 'password'
27 | },
28 | pool: {
29 | min: 2,
30 | max: 10
31 | },
32 | migrations: {
33 | tableName: 'knex_migrations'
34 | }
35 | },
36 |
37 | production: {
38 | client: 'postgresql',
39 | connection: {
40 | database: 'my_db',
41 | user: 'username',
42 | password: 'password'
43 | },
44 | pool: {
45 | min: 2,
46 | max: 10
47 | },
48 | migrations: {
49 | tableName: 'knex_migrations'
50 | }
51 | }
52 |
53 | };
54 |
--------------------------------------------------------------------------------
/src/index.scss:
--------------------------------------------------------------------------------
1 | @import "@blueprintjs/core";
2 |
3 | html, body {
4 | margin: 0;
5 | padding: 0;
6 | font-family: sans-serif;
7 | color: white;
8 | background-image: url('blur-bg.jpg');
9 | width: 100%;
10 | }
11 |
12 | table, thead, tr, td {
13 | color: white;
14 | }
15 |
16 | .leftMenuSpan {
17 | padding-right: 7px;
18 | cursor: pointer;
19 | }
20 |
21 | ul {
22 | list-style-type:none;
23 | margin: 0 0 1 0;
24 | padding: 0;
25 | }
26 |
27 | li {
28 | padding-top: 5px;
29 | }
30 |
31 | .chat {
32 | min-height: 400px;
33 | height: 100%;
34 | }
35 |
36 | .centeringText {
37 | align-items: center;
38 | text-align: center;
39 | }
40 |
41 | .page-container .bg {
42 | position: absolute;
43 | top: 0;
44 | left: 0;
45 | width: 100vw;
46 | height: 100vh;
47 | background-size: cover;
48 | mix-blend-mode: overlay;
49 | }
50 |
51 | .middlingVAlign {
52 | vertical-align: middle;
53 | }
54 |
55 | .w3-circle{border-radius:50%}
56 |
57 | .contacts{
58 | max-height: 20px;
59 | }
60 |
61 | .transparentThis {
62 | background-color: rgba(0, 0, 0, 0.2)
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/helpers/PageNotFound.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 |
4 | class PageNotFound extends Component {
5 | callMeBack() {
6 | setTimeout(() => {
7 | this.setState({redirect: true})
8 | }, 5000)
9 | }
10 |
11 | state = {
12 | redirect: false
13 | }
14 |
15 | componentDidMount() {
16 | this.callMeBack()
17 | }
18 |
19 | render() {
20 | return (
21 | (this.state.redirect)
22 | ?
23 | :
24 |
25 |
26 |
Page not found
27 |
28 |
29 | Well, this is embarassing. The path was not found.
30 |
31 | Redirecting in 5 seconds.
32 |
33 | Or, It is now safe to turn off your computer.
34 |
35 |
36 |
37 | )
38 | }
39 | }
40 |
41 | export default PageNotFound;
42 |
--------------------------------------------------------------------------------
/src/components/navigations/RightMenu.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | // import { Card } from '@blueprintjs/core';
3 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
4 | import 'react-tabs/style/react-tabs.css';
5 | import MainChat from '../communications/MainChat'
6 |
7 | class RightMenu extends Component {
8 |
9 | render() {
10 | return (
11 |
12 |
13 |
14 | Chats
15 | Contacts
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | onTabs2Change() {
29 |
30 | }
31 | }
32 |
33 | class Contacts extends Component {
34 | render() {
35 | return(
36 |
37 | Contacts
38 |
39 | )
40 | }
41 | }
42 |
43 | export default RightMenu;
44 |
--------------------------------------------------------------------------------
/src/server/seeds/02-staff.js:
--------------------------------------------------------------------------------
1 | const faker = require('faker');
2 | const bcrypt = require('bcrypt');
3 |
4 | var getDepartments = (knex) => {
5 | return knex.select().table('entities')
6 | }
7 |
8 | var createStaff = (knex, department_num) => {
9 | var firstname = faker.name.firstName();
10 | var lastname = faker.name.lastName();
11 |
12 | return knex('entities').insert({
13 | email: faker.internet.email(),
14 | password_digest: bcrypt.hashSync('password', 10),
15 | firstname,
16 | lastname,
17 | fullname: firstname + ' ' + lastname,
18 | designation: faker.name.jobTitle(),
19 | isstaff: true,
20 | department_num,
21 | is_allowed_to_login: true
22 | })
23 | }
24 |
25 | exports.seed = function(knex, Promise) {
26 | return knex('entities')
27 | .select('fullname')
28 | .where('isdepartment', true)
29 | .pluck('id')
30 | .then(departmentList => {
31 | let records = [];
32 |
33 | for (let i = 0; i < 100; i++) {
34 | records.push(createStaff(knex, departmentList[Math.floor(Math.random()*departmentList.length)]))
35 | }
36 | return Promise.all(records);
37 | })
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/helpers/ComposeEmail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button } from '@blueprintjs/core';
3 |
4 | class ComposeEmail extends Component {
5 | render() {
6 | return(
7 |
8 |
9 | To
10 | (required)
11 |
12 |
13 |
14 | Message
15 | (required)
16 |
17 |
18 |
19 |
20 | {
22 | e.preventDefault();
23 | this.props.setToCancel()
24 | }}
25 | />
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default ComposeEmail;
33 |
--------------------------------------------------------------------------------
/src/server/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb/base",
4 | "env": {
5 | "es6": true,
6 | "node": true,
7 | "mocha" : true
8 | },
9 | "ecmaFeatures": {
10 | "classes": true,
11 | "modules": true
12 | },
13 | "rules": {
14 | "comma-dangle": [2, "never"],
15 | "consistent-return": 0,
16 | "func-names": 0,
17 | "guard-for-in": 0,
18 | "import/no-extraneous-dependencies": 0,
19 | "import/newline-after-import": 0,
20 | "key-spacing": 0,
21 | "max-len": 1,
22 | "newline-per-chained-call": [2, { "ignoreChainWithDepth": 5 }],
23 | "no-confusing-arrow": 0,
24 | "no-console": 0,
25 | "no-continue": 0,
26 | "no-mixed-operators": 0,
27 | "no-multi-spaces": 0,
28 | "no-param-reassign": 0,
29 | "no-restricted-syntax": [2, "WithStatement"],
30 | "no-shadow": 0,
31 | "no-underscore-dangle": [0],
32 | "no-unused-expressions": [2, { "allowShortCircuit": true }],
33 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
34 | "space-before-function-paren": [2, "never"],
35 | "space-in-parens": 0,
36 | "padded-blocks": 0
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/server/sequelize/migrations/20170924190713-create-conversations-participants.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('conversation_participants', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | conversation_id: {
12 | type: Sequelize.INTEGER,
13 | allowNull: false
14 | },
15 | entity_id: {
16 | type: Sequelize.INTEGER,
17 | allowNull: false
18 | },
19 | isadmin: {
20 | type: Sequelize.BOOLEAN,
21 | allowNull: false,
22 | defaultValue: false
23 | },
24 | createdAt: {
25 | allowNull: false,
26 | type: Sequelize.DATE,
27 | defaultValue: Sequelize.fn('NOW')
28 | },
29 | updatedAt: {
30 | allowNull: false,
31 | type: Sequelize.DATE,
32 | defaultValue: Sequelize.fn('NOW')
33 | }
34 | });
35 | },
36 | down: (queryInterface, Sequelize) => {
37 | return queryInterface.dropTable('conversation_participants');
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/actions/userAuthentication.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import jwtDecode from 'jwt-decode';
3 | import setAuthorizationToken from '../utils/setAuthorizationToken';
4 | import { SET_CURRENT_USER } from './types';
5 | import { apiServer } from './config';
6 |
7 | export function setCurrentUser(user) {
8 | return {
9 | type: SET_CURRENT_USER,
10 | user
11 | };
12 | }
13 |
14 | export function userLoginRequest(userData) {
15 | return dispatch => {
16 | return axios.post(
17 | apiServer + '/api/login',
18 | userData
19 | ).then((response) => {
20 | const token = response.data.token;
21 | localStorage.setItem('jwtToken', token);
22 | setAuthorizationToken(token);
23 | dispatch(setCurrentUser(jwtDecode(token)));
24 | return {
25 | retStatus: true,
26 | errors: {}
27 | }
28 | }).catch((errors) => {
29 | return {
30 | retStatus: false,
31 | errors: errors.response.data
32 | }
33 | })
34 | }
35 | }
36 |
37 | export function logout() {
38 | return dispatch => {
39 | localStorage.removeItem('jwtToken');
40 | setAuthorizationToken(false);
41 | dispatch(setCurrentUser({}));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/server/sequelize/seeders/20170924202122-demo-entities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const bcrypt = require('bcrypt');
4 |
5 | module.exports = {
6 | up: (queryInterface, Sequelize) => {
7 | return queryInterface.bulkInsert('entities', [
8 | {
9 | fullname: 'John',
10 | lastname: 'Doe',
11 | fullname: 'John Doe',
12 | email: 'johndoe@gmail.com',
13 | password_digest: bcrypt.hashSync('password', 10),
14 | isstaff: true,
15 | createdAt: Sequelize.fn('NOW'),
16 | updatedAt: Sequelize.fn('NOW')
17 | }
18 | ], {})
19 |
20 | /*
21 | Add altering commands here.
22 | Return a promise to correctly handle asynchronicity.
23 |
24 | Example:
25 | return queryInterface.bulkInsert('Person', [{
26 | name: 'John Doe',
27 | isBetaMember: false
28 | }], {});
29 | */
30 | },
31 |
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.bulkDelete('entities', null, {});
34 | /*
35 | Add reverting commands here.
36 | Return a promise to correctly handle asynchronicity.
37 |
38 | Example:
39 | return queryInterface.bulkDelete('Person', null, {});
40 | */
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/src/components/humanresource/DepartmentDetails.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class DepartmentDetails extends Component {
4 | render() {
5 | return(
6 |
33 | )
34 | }
35 | }
36 |
37 | export default DepartmentDetails;
38 |
--------------------------------------------------------------------------------
/src/server/sequelize/migrations/20170924185717-create-conversations-messages.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('conversation_messages', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | conversation_id: {
12 | type: Sequelize.INTEGER,
13 | allowNull: false
14 | },
15 | message: {
16 | type: Sequelize.STRING
17 | },
18 | message_type: {
19 | type: Sequelize.INTEGER,
20 | allowNull: false,
21 | defaultValue: 0
22 | },
23 | entity_id: {
24 | type: Sequelize.INTEGER
25 | },
26 | target_id: {
27 | type: Sequelize.INTEGER
28 | },
29 | createdAt: {
30 | allowNull: false,
31 | type: Sequelize.DATE,
32 | defaultValue: Sequelize.fn('NOW')
33 | },
34 | updatedAt: {
35 | allowNull: false,
36 | type: Sequelize.DATE,
37 | defaultValue: Sequelize.fn('NOW')
38 | }
39 | });
40 | },
41 | down: (queryInterface, Sequelize) => {
42 | return queryInterface.dropTable('conversation_messages');
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/mobile/components/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, Text, View, Image } from 'react-native';
3 | import { Button, FormLabel, FormInput } from 'react-native-elements';
4 |
5 | export default class LoginForm extends React.Component {
6 | onPressSubmit() {
7 |
8 | }
9 | render() {
10 | return (
11 |
12 |
13 | Email
14 |
15 |
16 |
17 | Password
18 |
19 |
20 |
21 |
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | container: {
30 | flex: 1,
31 | backgroundColor: 'rgba(0,0,0,0)',
32 | alignItems: 'center',
33 | justifyContent: 'center',
34 | color: 'white'
35 | },
36 | subContainer: {
37 | color: 'white',
38 | alignItems: 'center',
39 | justifyContent: 'center',
40 | },
41 | label: {
42 | color: 'white'
43 | }
44 | })
45 |
--------------------------------------------------------------------------------
/src/components/dashboard/Graph.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {ComposedChart, Line, Area, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend} from 'recharts';
3 |
4 | class Graph extends Component {
5 | render() {
6 | const data = [{name: 'Page A', uv: 590, pv: 800, amt: 1400},
7 | {name: 'Page B', uv: 868, pv: 967, amt: 1506},
8 | {name: 'Page C', uv: 1397, pv: 1098, amt: 989},
9 | {name: 'Page D', uv: 1480, pv: 1200, amt: 1228},
10 | {name: 'Page E', uv: 1520, pv: 1108, amt: 1100},
11 | {name: 'Page F', uv: 1400, pv: 680, amt: 1700}];
12 | return(
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 | }
29 |
30 | export default Graph;
31 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { createStore, applyMiddleware } from 'redux';
5 | import thunk from 'redux-thunk';
6 | import { composeWithDevTools } from 'redux-devtools-extension'
7 | import rootReducer from './rootReducer';
8 | import App from './App';
9 | import registerServiceWorker from './registerServiceWorker';
10 | import setAuthorizationToken from './utils/setAuthorizationToken';
11 | import jwtDecode from 'jwt-decode';
12 | import { setCurrentUser } from './actions/userAuthentication';
13 | // import { setDisplayedStaff } from './actions/staffInformation';
14 |
15 | const store = createStore(
16 | rootReducer,
17 | composeWithDevTools(
18 | applyMiddleware(thunk)
19 | )
20 | );
21 |
22 | if (localStorage.jwtToken) {
23 | setAuthorizationToken(localStorage.jwtToken);
24 | store.dispatch(setCurrentUser(jwtDecode(localStorage.jwtToken)));
25 | // store.dispatch(setDisplayedStaff(localStorage.staffList));
26 | }
27 |
28 | // window.onbeforeunload = (e) => {
29 | // window.onunload = () => {
30 | // window.localStorage.removeItem('jwtToken');
31 | // }
32 | // return undefined();
33 | // }
34 |
35 | ReactDOM.render(
36 |
37 |
38 |
39 | ,document.getElementById('root')
40 | );
41 | registerServiceWorker();
42 |
--------------------------------------------------------------------------------
/mobile/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { StyleSheet, Text, View, Image } from 'react-native';
3 | import { Button } from 'react-native-elements';
4 | import MainContent from './components/MainContent'
5 | import LoginForm from './components/LoginForm'
6 |
7 | export default class App extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | isUserLoggedIn: false,
12 | }
13 | }
14 |
15 | async checkIfUserHasLoggedIn() {
16 | let isLoggedIn = false;
17 | try {
18 | const isLoggedIn = await AsyncStorage.getItem('@JWTAuth:key');
19 | if (isLoggedIn !== null) {
20 | this.setState({
21 | isUserLoggedIn: true
22 | })
23 | }
24 | } catch (error) {
25 | console.log('false');
26 | }
27 | return isLoggedIn;
28 | }
29 |
30 | render() {
31 | const isLoggedIn = this.checkIfUserHasLoggedIn();
32 | console.log('state: ' + this.state);
33 | return (
34 |
35 | { this.state.isUserLoggedIn ? : }
36 |
37 | );
38 | }
39 | }
40 |
41 | const styles = StyleSheet.create({
42 | backgroundImage: {
43 | flex: 1,
44 | // alignSelf: 'stretch',
45 | width: null,
46 | height: null,
47 | resizeMode: 'cover'
48 | }
49 | });
50 |
--------------------------------------------------------------------------------
/src/server/migrations/20170831073944_entities.js:
--------------------------------------------------------------------------------
1 |
2 | exports.up = function(knex, Promise) {
3 | return knex.schema.createTable('entities', (table) => {
4 | table.increments();
5 | table.string('entity_id');
6 | table.string('parent_id');
7 | table.string('email').unique().notNullable();
8 | table.string('password_digest')
9 | table.string('fullname').notNullable();
10 | table.string('firstname');
11 | table.string('lastname');
12 | table.string('designation');
13 | table.string('department_id');
14 | table.string('address1');
15 | table.string('address2');
16 | table.string('postcode');
17 | table.string('city');
18 | table.string('state');
19 | table.string('country');
20 | table.boolean('isstaff').defaultTo(0);
21 | table.boolean('isdepartment').defaultTo(0);
22 | table.boolean('iscustomer').defaultTo(0);
23 | table.boolean('issupplier').defaultTo(0);
24 | table.boolean('isfixedasset').defaultTo(0);
25 | table.integer('department_num').defaultTo(0);
26 | table.boolean('isonline').defaultTo(false);
27 | table.boolean('is_allowed_to_login').defaultTo(false);
28 | table.timestamp('created_at').notNullable().defaultTo(knex.raw('now()'));
29 | table.timestamp('updated_at').notNullable().defaultTo(knex.raw('now()'));
30 | });
31 | };
32 |
33 | exports.down = function(knex, Promise) {
34 | return knex.schema.dropTable('entities');
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | // import { Button
3 | // Dialog, Intent, Hotkey, Hotkeys, HotkeysTarget
4 | // } from "@blueprintjs/core";
5 | import Graph from './dashboard/Graph';
6 |
7 | export default class Dashboard extends Component {
8 | render() {
9 | return(
10 |
11 | Dashboard
12 |
13 |
14 |
Human Resource
15 |
3 onleave
16 |
2 claims
17 |
18 |
19 |
Customer
20 |
3 new orders
21 |
3 shipment
22 |
23 |
24 |
Supplier
25 |
1 pending payment
26 |
2 arrival
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "newbis",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@blueprintjs/core": "^3.10.0",
7 | "@blueprintjs/docs": "^1.3.1",
8 | "axios": "^0.18.0",
9 | "classnames": "^2.2.6",
10 | "history": "^4.6.3",
11 | "jwt-decode": "^2.2.0",
12 | "lodash": "^4.17.4",
13 | "node-sass-chokidar": "^0.0.3",
14 | "prop-types": "^15.5.10",
15 | "rc-pagination": "^1.12.1",
16 | "react": "^16.7.0",
17 | "react-addons-css-transition-group": "^15.6.0",
18 | "react-collapsible": "^1.5.0",
19 | "react-dom": "^16.7.0",
20 | "react-draggable": "^3.0.3",
21 | "react-redux": "^6.0.0",
22 | "react-responsive-accordion": "^1.0.0",
23 | "react-router": "^4.3.1",
24 | "react-router-dom": "^4.1.2",
25 | "react-router-redux": "^4.0.8",
26 | "react-scripts": "1.0.11",
27 | "react-tabs": "^1.1.0",
28 | "react-transition-group": "^2.2.0",
29 | "recharts": "^1.0.0-apha.5",
30 | "redux": "^3.7.2",
31 | "redux-devtools-extension": "^2.13.2",
32 | "redux-thunk": "^2.2.0",
33 | "socket.io-client": "^2.0.3",
34 | "switch-css-transition-group": "^0.1.2",
35 | "validator": "^9.1.0"
36 | },
37 | "scripts": {
38 | "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
39 | "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive",
40 | "start": "react-scripts start",
41 | "build": "react-scripts build",
42 | "test": "react-scripts test --env=jsdom",
43 | "eject": "react-scripts eject"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "app.js",
6 | "scripts": {
7 | "start": "node ./bin/start",
8 | "test": "mocha --recursive './test/*Test.js' --compilers js:babel-core/register"
9 | },
10 | "dependencies": {
11 | "apollo-server-express": "^1.1.2",
12 | "babel-core": "^6.23.1",
13 | "babel-polyfill": "^6.23.0",
14 | "babel-preset-es2015": "^6.22.0",
15 | "babel-preset-stage-0": "^6.22.0",
16 | "bcrypt": "^1.0.3",
17 | "body-parser": "~1.16.0",
18 | "bookshelf": "^0.10.4",
19 | "cookie-parser": "~1.4.3",
20 | "cors": "^2.8.4",
21 | "debug": "~2.6.0",
22 | "express": "~4.14.1",
23 | "faker": "^4.1.0",
24 | "graphql": "^0.11.3",
25 | "graphql-server-express": "^1.1.2",
26 | "graphql-tools": "^1.2.3",
27 | "jsonwebtoken": "^7.4.2",
28 | "knex": "^0.13.0",
29 | "knex-migrate": "^1.3.0",
30 | "lodash": "^4.17.4",
31 | "moment": "^2.18.1",
32 | "morgan": "~1.7.0",
33 | "node-sass-middleware": "^0.9.8",
34 | "passport": "^0.4.0",
35 | "pg": "^7.3.0",
36 | "pg-hstore": "^2.3.2",
37 | "pug": "^2.0.0-beta11",
38 | "sequelize": "^4.10.2",
39 | "serve-favicon": "~2.3.2",
40 | "socket.io": "^2.0.3",
41 | "validator": "^9.1.0"
42 | },
43 | "devDependencies": {
44 | "babel-eslint": "^7.1.1",
45 | "chai": "^3.5.0",
46 | "chai-as-promised": "^6.0.0",
47 | "eslint": "^3.1.1",
48 | "eslint-config-airbnb": "^14.1.0",
49 | "eslint-plugin-import": "^2.2.0",
50 | "eslint-plugin-jsx-a11y": "^4.0.0",
51 | "eslint-plugin-mocha": "^4.8.0",
52 | "eslint-plugin-react": "^6.10.0"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
MyERP
3 |
4 | This is a project to inspire small medium businesses to build and manage their own defination of ERP or Business Information System that is light-weight and modular as well.
5 |
6 |
7 | Demo Apps -
MyERP
8 |
9 | For demo purposes, please use Ezekiel.Botsford73@hotmail.com for email and password as password
10 |
11 |
12 |
13 |
14 |
15 | Tools
16 |
17 |
18 |
19 | ReactJS (via create-react-app)
20 | GraphQL
21 | NodeJS (Express ES6 framework)
22 | PostgreSQL (knexjs)
23 | JWT
24 |
25 |
26 |
27 |
28 |
29 |
Feel free to clone this project
30 |
31 | git clone https://github.com/iqbalsafian/MyERP.git
32 |
33 |
34 |
For server:
35 |
36 |
37 |
38 | cd MyERP/server
39 |
40 |
41 | yarn install
42 |
43 |
44 | node bin/start
45 |
46 |
47 |
48 |
49 |
For client:
50 |
51 |
52 |
53 | cd MyERP
54 |
55 |
56 | yarn install
57 |
58 |
59 | yarn start
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import './index.css';
5 | import './unsemantic-grid-responsive.css';
6 | import TopNavigation from './components/navigations/TopNavigation';
7 | import LeftMenu from './components/navigations/LeftMenu';
8 | import MainContent from './components/navigations/MainContent';
9 | import RightMenu from './components/navigations/RightMenu';
10 | import LoginForm from './components/users/LoginForm';
11 | import { BrowserRouter } from 'react-router-dom';
12 |
13 | class App extends Component {
14 | reRender = () => {
15 | this.render();
16 | }
17 | render() {
18 | const { isAuthenticated } = this.props.auth;
19 | const authenticatedUserLinks = (
20 |
21 |
22 |
23 | this.reRender.bind(this)} />
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | return (
40 |
41 |
42 | { isAuthenticated ? authenticatedUserLinks : this.reRender} /> }
43 |
44 |
45 | );
46 | }
47 | }
48 |
49 | App.propTypes = {
50 | auth: PropTypes.object.isRequired
51 | }
52 |
53 | function mapStateToProps(state) {
54 | return {
55 | auth: state.auth
56 | }
57 | }
58 |
59 | export default connect(mapStateToProps)(App);
60 |
--------------------------------------------------------------------------------
/src/server/migrations/20170910074905_conversations.js:
--------------------------------------------------------------------------------
1 |
2 | exports.up = function(knex, Promise) {
3 | return Promise.all([
4 | knex.schema.createTable('conversations', table => {
5 | table.increments().primary();
6 | table.integer('conversation_type');
7 | table.timestamp('created_at').notNullable().defaultTo(knex.raw('now()'));
8 | table.timestamp('updated_at').notNullable().defaultTo(knex.raw('now()'));
9 | }),
10 |
11 | knex.schema.createTable('conversation_participants', table => {
12 | table.increments().primary();
13 | table.integer('conversation_id').unsigned()
14 | .references('conversations.id');
15 | table.integer('entity_id').unsigned()
16 | .references('entities.id');
17 | table.boolean('isadmin');
18 | table.timestamp('created_at').notNullable().defaultTo(knex.raw('now()'));
19 | table.timestamp('updated_at').notNullable().defaultTo(knex.raw('now()'));
20 | }),
21 |
22 | knex.schema.createTable('conversation_messages', table => {
23 | table.increments().primary();
24 | table.integer('conversation_id').unsigned()
25 | .references('conversations.id');
26 | table.string('message');
27 | table.integer('message_type').unsigned();
28 | table.integer('entity_id').unsigned()
29 | .references('entities.id');
30 | table.integer('target_id').unsigned()
31 | .references('entities.id');
32 | table.timestamp('created_at').notNullable().defaultTo(knex.raw('now()'));
33 | table.timestamp('updated_at').notNullable().defaultTo(knex.raw('now()'));
34 | })
35 | ])
36 | };
37 |
38 | exports.down = function(knex, Promise) {
39 | return Promise.all([
40 | knex.schema.dropTable('conversation_messages'),
41 | knex.schema.dropTable('conversation_participants'),
42 | knex.schema.dropTable('conversations')
43 | ])
44 | };
45 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
24 | MyERP
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/server/routes/authentication.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Validator from 'validator';
4 | import knex from '../db/knex';
5 | import bcrypt from 'bcrypt';
6 | // import Entities from '../models-knex/entities';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 | // import {validateLogin} from '../../utils/validateLogin';
10 | import validator from 'validator';
11 |
12 | function validateLogin(email, password) {
13 | var errors = {};
14 |
15 | if (email === '') {
16 | errors.email = 'Email cannot be empty';
17 | }
18 | if (!validator.isEmail(email)) {
19 | errors.email = 'Invalid email format';
20 | }
21 | if (password === '') {
22 | errors.password = 'Password cannot be empty';
23 | }
24 |
25 | return errors;
26 | }
27 |
28 | module.exports = (router) => {
29 | router.post('/api/login', (req, res, next) => {
30 | var { email = '', password = ''} = req.body;
31 | let errors = validateLogin(email, password);
32 | if (errors.email || errors.password) {
33 | res.status(400).json(errors)
34 | } else {
35 | knex('entities')
36 | .select('id', 'fullname', 'password_digest')
37 | .where({
38 | 'email': email,
39 | 'isstaff': true
40 | })
41 | .then(staff => {
42 | if (staff.length) {
43 | console.log(staff);
44 | if (bcrypt.compareSync(password, staff[0].password_digest)) {
45 | const token = jwt.sign({ id: staff[0].id, email:email }, jwtConfig.jwtSecret);
46 | res.status(200).json({'statuscode':'200', token});
47 | } else {
48 | res.status(401).json({'errors': 'Invalid credentials'})
49 | }
50 | } else {
51 | res.status(401).json({'errors': 'Invalid credentials'})
52 | }
53 | })
54 | }
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/src/server/routes/staff.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Validator from 'validator';
4 | import knex from '../db/knex';
5 | import bcrypt from 'bcrypt';
6 | // import Entities from '../models-knex/entities';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 |
10 | module.exports = (router) => {
11 | router.get('/api/staff/:page*?', (req, res, next) => {
12 | const page = req.params.page ? req.params.page : 1;
13 | const perPage = 12;
14 | var countStaff = 0;
15 |
16 | knex('entities')
17 | .select('id', 'fullname', 'email', 'designation')
18 | .where(
19 | {
20 | 'isstaff': true
21 | }
22 | )
23 | .limit(perPage).offset((page > 0 ? (page-1) : 0)*perPage)
24 | .then((staffList) => {
25 | knex('entities')
26 | .count('id')
27 | .where({ 'isstaff': true })
28 | .then(result => {
29 | // console.log('#: ' + result[0].count);
30 | // return result[0].count;
31 | res.status(200).json({
32 | staffList,
33 | countStaff: result[0].count
34 | })
35 | })
36 | })
37 | })
38 |
39 | router.get('/api/staffdetails', (req, res, next) => {
40 | res.status(400).json({'staff':null});
41 | })
42 |
43 | router.get('/api/staffdetails/:id', (req, res, next) => {
44 | const id = req.params.id ? req.params.id : 0;
45 | if (id) {
46 | knex('entities')
47 | .select('id', 'fullname', 'email', 'designation', 'firstname', 'lastname')
48 | .where(
49 | {
50 | 'isstaff': true,
51 | 'id': id
52 | }
53 | )
54 | .limit(1)
55 | .then((staff) => {
56 | res.status(200).json({
57 | staff
58 | })
59 | })
60 | } else {
61 | res.status(204).json({
62 | 'staff': null
63 | })
64 | }
65 | })
66 | }
67 |
--------------------------------------------------------------------------------
/src/server/sequelize/models/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var Sequelize = require('sequelize');
6 | var basename = path.basename(__filename);
7 | var env = process.env.NODE_ENV || 'development';
8 | var config = require(__dirname + '/../config/config.json')[env];
9 | var db = {};
10 |
11 | if (config.use_env_variable) {
12 | var sequelize = new Sequelize(process.env[config.use_env_variable]);
13 | } else {
14 | var sequelize = new Sequelize(config.database, config.username, config.password, config);
15 | }
16 |
17 | fs
18 | .readdirSync(__dirname)
19 | .filter(file => {
20 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
21 | })
22 | .forEach(file => {
23 | var model = sequelize['import'](path.join(__dirname, file));
24 | db[model.name] = model;
25 | });
26 |
27 | Object.keys(db).forEach(modelName => {
28 | if (db[modelName].associate) {
29 | db[modelName].associate(db);
30 | }
31 | });
32 |
33 | db.sequelize = sequelize;
34 | db.Sequelize = Sequelize;
35 |
36 | db.entities = require('../models/entities');
37 | db.conversations = require('../models/conversations');
38 | db.conversation_messages = require('../models/conversation_messages');
39 | db.conversation_participants = require('../models/conversation_participants');
40 |
41 | db.conversations.hasMany(db.conversation_messages, { foreignKey: 'conversation_id', sourceKey: 'id' });
42 | // db.conversation_messages.belongsTo(db.conversation, { foreignKey: 'conversation_id', sourceKey: 'id' });
43 | db.entities.hasMany(db.conversation_messages, { foreignKey: 'entity_id', sourceKey: 'id' });
44 |
45 | db.conversations.hasMany(db.conversation_participants, { foreignKey: 'conversation_id', sourceKey: 'id' });
46 | // db.conversation_participants.belongsTo(db.conversation, { foreignKey: 'conversation_id', sourceKey: 'id' });
47 | db.entities.hasMany(db.conversation_participants, { foreignKey: 'entity_id', sourceKey: 'id' });
48 |
49 | module.exports = db;
50 |
--------------------------------------------------------------------------------
/src/components/navigations/Accordion.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import Collapsible from 'react-collapsible';
4 |
5 | class Accordian extends Component {
6 | state = {
7 | openPosition: this.props.startPosition | 0
8 | }
9 |
10 | handleTriggerClick(position) {
11 | this.setState({openPosition: position})
12 | }
13 | render() {
14 | var nodes = this.props.children.map((node, index) => {
15 |
16 | var triggerWhenOpen = (node.props['data-trigger-when-open']) ? node.props['data-trigger-when-open'] : node.props['data-trigger'];
17 | var triggerDisabled = (node.props['data-trigger-disabled']) || false;
18 | return ({node} );
29 | });
30 | return(
31 |
32 | {nodes}
33 |
34 | )
35 | }
36 | }
37 |
38 | Accordian.PropTypes = {
39 | transitionTime: PropTypes.number,
40 | easing: PropTypes.string,
41 | startPosition: PropTypes.number,
42 | classParentString: PropTypes.string,
43 | children: PropTypes.arrayOf(PropTypes.shape({
44 | props: PropTypes.shape({
45 | 'data-trigger': PropTypes.oneOfType([
46 | PropTypes.string,
47 | PropTypes.element
48 | ]).isRequired,
49 | 'data-triggerWhenOpen': PropTypes.oneOfType([
50 | PropTypes.string,
51 | PropTypes.element
52 | ]),
53 | 'data-triggerDisabled': PropTypes.bool,
54 | })
55 | }))
56 | }
57 |
58 | export default Accordian;
59 |
--------------------------------------------------------------------------------
/mobile/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 | .*/Libraries/react-native/ReactNative.js
16 |
17 | ; Additional create-react-native-app ignores
18 |
19 | ; Ignore duplicate module providers
20 | .*/node_modules/fbemitter/lib/*
21 |
22 | ; Ignore misbehaving dev-dependencies
23 | .*/node_modules/xdl/build/*
24 | .*/node_modules/reqwest/tests/*
25 |
26 | ; Ignore missing expo-sdk dependencies (temporarily)
27 | ; https://github.com/expo/expo/issues/162
28 | .*/node_modules/expo/src/*
29 |
30 | ; Ignore react-native-fbads dependency of the expo sdk
31 | .*/node_modules/react-native-fbads/*
32 |
33 | [include]
34 |
35 | [libs]
36 | node_modules/react-native/Libraries/react-native/react-native-interface.js
37 | node_modules/react-native/flow
38 | flow/
39 |
40 | [options]
41 | module.system=haste
42 |
43 | emoji=true
44 |
45 | experimental.strict_type_args=true
46 |
47 | munge_underscores=true
48 |
49 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
50 |
51 | suppress_type=$FlowIssue
52 | suppress_type=$FlowFixMe
53 | suppress_type=$FixMe
54 |
55 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
56 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-9]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
57 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
58 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
59 |
60 | unsafe.enable_getters_and_setters=true
61 |
62 | [version]
63 | ^0.49.1
64 |
--------------------------------------------------------------------------------
/src/server/utils/chatnew.js:
--------------------------------------------------------------------------------
1 | import bcrypt from 'bcrypt';
2 | import jwt from 'jsonwebtoken';
3 | import jwtConfig from '../jwtConfig';
4 | // import chatToken from './chattoken';
5 | import knex from '../db/knex';
6 | var decodedJson;
7 |
8 | module.exports = (io) => {
9 | var error;
10 | io.use((socket, next) => {
11 | if (socket.handshake.query && socket.handshake.query.token){
12 | jwt.verify(socket.handshake.query.token, jwtConfig.jwtSecret, function(err, decoded) {
13 | if(err) {
14 | return next(new Error('Authentication error'));
15 | error = err;
16 | }
17 | decodedJson = decoded;
18 | next();
19 | });
20 | }
21 | next(new Error('Authentication error'));
22 | })
23 | .on('connection', socket => {
24 | if (error) socket.emit('reply', 'There is an error: ' + error);
25 | else {
26 | const { id } = decodedJson;
27 | socket.on("displaySnapshot", (data) => {
28 | socket.emit('reply', [{'message': 'Hoolla'}])
29 | knex('conversations')
30 | .select(
31 | 'conversation_messages.message',
32 | 'conversation_messages.updated_at',
33 | 'conversations.conversation_type',
34 | 'entities.fullname as e_fullname',
35 | 'entities.id',
36 | 'conversations.id as conv_id'
37 | )
38 | .join('conversation_messages', 'conversations.id', 'conversation_messages.conversation_id')
39 | .join('entities', 'conversation_messages.entity_id', 'entities.id')
40 | .where('conversation_messages.entity_id', id)
41 | .debug()
42 | .then(conversations => {
43 | if (conversations.length) {
44 | socket.emit('reply', conversations);
45 | } else {
46 | socket.emit('reply', [{'message': 'Hoolla'}])
47 | }
48 | })
49 | });
50 | socket.on("sendChat", (data) => {
51 | socket.emit("reply", "msg received");
52 | });
53 | socket.on("disconnect", () => {
54 | // console.log('client disconnected');
55 | });
56 | }
57 | });
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/navigations/MainContent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Switch, Route } from 'react-router-dom';
3 | import PageNotFound from '../helpers/PageNotFound';
4 | // import { CSSTransitionGroup } from 'react-transition-group';
5 |
6 | import Dashboard from '../Dashboard';
7 | import CustomerList from '../customers/CustomerList';
8 | import CustomerNew from '../customers/CustomerNew';
9 | import StaffList from '../humanresource/StaffList';
10 | import StaffDetails from '../humanresource/StaffDetails';
11 | import LeaveApplication from '../humanresource/LeaveApplication';
12 | import PayAdvice from '../humanresource/PayAdvice';
13 | import AllowanceAndBenefits from '../humanresource/AllowanceAndBenefits';
14 | import Department from '../humanresource/Department';
15 | import UserRoles from '../users/UserRoles';
16 |
17 | export default class MainContent extends Component {
18 | render() {
19 | return(
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Copy Your Rights! @ Forever
39 |
40 |
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/server/app.js:
--------------------------------------------------------------------------------
1 | import bodyParser from 'body-parser';
2 | import cookieParser from 'cookie-parser';
3 | import Debug from 'debug';
4 | import express from 'express';
5 | import logger from 'morgan';
6 | import cors from 'cors';
7 | // import favicon from 'serve-favicon';
8 | import path from 'path';
9 | import sassMiddleware from 'node-sass-middleware';
10 | import index from './routes/index1';
11 | import index2 from './routes/index2';
12 | // import customer from './routes/customer';
13 | import graphql from './routes/graphql';
14 | // import authorization from './routes/authorization';
15 |
16 | const app = express();
17 | const debug = Debug('server:app');
18 |
19 | // view engine setup
20 | app.set('views', path.join(__dirname, 'views'));
21 | app.set('view engine', 'pug');
22 |
23 | // uncomment after placing your favicon in /public
24 | // app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
25 | app.use(logger('dev'));
26 | app.use(cors({ origin: 'http://localhost:3000' , credentials : true}));
27 | app.use(bodyParser.json());
28 | app.use(bodyParser.urlencoded({
29 | extended: false
30 | }));
31 |
32 | app.use(cookieParser());
33 | app.use(sassMiddleware({
34 | src: path.join(__dirname, 'public'),
35 | dest: path.join(__dirname, 'public'),
36 | indentedSyntax: true,
37 | sourceMap: true
38 | }));
39 | app.use(express.static(path.join(__dirname, 'public')));
40 |
41 | // app.use('/', index);
42 | app.use('/', index2);
43 | // app.use('/customer', customer);
44 | app.use('/graphql', graphql);
45 | // app.use('/login', authorization);
46 |
47 | // catch 404 and forward to error handler
48 | app.use((req, res, next) => {
49 | const err = new Error('Not Found');
50 | err.status = 404;
51 | next(err);
52 | });
53 |
54 | // error handler
55 | /* eslint no-unused-vars: 0 */
56 | app.use((err, req, res, next) => {
57 | // set locals, only providing error in development
58 | res.locals.message = err.message;
59 | res.locals.error = req.app.get('env') === 'development' ? err : {};
60 | // render the error page
61 | res.status(err.status || 500);
62 | res.render('error');
63 | });
64 |
65 | // Handle uncaughtException
66 | process.on('uncaughtException', (err) => {
67 | debug('Caught exception: %j', err);
68 | process.exit(1);
69 | });
70 |
71 | export default app;
72 |
--------------------------------------------------------------------------------
/src/server/bin/start:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /* eslint no-console: 0 */
3 | /* eslint prefer-template: 0 */
4 | /* eslint strict: 0 */
5 |
6 | 'use strict';
7 |
8 | /**
9 | * Module dependencies.
10 | */
11 |
12 | // enables ES6 ('import'.. etc) in Node
13 | require('babel-core/register');
14 | require('babel-polyfill');
15 |
16 | const app = require('../app').default;
17 | const debug = require('debug')('server:server');
18 | const http = require('http');
19 | /**
20 | * Normalize a port into a number, string, or false.
21 | */
22 |
23 | const normalizePort = function(val) {
24 | const port = parseInt(val, 10);
25 |
26 | if (isNaN(port)) {
27 | // named pipe
28 | return val;
29 | }
30 |
31 | if (port >= 0) {
32 | // port number
33 | return port;
34 | }
35 |
36 | return false;
37 | };
38 |
39 | /**
40 | * Get port from environment and store in Express.
41 | */
42 |
43 | const port = normalizePort(process.env.PORT || '3003');
44 | app.set('port', port);
45 |
46 | /**
47 | * Create HTTP server.
48 | */
49 |
50 | const server = http.createServer(app);
51 | const io = require('socket.io')(server);
52 | var chat = require('../utils/chatnew')(io);
53 |
54 | /**
55 | * Listen on provided port, on all network interfaces.
56 | */
57 |
58 | server.listen(port);
59 |
60 | /**
61 | * Event listener for HTTP server "error" event.
62 | */
63 |
64 | const onError = function(error) {
65 | if (error.syscall !== 'listen') {
66 | throw error;
67 | }
68 |
69 | const bind = typeof port === 'string'
70 | ? 'Pipe ' + port
71 | : 'Port ' + port;
72 |
73 | // handle specific listen errors with friendly messages
74 | switch (error.code) {
75 | case 'EACCES':
76 | console.error(bind + ' requires elevated privileges');
77 | process.exit(1);
78 | break;
79 | case 'EADDRINUSE':
80 | console.error(bind + ' is already in use');
81 | process.exit(1);
82 | break;
83 | default:
84 | throw error;
85 | }
86 | };
87 |
88 | /**
89 | * Event listener for HTTP server "listening" event.
90 | */
91 |
92 | const onListening = function() {
93 | const addr = server.address();
94 | const bind = typeof addr === 'string'
95 | ? 'pipe ' + addr
96 | : 'port ' + addr.port;
97 | debug('Listening on ' + bind);
98 | };
99 |
100 | server.on('error', onError);
101 | server.on('listening', onListening);
102 |
--------------------------------------------------------------------------------
/src/components/communications/MainChat.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import socketIOClient from 'socket.io-client';
3 | import { connect } from 'react-redux';
4 | import { apiServer } from '../../actions/config';
5 |
6 | class MainChat extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | response: [],
11 | endpoint: apiServer
12 | }
13 | const { endpoint } = this.state;
14 | this.socket = socketIOClient(endpoint, {
15 | query: { token: localStorage.jwtToken }
16 | });
17 | // console.log('socket');
18 | }
19 |
20 | componentDidMount() {
21 | this.retrieveConversations();
22 | }
23 |
24 | componentDidUpdate(prevProps, prevState) {
25 | // console.log(this.state.response);
26 | if (JSON.stringify(prevState.response) !== JSON.stringify(this.state.response))
27 | return true;
28 | }
29 |
30 | retrieveConversations = () => {
31 | this.socket.emit("displaySnapshot", 200);
32 | this.socket.on("reply", (response) => {
33 | // console.log(response);
34 | this.setState({response})
35 | })
36 | }
37 |
38 | render() {
39 | const { response } = this.state
40 | // console.log(localStorage.jwtToken);
41 | // console.log(response);
42 | return(
43 |
44 | {
45 | response.length ? response.map((resp, key) => {
46 | return (
47 |
48 |
49 |
50 |
51 |
52 |
53 | {
54 | (resp.id === this.props.user.id) ? 'You' : resp.e_fullname
55 | }
56 |
57 |
58 | {
59 | resp.message.slice(0, 20)
60 | }
61 |
62 |
63 |
64 | )
65 | }) : 'No conversation was found'
66 | }
67 |
68 | )
69 | }
70 | }
71 |
72 | function mapStateToProps(state) {
73 | return {
74 | user: state.auth.user
75 | }
76 | }
77 |
78 | export default connect(mapStateToProps, {})(MainChat);
79 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/server/utils/chat.js:
--------------------------------------------------------------------------------
1 | // import knex from 'knex';
2 | import bcrypt from 'bcrypt';
3 | import User from '../models/user';
4 | import conversationsMessages from '../models/conversation_messages';
5 | import conversationsParticipants from '../models/conversation_participants';
6 | import conversations from '../models/conversations';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 | import chatToken from './chattoken';
10 | import knexfile from '../knexfile';
11 | const knex = require('knex')(knexfile.development);
12 | var nsp = io.of('/');
13 | module.exports = (nsp) => {
14 | var error;
15 | // console.log('erww');
16 |
17 | nsp.use(function(socket, next){
18 | if (socket.handshake.query && socket.handshake.query.token){
19 | jwt.verify(socket.handshake.query.token, jwtConfig.jwtSecret, function(err, decoded) {
20 | if(err) {
21 | return next(new Error('Authentication error'));
22 | error = err;
23 | }
24 | socket.decoded = decoded;
25 | next();
26 | });
27 | }
28 | next(new Error('Authentication error'));
29 | })
30 | .on('connection', socket => {
31 | if (error) socket.emit('reply', 'There is an error: ' + error);
32 | else {
33 | const { token } = socket.handshake.query;
34 | const { id } = jwt.decode(token);
35 | socket.on("displaySnapshot", (data) => {
36 | conversationsMessages
37 | .query(qb => {
38 | qb.select(
39 | knex.raw(
40 | `max(conversation_messages.id) as maxconversationId, max(conversation_messages.message) as message,
41 | max(conversation_messages.created_at) as created_at`),
42 | 'conversations.id'
43 | )
44 | qb.leftJoin(
45 | 'conversations',
46 | 'conversations.id',
47 | 'conversation_messages.conversation_id'
48 | )
49 | qb.groupBy('conversations.id')
50 | qb.where({entity_id: id})
51 | qb.debug(true)
52 | })
53 | .fetchAll()
54 | .then(results => {
55 | // console.log(results);
56 | if (results.length) {
57 | socket.emit("reply", results);
58 | } else {
59 | socket.emit('reply', '');
60 | }
61 | })
62 | });
63 | socket.on("sendChat", (data) => {
64 | socket.emit("reply", "msg received");
65 | });
66 | socket.on("disconnect", () => {
67 | // console.log('client disconnected');
68 | });
69 | }
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import '../node_modules/@blueprintjs/core/dist/blueprint.css';
2 | html, body {
3 | margin: 0;
4 | padding: 0;
5 | font-family: sans-serif;
6 | color: white;
7 | background-image: url("./images/blur-bg.jpg");
8 | width: 100%; }
9 |
10 | table {
11 | border-collapse: collapse;
12 | }
13 |
14 | table, thead, tr, td {
15 | border: 1px solid black;
16 | color: white; }
17 |
18 | ul {
19 | list-style-type: none;
20 | margin: 0 0 1 0;
21 | padding: 0; }
22 |
23 | li {
24 | padding-top: 5px; }
25 |
26 | .chat {
27 | min-height: 400px;
28 | height: 100%; }
29 |
30 | .centeringText {
31 | align-items: center;
32 | text-align: center; }
33 |
34 | .leftingText {
35 | align-items: left;
36 | text-align: left;
37 | }
38 |
39 | .page-container .bg {
40 | position: absolute;
41 | top: 0;
42 | left: 0;
43 | width: 100vw;
44 | height: 100vh;
45 | background-size: cover;
46 | mix-blend-mode: overlay; }
47 |
48 | .middlingVAlign {
49 | vertical-align: middle; }
50 |
51 | .w3-circle {
52 | border-radius: 50%; }
53 |
54 | .contacts {
55 | max-height: 20px; }
56 |
57 | .transparentThis {
58 | background-color: rgba(0, 0, 0, 0.2); }
59 |
60 | .transparentThisDeeper {
61 | background-color: rgba(0, 0, 0, 0.7); }
62 |
63 | .cursorPointer {
64 | cursor: pointer;
65 | }
66 |
67 | li:focus {
68 | background-color: #394B59
69 | }
70 |
71 | .activeNavLink{
72 | background-color: #394B59
73 | }
74 |
75 | .card-padding {
76 | margin: -5px -20px -10px -20px;
77 | text-align: center;
78 | }
79 |
80 | #react-paginate ul {
81 | display: inline-block;
82 | padding-left: 15px;
83 | padding-right: 15px;
84 | }
85 |
86 | #react-paginate li {
87 | display: inline-block;
88 | }
89 |
90 | #react-paginate .break a {
91 | cursor: default;
92 | color: #000000;
93 | }
94 |
95 | .leftMenuSpan {
96 | padding-right: 9px;
97 | cursor: pointer;
98 | }
99 |
100 | .chatBorder {
101 | border-bottom: thin solid #000000;
102 | }
103 |
104 | .pagination {
105 | display: inline-block;
106 | }
107 |
108 | .pagination a {
109 | color: black;
110 | float: left;
111 | width: 40px;
112 | min-height: 20px;
113 | text-decoration: none;
114 | transition: background-color .3s;
115 | border: 1px solid #394B59;
116 | margin: 0 4px;
117 | }
118 |
119 | .pagination a.active {
120 | background-color: #4CAF50;
121 | color: white;
122 | border: 1px solid #4CAF50;
123 | }
124 |
125 | .pagination a:hover:not(.active) {background-color: #ddd;}
126 |
127 | .defaultTextColor {
128 | color: white;
129 | }
130 |
131 | .bg-green {
132 | background-color: #3C643C;
133 | }
134 |
135 | #container {
136 | -webkit-box-sizing: border-box;
137 | -moz-box-sizing: border-box;
138 | box-sizing: border-box;
139 | padding: 10px;
140 | width: 100%;
141 | height: 300px;
142 | background-color: #fff;
143 | text-align: center;
144 | }
145 |
146 | .white-color {
147 | color: white;
148 | }
149 |
150 | .red-color {
151 | color: red;
152 | text-emphasis-color: red;
153 | }
154 |
--------------------------------------------------------------------------------
/src/server/sequelize/migrations/20170924182938-create-entities.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('entities', {
5 | id: {
6 | allowNull: false,
7 | autoIncrement: true,
8 | primaryKey: true,
9 | type: Sequelize.INTEGER
10 | },
11 | entityid: {
12 | type: Sequelize.STRING,
13 | allowNull: true
14 | },
15 | parent_id: {
16 | type: Sequelize.INTEGER,
17 | allowNull: true,
18 | defaultValue: 0
19 | },
20 | email: {
21 | type: Sequelize.STRING,
22 | allowNull: true
23 | },
24 | password_digest: {
25 | type: Sequelize.STRING,
26 | allowNull: false
27 | },
28 | fullname: {
29 | type: Sequelize.STRING,
30 | allowNull: false
31 | },
32 | firstname: {
33 | type: Sequelize.STRING,
34 | allowNull: true
35 | },
36 | lastname: {
37 | type: Sequelize.STRING,
38 | allowNull: true
39 | },
40 | designation: {
41 | type: Sequelize.STRING,
42 | allowNull: true
43 | },
44 | department_id: {
45 | type: Sequelize.INTEGER,
46 | allowNull: true
47 | },
48 | address1: {
49 | type: Sequelize.STRING,
50 | allowNull: true
51 | },
52 | address2: {
53 | type: Sequelize.STRING,
54 | allowNull: true
55 | },
56 | postcode: {
57 | type: Sequelize.STRING,
58 | allowNull: true
59 | },
60 | city: {
61 | type: Sequelize.STRING,
62 | allowNull: true
63 | },
64 | state: {
65 | type: Sequelize.STRING,
66 | allowNull: true
67 | },
68 | country: {
69 | type: Sequelize.STRING,
70 | allowNull: true
71 | },
72 | isstaff: {
73 | type: Sequelize.BOOLEAN,
74 | allowNull: false,
75 | defaultValue: false
76 | },
77 | isdepartment: {
78 | type: Sequelize.BOOLEAN,
79 | allowNull: false,
80 | defaultValue: false
81 | },
82 | iscustomer: {
83 | type: Sequelize.BOOLEAN,
84 | allowNull: true,
85 | defaultValue: false
86 | },
87 | issupplier: {
88 | type: Sequelize.BOOLEAN,
89 | allowNull: true,
90 | defaultValue: false
91 | },
92 | isfixedasset: {
93 | type: Sequelize.BOOLEAN,
94 | allowNull: true,
95 | defaultValue: false
96 | },
97 | isonline: {
98 | type: Sequelize.BOOLEAN,
99 | allowNull: true,
100 | defaultValue: false
101 | },
102 | isallowedtologin: {
103 | type: Sequelize.BOOLEAN,
104 | allowNull: true,
105 | defaultValue: false
106 | },
107 | createdAt: {
108 | allowNull: false,
109 | type: Sequelize.DATE,
110 | defaultValue: Sequelize.fn('NOW')
111 | },
112 | updatedAt: {
113 | allowNull: false,
114 | type: Sequelize.DATE,
115 | defaultValue: Sequelize.fn('NOW')
116 | }
117 | });
118 | },
119 | down: (queryInterface, Sequelize) => {
120 | return queryInterface.dropTable('entities');
121 | }
122 | };
123 |
--------------------------------------------------------------------------------
/src/components/humanresource/LeaveApplication.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Dialog
3 | // , Button, Intent, Hotkey, Hotkeys, HotkeysTarget
4 | } from "@blueprintjs/core";
5 | import NewLeaveApplication from './NewLeaveApplication';
6 | import LeaveApplicationDetails from './LeaveApplicationDetails';
7 |
8 | class LeaveApplication extends Component {
9 | state = {
10 | openDialog: false,
11 | selectedLeave: 0,
12 | dialogTitle: 'New Leave'
13 | }
14 |
15 | toggleOverlay = () => {
16 | this.setState({
17 | openDialog: this.state.openDialog ? false : true,
18 | selectedLeave: this.state.selectedLeave ? 0 : -1
19 | })
20 | }
21 |
22 | openLeaveDialog (leaveId) {
23 | this.setState({ selectedLeave: leaveId })
24 | }
25 |
26 | componentDidUpdate (prevProps, prevState) {
27 | if (prevState.selectedLeave !== this.state.selectedLeave)
28 | this.setState({ openDialog: this.state.selectedLeave ? true : false })
29 | }
30 |
31 | render() {
32 | const dataList = [
33 | {
34 | applicant: 'ALi',
35 | type: 'Sick',
36 | date_from: '20/12/2017',
37 | date_to: '21/12/2017',
38 | duration: '2 days',
39 | approved_by: 'Abu'
40 | },
41 | {
42 | applicant: 'Aki',
43 | type: 'Annual',
44 | date_from: '20/12/2017',
45 | date_to: '21/12/2017',
46 | duration: '2 days',
47 | approved_by: 'Abu'
48 | }
49 | ]
50 |
51 | return(
52 |
53 | Leave Application
54 |
55 |
56 |
57 |
58 | No
59 | Applicant
60 | Type
61 | From
62 | To
63 | Approved By
64 |
65 |
66 |
67 | {
68 | dataList.map((data, key) => {
69 | return (
70 | this.openLeaveDialog(key+1)}>
71 | {key+1}
72 | {data.applicant}
73 | {data.type}
74 | {data.date_from}
75 | {data.date_to}
76 | {data.approved_by}
77 |
78 | )
79 | })
80 | }
81 |
82 |
83 |
84 |
91 |
92 | {
93 | this.state.selectedLeave ?
94 | :
95 |
96 | }
97 |
98 |
99 |
100 | )
101 | }
102 | }
103 |
104 | export default LeaveApplication;
105 |
--------------------------------------------------------------------------------
/src/components/humanresource/Department.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { Dialog
5 | // , Button, Intent
6 | } from "@blueprintjs/core";
7 | import { setDisplayedDepartment, getDepartmentById } from '../../actions/department';
8 | import DepartmentDetails from './DepartmentDetails';
9 | import NewDepartmentForm from './NewDepartmentForm';
10 |
11 | class Department extends Component {
12 | constructor(props) {
13 | super(props)
14 | this.props.setDisplayedDepartment();
15 | }
16 |
17 | state = {
18 | isOpen: false,
19 | dialogTitle: 'New Department',
20 | selectedDepartmentid: 0
21 | }
22 |
23 | componentWillReceiveProps(nextProps) {
24 | // console.log(nextProps);
25 | const { humanresource = {} } = this.props;
26 | const { departmentList = {} } = humanresource;
27 | const { departments = [] } = departmentList;
28 |
29 | if (
30 | !(this.props.humanresource.departmentList) || JSON.stringify(departments[0].id) !== JSON.stringify(nextProps.humanresource.departmentList.departments[0].id)
31 | ) {
32 | this.render()
33 | }
34 | }
35 |
36 | toggleOverlay = () => {
37 | this.setState({ isOpen: this.state.isOpen ? false : true })
38 | }
39 |
40 | showDepartmentDetails = (departmentId) => {
41 | const { id } = this.props.humanresource.departmentList.departments.find(department => department.id === departmentId);
42 | getDepartmentById(departmentId)
43 | .then(response => {
44 | if (response.data)
45 | {
46 | this.setState({
47 | dialogTitle: 'Department Details Information - ' + response.data.fullname,
48 | selectedDepartmentid: id,
49 | isOpen: true,
50 | selectedDepartment: response.data
51 | })
52 | }
53 | })
54 | .catch(err => {
55 | alert('There is an error while connecting to the server: ' + err)
56 | })
57 | }
58 |
59 | render() {
60 | const { humanresource = {} } = this.props;
61 | const { departmentList = {} } = humanresource;
62 | const { departments = [] } = departmentList;
63 | return(
64 |
65 | Department
66 |
67 | {
68 | departments.map((department, key) => {
69 | return (
70 |
this.showDepartmentDetails(department.id)} className="pt-card pt-elevation-1 pt-interactive transparentThis grid-30 grid-container card-padding" style={{margin:'10px 10px 0px 10px'}}>
71 |
72 | { department.fullname }
73 |
74 |
75 | Head:
76 |
77 |
78 | Staff count: 3
79 |
80 |
81 | )
82 | })
83 | }
84 |
85 |
92 |
93 | {
94 | this.state.selectedDepartmentid ?
95 | :
96 |
97 | }
98 |
99 |
100 |
101 | )
102 | }
103 | }
104 |
105 | Department.PropTypes = {
106 | setDisplayedDepartment: PropTypes.func.isRequired
107 | }
108 |
109 | function mapStateToProps(state) {
110 | return {
111 | humanresource: state.humanresource
112 | }
113 | }
114 |
115 | export default connect(mapStateToProps, { setDisplayedDepartment })(Department);
116 |
--------------------------------------------------------------------------------
/src/components/navigations/TopNavigation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { logout } from '../../actions/userAuthentication';
5 | import { Popover, PopoverInteractionKind, Menu, MenuItem, Position, MenuDivider
6 | // ,Button
7 | } from "@blueprintjs/core";
8 | import { NavLink } from 'react-router-dom';
9 |
10 | class TopNavigation extends Component {
11 | logout(e) {
12 | e.preventDefault();
13 | this.props.logout();
14 | this.props.reRender();
15 | }
16 |
17 | showSettingMenu() {
18 | const compassMenu = (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | return (
32 |
35 |
36 |
37 | )
38 | }
39 |
40 | showProfileMenu() {
41 | const compassMenu = (
42 |
43 |
44 |
45 |
46 | );
47 | return (
48 |
51 |
52 |
53 | )
54 | }
55 |
56 | enterSearchMode() {
57 | alert('You press enter!');
58 | }
59 |
60 | checkEnterKey(e) {
61 | if (e.keyCode === 13) {
62 | this.enterSearchMode();
63 | }
64 | }
65 |
66 | render() {
67 | return (
68 |
69 |
70 |
71 |
72 |
73 |
74 | MyERP
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | {/*
*/}
85 | {this.showProfileMenu()}
86 |
87 | {this.showSettingMenu()}
88 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | )
98 | }
99 | }
100 |
101 | TopNavigation.propTypes = {
102 | logout: PropTypes.func.isRequired,
103 | reRender: PropTypes.func.isRequired
104 | }
105 |
106 | export default connect((state) => { return {} }, {logout})(TopNavigation);
107 |
--------------------------------------------------------------------------------
/src/components/humanresource/NewStaffForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class NewStaffForm extends Component {
4 | render() {
5 | return(
6 |
7 |
Personel Details
8 |
9 |
10 |
11 |
First Name
12 |
13 |
15 |
16 |
17 |
18 |
Last Name
19 |
20 |
22 |
23 |
24 |
25 |
Staff ID
26 |
27 |
29 |
30 |
31 |
32 |
Designation
33 |
34 |
36 |
37 |
38 |
39 |
Email
40 |
41 |
43 |
44 |
45 |
46 |
Department
47 |
48 |
49 | Select Department
50 |
51 |
52 |
53 |
54 |
55 |
56 |
Street Address
57 |
58 |
60 |
61 |
62 |
63 |
Street Address 2
64 |
65 |
67 |
68 |
69 |
70 |
Postcode
71 |
72 |
74 |
75 |
76 |
77 |
City/Province
78 |
79 |
81 |
82 |
83 |
84 |
State
85 |
86 |
88 |
89 |
90 |
91 |
Country
92 |
93 |
95 |
96 |
97 |
98 |
99 |
100 | )
101 | }
102 | }
103 |
104 | export default NewStaffForm;
105 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (!isLocalhost) {
36 | // Is not local host. Just register service worker
37 | registerValidSW(swUrl);
38 | } else {
39 | // This is running on localhost. Lets check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/server/routes/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Validator from 'validator';
4 | import knex from 'knex';
5 | import bcrypt from 'bcrypt';
6 | import Entities from '../models/user';
7 | import jwt from 'jsonwebtoken';
8 | import jwtConfig from '../jwtConfig';
9 |
10 | const router = express.Router();
11 |
12 | /* GET index page. */
13 | router.get('/', (req, res, next) => {
14 | res.render('index', {
15 | title: 'Express'
16 | });
17 | });
18 |
19 | router.get('/knex', (req, res, next) => {
20 | knex('entities')
21 | .select("fullname")
22 | .then(entity => {
23 | res.status(200).json(entity)
24 | })
25 | })
26 |
27 | export default router;
28 |
29 | function validateInput(data) {
30 | let errors = {};
31 |
32 | if (Validator.isEmpty(data.email)) {
33 | errors.email = 'Username is required';
34 | }
35 |
36 | if (!Validator.isEmail(data.email)) {
37 | errors.email = 'Username should be in email format';
38 | }
39 |
40 | if (Validator.isEmpty(data.password)) {
41 | errors.password = 'Password is required';
42 | }
43 |
44 | return {
45 | errors, isValid: isEmpty(errors)
46 | }
47 | }
48 |
49 | router.post('/api/login', (req, res, next) => {
50 | const { errors, isValid } = validateInput(req.body);
51 | if (!isValid) {
52 | res.status(400).json(errors);
53 | } else {
54 | const { email, password } = req.body;
55 |
56 | setTimeout(() => {
57 | Entities
58 | .where({email: email})
59 | .fetch()
60 | .tap((staff) => {
61 | if (staff != null) {
62 | if (bcrypt.compareSync(password, staff.get('password_digest'))) {
63 | const token = jwt.sign({ id: staff.get('id'), email:email }, jwtConfig.jwtSecret);
64 | res.status(200).json({'statuscode':'200', token});
65 | } else {
66 | res.status(303).json({'errors':'Invalid credentials'})
67 | }
68 | }
69 | else {
70 | res.status(302).json({'errors': 'Invalid credentials'})
71 | }
72 | })
73 | }, 1000);
74 |
75 | }
76 |
77 | });
78 |
79 | router.post('/api/staff/new', (req, res, next) => {
80 | const { email, password } = req.body;
81 | new Entities({
82 | email,
83 | password_digest: bcrypt.hashSync(password, 13)
84 | }).save()
85 | .then((saved) => {
86 | res.status(200).json(saved);
87 | })
88 | .catch((errors) => {
89 | res.status(300).json(errors);
90 | })
91 | });
92 |
93 | router.get('/api/department', (req, res, next) => {
94 | Entities.where('isdepartment', true)
95 | .fetchPage({
96 | pageSize: 9,
97 | page: 1
98 | })
99 | .then((results) => {
100 | res.status(200).send(results)
101 | })
102 | .catch((errors) => {
103 | res.status(401).send(errors)
104 | })
105 | })
106 |
107 | router.get('/api/departmentdetails/:departmentId', (req, res, next) => {
108 | Entities.where({
109 | isdepartment: true,
110 | id: req.params.departmentId
111 | }).fetch()
112 | .then((results) => {
113 | res.status(200).send(results)
114 | })
115 | .catch((err) => {
116 | res.status(401).send(err)
117 | })
118 | })
119 |
120 | router.get('/api/department/:pageNumber', (req, res, next) => {
121 | Entities.where('isdepartment', true)
122 | .fetchPage({
123 | pageSize: 9,
124 | page: req.params.pageNumber ? req.params.pageNumber : 1
125 | })
126 | .then((results) => {
127 | res.status(200).send(results)
128 | })
129 | .catch((errors) => {
130 | res.status(401).send(errors)
131 | })
132 | })
133 |
134 | router.get('/api/staff', (req, res, next) => {
135 | Entities
136 | .where('isstaff', 'true')
137 | .fetchPage({
138 | pageSize: 9,
139 | page: 1
140 | })
141 | .then((results) => {
142 | res.status(200).send(results)
143 | })
144 | .catch((errors) => {
145 | res.status(401).send(errors)
146 | })
147 | });
148 |
149 | router.get('/api/staff/:pageNumber', (req, res, next) => {
150 | Entities.where('isstaff', true).fetchPage({
151 | pageSize: 12,
152 | page: req.params.pageNumber ? req.params.pageNumber : 1
153 | })
154 | .then((results) => {
155 | var theResults = {
156 | results,
157 | pagination: results.pagination
158 | }
159 | res.status(200).json(theResults)
160 | })
161 | .catch((errors) => {
162 | res.status(401).send(errors)
163 | })
164 | });
165 |
166 | router.get('/api/staffdetails/:id', (req, res, next) => {
167 | Entities.where('id', req.params.id).fetch()
168 | .then((results) => {
169 | res.status(200).json(
170 | results
171 | )
172 | })
173 | .catch((errors) => {
174 | res.status(401).json(errors)
175 | })
176 | })
177 |
178 | router.get('/chat', (req, res, next) => {
179 | res.status(200).json({
180 | response: 'Here i am'
181 | })
182 | })
183 |
--------------------------------------------------------------------------------
/src/components/humanresource/StaffList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import { setDisplayedStaff, getStaffById } from '../../actions/staffInformation';
5 | import { Dialog, Hotkey, Hotkeys, HotkeysTarget
6 | // Button, Intent,
7 | } from "@blueprintjs/core";
8 | import StaffDetails from './StaffDetails';
9 | import NewStaffForm from './NewStaffForm';
10 | import Pagination from '../helpers/Pagination'
11 |
12 | class StaffList extends Component {
13 | constructor(props) {
14 | super(props)
15 | this.props.setDisplayedStaff()
16 | }
17 | state = {
18 | isOpen: false,
19 | dialogTitle: 'New Staff',
20 | selectedStaffId: 0,
21 | selectedStaff: {},
22 | pageNum: 1
23 | }
24 |
25 | showStaffDetails = (staffId) => {
26 | const { id, fullname } = this.props.humanresource.staffList.staffList.find(staff => staff.id === staffId);
27 | getStaffById(staffId)
28 | .then(response => {
29 | if (response.data.staff)
30 | {
31 | this.setState({
32 | dialogTitle: 'Staff Details Information - ' + fullname,
33 | selectedStaffId: id,
34 | isOpen: true,
35 | selectedStaff: response.data.staff[0]
36 | })
37 | }
38 | })
39 | }
40 |
41 | newStaff = () => {
42 | this.setState({
43 | dialogTitle: 'New Staff',
44 | isOpen: true,
45 | selectedStaffId: 0
46 | })
47 | }
48 |
49 | toggleOverlay = () => {
50 | this.setState({ isOpen: this.state.isOpen ? false : true })
51 | }
52 |
53 | componentWillReceiveProps(nextProps) {
54 | if (
55 | !(this.props.humanresource.staffList) ||
56 | JSON.stringify(this.props.humanresource.staffList.staffList[0].id) !== JSON.stringify(nextProps.humanresource.staffList.staffList[0].id)
57 | ) {
58 | this.render()
59 | }
60 | }
61 |
62 | renderHotkeys() {
63 | return
64 |
70 |
71 | }
72 |
73 | reRender(fromChild) {
74 | this.props.setDisplayedStaff(fromChild);
75 | }
76 |
77 | render() {
78 | const { staffList = {} } = this.props.humanresource;
79 | const { countStaff = 0 } = staffList;
80 | const pageCount = Math.round(countStaff / 12);
81 |
82 | const theElements = []
83 | for(let i = 0; i < pageCount; i++) {
84 | theElements.push({linkTo: '/hr/page/' + (i+1), display: (i+1)})
85 | }
86 |
87 | return(
88 |
89 | Staff List
90 |
91 | {
92 | staffList.staffList ?
93 | staffList.staffList.map((staff, key) => {
94 | return (
95 |
this.showStaffDetails(staff.id)} className="pt-card pt-elevation-1 pt-interactive transparentThis grid-30 card-padding" style={{margin:'10px 10px 0px 10px'}}>
96 |
97 |
98 |
99 |
100 |
101 | {staff.fullname.slice(0, 15)}
102 |
103 |
{staff.designation.slice(0, 15)}
104 |
105 |
106 | )
107 | })
108 | :
109 | ''
110 | }
111 |
112 |
115 |
116 |
123 |
124 | {
125 | this.state.selectedStaffId ?
126 | :
127 |
128 | }
129 |
130 |
131 |
132 | )
133 | }
134 | }
135 |
136 | StaffList.PropTypes = {
137 | setDisplayedStaff: PropTypes.func.isRequired,
138 | humanresource: PropTypes.object.isRequired
139 | }
140 |
141 | function mapStateToProps(state) {
142 | return {
143 | humanresource: state.humanresource
144 | }
145 | }
146 |
147 | HotkeysTarget(StaffList)
148 | export default connect(mapStateToProps, { setDisplayedStaff })(StaffList);
149 |
--------------------------------------------------------------------------------
/src/components/tests/TryOverlay.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Palantir Technologies, Inc. All rights reserved.
3 | * Licensed under the BSD-3 License as modified (the “License”); you may obtain a copy
4 | * of the license at https://github.com/palantir/blueprint/blob/master/LICENSE
5 | * and https://github.com/palantir/blueprint/blob/master/PATENTS
6 | */
7 |
8 | import * as classNames from "classnames";
9 | import * as React from "react";
10 |
11 | import {
12 | Button,
13 | Classes,
14 | IBackdropProps,
15 | Intent,
16 | IOverlayableProps,
17 | Overlay,
18 | Switch,
19 | } from "@blueprintjs/core";
20 | import { BaseExample, handleBooleanChange } from "@blueprintjs/docs";
21 |
22 |
23 | const OVERLAY_EXAMPLE_CLASS = "docs-overlay-example-transition";
24 |
25 | export interface IOverlayExampleState extends IOverlayableProps, IBackdropProps {
26 | isOpen?: boolean;
27 | }
28 |
29 | export class OverlayExample extends BaseExample {
30 | state: IOverlayExampleState = {
31 | autoFocus: true,
32 | canEscapeKeyClose: true,
33 | canOutsideClickClose: true,
34 | enforceFocus: true,
35 | hasBackdrop: true,
36 | inline: false,
37 | isOpen: false,
38 | };
39 |
40 | button: HTMLButtonElement;
41 | refHandlers = {
42 | button: (ref: HTMLButtonElement) => this.button = ref,
43 | };
44 |
45 | handleAutoFocusChange = handleBooleanChange((autoFocus) => this.setState({ autoFocus }));
46 | handleBackdropChange = handleBooleanChange((hasBackdrop) => this.setState({ hasBackdrop }));
47 | handleEnforceFocusChange = handleBooleanChange((enforceFocus) => this.setState({ enforceFocus }));
48 | handleEscapeKeyChange = handleBooleanChange((canEscapeKeyClose) => this.setState({ canEscapeKeyClose }));
49 | handleInlineChange = handleBooleanChange((inline) => this.setState({ inline }));
50 | handleOutsideClickChange = handleBooleanChange((val) => this.setState({ canOutsideClickClose: val }));
51 |
52 | renderExample() {
53 | const classes = classNames(
54 | Classes.CARD,
55 | Classes.ELEVATION_4,
56 | OVERLAY_EXAMPLE_CLASS,
57 | this.props.themeName,
58 | );
59 |
60 | return (
61 |
62 |
63 | Show overlay
64 |
65 |
66 |
67 |
I'm an Overlay!
68 |
69 | This is a simple container with some inline styles to position it on the screen.
70 | Its CSS transitions are customized for this example only to demonstrate how
71 | easily custom transitions can be implemented.
72 |
73 |
74 | Click the right button below to transfer focus to the "Show overlay" trigger
75 | button outside of this overlay. If persistent focus is enabled, focus will
76 | be constrained to the overlay. Use the tab key to move to the
77 | next focusable element to illustrate this effect.
78 |
79 |
80 |
Close
81 |
Focus button
82 |
83 |
84 |
85 | );
86 | }
87 |
88 | renderOptions() {
89 | const { hasBackdrop, inline } = this.state;
90 | return [
91 | [
92 | ,
98 | ,
104 | ,
110 | ], [
111 | ,
117 | ,
123 | ,
129 | ],
130 | ];
131 | }
132 |
133 | handleOpen = () => this.setState({ isOpen: true });
134 | handleClose = () => this.setState({ isOpen: false });
135 |
136 | focusButton = () => this.button.focus();
137 | }
138 |
--------------------------------------------------------------------------------
/src/pagination.css:
--------------------------------------------------------------------------------
1 | .rc-pagination {
2 | font-size: 12px;
3 | font-family: 'Arial';
4 | user-select: none;
5 | padding: 0;
6 | text-align: center;
7 | }
8 | .rc-pagination-total-text {
9 | /*float: left;*/
10 | height: 30px;
11 | line-height: 30px;
12 | list-style: none;
13 | padding: 0;
14 | margin: 0 8px 0 0;
15 | }
16 | .rc-pagination:after {
17 | content: " ";
18 | display: block;
19 | height: 0;
20 | clear: both;
21 | overflow: hidden;
22 | visibility: hidden;
23 | }
24 | .rc-pagination-item {
25 | cursor: pointer;
26 | border-radius: 6px;
27 | min-width: 28px;
28 | height: 28px;
29 | line-height: 28px;
30 | text-align: center;
31 | list-style: none;
32 | float: left;
33 | border: 1px solid #d9d9d9;
34 | background-color: rgba(0, 0, 0, 0.1);
35 | margin-right: 8px;
36 | }
37 | .rc-pagination-item a {
38 | text-decoration: none;
39 | color: #FFF;
40 | }
41 | .rc-pagination-item:hover {
42 | border-color: #2db7f5;
43 | }
44 | .rc-pagination-item:hover a {
45 | color: #2db7f5;
46 | }
47 | .rc-pagination-item-active {
48 | background-color: rgba(0, 0, 0, 0.2);
49 | border-color: #2db7f5;
50 | }
51 | .rc-pagination-item-active a {
52 | color: #fff;
53 | }
54 | .rc-pagination-item-active:hover a {
55 | color: #fff;
56 | }
57 | .rc-pagination-jump-prev:after,
58 | .rc-pagination-jump-next:after {
59 | content: "•••";
60 | display: block;
61 | letter-spacing: 2px;
62 | color: #ccc;
63 | font-size: 12px;
64 | margin-top: 1px;
65 | }
66 | .rc-pagination-jump-prev:hover:after,
67 | .rc-pagination-jump-next:hover:after {
68 | color: #2db7f5;
69 | }
70 | .rc-pagination-jump-prev:hover:after {
71 | content: "«";
72 | }
73 | .rc-pagination-jump-next:hover:after {
74 | content: "»";
75 | }
76 | .rc-pagination-prev,
77 | .rc-pagination-jump-prev,
78 | .rc-pagination-jump-next {
79 | margin-right: 8px;
80 | }
81 | .rc-pagination-prev,
82 | .rc-pagination-next,
83 | .rc-pagination-jump-prev,
84 | .rc-pagination-jump-next {
85 | cursor: pointer;
86 | color: #fff;
87 | font-size: 10px;
88 | border-radius: 6px;
89 | list-style: none;
90 | min-width: 28px;
91 | height: 28px;
92 | line-height: 28px;
93 | float: left;
94 | text-align: center;
95 | }
96 | .rc-pagination-prev a:after {
97 | content: "‹";
98 | display: block;
99 | }
100 | .rc-pagination-next a:after {
101 | content: "›";
102 | display: block;
103 | }
104 | .rc-pagination-prev,
105 | .rc-pagination-next {
106 | border: 1px solid #d9d9d9;
107 | font-size: 18px;
108 | }
109 | .rc-pagination-prev a,
110 | .rc-pagination-next a {
111 | color: #fff;
112 | }
113 | .rc-pagination-prev a:after,
114 | .rc-pagination-next a:after {
115 | margin-top: -1px;
116 | }
117 | .rc-pagination-disabled {
118 | cursor: not-allowed;
119 | }
120 | .rc-pagination-disabled a {
121 | color: #ccc;
122 | }
123 | .rc-pagination-options {
124 | float: left;
125 | margin-left: 15px;
126 | }
127 | .rc-pagination-options-size-changer {
128 | float: left;
129 | width: 80px;
130 | }
131 | .rc-pagination-options-quick-jumper {
132 | float: left;
133 | margin-left: 16px;
134 | height: 28px;
135 | line-height: 28px;
136 | }
137 | .rc-pagination-options-quick-jumper input {
138 | margin: 0 8px;
139 | box-sizing: border-box;
140 | background-color: #fff;
141 | border-radius: 6px;
142 | border: 1px solid #d9d9d9;
143 | outline: none;
144 | padding: 3px 12px;
145 | width: 50px;
146 | height: 28px;
147 | }
148 | .rc-pagination-options-quick-jumper input:hover {
149 | border-color: #2db7f5;
150 | }
151 | .rc-pagination-options-quick-jumper button {
152 | display: inline-block;
153 | margin: 0 8px;
154 | font-weight: 500;
155 | text-align: center;
156 | touch-action: manipulation;
157 | cursor: pointer;
158 | background-image: none;
159 | border: 1px solid transparent;
160 | white-space: nowrap;
161 | padding: 0 15px;
162 | font-size: 12px;
163 | border-radius: 6px;
164 | height: 28px;
165 | user-select: none;
166 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
167 | position: relative;
168 | color: rgba(0, 0, 0, 0.65);
169 | background-color: #fff;
170 | border-color: #d9d9d9;
171 | }
172 | .rc-pagination-options-quick-jumper button:hover,
173 | .rc-pagination-options-quick-jumper button:active,
174 | .rc-pagination-options-quick-jumper button:focus {
175 | color: #2db7f5;
176 | background-color: #fff;
177 | border-color: #2db7f5;
178 | }
179 | .rc-pagination-simple .rc-pagination-prev,
180 | .rc-pagination-simple .rc-pagination-next {
181 | border: none;
182 | height: 24px;
183 | line-height: 24px;
184 | margin: 0;
185 | font-size: 18px;
186 | }
187 | .rc-pagination-simple .rc-pagination-simple-pager {
188 | float: left;
189 | margin-right: 8px;
190 | list-style: none;
191 | }
192 | .rc-pagination-simple .rc-pagination-simple-pager .rc-pagination-slash {
193 | margin: 0 10px;
194 | }
195 | .rc-pagination-simple .rc-pagination-simple-pager input {
196 | margin: 0 8px;
197 | box-sizing: border-box;
198 | background-color: #fff;
199 | border-radius: 6px;
200 | border: 1px solid #d9d9d9;
201 | outline: none;
202 | padding: 5px 8px;
203 | min-height: 20px;
204 | }
205 | .rc-pagination-simple .rc-pagination-simple-pager input:hover {
206 | border-color: #2db7f5;
207 | }
208 | .rc-pagination-simple .rc-pagination-simple-pager button {
209 | display: inline-block;
210 | margin: 0 8px;
211 | font-weight: 500;
212 | text-align: center;
213 | touch-action: manipulation;
214 | cursor: pointer;
215 | background-image: none;
216 | border: 1px solid transparent;
217 | white-space: nowrap;
218 | padding: 0 8px;
219 | font-size: 12px;
220 | border-radius: 6px;
221 | height: 26px;
222 | user-select: none;
223 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
224 | position: relative;
225 | color: rgba(0, 0, 0, 0.65);
226 | background-color: #fff;
227 | border-color: #d9d9d9;
228 | }
229 | .rc-pagination-simple .rc-pagination-simple-pager button:hover,
230 | .rc-pagination-simple .rc-pagination-simple-pager button:active,
231 | .rc-pagination-simple .rc-pagination-simple-pager button:focus {
232 | color: #2db7f5;
233 | background-color: #fff;
234 | border-color: #2db7f5;
235 | }
236 | @media only screen and (max-width: 1024px) {
237 | .rc-pagination-item-after-jump-prev,
238 | .rc-pagination-item-before-jump-next {
239 | display: none;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/components/users/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { userLoginRequest } from '../../actions/userAuthentication';
4 | // import { setDisplayedStaff } from '../../actions/staffInformation';
5 | import { Button, Intent, Dialog } from "@blueprintjs/core";
6 | import classNames from 'classnames';
7 | import PropTypes from 'prop-types';
8 |
9 | class LoginForm extends Component {
10 | state = {
11 | email: '',
12 | password: '',
13 | isLoading: false,
14 | errors: [],
15 | disabledSubmit: false
16 | }
17 |
18 | style = {
19 | loginBody: {
20 | margin: 'auto',
21 | width: '400px',
22 | height: '300px',
23 | textAlign: 'center',
24 | transform: 'translate('+0+'%, '+30+'%)'
25 | },
26 | centeringAlignment: {
27 | margin: 'auto',
28 | transform: 'translate('+0+'%, '+5+'%)'
29 | }
30 | }
31 |
32 | onChange = (e) => {
33 | if (!!this.state.errors[e.target.name]) {
34 | let errors = Object.assign({}, this.state.errors);
35 | delete errors[e.target.name];
36 | this.setState({
37 | [e.target.name]: e.target.value,
38 | errors
39 | });
40 | } else {
41 | this.setState({
42 | [e.target.name]: e.target.value
43 | })
44 | }
45 | }
46 |
47 | handleSubmit = (e) => {
48 | e.preventDefault();
49 | this.setState({ disabledSubmit: true });
50 | let errors = {}
51 |
52 | if (this.state.email === '') errors.email = "Email can't be empty";
53 | if (this.state.password === '') errors.password = "Password can't be empty";
54 |
55 | this.setState({ errors });
56 |
57 | this.props.userLoginRequest({
58 | email: this.state.email,
59 | password: this.state.password
60 | }).then((response) => {
61 | // this.props.setDisplayedStaff();
62 | // console.log(response);
63 | if (!response.retStatus) {
64 | this.setState({ errors: response.errors})
65 | }
66 | })
67 | this.setState({ disabledSubmit: false });
68 | }
69 |
70 | render() {
71 | return(
72 |
156 | )
157 | }
158 | }
159 |
160 | LoginForm.propTypes = {
161 | userLoginRequest: PropTypes.func.isRequired,
162 | reRender: PropTypes.func.isRequired
163 | }
164 |
165 | export default connect((state) => { return {}}, { userLoginRequest })(LoginForm);
166 |
--------------------------------------------------------------------------------
/src/components/navigations/LeftMenu.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import Accordion from 'react-responsive-accordion';
4 | import Draggable from 'react-draggable';
5 | import ComposeEmail from '../helpers/ComposeEmail'
6 | // import classNames from 'classnames';
7 |
8 | class LeftMenu extends Component {
9 | state = {
10 | displayEmail: 'none'
11 | }
12 | eventLogger = (e: MouseEvent, data: Object) => {
13 | console.log('Event: ', e);
14 | console.log('Data: ', data);
15 | };
16 | render() {
17 | return(
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Staff List
26 |
27 |
28 |
29 |
30 |
31 | Department
32 |
33 |
34 |
35 |
36 |
37 | Leave Application
38 |
39 |
40 |
41 |
42 |
43 | Allowance & Benefits
44 |
45 |
46 |
47 |
48 |
49 | Pay Advice
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Customer List
60 |
61 |
62 |
63 |
64 |
65 | Quotation
66 |
67 |
68 |
69 |
70 |
71 | Purchase Order
72 |
73 |
74 |
75 |
76 |
77 | Delivery Order
78 |
79 |
80 |
81 |
82 |
83 | Invoice
84 |
85 |
86 |
87 |
88 |
89 | Pay Advice
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Supplier List
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | Dashboard
110 |
111 |
112 |
113 |
114 |
115 | Cash Statement
116 |
117 |
118 |
119 |
120 |
121 | Balance Sheet
122 |
123 |
124 |
125 |
126 |
127 | Profit Loss Statement
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | Email
137 |
138 | this.setState({ displayEmail: 'block'})}>
139 |
140 | Compose
141 |
142 |
143 |
144 | Inbox
145 |
146 |
147 |
148 | Sent
149 |
150 |
151 |
152 |
159 |
160 |
Compose New Email
161 |
162 |
this.setState({displayEmail: 'none'}) } />
163 |
164 |
165 |
166 | )
167 | }
168 | }
169 |
170 | export default LeftMenu;
171 |
--------------------------------------------------------------------------------
/src/components/humanresource/StaffDetails.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
3 | import { connect } from 'react-redux';
4 |
5 | class PersonnelDetails extends Component {
6 | handleOnChange() {
7 |
8 | }
9 |
10 | render(){
11 | var { staffDetails } = this.props
12 | const labelWidth = 'grid-40'
13 | const fieldWidth = 'grid-60'
14 | return (
15 |
16 |
17 |
18 |
First Name
19 |
20 |
25 |
26 |
27 |
28 |
Last Name
29 |
30 |
33 |
34 |
35 |
36 |
Staff ID
37 |
38 |
41 |
42 |
43 |
44 |
Designation
45 |
46 |
49 |
50 |
51 |
52 |
Department
53 |
54 |
57 |
58 |
59 |
60 |
Email
61 |
62 |
65 |
66 |
67 |
68 |
69 |
70 |
Street Address 1
71 |
72 |
77 |
78 |
79 |
80 |
Street Address 2
81 |
82 |
87 |
88 |
89 |
90 |
City
91 |
92 |
97 |
98 |
99 |
100 |
State
101 |
102 |
107 |
108 |
109 |
110 |
Country
111 |
112 |
117 |
118 |
119 |
120 |
121 | )
122 | }
123 | }
124 |
125 | class AllowanceAndBenefits extends Component {
126 | render() {
127 | return (
128 |
129 | Allowance And Benefits
130 |
131 | )
132 | }
133 | }
134 |
135 | class StaffDetails extends Component {
136 | componentWillMount() {
137 | const { selectedStaff } = this.props
138 | this.state = {
139 | staffDetails: selectedStaff,
140 | selectedStaffId: selectedStaff.id
141 | }
142 | }
143 |
144 | handleSubmit() {}
145 |
146 | render() {
147 | return(
148 |
174 | )
175 | }
176 | }
177 |
178 | export default connect(null)(StaffDetails);
179 |
--------------------------------------------------------------------------------
/public/flexboxgrid.min.css:
--------------------------------------------------------------------------------
1 | .container,.container-fluid{margin-right:auto;margin-left:auto}.container-fluid{padding-right:2rem;padding-left:2rem}.row{box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-.5rem;margin-left:-.5rem}.row.reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.col.reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-offset-0,.col-xs-offset-1,.col-xs-offset-10,.col-xs-offset-11,.col-xs-offset-12,.col-xs-offset-2,.col-xs-offset-3,.col-xs-offset-4,.col-xs-offset-5,.col-xs-offset-6,.col-xs-offset-7,.col-xs-offset-8,.col-xs-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-xs{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-xs-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-xs-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-xs-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-xs-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-xs-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-xs-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-xs-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-xs-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-xs-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-xs-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-xs-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-xs-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.start-xs{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-xs{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-xs{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-xs{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-xs{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-xs{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-xs{-ms-flex-pack:distribute;justify-content:space-around}.between-xs{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-xs{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-xs{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}@media only screen and (min-width:48em){.container{width:49rem}.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-offset-0,.col-sm-offset-1,.col-sm-offset-10,.col-sm-offset-11,.col-sm-offset-12,.col-sm-offset-2,.col-sm-offset-3,.col-sm-offset-4,.col-sm-offset-5,.col-sm-offset-6,.col-sm-offset-7,.col-sm-offset-8,.col-sm-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-sm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-sm-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-sm-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-sm-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-sm-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-sm-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-sm-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-sm-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-sm-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-sm-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-sm-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-sm-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-sm-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.start-sm{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-sm{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-sm{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-sm{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-sm{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-sm{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-sm{-ms-flex-pack:distribute;justify-content:space-around}.between-sm{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-sm{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-sm{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:64em){.container{width:65rem}.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-offset-0,.col-md-offset-1,.col-md-offset-10,.col-md-offset-11,.col-md-offset-12,.col-md-offset-2,.col-md-offset-3,.col-md-offset-4,.col-md-offset-5,.col-md-offset-6,.col-md-offset-7,.col-md-offset-8,.col-md-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-md{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-md-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-md-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-md-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-md-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-md-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-md-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-md-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-md-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-md-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-md-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-md-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-md-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.start-md{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-md{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-md{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-md{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-md{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-md{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-md{-ms-flex-pack:distribute;justify-content:space-around}.between-md{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-md{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-md{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:75em){.container{width:76rem}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-offset-0,.col-lg-offset-1,.col-lg-offset-10,.col-lg-offset-11,.col-lg-offset-12,.col-lg-offset-2,.col-lg-offset-3,.col-lg-offset-4,.col-lg-offset-5,.col-lg-offset-6,.col-lg-offset-7,.col-lg-offset-8,.col-lg-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-lg{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-lg-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-lg-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-lg-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-lg-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-lg-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-lg-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-lg-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-lg-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-lg-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-lg-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-lg-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-lg-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.start-lg{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-lg{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-lg{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-lg{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-lg{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-lg{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-lg{-ms-flex-pack:distribute;justify-content:space-around}.between-lg{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-lg{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-lg{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}
--------------------------------------------------------------------------------
/mobile/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React Native App](https://github.com/react-community/create-react-native-app).
2 |
3 | Below you'll find information about performing common tasks. The most recent version of this guide is available [here](https://github.com/react-community/create-react-native-app/blob/master/react-native-scripts/template/README.md).
4 |
5 | ## Table of Contents
6 |
7 | * [Updating to New Releases](#updating-to-new-releases)
8 | * [Available Scripts](#available-scripts)
9 | * [npm start](#npm-start)
10 | * [npm test](#npm-test)
11 | * [npm run ios](#npm-run-ios)
12 | * [npm run android](#npm-run-android)
13 | * [npm run eject](#npm-run-eject)
14 | * [Writing and Running Tests](#writing-and-running-tests)
15 | * [Environment Variables](#environment-variables)
16 | * [Configuring Packager IP Address](#configuring-packager-ip-address)
17 | * [Adding Flow](#adding-flow)
18 | * [Customizing App Display Name and Icon](#customizing-app-display-name-and-icon)
19 | * [Sharing and Deployment](#sharing-and-deployment)
20 | * [Publishing to Expo's React Native Community](#publishing-to-expos-react-native-community)
21 | * [Building an Expo "standalone" app](#building-an-expo-standalone-app)
22 | * [Ejecting from Create React Native App](#ejecting-from-create-react-native-app)
23 | * [Build Dependencies (Xcode & Android Studio)](#build-dependencies-xcode-android-studio)
24 | * [Should I Use ExpoKit?](#should-i-use-expokit)
25 | * [Troubleshooting](#troubleshooting)
26 | * [Networking](#networking)
27 | * [iOS Simulator won't open](#ios-simulator-wont-open)
28 | * [QR Code does not scan](#qr-code-does-not-scan)
29 |
30 | ## Updating to New Releases
31 |
32 | You should only need to update the global installation of `create-react-native-app` very rarely, ideally never.
33 |
34 | Updating the `react-native-scripts` dependency of your app should be as simple as bumping the version number in `package.json` and reinstalling your project's dependencies.
35 |
36 | Upgrading to a new version of React Native requires updating the `react-native`, `react`, and `expo` package versions, and setting the correct `sdkVersion` in `app.json`. See the [versioning guide](https://github.com/react-community/create-react-native-app/blob/master/VERSIONS.md) for up-to-date information about package version compatibility.
37 |
38 | ## Available Scripts
39 |
40 | If Yarn was installed when the project was initialized, then dependencies will have been installed via Yarn, and you should probably use it to run these commands as well. Unlike dependency installation, command running syntax is identical for Yarn and NPM at the time of this writing.
41 |
42 | ### `npm start`
43 |
44 | Runs your app in development mode.
45 |
46 | Open it in the [Expo app](https://expo.io) on your phone to view it. It will reload if you save edits to your files, and you will see build errors and logs in the terminal.
47 |
48 | Sometimes you may need to reset or clear the React Native packager's cache. To do so, you can pass the `--reset-cache` flag to the start script:
49 |
50 | ```
51 | npm start -- --reset-cache
52 | # or
53 | yarn start -- --reset-cache
54 | ```
55 |
56 | #### `npm test`
57 |
58 | Runs the [jest](https://github.com/facebook/jest) test runner on your tests.
59 |
60 | #### `npm run ios`
61 |
62 | Like `npm start`, but also attempts to open your app in the iOS Simulator if you're on a Mac and have it installed.
63 |
64 | #### `npm run android`
65 |
66 | Like `npm start`, but also attempts to open your app on a connected Android device or emulator. Requires an installation of Android build tools (see [React Native docs](https://facebook.github.io/react-native/docs/getting-started.html) for detailed setup). We also recommend installing Genymotion as your Android emulator. Once you've finished setting up the native build environment, there are two options for making the right copy of `adb` available to Create React Native App:
67 |
68 | ##### Using Android Studio's `adb`
69 |
70 | 1. Make sure that you can run adb from your terminal.
71 | 2. Open Genymotion and navigate to `Settings -> ADB`. Select “Use custom Android SDK tools” and update with your [Android SDK directory](https://stackoverflow.com/questions/25176594/android-sdk-location).
72 |
73 | ##### Using Genymotion's `adb`
74 |
75 | 1. Find Genymotion’s copy of adb. On macOS for example, this is normally `/Applications/Genymotion.app/Contents/MacOS/tools/`.
76 | 2. Add the Genymotion tools directory to your path (instructions for [Mac](http://osxdaily.com/2014/08/14/add-new-path-to-path-command-line/), [Linux](http://www.computerhope.com/issues/ch001647.htm), and [Windows](https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/)).
77 | 3. Make sure that you can run adb from your terminal.
78 |
79 | #### `npm run eject`
80 |
81 | This will start the process of "ejecting" from Create React Native App's build scripts. You'll be asked a couple of questions about how you'd like to build your project.
82 |
83 | **Warning:** Running eject is a permanent action (aside from whatever version control system you use). An ejected app will require you to have an [Xcode and/or Android Studio environment](https://facebook.github.io/react-native/docs/getting-started.html) set up.
84 |
85 | ## Customizing App Display Name and Icon
86 |
87 | You can edit `app.json` to include [configuration keys](https://docs.expo.io/versions/latest/guides/configuration.html) under the `expo` key.
88 |
89 | To change your app's display name, set the `expo.name` key in `app.json` to an appropriate string.
90 |
91 | To set an app icon, set the `expo.icon` key in `app.json` to be either a local path or a URL. It's recommended that you use a 512x512 png file with transparency.
92 |
93 | ## Writing and Running Tests
94 |
95 | This project is set up to use [jest](https://facebook.github.io/jest/) for tests. You can configure whatever testing strategy you like, but jest works out of the box. Create test files in directories called `__tests__` or with the `.test` extension to have the files loaded by jest. See the [the template project](https://github.com/react-community/create-react-native-app/blob/master/react-native-scripts/template/App.test.js) for an example test. The [jest documentation](https://facebook.github.io/jest/docs/getting-started.html) is also a wonderful resource, as is the [React Native testing tutorial](https://facebook.github.io/jest/docs/tutorial-react-native.html).
96 |
97 | ## Environment Variables
98 |
99 | You can configure some of Create React Native App's behavior using environment variables.
100 |
101 | ### Configuring Packager IP Address
102 |
103 | When starting your project, you'll see something like this for your project URL:
104 |
105 | ```
106 | exp://192.168.0.2:19000
107 | ```
108 |
109 | The "manifest" at that URL tells the Expo app how to retrieve and load your app's JavaScript bundle, so even if you load it in the app via a URL like `exp://localhost:19000`, the Expo client app will still try to retrieve your app at the IP address that the start script provides.
110 |
111 | In some cases, this is less than ideal. This might be the case if you need to run your project inside of a virtual machine and you have to access the packager via a different IP address than the one which prints by default. In order to override the IP address or hostname that is detected by Create React Native App, you can specify your own hostname via the `REACT_NATIVE_PACKAGER_HOSTNAME` environment variable:
112 |
113 | Mac and Linux:
114 |
115 | ```
116 | REACT_NATIVE_PACKAGER_HOSTNAME='my-custom-ip-address-or-hostname' npm start
117 | ```
118 |
119 | Windows:
120 | ```
121 | set REACT_NATIVE_PACKAGER_HOSTNAME='my-custom-ip-address-or-hostname'
122 | npm start
123 | ```
124 |
125 | The above example would cause the development server to listen on `exp://my-custom-ip-address-or-hostname:19000`.
126 |
127 | ## Adding Flow
128 |
129 | Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept.
130 |
131 | React Native works with [Flow](http://flowtype.org/) out of the box, as long as your Flow version matches the one used in the version of React Native.
132 |
133 | To add a local dependency to the correct Flow version to a Create React Native App project, follow these steps:
134 |
135 | 1. Find the Flow `[version]` at the bottom of the included [.flowconfig](.flowconfig)
136 | 2. Run `npm install --save-dev flow-bin@x.y.z` (or `yarn add --dev flow-bin@x.y.z`), where `x.y.z` is the .flowconfig version number.
137 | 3. Add `"flow": "flow"` to the `scripts` section of your `package.json`.
138 | 4. Add `// @flow` to any files you want to type check (for example, to `App.js`).
139 |
140 | Now you can run `npm run flow` (or `yarn flow`) to check the files for type errors.
141 | You can optionally use a [plugin for your IDE or editor](https://flow.org/en/docs/editors/) for a better integrated experience.
142 |
143 | To learn more about Flow, check out [its documentation](https://flow.org/).
144 |
145 | ## Sharing and Deployment
146 |
147 | Create React Native App does a lot of work to make app setup and development simple and straightforward, but it's very difficult to do the same for deploying to Apple's App Store or Google's Play Store without relying on a hosted service.
148 |
149 | ### Publishing to Expo's React Native Community
150 |
151 | Expo provides free hosting for the JS-only apps created by CRNA, allowing you to share your app through the Expo client app. This requires registration for an Expo account.
152 |
153 | Install the `exp` command-line tool, and run the publish command:
154 |
155 | ```
156 | $ npm i -g exp
157 | $ exp publish
158 | ```
159 |
160 | ### Building an Expo "standalone" app
161 |
162 | You can also use a service like [Expo's standalone builds](https://docs.expo.io/versions/latest/guides/building-standalone-apps.html) if you want to get an IPA/APK for distribution without having to build the native code yourself.
163 |
164 | ### Ejecting from Create React Native App
165 |
166 | If you want to build and deploy your app yourself, you'll need to eject from CRNA and use Xcode and Android Studio.
167 |
168 | This is usually as simple as running `npm run eject` in your project, which will walk you through the process. Make sure to install `react-native-cli` and follow the [native code getting started guide for React Native](https://facebook.github.io/react-native/docs/getting-started.html).
169 |
170 | #### Should I Use ExpoKit?
171 |
172 | If you have made use of Expo APIs while working on your project, then those API calls will stop working if you eject to a regular React Native project. If you want to continue using those APIs, you can eject to "React Native + ExpoKit" which will still allow you to build your own native code and continue using the Expo APIs. See the [ejecting guide](https://github.com/react-community/create-react-native-app/blob/master/EJECTING.md) for more details about this option.
173 |
174 | ## Troubleshooting
175 |
176 | ### Networking
177 |
178 | If you're unable to load your app on your phone due to a network timeout or a refused connection, a good first step is to verify that your phone and computer are on the same network and that they can reach each other. Create React Native App needs access to ports 19000 and 19001 so ensure that your network and firewall settings allow access from your device to your computer on both of these ports.
179 |
180 | Try opening a web browser on your phone and opening the URL that the packager script prints, replacing `exp://` with `http://`. So, for example, if underneath the QR code in your terminal you see:
181 |
182 | ```
183 | exp://192.168.0.1:19000
184 | ```
185 |
186 | Try opening Safari or Chrome on your phone and loading
187 |
188 | ```
189 | http://192.168.0.1:19000
190 | ```
191 |
192 | and
193 |
194 | ```
195 | http://192.168.0.1:19001
196 | ```
197 |
198 | If this works, but you're still unable to load your app by scanning the QR code, please open an issue on the [Create React Native App repository](https://github.com/react-community/create-react-native-app) with details about these steps and any other error messages you may have received.
199 |
200 | If you're not able to load the `http` URL in your phone's web browser, try using the tethering/mobile hotspot feature on your phone (beware of data usage, though), connecting your computer to that WiFi network, and restarting the packager.
201 |
202 | ### iOS Simulator won't open
203 |
204 | If you're on a Mac, there are a few errors that users sometimes see when attempting to `npm run ios`:
205 |
206 | * "non-zero exit code: 107"
207 | * "You may need to install Xcode" but it is already installed
208 | * and others
209 |
210 | There are a few steps you may want to take to troubleshoot these kinds of errors:
211 |
212 | 1. Make sure Xcode is installed and open it to accept the license agreement if it prompts you. You can install it from the Mac App Store.
213 | 2. Open Xcode's Preferences, the Locations tab, and make sure that the `Command Line Tools` menu option is set to something. Sometimes when the CLI tools are first installed by Homebrew this option is left blank, which can prevent Apple utilities from finding the simulator. Make sure to re-run `npm/yarn run ios` after doing so.
214 | 3. If that doesn't work, open the Simulator, and under the app menu select `Reset Contents and Settings...`. After that has finished, quit the Simulator, and re-run `npm/yarn run ios`.
215 |
216 | ### QR Code does not scan
217 |
218 | If you're not able to scan the QR code, make sure your phone's camera is focusing correctly, and also make sure that the contrast on the two colors in your terminal is high enough. For example, WebStorm's default themes may [not have enough contrast](https://github.com/react-community/create-react-native-app/issues/49) for terminal QR codes to be scannable with the system barcode scanners that the Expo app uses.
219 |
220 | If this causes problems for you, you may want to try changing your terminal's color theme to have more contrast, or running Create React Native App from a different terminal. You can also manually enter the URL printed by the packager script in the Expo app's search bar to load it manually.
221 |
--------------------------------------------------------------------------------