diff --git a/package.json b/package.json
index f6c7705..1101d14 100755
--- a/package.json
+++ b/package.json
@@ -9,16 +9,26 @@
"eject": "react-scripts eject"
},
"dependencies": {
+ "antd": "^4.15.2",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
+ "node-sass": "4.14.1",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.2.0",
- "redux": "^4.0.5"
+ "redux": "^4.0.5",
+ "typescript": "^4.2.4",
+ "uuid": "^8.3.2",
+ "yarn": "^1.22.10"
},
"devDependencies": {
- "react-scripts": "3.4.0"
+ "react-scripts": "^3.4.0"
},
- "browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"]
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
}
diff --git a/src/actions/PlayersActions.js b/src/actions/PlayersActions.js
index 8f427c2..01bab15 100755
--- a/src/actions/PlayersActions.js
+++ b/src/actions/PlayersActions.js
@@ -1,9 +1,9 @@
-import * as types from '../constants/ActionTypes';
+import * as types from "../constants/ActionTypes";
-export function addPlayer(name) {
+export function addPlayer(newPlayer) {
return {
type: types.ADD_PLAYER,
- name,
+ newPlayer,
};
}
@@ -20,3 +20,10 @@ export function starPlayer(id) {
id,
};
}
+
+export const filterPlayer = (position) => {
+ return {
+ type: types.FILTER_PLAYER,
+ position,
+ };
+};
diff --git a/src/components/AddPlayerInput.css b/src/components/AddPlayerInput.css
deleted file mode 100755
index 1507c6e..0000000
--- a/src/components/AddPlayerInput.css
+++ /dev/null
@@ -1,6 +0,0 @@
-:local(.addPlayerInput) {
- border-radius: 0;
- border-color: #abaaaa;
- border-left: 0;
- border-right: 0;
-}
diff --git a/src/components/AddPlayerInput.js b/src/components/AddPlayerInput.js
deleted file mode 100755
index 5d914d8..0000000
--- a/src/components/AddPlayerInput.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { Component } from 'react';
-import classnames from 'classnames';
-import PropTypes from 'prop-types';
-import styles from './AddPlayerInput.css';
-
-class AddPlayerInput extends Component {
- render() {
- return (
-
- );
- }
-
- constructor(props, context) {
- super(props, context);
- this.state = {
- name: this.props.name || '',
- };
- }
-
- handleChange(e) {
- this.setState({ name: e.target.value });
- }
-
- handleSubmit(e) {
- const name = e.target.value.trim();
- if (e.which === 13) {
- this.props.addPlayer(name);
- this.setState({ name: '' });
- }
- }
-}
-
-AddPlayerInput.propTypes = {
- addPlayer: PropTypes.func.isRequired,
-};
-
-export default AddPlayerInput;
diff --git a/src/components/PlayerList.js b/src/components/PlayerList.js
deleted file mode 100755
index 7b40246..0000000
--- a/src/components/PlayerList.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import styles from './PlayerList.css';
-import PlayerListItem from './PlayerListItem';
-
-class PlayerList extends Component {
- render() {
- return (
-
- {this.props.players.map((player, index) => {
- return (
-
- );
- })}
-
- );
- }
-}
-
-PlayerList.propTypes = {
- players: PropTypes.array.isRequired,
- actions: PropTypes.object.isRequired,
-};
-
-export default PlayerList;
diff --git a/src/components/add-player-input/AddPlayerInput.js b/src/components/add-player-input/AddPlayerInput.js
new file mode 100644
index 0000000..de5c9ef
--- /dev/null
+++ b/src/components/add-player-input/AddPlayerInput.js
@@ -0,0 +1,54 @@
+import React, { Component } from "react";
+import classnames from "classnames";
+import PropTypes from "prop-types";
+import { POS_TYPES, Options } from "../../constants/posTypes";
+
+import styles from "./AddPlayerInput.module.scss";
+
+class AddPlayerInput extends Component {
+ render() {
+ return (
+
+
+
+
+ );
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ name: "",
+ position: POS_TYPES.SF,
+ };
+ }
+
+ handleChange(e) {
+ this.setState({ name: e.target.value });
+ }
+ handlePositionSelect = (e) => {
+ this.setState({ position: e.target.value });
+ };
+ handleSubmit(e) {
+ const name = e.target.value.trim();
+ if (e.which === 13) {
+ console.log(this.state.position);
+ this.props.addPlayer({ name, position: this.state.position });
+ this.setState({ name: "" });
+ }
+ }
+}
+
+AddPlayerInput.propTypes = {
+ addPlayer: PropTypes.func.isRequired,
+};
+
+export default AddPlayerInput;
diff --git a/src/components/add-player-input/AddPlayerInput.module.scss b/src/components/add-player-input/AddPlayerInput.module.scss
new file mode 100644
index 0000000..2d7deef
--- /dev/null
+++ b/src/components/add-player-input/AddPlayerInput.module.scss
@@ -0,0 +1,14 @@
+.addPlayerFeild {
+ display: flex;
+ margin-bottom: 4px;
+ select {
+ padding: 0 10px;
+ }
+ .addPlayerInput {
+ border-radius: 0;
+ border-color: #abaaaa;
+ border-left: 0;
+ border-right: 0;
+ width: 100%;
+ }
+}
diff --git a/src/components/index.js b/src/components/index.js
index 12ce117..cd34e2c 100755
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,3 +1,3 @@
-export { default as AddPlayerInput } from './AddPlayerInput';
-export { default as PlayerList } from './PlayerList';
-export { default as PlayerListItem } from './PlayerListItem';
+export { default as AddPlayerInput } from './add-player-input/AddPlayerInput';
+export { default as PlayerList } from './player-list/PlayerList';
+export { default as PlayerListItem } from './player-list-item/PlayerListItem';
diff --git a/src/components/pagination/index.module.scss b/src/components/pagination/index.module.scss
new file mode 100644
index 0000000..1861af3
--- /dev/null
+++ b/src/components/pagination/index.module.scss
@@ -0,0 +1,11 @@
+.paginationWrapper {
+ width: 100%;
+ padding: 5px 0;
+ text-align: center;
+ color: antiquewhite;
+ font-size: larger;
+
+ .arrow {
+ cursor: pointer;
+ }
+}
diff --git a/src/components/pagination/index.tsx b/src/components/pagination/index.tsx
new file mode 100644
index 0000000..a5f597f
--- /dev/null
+++ b/src/components/pagination/index.tsx
@@ -0,0 +1,47 @@
+import React, { useEffect, useState } from 'react'
+import styles from './index.module.scss'
+
+interface IPagination {
+ pageNo: number
+ pageSize: number
+}
+
+interface IProps {
+ initPagination: IPagination
+ total: number
+ onChange: Function
+}
+
+export default (props: IProps) => {
+ const { total, initPagination, onChange } = props
+ const [pageNo, setNo] = useState(initPagination.pageNo)
+ const [pageSize] = useState(initPagination.pageSize)
+ const [maxPages, setMaxPages] = useState(Math.ceil(total / pageSize) || 1)
+
+ useEffect(() => {
+ setMaxPages(Math.ceil(total / pageSize) || 1)
+ setNo(initPagination.pageNo)
+ onChange({ pageNo: initPagination.pageNo, pageSize })
+ }, [total, pageSize])
+
+ const handlePaginationChange = (accumlation: number) => {
+ let no = accumlation + pageNo
+ if (no > maxPages) no = maxPages
+ else if (no < 1) no = 1
+
+ onChange({ pageNo: no, pageSize })
+ setNo(no)
+ }
+
+ return (
+
+ handlePaginationChange(-1)}>
+ ←
+
+ {pageNo} / {maxPages}
+ handlePaginationChange(1)}>
+ →
+
+
+ )
+}
diff --git a/src/components/PlayerListItem.js b/src/components/player-list-item/PlayerListItem.js
old mode 100755
new mode 100644
similarity index 77%
rename from src/components/PlayerListItem.js
rename to src/components/player-list-item/PlayerListItem.js
index ec9758c..145aabf
--- a/src/components/PlayerListItem.js
+++ b/src/components/player-list-item/PlayerListItem.js
@@ -1,7 +1,7 @@
-import React, { Component } from 'react';
-import classnames from 'classnames';
-import PropTypes from 'prop-types';
-import styles from './PlayerListItem.css';
+import React, { Component } from "react";
+import classnames from "classnames";
+import PropTypes from "prop-types";
+import styles from "./PlayerListItem.module.scss";
class PlayerListItem extends Component {
render() {
@@ -23,9 +23,9 @@ class PlayerListItem extends Component {
onClick={() => this.props.starPlayer(this.props.id)}
>
@@ -42,7 +42,7 @@ class PlayerListItem extends Component {
}
PlayerListItem.propTypes = {
- id: PropTypes.number.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
team: PropTypes.string.isRequired,
position: PropTypes.string.isRequired,
diff --git a/src/components/PlayerListItem.css b/src/components/player-list-item/PlayerListItem.module.scss
old mode 100755
new mode 100644
similarity index 58%
rename from src/components/PlayerListItem.css
rename to src/components/player-list-item/PlayerListItem.module.scss
index 4ed1654..d8f2c07
--- a/src/components/PlayerListItem.css
+++ b/src/components/player-list-item/PlayerListItem.module.scss
@@ -1,4 +1,4 @@
-:local(.playerListItem) {
+.playerListItem {
list-style: none;
padding: 20px 10px 20px 20px;
background-color: white;
@@ -6,22 +6,22 @@
display: flex;
}
-:local(.playerInfos) {
+.playerInfos {
flex: 1 0 auto;
}
-:local(.playerInfos span) {
+.playerInfos span {
font-weight: bold;
}
-:local(.playerActions) {
+.playerActions {
flex: 0 0 90px;
}
-:local(.btnAction),
-:local(.btnAction):active,
-:local(.btnAction):focus,
-:local(.btnAction):hover {
+.btnAction,
+.btnAction:active,
+.btnAction:focus,
+.btnAction:hover {
margin-right: 5px;
color: #5c75b0;
}
diff --git a/src/components/player-list/PlayerList.js b/src/components/player-list/PlayerList.js
new file mode 100644
index 0000000..35ce34b
--- /dev/null
+++ b/src/components/player-list/PlayerList.js
@@ -0,0 +1,74 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import styles from './PlayerList.module.scss'
+import Pagination from '../pagination'
+import PlayerListItem from '../player-list-item/PlayerListItem'
+
+const initPagination = {
+ pageSize: 5,
+ pageNo: 1,
+}
+
+class PlayerList extends Component {
+ constructor() {
+ super()
+ this.state = {
+ ...initPagination,
+ }
+ }
+
+ getFilteredPlayer = (players) => {
+ const result = []
+ const { showPosition } = this.props
+ const { pageNo, pageSize } = this.state
+ const currentLastPage = pageNo * pageSize
+ const filteredByPosition = players.filter(
+ (player) => player.position === showPosition || !showPosition
+ )
+
+ result.push(filteredByPosition.length)
+
+ const filteredByPagination = filteredByPosition.filter(
+ (_player, index) =>
+ index < currentLastPage && index >= currentLastPage - pageSize
+ )
+
+ result.push(filteredByPagination)
+ return result
+ }
+
+ render() {
+ const { players } = this.props
+ const [total, filtered] = this.getFilteredPlayer(players)
+ return (
+
+ {filtered.map((player) => (
+
+ ))}
+
+ {
+ this.setState({ pageNo, pageSize })
+ }}
+ />
+
+ )
+ }
+}
+
+PlayerList.propTypes = {
+ players: PropTypes.array.isRequired,
+ actions: PropTypes.object.isRequired,
+}
+
+export default PlayerList
diff --git a/src/components/PlayerList.css b/src/components/player-list/PlayerList.module.scss
old mode 100755
new mode 100644
similarity index 57%
rename from src/components/PlayerList.css
rename to src/components/player-list/PlayerList.module.scss
index b8d6f00..5e9ecab
--- a/src/components/PlayerList.css
+++ b/src/components/player-list/PlayerList.module.scss
@@ -1,4 +1,5 @@
-:local(.playerList) {
+.playerList {
padding-left: 0;
margin-bottom: 0;
+ height: auto;
}
diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js
index b796fae..3df755b 100755
--- a/src/constants/ActionTypes.js
+++ b/src/constants/ActionTypes.js
@@ -1,3 +1,4 @@
export const ADD_PLAYER = 'ADD_PLAYER';
export const STAR_PLAYER = 'STAR_PLAYER';
export const DELETE_PLAYER = 'DELETE_PLAYER';
+export const FILTER_PLAYER = 'FILTER_PLAYER'
diff --git a/src/constants/posTypes.tsx b/src/constants/posTypes.tsx
new file mode 100644
index 0000000..42f6d2c
--- /dev/null
+++ b/src/constants/posTypes.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+
+export const POS_TYPES = {
+ SF:'SF',
+ SG:'SG',
+ PF:'PF',
+ PG:'PG'
+}
+
+export const Options = Object.keys(POS_TYPES).map((postion, index) => {
+ return (
+
+ );
+ });
+
\ No newline at end of file
diff --git a/src/containers/PlayerListApp.css b/src/containers/PlayerListApp.css
deleted file mode 100755
index f438bf1..0000000
--- a/src/containers/PlayerListApp.css
+++ /dev/null
@@ -1,24 +0,0 @@
-body {
- background-color: #f4f3f0;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-:local(.playerListApp) {
- width: 540px;
- margin-top: 10px;
- padding-top: 18px;
- background-color: #5c75b0;
- border: 1px solid #e3e3e3;
-}
-
-:local(.playerListApp h1) {
- color: white;
- font-size: 16px;
- line-height: 20px;
- margin-bottom: 10px;
- margin-top: 0;
- padding-left: 10px;
- font-family: Helvetica;
-}
diff --git a/src/containers/PlayerListApp.js b/src/containers/PlayerListApp.js
index 0e4bfa5..3966b74 100755
--- a/src/containers/PlayerListApp.js
+++ b/src/containers/PlayerListApp.js
@@ -1,16 +1,25 @@
-import React, { Component } from 'react';
-import styles from './PlayerListApp.css';
-import { connect } from 'react-redux';
+import React, { Component } from "react";
+import styles from "./PlayerListApp.module.scss";
+import { connect } from "react-redux";
-import { addPlayer, deletePlayer, starPlayer } from '../actions/PlayersActions';
-import { PlayerList, AddPlayerInput } from '../components';
+import {
+ addPlayer,
+ deletePlayer,
+ starPlayer,
+ filterPlayer,
+} from "../actions/PlayersActions";
+import { PlayerList, AddPlayerInput } from "../components";
+import { Options } from "../constants/posTypes";
class PlayerListApp extends Component {
+ handleOnPostionChange = (e) => {
+ this.props.filterPlayer(e.target.value);
+ };
+
render() {
const {
- playerlist: { playersById },
+ playerlist: { playersById, showPosition },
} = this.props;
-
const actions = {
addPlayer: this.props.addPlayer,
deletePlayer: this.props.deletePlayer,
@@ -19,9 +28,19 @@ class PlayerListApp extends Component {
return (
-
NBA Players
+
+
NBA Players
+
+
-
+
);
}
@@ -31,11 +50,9 @@ function mapStateToProps(state) {
return state;
}
-export default connect(
- mapStateToProps,
- {
- addPlayer,
- deletePlayer,
- starPlayer,
- },
-)(PlayerListApp);
+export default connect(mapStateToProps, {
+ addPlayer,
+ deletePlayer,
+ starPlayer,
+ filterPlayer,
+})(PlayerListApp);
diff --git a/src/containers/PlayerListApp.module.scss b/src/containers/PlayerListApp.module.scss
new file mode 100644
index 0000000..a96cf24
--- /dev/null
+++ b/src/containers/PlayerListApp.module.scss
@@ -0,0 +1,24 @@
+body {
+ background-color: #f4f3f0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .playerListApp {
+ width: 540px;
+ margin-top: 10px;
+ padding-top: 18px;
+ background-color: #5c75b0;
+ border: 1px solid #e3e3e3;
+
+ h1 {
+ color: white;
+ font-size: 16px;
+ line-height: 20px;
+ margin-bottom: 10px;
+ margin-top: 0;
+ padding-left: 10px;
+ font-family: Helvetica;
+ }
+ }
+}
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/reducers/initData.ts b/src/reducers/initData.ts
new file mode 100644
index 0000000..08adafb
--- /dev/null
+++ b/src/reducers/initData.ts
@@ -0,0 +1,48 @@
+import { POS_TYPES } from "../constants/posTypes";
+export const initialState = {
+ playersById: [
+ {
+ id:'asjdlk1jlkjd1i1j',
+ name: 'LeBron James',
+ team: 'LOS ANGELES LAKERS',
+ position: POS_TYPES.SF,
+ starred: true,
+ },
+ {
+ id:'lksajdkjdadshasdsjlk',
+ name: 'Kevin Duran',
+ team: 'GOLDEN STATE WARRIORS',
+ position: POS_TYPES.SF,
+ starred: false,
+ },
+ {
+ id:'asi12e12klj313oiu12u31o2ij3kl',
+ name: 'Anthony Davis',
+ team: 'NEW ORLEANS PELICANS',
+ position: POS_TYPES.PF,
+ starred: false,
+ },
+ {
+ id:'79h97hu8youij80uioiuo68ty',
+ name: 'Stephen Curry',
+ team: 'GOLDEN STATE WARRIORS',
+ position: POS_TYPES.PG,
+ starred: false,
+ },
+ {
+ id:'812d8j182oud2186diweuhfw7832y',
+ name: 'James Harden',
+ team: 'HOUSTON ROCKETS',
+ position: POS_TYPES.SG,
+ starred: false,
+ },
+ {
+ id:'s8dusdasdnj1829218712',
+ name: 'Kawhi Leonard',
+ team: 'TORONTO RAPTORS',
+ position: POS_TYPES.SF,
+ starred: false,
+ },
+ ],
+ };
+
\ No newline at end of file
diff --git a/src/reducers/playerlist.js b/src/reducers/playerlist.js
index 1bc7457..fb55cc2 100755
--- a/src/reducers/playerlist.js
+++ b/src/reducers/playerlist.js
@@ -1,45 +1,7 @@
-import * as types from '../constants/ActionTypes';
-
-const initialState = {
- playersById: [
- {
- name: 'LeBron James',
- team: 'LOS ANGELES LAKERS',
- position: 'SF',
- starred: true,
- },
- {
- name: 'Kevin Duran',
- team: 'GOLDEN STATE WARRIORS',
- position: 'SF',
- starred: false,
- },
- {
- name: 'Anthony Davis',
- team: 'NEW ORLEANS PELICANS',
- position: 'PF',
- starred: false,
- },
- {
- name: 'Stephen Curry',
- team: 'GOLDEN STATE WARRIORS',
- position: 'PG',
- starred: false,
- },
- {
- name: 'James Harden',
- team: 'HOUSTON ROCKETS',
- position: 'SG',
- starred: false,
- },
- {
- name: 'Kawhi Leonard',
- team: 'TORONTO RAPTORS',
- position: 'SF',
- starred: false,
- },
- ],
-};
+import { v4 as uuidv4 } from 'uuid'
+import * as types from '../constants/ActionTypes'
+import { POS_TYPES } from '../constants/posTypes'
+import { initialState } from './initData'
export default function players(state = initialState, action) {
switch (action.type) {
@@ -49,29 +11,35 @@ export default function players(state = initialState, action) {
playersById: [
...state.playersById,
{
- name: action.name,
+ id: uuidv4(),
+ name: action.newPlayer.name,
team: 'LOS ANGELES LAKERS',
- position: 'SF',
+ position: action.newPlayer.position || POS_TYPES.SF,
+ starred: false,
},
],
- };
+ }
case types.DELETE_PLAYER:
return {
...state,
- playersById: state.playersById.filter(
- (item, index) => index !== action.id,
- ),
- };
+ playersById: state.playersById.filter((item) => item.id !== action.id),
+ }
case types.STAR_PLAYER:
- let players = [...state.playersById];
- let player = players.find((item, index) => index === action.id);
- player.starred = !player.starred;
+ let players = [...state.playersById]
+ let player = players.find((item) => item.id === action.id)
+ player.starred = !player.starred
return {
...state,
playersById: players,
- };
+ }
+
+ case types.FILTER_PLAYER:
+ return {
+ ...state,
+ showPosition: action.position,
+ }
default:
- return state;
+ return state
}
}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..f2850b7
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react"
+ },
+ "include": [
+ "src"
+ ]
+}