WorkShop WebApplication ดว้ ย React : 201
9. แก้ไขไฟล์ src/index.js เป็นโค้ดด้านลา่ งน้ี
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import registerServiceWorker from './registerServiceWorker';
import { Router, browserHistory } from 'react-router'
import routes from './routes'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducers from './redux/reducers'
import jwtDecode from 'jwt-decode'
const store = createStore(
reducers,
applyMiddleware(thunk)
)
//get เอาคา่ token จาก localStorage ของ Browser เพอ่ื เช็คว่ามกี าร Login แลว้ หรอื หยัง นัน่ ก็เพราะ
//ถา้ เรา refresh browser คา่ จาก state ทไ่ี ดจ้ าก redux ก็จะเริม่ ใหม่
//ดงั นั้นเราจงึ ตอ้ งไป get token จาก localStorage มาใหมแ่ ล้วสั่ง dispatch ใหม่อกี คร้ังครับ
const token = localStorage.getItem('token')
if (token) {
const decodeToken = jwtDecode(token)
store.dispatch({
type: 'AUTH_USER',
payload: decodeToken
})
} else {
//ถา้ ไมม่ ี token ให้ redirect ไปยงั หน้า signin
browserHistory.push('signin')
}
ReactDOM.render(
<Provider store={store}>
<Router
history={browserHistory}
routes={routes}
/>
</Provider>
,document.getElementById('root')
);
registerServiceWorker();
201
WorkShop WebApplication ด้วย React : 202
กลับไปดูที่ browser กจ็ ะเหน็ หน้าสำ� หรบั signin แลว้ ครบั (ถ้าไมแ่ สดงให้ refresh ดนู ะครบั )
ดูแล้วก็เหมือนว่ามันท�ำงานได้ปกติใช่ม้ัยครบั ลองคลิกเมนูอน่ื ดูกจ็ ะเห็นว่ามันสามารถแสดงผลของหน้าเพจนนั้ ๆ ได้
ปกติ ซึ่งผิด !! กบั ส่ิงทีเ่ ราต้ังใจทำ� ไว้ว่า เพราะถ้าหากไมไ่ ด้ Signin เวลาคลิกเมนอู ่ืนๆ ไมค่ วรทจี่ ะแสดงผลหน้านัน้ ๆ แตถ่ ้าลอง
refresh browser ดูก็จะเหน็ ว่ามันกจ็ ะกลับมาหนา้ Signin ใหม่ สาเหตุท่ีเปน็ เชน่ น้นั เพราะ
การทีเ่ ราเขียนเช็คว่ามีค่า token ท่ีไฟล์ src/index.js หรือเปล่าในครั้งแรกท่เี ขา้ สรู่ ะบบ React มนั ก็จะนบั เฉพาะ
เหตุการณ์ที่ browser ทำ� การ refresh เทา่ น้ันซง่ึ เป็นธรรมชาติของ javascript ทเ่ี ปน็ แบบ SPA อยูแ่ ล้วครบั จึงทำ� ให้ไมม่ ีผล
การคลิกลงิ ค์เพ่ือเปลี่ยนไปยัง route ต่างๆ
วิธกี ารแกไ้ ขคือ เราต้องไปควบคมุ การเช็คการ Authentication ท่ี route ด้วย (น่าจะเปน็ วิธที ด่ี ี และง่ายที่สุด
ส�ำหรบั ความคิดผมนะครับ) โดยผมจะทำ� เปน็ HOC (Higher-Order Components) มาครอบ route ตา่ งๆ ครับ ส�ำหรับการ
ทำ� HOC นั้นกไ็ ม่ได้งา่ ยครบั แต่ไม่ได้ยาก หลกั ๆ ของมนั คอื ท�ำ Component ไปครอบ Componet อนื่ ไว้ #มันก็จะ งงๆ เอา
เปน็ วา่ ลองดูตัวอย่างที่ผมว่า Simple ทีส่ ดุ ของ HOC ได้ท่ีลงิ คน์ ค้ี รับ https://www.tutorialspoint.com/reactjs/
reactjs_higher_order_components.htm ตัวอยา่ งจากเว็บฝร่ังเว็บนผี้ มวา่ simple สดุ ๆ ละ
เกร่นิ ซะยาวเลย กลบั มาที่ WorkShop ของเราดีกวา่ ครบั
สร้าง HOC (Higher-Order Components) เช็คการ Authen กับ Route
1. สร้างไฟล์ src/pages/Auth/Authentication.js ขน้ึ มาใหม่ แลว้ พมิ พโ์ คด้ ดา้ นล่างนี้
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
//รูปแบบการ export ท�ำตามนี้เลยครับ
export default function(ComposedComponent) {
class Authentication extends Component {
static contextTypes = {
router: PropTypes.object
}
มีโคด้ ต่อด้านลา่ ง
202
WorkShop WebApplication ด้วย React : 203
//เริ่มแรกเลยตอ้ งเชค็ props ทเ่ี รา map ไว้กบั authReducers โค้ดตอ่ จากด้านบน
//src/redux/reducers/authReducers.js
//ว่า authenticated เปน็ true หรอื เปล่า ถา้ ไมใ่ ช่ก็ redirect ไปหน้าแรก
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('signin');
}
}
//เช็ค props authenticated ไวท้ น่ี ี้ด้วยนะครับ
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('signin');
}
}
render() {
// รปู แบบการท�ำเปน็ HOC เราจะตอ้ งเอา ส่ง ComposedComponent กลบั ไป
// พร้อม props เดมิ ของ component น้นั ๆ ด้วย
return <ComposedComponent {...this.props} />
}
}
//map เข้ากับ authReducers
function mapStateToProps(state) {
return {
authenticated: state.authReducers.authenticated
};
}
return connect(mapStateToProps)(Authentication);
}
2. แกไ้ ขไฟล์ src/routes.js เปน็ โค้ดดา้ นลา่ งน้ี โดยหากตอ้ งการให้ route ไหนต้องตรวจสอบก่อนว่า Signin
หรอื ยังกใ็ ห้เอา RequireAuth ไปครอบ Component ใน route นั้นไว้ ซ่งึ แนน่ อนว่าเราตอ้ งการตรวจสอบทุก route
ยกเว้นการ Signin และ Signout
import App from './pages/App'
import Home from './pages/Home'
import RequireAuth from './pages/Auth/Authentication'
import Signin from './pages/Auth/Signin'
import Signout from './pages/Auth/Signout'
import Work from './pages/Work'
import WorkRepair from './pages/WorkRepair'
import User from './pages/User'
import Location from './pages/Location'
203
WorkShop WebApplication ดว้ ย React : 204
const routes = [{
path: '/',
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'signin', component: Signin },
{ path: 'signout', component: Signout },
{ path: 'work', component: RequireAuth(Work) },
{ path: 'workrepair', component: RequireAuth(WorkRepair) },
{ path: 'user', component: RequireAuth(User)},
{ path: 'location', component: RequireAuth(Location)},
]
}]
export default routes
ลองดูท่ี browser ใหมอ่ ีกครั้งครับ จะเห็นว่าคร้งั นี้เมอื่ เราคลิกเมนูอนื่ ระบบก็จะ redirect กลบั มาหน้า Signin เสมอ
หากยังไม่ไดท้ ำ� การ Signin ทีถ่ กู ต้อง ซ่ึงถอื ว่าครง้ั น้สี อบผา่ น yes!!
แต่มนั ยงั มีส่ิงหนึ่งท่ดี ูแล้วขดั หูหัดตาใชม่ ัย้ คบั นนั่ กค็ อื เมนู ซึ่งตอนน้ีเรายังไมไ่ ด้ Signin เขา้ ระบบเลยแตเ่ มนดู ัน
แสดงออกมาให้เห็นท้ังหมด ซะง้ัน...ดงั น้นั เราจะตอ้ งไปแกไ้ ขเมนูกันซะหน่อย
และเพื่อให้การแกไ้ ขเมนูครงั้ นม้ี ัน จบๆ ไปเลย #เจ็บแตจ่ บ เรากจ็ ะตอ้ งท�ำการเชค็ ประเภทผูใ้ ช้งานดว้ ยครบั วา่ เปน็ ผู้
ใช้งานปกติ หรือเป็นผ้ดู แู ลระบบ ถ้าเปน็ ผูใ้ ช้งานปกติ เมนนู ท่ีมองเห็นคอื เมนแู จ้งซอ่ ม และเมนู Signout
หากเปน็ ผใู้ ชง้ านประเภทผ้ดู แู ลระบบ เมนทู ่มี องเห็นคอื เมนแู จ้งซอ่ ม, เมนงู านซ่อม, เมนสู ถานที่, เมนูผู้ใช้งาน และ
เมนู Signout ดงั นนั้ เราจะแก้ไขเมนกู ันซะหน่อยครับ (ดทู ่ีหนา้ ถดั ไปนะครับ)
204
WorkShop WebApplication ด้วย React : 205
แสดงเมนตู ามประเภทผใู้ ชง้ าน
แก้ไขไฟล์ src/components/Header/Header.js เป็นโคด้ ดา้ นลา่ งน้ี
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import { Collapse, NavbarToggler, NavbarBrand } from 'reactstrap';
class Header extends Component {
state = {
isOpen: false
};
toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
renderLinks() { 205
//ตรวจสอบจาก reducer authReducers.js ทเ่ี ราได้ map ไว้ดา้ นลา่ งครับ
//โดยเช็ควา่ ถา้ หากมกี าร signin กจ็ ะแสดงเมนูตามเงือ่ นไข
if (this.props.authentication) {
//ถา้ เปน็ ผใู้ ชง้ านปกตจิ ะแสดงแค่ 2 เมนู
if (this.props.data.user_type === 0) {
return [
<li className="nav-item" key={2} >
<Link to="/work" className="nav-link">แจ้งซอ่ ม</Link>
</li>,
<li className="nav-item" key={6}>
<Link to="/signout" className="nav-link">Sign Out</Link>
</li>
]
} else {
//ถ้าเปน็ ผู้ใชง้ านประเภทผ้ดู แู ลปกติ จะแสดงทุกเมนูครับ
return [
<li className="nav-item" key={2} >
<Link to="/work" className="nav-link">แจง้ ซ่อม</Link>
</li>,
<li className="nav-item" key={3}>
<Link to="/workrepair" className="nav-link">งานซอ่ ม</Link>
</li>,
<li className="nav-item" key={4}>
<Link to="/location" className="nav-link">สถานที<่ /Link>
</li>,
<li className="nav-item" key={5}>
<Link to="/user" className="nav-link">ผู้ใช้งาน</Link>
</li>,
<li className="nav-item" key={6}>
<Link to="/signout" className="nav-link">Sign Out</Link>
</li>
]
} มีโคด้ ต่อด้านลา่ ง
WorkShop WebApplication ด้วย React : 206
} else { โคด้ ต่อจากด้านบน
//ถา้ ยังไม่ได้ท�ำการ Signin จะแสดงเมนูเดยี วเทา่ น้ัน
return (
<li className="nav-item" key={1}>
<Link to="/signin" className="nav-link">Sign In</Link>
</li>
)
}
}
render() {
return (
<nav className="navbar navbar-expand-md navbar-dark bg-primary">
<NavbarBrand href="/">itService</NavbarBrand>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<ul className="navbar-nav">
{this.renderLinks()}
</ul>
</Collapse>
</nav>
)
}
}
//authentication เชค็ ว่าเปน็ true หรือ false
//data ตัวน้จี ะได้ขอ้ มูล PAYLOAD จาก token ครับ
//ทง้ั หมดทงั้ มวลเราจัดการมาตั้งแต่ action, reducer แลว้ ครบั
function mapStateToProps(state) {
return {
authentication: state.authReducers.authenticated,
data: state.authReducers.data
}
}
export default connect(mapStateToProps)(Header)
จากน้นั กก็ ลับไปดูที่ browser กจ็ ะเหน็ วา่ เมนูแสดงตามโคด้ ทเ่ี ราเขียนแลว้ ครับ
206
WorkShop WebApplication ด้วย React : 207
ลอง Signin เข้าสูร่ ะบบดว้ ย username ที่เปน็ ประเภทผู้ใช้งานปกติ (user_type=0) เพอื่ ทดสอบว่า เมนูจะแสดง
เฉพาะสิทธ์ขิ องผใู้ ชง้ านจรงิ ๆ หรอื ไม่
ใช้ user1 ทำ� การ singin เขา้ ระบบครับ
Yes! แสดงเมนูตามประเภทของ user แลว้ ครับ
เพราะ user ท่ี user_type = 0 จะให้มองเหน็ แค่ 2 เมนู
207
WorkShop WebApplication ด้วย React : 208
แต่โลกมนั ไมไ่ ด้สวยงามอยา่ งที่เห็นนะครบั แมต้ อนน้ีการแสดงรายชอ่ื เมนจู ะแสดงตามทีเ่ ราก�ำหนดใน Header.js
แลว้ กต็ าม ทำ� ให้เราคลิกเมนูอ่ืนๆ ไม่ได้ แต่ถา้ หากเราพมิ พ์ url เองหละ เหมือนตวั อย่างด้านลา่ งนคี้ รับ
พิมพ์ url เป็น http://localhost:3000/location
เหน็ มย้ั ครับวา่ มนั แสดงหน้าเพจไดต้ าม url ทเี่ ราพมิ พเ์ ข้าไป ซ่งึ เราตอ้ งแกไ้ ขเหตุการแบบน้ดี ้วย...ผมให้เวลาคิดว่า
ควรจะแกไ้ ขเหตุการแบบนยี้ งั ไง...1...2...3...4...5 หมดเวลาครบั
การแก้ปัญหาท่ีนา่ จะดีท่ีสุดคอื การทำ� HOC มาครอบ route ไวเ้ หมือนๆ HOC ในการเชค็ Authen ครบั
สรา้ ง HOC (Higher-Order Components) เชค็ สทิ ธ์ิการเข้าถึง Route
1. สรา้ งไฟล์ src/pages/Auth/AuthenticationAdmin.js แลว้ พิมพ์โค้ดดา้ นล่างน ้ี โดยเราจะยึดวา่ เราจะเช็ค
สิทธ์ขิ องผู้ใชท้ เ่ี ปน็ ผดู้ ูแลระบบ
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
//รูปแบบการ export ท�ำตามนีเ้ ลยครับ
export default function (ComposedComponent) {
class AuthenticationAdmin extends Component {
static contextTypes = {
router: PropTypes.object
}
//เร่ิมแรกเลยต้องเชค็ props ท่เี รา map ไวก้ ับ authReducers
//src/redux/reducers / authReducers.js
//วา่ data ที่รับมาจาก token แลว้ user_type เป็น 0 หรือไม่
//ถาเป็น 0 แสดงวา่ เปน็ ผู้ดูแลระบบ ถา้ ไม่ใชใ้ ห้ redirect ไปหนา้ แรก
componentWillMount() {
if (this.props.data) {
if (this.props.data.user_type === 0) {
this.context.router.push('/');
}
}
}
//เชค็ props authenticated ไว้ทีน่ ้ดี ว้ ยนะครับ
componentWillUpdate(nextProps) {
มโี ค้ดตอ่ ดา้ นลา่ ง
208
if (nextProps.data) { WorkShop WebApplication ดว้ ย React : 209
if (nextProps.data.user_type === 0) { โคด้ ตอ่ จากดา้ นบน
this.context.router.push('/');
}
}
}
render() {
// รปู แบบการท�ำเป็น HOC เราจะต้องเอา สง่ ComposedComponent กลบั ไป
// พรอ้ ม props เดมิ ของ component นั้นๆ ดว้ ย
return <ComposedComponent {...this.props} />
}
}
//map เข้ากับ authReducers
function mapStateToProps(state) {
return {
authenticated: state.authReducers.authenticated,
data: state.authReducers.data
};
}
return connect(mapStateToProps)(AuthenticationAdmin);
}
2. แกไ้ ขไฟล์ src/routes.js เปน็ โคด้ ด้านลา่ งน้ี โดยหากต้องการให้ route ไหนสามารถเข้าถึงไดเ้ ฉพาะสทิ ธผิ์ ู้
ดูแลระบบก็ให้เอา RequireAuthAdmin ไปครอบ Component ใน route นน้ั ไว้ (ซอ้ น RequireAuth)
import App from './pages/App'
import Home from './pages/Home'
import RequireAuth from './pages/Auth/Authentication'
import RequireAuthAdmin from './pages/Auth/AuthenticationAdmin'
import Signin from './pages/Auth/Signin'
import Signout from './pages/Auth/Signout'
import Work from './pages/Work'
import WorkRepair from './pages/WorkRepair'
import User from './pages/User'
import Location from './pages/Location'
const routes = [{ 209
path: '/',
component: App,
indexRoute: { component: Home },
childRoutes: [
{ path: 'signin', component: Signin },
{ path: 'signout', component: Signout },
{ path: 'work', component: RequireAuth(Work) },
{ path: 'workrepair', component: RequireAuth(RequireAuthAdmin(WorkRepair)) },
{ path: 'user', component: RequireAuth(RequireAuthAdmin(User))},
{ path: 'location', component: RequireAuth(RequireAuthAdmin(Location))},
]
}]
export default routes
WorkShop WebApplication ด้วย React : 210
กลับมาที่ browser อกี ครัง้ ซึ่งตอนนี้เรายงั login อยใู่ นระบบอยู่นะครบั จากน้ันลองพมิ พ์ url เป็น http://
localhost:3000/location จะเหน็ วา่ โปรแกรมจะ redirect มาหนา้ แรก
ลอง Sign Out แลว้ Sign In เข้าระบบใหม่ด้วย user ท่เี ป็นผู้ดแู ลระบบ (user_type=0) ในข้อมลู ของผมจะเป็น
username: admin, password: 1234 กจ็ ะพบว่าเมนแู สดงมาตามสิทธ์ิไดถ้ ูกตอ้ งครบั
Local Storage ของ Browser อยไู่ หน
ส�ำหรับการดู Local Storage ของ Chrome Browser (ของ browser อื่นๆ กจ็ ะอยคู่ นละเมนูกันนะครับ) สามารถ
ดูได้ตามภาพด้านลา่ งครบั
คลกิ ขวา
เลือก Inspect
ตวั เลอื ก Application อาจจะยงั ไมแ่ สดงใหค้ ลกิ ที่เครอ่ื งหมาย >> เพ่อื เลอื ก Application นะครบั
210
WorkShop WebApplication ด้วย React : 211
หนา้ จอข้อมลู ผใู้ ช้
1. สรา้ งไฟล์ src/redux/reducers/userReducers.js แล้วพมิ พ์โค้ดด้านลา่ งนี้
//ก�ำหนดคา่ เรม่ิ ต้นให้ state เชน่ เช็ควา่ ขอ้ มลู ทีด่ งึ มา error หรือไมเ่ รากจ็ ะเชค็ จาก isRejected
//ซ่งึ ถา้ เราไมก่ ำ� หนด state เรม่ิ ตน้ กจ็ ะไม่มี object ช่อื isRejected ใหเ้ รียกใชง้ าน
const initialState = {
users: { data: null, isLoading: true, isRejected: false },
user: { data: null, isLoading: true, isRejected: false },
userDelete: { success: false, isLoading: true, isRejected: false },
userSave: { data: null, isLoading: true, isRejected: false },
}
export default (state = initialState, action) => {
switch (action.type) {
//เกบ็ state การดึงข้อมลู ผูใ้ ช้ทงั้ หมด
case 'LOAD_USERS_PENDING':
return { ...state, users: { data: null, isLoading: true, isRejected: false } }
case 'LOAD_USERS_SUCCESS':
return { ...state, users: { data: action.payload, isLoading: false, isRejected: false } }
case 'LOAD_USERS_REJECTED':
return { ...state, users: { data: action.payload, isLoading: false, isRejected: true } }
//เก็บ state การดึงขอ้ มลู ผู้ใช้ตาม id ทีส่ ง่ ไป
case 'LOAD_USER_PENDING':
return { ...state, user: { data: null, isLoading: true, isRejected: false } }
case 'LOAD_USER_SUCCESS':
return { ...state, user: { data: action.payload, isLoading: false, isRejected: false } }
case 'LOAD_USER_REJECTED':
return { ...state, user: { data: action.payload, isLoading: false, isRejected: true } }
//เก็บ state การลบข้อมลู ผู้ใช้
case 'DELETE_USER_SUCCESS':
return { ...state, userDelete: { data: true, isLoading: false, isRejected: false } }
case 'DELETE_USER_REJECTED':
return { ...state, userDelete: { data: action.payload, isLoading: false, isRejected: true } }
มีโคด้ ต่อดา้ นลา่ ง
211
WorkShop WebApplication ด้วย React : 212
//เก็บ state สถานะการบันทกึ ขอ้ มูลผใู้ ช้ โคด้ ตอ่ จากด้านบน
case 'SAVE_USER_SUCCESS':
return { ...state, userSave: { data: null, isLoading: false, isRejected: false } }
case 'SAVE_USER_REJECTED':
return { ...state, userSave: { data: action.payload, isLoading: false, isRejected: true } }
default:
return state
}
}
2. แกไ้ ขไฟล์ src/redux/reducers/index.js เป็นโคด้ ดา้ นล่างนเ้ี พอ่ื ใช้งาน userReducers.js
import { combineReducers } from 'redux'
//redux-form จะท�ำการเก็บ state และมี reducer ในตวั ของมนั เอง
//ดงั น้นั เวลาเราจะใชง้ าน redux-form เราตอ้ งทำ� เหมอื นว่ามนั คือ reducer ตวั หนึง่ ดว้ ยครับ
import { reducer as formReducer } from 'redux-form'
import authReducers from './authReducers'
import userReducers from './userReducers'
const rootReducers = combineReducers({
form: formReducer, //ก�ำหนดช่ือ reducer ไวว้ า่ ชอื่ form นะครบั ตามค�ำแนะนำ� ของ redux-form
authReducers,
userReducers,
})
export default rootReducers
3. สร้างไฟลใ์ หม่ src/redux/actions/userActions.js แล้วพมิ พ์โค้ดด้านล่างน้ีครับ 212
import axios from 'axios'
import config from '../../configure'
//ดึงเอา url ทใี่ ช้ fetch data มาเกบ็ ไวใ้ น BASE_URL
const BASE_URL = config.BASE_URL
//ฟงั กช์ ันดึงขอ้ มลู ผใู้ ช้ทกุ รายการโดยจะสง่ query ชื่อ term เข้าไปดว้ ยเพื่อน�ำไป filter
//ส�ำหรับ es6 เราสามารถกำ� หนดค่า default ของ parameter ไดด้ ว้ ยครับ
export const loadUsers = (term = '') => {
return (dispatch) => {
//ก่อนดึงขอ้ มลู ส่ัง dispatch ให้ reducer รูว้ า่ กอ่ นเพื่อจะแสดง loading
dispatch({ type: 'LOAD_USERS_PENDING' })
return axios.get(`${BASE_URL}/users?term=${term}`, {
//ต้องส่ง heder ชอื่ authorization โดยสง่ token เขาไป
//เพอ่ื บอกให้ server รวู้ า่ เราได้ signin ถกู ต้องแลว้
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เมอ่ื ขอ้ มลู สง่ กลบั มาก็ส่ัง dispatch ให้ reducer รูพ้ รอ้ มส่ง payload
//เนือ่ งจากเราใช้ axios แทน fetch ดังนัน้ ขอ้ มลู ท่สี ่งมาจะอยใู่ น object ช่ือ data
//ท่มี ี Array อยขู่ ้างใน ดงั นนั้ น�ำไป data.map ไดเ้ ลยครบั
มโี ค้ดต่อด้านลา่ ง
WorkShop WebApplication ดว้ ย React : 213
dispatch({ type: 'LOAD_USERS_SUCCESS', payload: results.data }) โคด้ ต่อจากดา้ นบน
}).catch(err => {
//กรณี error
dispatch({ type: 'LOAD_USERS_REJECTED', payload: err.message })
})
}
}
//ฟังก์ชนั ดึงขอ้ มูลผใู้ ช้ตาม id ทส่ี ่ง
export const getUser = (id) => {
return (dispatch) => {
dispatch({ type: 'LOAD_USER_PENDING' })
return axios.get(`${BASE_URL}/users/${id}`, {
//ต้องส่ง heder ชอื่ authorization โดยส่ง token เขาไป
//เพ่อื บอกให้ server ร้วู า่ เราได้ signin ถูกตอ้ งแล้ว
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เม่ือขอ้ มูลสง่ กลับมากส็ ่งั dispatch ให้ reducer ร้พู ร้อมส่ง payload
//axios จะสง่ ขอ้ มูลกลบั มากบั object ชื่อ data
dispatch({ type: 'LOAD_USER_SUCCESS', payload: results.data })
}).catch(err => {
//กรณี error
dispatch({ type: 'LOAD_USER_REJECTED', payload: err.message })
})
}
}
//ฟงั กช์ นั บันทึกขอ้ มลู ผใู้ ช้ โดยเราจะเช็คว่าเป็นการเพ่มิ ขอ้ มูลใหม่ หรอื ปรบั ปรุงข้อมลู
export const saveUser = (values) => {
//ถา้ มี values.id แสดงวา่ เปน็ การบันทึกการปรับปรงุ ข้อมลู จึงตอ้ งส่ง method put
//put จะไป match กบั route ฝั่ง server คอื app.put('/users/:id', requireAuth, users.update)
//แต่ถ้าไมใ่ ชใ่ หส้ ่ง method post เพ่ือเพมิ่ ข้อมูลใหม่
//post จะไป match กับ route ฝ่ัง server คอื app.post('/users', requireAuth, users.create)
let _id = ''
let _method = 'post'
if (values.id) {
_id = values.id
_method = 'put'
}
return (dispatch) => {
//รปู แบบการใช้ axios อกี รูปแบบในการจะบุ method ท่ีต้องการ
//ตอ้ งส่ง heder ช่ือ authorization โดยส่ง token เขาไปด้วยครบั
return axios({
method: _method,
url: `${BASE_URL}/users/${_id}`,
data: values,
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เมื่อข้อมลู สง่ กลับมาตอ้ งเช็คสถานะก่อนว่า username ซำ้� หรอื ไม่
//โดยserver จะสง่ object ทช่ี ือ่ ว่า status และ message กลบั มา
if (results.data.status) {
มีโค้ดตอ่ ดา้ นลา่ ง 213
WorkShop WebApplication ด้วย React : 214
dispatch({ type: 'SAVE_USER_REJECTED', payload: results.data.message }) โคด้ ต่อจากดา้ นบน
} else {
dispatch({ type: 'SAVE_USER_SUCCESS' })
}
}).catch(err => {
//กรณี error
dispatch({ type: 'SAVE_USER_REJECTED', payload: err.message })
})
}
}
//ฟังก์ชนั ลบขอ้ มลู ผู้ใช้ตาม id ท่ีส่งเขา้ มา
export const deleteUser = (id) => {
return (dispatch) => {
return axios.delete(`${BASE_URL}/users/${id}`, {
//ตอ้ งสง่ heder ช่อื authorization โดยส่ง token เขาไปด้วยครับ
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//ลบข้อมูลสำ� เรจ็
dispatch({ type: 'DELETE_USER_SUCCESS' })
}).catch(err => {
//กรณี error
dispatch({ type: 'DELETE_USER_REJECTED', payload: err.message })
})
}
}
//ฟงั ก์ชันสำ� หรบั reset คา่ status เพ่อื ลา้ งข้อความ error ทคี่ า้ งอยู่
export const resetStatus = () => {
return (dispatch) => {
dispatch({ type: 'SAVE_USER_SUCCESS' })
}
}
4. แก้ไขไฟล์ src/pages/User.js เป็นโคด้ ด้านล่างน้ี
import React, { Component } from 'react'
import { debounce } from 'lodash'
import { connect } from 'react-redux'
import {
loadUsers, getUser, saveUser,
deleteUser, resetStatus
} from '../redux/actions/userActions'
import { Modal, ModalHeader } from 'reactstrap';
import { confirmModalDialog } from '../Utils/reactConfirmModalDialog'
import SearchBar from ' ../Utils/searchBar'
import UserTable from '../components/Users/UserTable'
import UserForm from '../components/Users/UserForm'
class User extends Component {
มโี คด้ ตอ่ ดา้ นลา่ ง
214
WorkShop WebApplication ดว้ ย React : 215
//มกี ารใช้ Modal ของ reactstrap ซึ่งจะต้องเก็บ State การแสดง modal ไว้ โคด้ ตอ่ จากด้านบน
state = {
modal: false,
modalTitle: ''
}
//สัง่ dispach ฟงั กช์ ัน loadUsers
componentDidMount() {
this.props.dispatch(loadUsers())
}
render() {
const { users, user, userSave } = this.props
if (users.isRejected) {
//ถา้ มี error
return <div>{users.data}</div>
}
//debounce เปน็ การหน่วงการสง่ ตวั อกั ษรเปน็ ฟงั กช์ นั ของ lodash ท�ำเพอ่ื เรยี กใชก้ าร filter ข้อมูล
const userSearch = debounce(term => { this.handleSearch(term) }, 500);
return (
<div>
<h4>ผู้ใช้งาน</h4>
<div className="form-group row">
<div className="col-sm-6">
{/* สง่ props onSearchTermChange ให้ Component SearchBar เพื่อ filgter
โดยฝ่งั SearchBar จะนำ� ไปใช้กบั event onChange */}
<SearchBar
onSearchTermChange={userSearch}
placeholder="คน้ หา...ชือ่ -สกุล, Username" />
</div>
</div>
{/* แสดงขอ้ ความ Loading ก่อน */}
{users.isLoading && <div>Loading...</div>}
{/* Component UserTable จะส่ง props ไป 4 ตัว */}
<UserTable
data={users.data}
buttonNew={this.handleNew}
buttonEdit={this.handleEdit}
buttonDelete={this.handleDelete}
/>
{/* เป็น Component ส�ำหรบั แสดง Modal ของ reactstrap
ซ่ึงเราตอ้ งควบคมุ การแสดงไวท้ ่ไี ฟลน์ ี้ ถ้าท�ำแยกไฟล์จะควบคมุ ยากมากครับ */}
<Modal isOpen={this.state.modal} toggle={this.toggle}
className="modal-primary" autoFocus={false}>
<ModalHeader toggle={this.toggle}>{this.state.modalTitle}ผู้ใช้งาน</ModalHeader>
{/* เรียกใช้งาน Component UserForm และสง่ props ไปดว้ ย 4 ตัว */}
มีโค้ดตอ่ ดา้ นลา่ ง
215
<UserForm WorkShop WebApplication ดว้ ย React : 216
data={user.data} โคด้ ตอ่ จากด้านบน
userSave={userSave}
onSubmit={this.handleSubmit}
onToggle={this.toggle} />
</Modal>
</div>
)
}
//ฟังกช์ นั ส่งั แสดง/ปดิ modal
toggle = () => {
this.setState({
modal: !this.state.modal
})
}
//ฟงั กช์ ัน filter ข้อมลู
handleSearch = (term) => {
this.props.dispatch(loadUsers(term))
}
//ฟังก์ชนั สรา้ งขอ้ มูลใหม่โดยจะสงั่ ให้เปดิ Modal
handleNew = () => {
this.props.dispatch(resetStatus())
this.props.user.data = []
this.setState({ modalTitle: 'เพ่มิ ' })
this.toggle();
}
//ฟังกช์ นั แก้ไขข้อมลู และสัง่ ให้เปดิ Modal โดยส่งขอ้ มลู ไปแป๊ะให้กับฟอรม์ ด้วย
handleEdit = (id) => {
this.props.dispatch(resetStatus())
this.setState({ modalTitle: 'แก้ไข' })
this.props.dispatch(getUser(id)).then(() => {
this.toggle()
})
}
//ฟงั ก์ชันบนั ทึกขอ้ มลู มีโคด้ ต่อด้านลา่ ง
handleSubmit = (values) => {
this.props.dispatch(saveUser(values)).then(() => {
if (!this.props.userSave.isRejected) {
this.toggle()
this.props.dispatch(loadUsers())
}
})
}
216
WorkShop WebApplication ดว้ ย React : 217
โคด้ ตอ่ จากดา้ นบน
//ฟังก์ชนั ลบข้อมลู
handleDelete = (id) => {
confirmModalDialog({
show: true,
title: 'ยืนยนั การลบ',
message: 'คุณต้องการลบข้อมลู น้ใี ชห่ รอื ไม'่ ,
confirmLabel: 'ยนื ยนั ลบทนั ท!ี !',
onConfirm: () => this.props.dispatch(deleteUser(id)).then(() => {
this.props.dispatch(loadUsers())
})
})
}
}
function mapStateToProps(state) {
return {
users: state.userReducers.users,
user: state.userReducers.user,
userDelete: state.userReducers.userDelete,
userSave: state.userReducers.userSave
}
}
export default connect(mapStateToProps)(User)
จากโคด้ จะมี Component ทเี่ ราจะตอ้ งไปสรา้ งอีก 4 ตัวขา้ งล่างนี้
import { confirmModalDialog } from ‘../components/Utils/reactConfirmModalDialog’
import SearchBar from ‘../components/Utils/searchBar’
import UserTable from ‘../components/Users/UserTable’
import UserForm from ‘../components/Users/UserForm’
Component แสดง Modal ส�ำหรบั ยนื ยันการทำ� งาน (Confirm Dialog)
เราจะสร้าง Component แสดง Modal สำ� หรับยนื ยนั การทำ� งาน เชน่ ยืนยนั การลบ ซ่ึงถอื ว่าเป็น Component
ทม่ี ีประโยชน์และคุ้มคา่ ในการท�ำมาก โดยมขี ้นั ตอนดังน้ี
1. สร้างไฟลใ์ หม่ไวท้ ี่ src/Utils/reactConfirmModalDialog.js แล้วพมิ พ์โค้ดดา้ นลา่ งนี้
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { render } from 'react-dom';
import { Modal, ModalHeader, Button, ModalBody, ModalFooter } from 'reactstrap';
//รปู แบบการเขยี นตอ่ ไปนจ้ี ะเขา้ ใจยากครับตอ้ งใช้เวลานานเพอื่ ท�ำและทดสอบ
//มนั เป็นรูปแบบของการสร้าง Element ขึน้ มาใหม่
export default class ReactConfirmModalDialog extends Component {
มโี คด้ ตอ่ ด้านล่าง
217
//เกบ็ State เพ่ือก�ำหนดวา่ จะใหแ้ สดง Modal หรอื ไม่ WorkShop WebApplication ด้วย React : 218
state = { โคด้ ต่อจากดา้ นบน
modal: this.props.show
}
//ใชง้ าน PropTypes เปน็ การเช็คค่า Props ท่สี ง่ เข้ามาวา่ ตรงตามทเ่ี รากำ� หนดหรอื ไม่
//เหมอื นการตรวจสอบการทำ� งานของโปรแกรมเพื่อไม่ให้เกดิ ขอ้ ผดิ พลาด
static propTypes = {
type: PropTypes.string, //รบั ค่าตัวขอ้ ความ warning, info
show: PropTypes.bool, //รับค่า true , false เพ่ือกำ� หนดว่าจะแสดง Modal หรือไม่
title: PropTypes.string, //รบั ค่าข้อความเพอ่ื แสดงหวั ของ Modal
message: PropTypes.string, //ขอ้ ความทีต่ ้องการใหป้ รากฏใน Modal
confirmLabel: PropTypes.string, //ขอ้ ความปุม่ ยืนยัน
cancelLabel: PropTypes.string, //ข้อความปุ่มยกเลือก
onConfirm: PropTypes.func, //เมื่อยนื ยันจะให้เรยี กใช้ function อะไร
onCancel: PropTypes.func, //เม่อื ยกเลอื กจะใหเ้ รยี กใช้ function อะไร
children: PropTypes.node, //สามารถระบุ element ยอ่ ยได้ ปกติจะไมไ่ ด้ใช้
};
//กำ� หนด Default Props
static defaultProps = {
type: 'warning',
show: false,
title: '',
message: '',
childrenElement: () => null,
confirmLabel: '',
cancelLabel: 'ปิด',
};
//ควบคมุ การแสดง Modal
toggle = () => {
this.setState({
modal: !this.state.modal
})
const target = document.getElementById('react-widget-dialog');
if (target) {
target.parentNode.removeChild(target);
}
}
onClickConfirm = () => {
this.props.onConfirm();
this.toggle()
};
onClickCancel = () => { มโี ค้ดต่อด้านลา่ ง
this.props.onCancel(); 218
this.toggle()
};
WorkShop WebApplication ดว้ ย React : 219
render() { โคด้ ตอ่ จากดา้ นบน
const { title, message, confirmLabel, cancelLabel, type } = this.props;
let buttonColor, modalColor;
switch (type) {
case 'info':
buttonColor = "info";
modalColor = "modal-info";
break;
default:
buttonColor = "warning";
modalColor = "modal-warning";
break;
}
return (
<div>
<Modal isOpen={this.state.modal} toggle={this.toggle} className={modalColor}>
<ModalHeader toggle={this.toggle}>{title}</ModalHeader>
<ModalBody>
{message}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{cancelLabel}</Button>
{confirmLabel && <Button color={buttonColor} onClick={this.onClickConfirm}>
{confirmLabel}</Button>}
</ModalFooter>
</Modal>
</div>
)
}
}
//ฟงั ก์ชนั สรา้ ง Element โดยจะวาดลงภายใน Div
function createElementDialog(properties) {
const divTarget = document.createElement('div');
divTarget.id = 'react-widget-dialog';
document.body.appendChild(divTarget);
render(<ReactConfirmModalDialog {...properties} />, divTarget);
}
//สุดทา้ ยส่งออกเป็นชือ่ confirmModalDialog
export function confirmModalDialog(properties) {
createElementDialog(properties);
}
219
WorkShop WebApplication ด้วย React : 220
สร้าง Component ช่องกรอกข้อมลู เพ่ือใช้ในการ Filter
Component นี้ทำ� หน้าทีเ่ พียงสง่ ตวั อักษรกลบั ไปเพอ่ื ใชใ้ นการ filter ขอ้ มูลซงึ่ จะถกู น�ำไปใชใ้ นอกี หลายๆ หน้าจอ
การทเี่ ราทำ� เปน็ Component ไวเ้ พอ่ื เป็นแนวทางในการสรา้ ง Component ท่ี reuse ได้
ใหส้ รา้ งไฟลใ์ หม่ขึน้ มาที่ /src/Utils/searchBar.js แล้วพิมพ์โค้ดด้านลา่ งนี้
import React, { Component } from "react";
import PropTypes from "prop-types";
class SearchBar extends Component {
//รับค่า props และก�ำหนด state ผา่ น constructor เป็นอีกรูปแบบหนงึ่
constructor(props) {
super(props);
this.state = { term: "" }
}
//รูปแบบการก�ำหนด PropTypes เพื่อเช็ค props ที่ส่งเขา้ มา
static propTypes = {
onSearchTermChange: PropTypes.func.isRequired,
placeholder: PropTypes.string
}
render() {
return (
<input
className="form-control"
placeholder={this.props.placeholder}
value={this.state.term}
onChange={e => this.onInputChange(e.target.value)} />
)
}
//ฟังก์ชันสำ� หรบั เรียก props ท่ใี ช้ filter ข้อมูล
//ดงั น้นั ฟงั กช์ ัน onSearchTermChange ท่สี ง่ เข้ามาต้องเป็นฟงั กช์ ันท่ีใช้ในการ filter ข้อมูล
//Component นที้ ำ� หนา้ ทีเ่ พยี งสง่ ตวั อกั ษรกลับไป
onInputChange(term) {
this.setState({ term });
this.props.onSearchTermChange(term);
}
}
export default SearchBar;
220
WorkShop WebApplication ดว้ ย React : 221
Component แสดงขอ้ มูลรายช่ือผู้ใชแ้ บบ TABLE
สร้างไฟลใ์ หม่ไว้ที่ src/components/Users/UserTable.js แล้วพมิ พโ์ ค้ดดา้ นลา่ งนี้
import React, { Component } from 'react'
import { Table, Button } from 'reactstrap';
//แสดงรายช่ือข้อมูลผ้ใู ช้ แสดงแบบ HTML TABLE
class UserTable extends Component {
render() {
//Destructuring ค่า props ที่สง่ มาจาก src/pages/User.js
const { data, buttonNew, buttonEdit, buttonDelete } = this.props
return (
<Table striped bordered hover>
<thead>
<tr>
<th width="120" className="text-center">ประเภทผใู้ ช<้ /th>
<th>ชอื่ -สกลุ </th>
<th>Username</th>
<th width="120" className="text-center">
<Button color="success" size="sm"
onClick={buttonNew}>เพม่ิ ข้อมูล</Button>
</th>
</tr>
</thead>
<tbody>
{/* loop ข้อมูลที่ไดร้ บั มา */}
{data && data.map(e => {
return (
<tr key={e.id}>
<td className="text-center">
{(e.user_type === 0) ? 'ทั่วไป' : 'ผูด้ แู ลระบบ'}
</td>
<td>{e.name}</td>
<td>{e.username}</td>
<td className="text-center">
<Button color="secondary" size="sm"
onClick={() => buttonEdit(e.id)}>แก้ไข</Button>{' '}
<Button color="danger" size="sm"
onClick={() => buttonDelete(e.id)}>ลบ</Button>
</td>
</tr>
)
})}
</tbody>
</Table>
)
}
}
export default UserTable
221
WorkShop WebApplication ด้วย React : 222
Component ฟอรม์ กรอกขอ้ มลู ผู้ใช ้
ฟอรม์ กรอกข้อมลู ผ้ใู ชจ้ ะถกู น�ำไปใชง้ านรว่ มกับ Modal ทีเ่ ราสรา้ งไวใ้ นไฟล์ src/pages/User.js โดยให้สรา้ งไฟล์
ใหมไ่ ว้ท่ี src/components/Users/UserForm.js แลว้ พมิ พ์โคด้ ด้านลา่ งน้ี
import React, { Component } from 'react'
import { Button, ModalBody, ModalFooter } from 'reactstrap';
import { Field, reduxForm } from 'redux-form';
import renderField from '../../Utils/renderFields'
class UserForm extends Component {
componentDidMount() {
//เรยี กใช้ฟังก์ชันในการก�ำหนด value ให้กบั textbox และ control ตา่ งๆ
this.handleInitialize()
}
//ก�ำหนดค่า value ให้กบั textbox หรอื control ตา่ งๆ ในฟอรม์
//ถา้ เปน็ HTML ธรรมดากจ็ ะกำ� หนดเป็น value="xxx" แต่สำ� หรับ redux-form
//ตอ้ งใช้ initialize ถา้ เป็น redux-form v.6 ตอ้ งประกาศใช้ initialize แต่ v.7 เรียกใชไ้ ด้เลย
handleInitialize() {
let initData = {
"user_type": "0",
"name": '',
"username": '',
"password": ''
};
//ตรวจสอบกอ่ นว่ามี data.id หรอื ไม่
//ถ้าไมม่ แี สดงว่าเป็นการสร้างรายการใหม่
//ถา้ มแี สดงว่ามีการ get ขอ้ มลู ผใู้ ชง้ านจงึ เปน็ การปรับปรุง
if (this.props.data.id) {
initData = this.props.data
//user_type ท่รี ับมาเปน็ init แตv่ alue ตอ้ งแปลงเป็น string ก่อน
initData.user_type = this.props.data.user_type.toString()
}
this.props.initialize(initData);
}
render() {
//redux-form จะมี props ที่ช่อื handleSubmit เพ่ือใช้ submit คา่
const { handleSubmit, userSave } = this.props
return (
<div>
<ModalBody>
{/* ตรวจสอบวา่ มี err หรือไม่ */}
{userSave.isRejected && <div className="alert alert-danger">{userSave.data}</div>}
{/* รูปแบบการแสดงผลจดั ตาม Bootstrap 4 */} มีโคด้ ต่อดา้ นลา่ ง
<div className="form-group row"> 222
<label className="col-sm-3 col-form-label">ประเภทผู้ใช<้ /label>
<div className="col-sm-9">
WorkShop WebApplication ดว้ ย React : 223
<div className="form-check form-check-inline"> โค้ดต่อจากดา้ นบน
<label className="form-check-label">
<Field
className="form-check-input"
name="user_type"
component="input"
type="radio"
value='0'
/>{' '}
ทวั่ ไป
</label>
</div>
<div className="form-check form-check-inline">
<label className="form-check-label">
<Field
className="form-check-input"
name="user_type"
component="input"
type="radio"
value="1"
/>{' '}ผู้ดูแลระบบ
</label>
</div>
</div>
</div>
<Field name="name" component={renderField} type="text" label="ช่ือ-สกลุ " autoFocus />
<Field name="username" component={renderField} type="text" label="Username" />
<Field name="password" component={renderField} type="password" label="Password" />
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={handleSubmit(this.onSubmit)}>บนั ทึก</Button>{' '}
<Button color="secondary" onClick={this.toggle}>ยกเลิก</Button>
</ModalFooter>
</div>
)
}
//ฟังก์ชนั นเี้ รียกใช้ props ช่อื onToggle จาก src/pages/User.js เพ่อื ปิด Modal
toggle = () => {
this.props.onToggle()
}
//ฟังกช์ นั สง่ การค่าการ submit โดยส่งใหฟ้ ังก์ชนั ชือ่ onSubmit ทไ่ี ด้จาก props
onSubmit = (values) => {
this.props.onSubmit(values);
}
}
//validate ข้อมูลก่อน submit
function validate(values) {
const errors = {};
มีโค้ดต่อดา้ นลา่ ง 223
if (!values.name) { WorkShop WebApplication ดว้ ย React : 224
errors.name = 'จำ� เปน็ ตอ้ งกรอกชอ่ื -สกลุ '; โค้ดต่อจากด้านบน
}
if (!values.username) {
errors.username = 'จ�ำเป็นต้องกรอก Username !';
} else if (values.username.length < 3) {
errors.username = 'Username ตอ้ งมากกวา่ 3 ตัวอกั ษร !';
}
return errors;
}
//เรียกใช้ redux-form โดยใหม้ กี ารเรยี กใช้การ validate ด้วย
const form = reduxForm({
form: 'UserForm',
validate
})
//สงั เกตุวา่ ไมม่ ีการใช้ connect เลยเพราะเราไมไ่ ดเ้ ปน็ ตวั จัดการ data โดยตรง
//แต่ส่งสิ่งต่างผ่าน props ทไ่ี ดจ้ าก src/pages/User.js
export default form(UserForm)
กลบั มาดูผลงานที่ browser ก็จะเหน็ ว่าหน้าจอผใู้ ช้งานพรอ้ มใชแ้ ลว้ ครับ ลองค้นหาขอ้ มูล, เพิ่ม, แกไ้ ข, ลบ กรณี
คลกิ ปุ่ม ลบ โปรแกรมจะแสดง Dialog ขน้ึ มาให้ยืนยัน ส�ำหรบั Dialog นีก้ ม็ าจากที่เราสรา้ งในไฟล์
src/Utils/reactConfirmModalDialog.js
224
WorkShop WebApplication ดว้ ย React : 225
หนา้ จอข้อมลู สถานที่
1. สรา้ งไฟล์ src/redux/reducers/locationReducers.js แล้วพิมพโ์ คด้ ดา้ นล่างน้ี
//ก�ำหนดค่าเร่มิ ตน้ ให้ state เชน่ เช็ควา่ ข้อมูลท่ดี ึงมา error หรอื ไม่เราก็จะเชค็ จาก isRejected
//ซง่ึ ถา้ เราไม่กำ� หนด state เริม่ ตน้ ก็จะไม่มี object ช่อื isRejected ให้เรยี กใช้งาน
const initialState = {
locations: { data: null, isLoading: true, isRejected: false },
location: { data: null, isLoading: true, isRejected: false },
locationDelete: { success: false, isLoading: true, isRejected: false },
locationSave: { data: null, isLoading: true, isRejected: false },
}
export default (state = initialState, action) => {
switch (action.type) {
//เกบ็ state การดงึ ขอ้ มูลสถานที่ทงั้ หมด
case 'LOAD_LOCATIONS_PENDING':
return { ...state, locations: { data: null, isLoading: true, isRejected: false } }
case 'LOAD_LOCATIONS_SUCCESS':
return { ...state, locations: { data: action.payload, isLoading: false, isRejected: false } }
case 'LOAD_LOCATIONS_REJECTED':
return { ...state, locations: { data: action.payload, isLoading: false, isRejected: true } }
//เก็บ state การดงึ ข้อมลู สถานทต่ี าม id ทีส่ ง่ ไป
case 'LOAD_LOCATION_PENDING':
return { ...state, location: { data: null, isLoading: true, isRejected: false } }
case 'LOAD_LOCATION_SUCCESS':
return { ...state, location: { data: action.payload, isLoading: false, isRejected: false } }
case 'LOAD_LOCATION_REJECTED':
return { ...state, location: { data: action.payload, isLoading: false, isRejected: true } }
//เกบ็ state การลบข้อสถานที่
case 'DELETE_LOCATION_SUCCESS':
return { ...state, locationDelete: { data: true, isLoading: false, isRejected: false } }
case 'DELETE_LOCATION_REJECTED':
return { ...state, locationDelete: { data: action.payload, isLoading: false, isRejected: true } }
มโี คด้ ตอ่ ด้านลา่ ง
225
WorkShop WebApplication ดว้ ย React : 226
//เกบ็ state สถานะการบนั ทกึ ข้อมลู สถานท่ี โค้ดต่อจากดา้ นบน
case 'SAVE_LOCATION_SUCCESS':
return { ...state, locationSave: { data: null, isLoading: false, isRejected: false } }
case 'SAVE_LOCATION_REJECTED':
return { ...state, locationSave: { data: action.payload, isLoading: false, isRejected: true } }
default:
return state
}
}
2. แก้ไขไฟล์ src/redux/reducers/index.js เปน็ โค้ดด้านล่างน้ี
import { combineReducers } from 'redux'
//redux-form จะทำ� การเก็บ state และมี reducer ในตวั ของมันเอง
//ดงั นั้นเวลาเราจะใชง้ าน redux-form เราต้องท�ำเหมือนว่ามนั คือ reducer ตวั หน่งึ ดว้ ยครบั
import { reducer as formReducer } from 'redux-form'
import authReducers from './authReducers'
import userReducers from './userReducers'
import locationReducers from './locationReducers'
const rootReducers = combineReducers({
form: formReducer, //ก�ำหนดชือ่ reducer ไว้วา่ ชอ่ื form นะครับตามคำ� แนะนำ� ของ redux-form
authReducers,
userReducers,
locationReducers,
})
export default rootReducers
3. สร้างไฟล์ src/redux/actions/locationActions.js แล้วพิมพ์โคด้ ดา้ นล่างนี้
import axios from 'axios'
import config from '../../configure'
//ดงึ เอา url ทีใ่ ช้ fetch data มาเก็บไวใ้ น BASE_URL
const BASE_URL = config.BASE_URL
//ฟังกช์ นั ดงึ ขอ้ มูลสถานทที่ ุกรายการโดยจะสง่ query ชอ่ื term เขา้ ไปด้วยเพือ่ นำ� ไป filter
//ส�ำหรบั es6 เราสามารถกำ� หนดค่า default ของ parameter ไดด้ ้วยครับ
export const loadLocations = (term = '') => {
return (dispatch) => {
//ก่อนดึงขอ้ มูลสง่ั dispatch ให้ reducer รูว้ ่าก่อนเพ่ือจะแสดง loading
dispatch({ type: 'LOAD_LOCATIONS_PENDING' })
มีโค้ดต่อดา้ นลา่ ง
226
WorkShop WebApplication ดว้ ย React : 227
โคด้ ต่อจากดา้ นบน
return axios.get(`${BASE_URL}/locations?term=${term}`, {
//ตอ้ งสง่ heder ชือ่ authorization โดยสง่ token เขาไป
//เพือ่ บอกให้ server รูว้ า่ เราได้ signin ถกู ต้องแลว้
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เมอ่ื ขอ้ มลู สง่ กลับมาก็ส่งั dispatch ให้ reducer รพู้ ร้อมสง่ payload
//เนือ่ งจากเราใช้ axios แทน fetch ดังนนั้ ขอ้ มลู ท่ีส่งมาจะอย่ใู น object ชอื่ data
//ทมี่ ี Array อยขู่ ้างใน ดังนน้ั นำ� ไป data.map ไดเ้ ลยครบั
dispatch({ type: 'LOAD_LOCATIONS_SUCCESS', payload: results.data })
}).catch(err => {
//กรณี error
dispatch({ type: 'LOAD_LOCATIONS_REJECTED', payload: err.message })
})
}
}
//ฟังกช์ นั ดึงขอ้ มลู สถานท่ตี าม id ทีส่ ่ง
export const getLocation = (id) => {
return (dispatch) => {
dispatch({ type: 'LOAD_LOCATION_PENDING' })
return axios.get(`${BASE_URL}/locations/${id}`, {
//ต้องสง่ heder ช่ือ authorization โดยส่ง token เขาไป
//เพอ่ื บอกให้ server รู้ว่าเราได้ signin ถกู ตอ้ งแลว้
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เมอื่ ข้อมลู ส่งกลับมากส็ ่งั dispatch ให้ reducer ร้พู ร้อมส่ง payload
//axios จะสง่ ขอ้ มลู กลับมากับ object ชือ่ data
dispatch({ type: 'LOAD_LOCATION_SUCCESS', payload: results.data })
}).catch(err => {
//กรณี error
dispatch({ type: 'LOAD_LOCATION_REJECTED', payload: err.message })
})
}
}
//ฟงั ก์ชันบันทึกข้อมูลสถานท่ี โดยเราจะเช็คว่าเปน็ การเพ่ิมขอ้ มูลใหม่ หรือปรบั ปรุงขอ้ มูล
export const saveLocation = (values) => {
//ถ้ามี values.id แสดงวา่ เปน็ การบันทกึ การปรับปรุงข้อมลู จงึ ตอ้ งส่ง method put
//put จะไป match กบั route ฝ่ัง server คอื app.put('/locations/:id', requireAuth, locations.update)
//แต่ถา้ ไม่ใชใ่ ห้ส่ง method post เพ่อื เพิม่ ข้อมลู ใหม่
//post จะไป match กับ route ฝ่ัง server คือ app.post('/locations', requireAuth, locations.create)
let _id = ''
let _method = 'post'
มีโคด้ ตอ่ ด้านลา่ ง
227
if (values.id) { WorkShop WebApplication ด้วย React : 228
_id = values.id โค้ดต่อจากด้านบน
_method = 'put'
}
return (dispatch) => {
//รปู แบบการใช้ axios อีกรปู แบบในการจะบุ method ท่ตี อ้ งการ
//ต้องส่ง heder ชื่อ authorization โดยสง่ token เขาไปด้วยครบั
return axios({
method: _method,
url: `${BASE_URL}/locations/${_id}`,
data: values,
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เม่ือขอ้ มลู สง่ กลบั มาต้องเช็คสถานะกอ่ นว่า code ซ�้ำหรอื ไม่
//โดยserver จะส่ง object ท่ีชือ่ วา่ status และ message กลับมา
if (results.data.status) {
dispatch({ type: 'SAVE_LOCATION_REJECTED', payload: results.data.message })
} else {
dispatch({ type: 'SAVE_LOCATION_SUCCESS' })
}
}).catch(err => {
//กรณี error
dispatch({ type: 'SAVE_LOCATION_REJECTED', payload: err.message })
})
}
}
//ฟงั กช์ ันลบขอ้ มลู สถานทตี่ าม id ทีส่ ่งเข้ามา
export const deleteLocation = (id) => {
return (dispatch) => {
return axios.delete(`${BASE_URL}/locations/${id}`, {
//ต้องส่ง heder ชื่อ authorization โดยส่ง token เขาไปด้วยครับ
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//ลบข้อมูลสำ� เร็จ
dispatch({ type: 'DELETE_LOCATION_SUCCESS' })
}).catch(err => {
//กรณี error
dispatch({ type: 'DELETE_LOCATION_REJECTED', payload: err.message })
})
}
}
มโี คด้ ตอ่ ดา้ นลา่ ง
228
WorkShop WebApplication ด้วย React : 229
//ฟังก์ชนั ส�ำหรับ reset ค่า status เพือ่ ล้างข้อความ error ทคี่ า้ งอยู่ โค้ดต่อจากด้านบน
export const resetStatus = () => {
return (dispatch) => {
dispatch({ type: 'SAVE_LOCATION_SUCCESS' })
}
}
4. แกไ้ ขไฟล์ src/pages/Location.js เปน็ โค้ดดา้ นลา่ งน้ี
import React, { Component } from 'react'
import { debounce } from 'lodash'
import { connect } from 'react-redux'
import {
loadLocations, getLocation, saveLocation, deleteLocation, resetStatus
} from '../redux/actions/locationActions'
import { Modal, ModalHeader } from 'reactstrap';
import { confirmModalDialog } from '../Utils/reactConfirmModalDialog'
import SearchBar from '../Utils/searchBar'
import LocationTable from '../components/Locations/LocationTable'
import LocationForm from '../components/Locations/LocationForm'
class Location extends Component {
//มีการใช้ Modal ของ reactstrap ซึง่ จะตอ้ งเก็บ State การแสดง modal ไว้
state = {
modal: false,
modalTitle: ''
}
//สง่ั dispach ฟงั ก์ชนั loadLocations ดงึ เอาข้อมลู รายช่อื สถานที่
componentDidMount() {
this.props.dispatch(loadLocations())
}
render() {
const { locations, location, locationSave } = this.props
//ถ้ามี error
if (locations.isRejected) {
return <div>{locations.data}</div>
}
//debounce เปน็ การหน่วงการสง่ ตัวอกั ษรเปน็ ฟงั ก์ชนั ของ lodash ท�ำเพื่อเรียกใช้การ filter ข้อมลู
const locationSearch = debounce(term => { this.handleSearch(term) }, 500);
return (
<div>
<h4>สถานท่ี</h4>
มโี คด้ ตอ่ ดา้ นล่าง
229
WorkShop WebApplication ด้วย React : 230
<div className="form-group row"> โคด้ ต่อจากด้านบน
<div className="col-sm-6">
{/* สง่ props onSearchTermChange ให้ Component SearchBar เพ่อื filgter
โดยฝงั่ SearchBar จะน�ำไปใชก้ บั event onChange */}
<SearchBar
onSearchTermChange={locationSearch}
placeholder="ค้นหา...รหัส, ชอ่ื สถานที่" />
</div>
</div>
{/* แสดงข้อความ Loading ก่อน */}
{locations.isLoading && <div>Loading...</div>}
{/* Component LocationTable จะสง่ props ไป 4 ตัว */}
<LocationTable
data={locations.data}
buttonNew={this.handleNew}
buttonEdit={this.handleEdit}
buttonDelete={this.handleDelete}
/>
{/* เปน็ Component ส�ำหรบั แสดง Modal ของ reactstrap
ซ่ึงเราต้องควบคมุ การแสดงไว้ที่ไฟล์นี้ ถ้าท�ำแยกไฟล์จะควบคุมยากมากครบั */}
<Modal isOpen={this.state.modal} toggle={this.toggle}
className="modal-primary" autoFocus={false}>
<ModalHeader toggle={this.toggle}>{this.state.modalTitle}สถานที่</ModalHeader>
{/* เรยี กใช้งาน Component LocationForm และส่ง props ไปดว้ ย 4 ตวั */}
<LocationForm
data={location.data}
locationSave={locationSave}
onSubmit={this.handleSubmit}
onToggle={this.toggle} />
</Modal>
</div>
)
}
//ฟังกช์ ันส่งั แสดง/ปิด modal
toggle = () => {
this.setState({
modal: !this.state.modal
})
}
//ฟงั กช์ ัน filter ขอ้ มูล
handleSearch = (term) => {
this.props.dispatch(loadLocations(term))
}
//ฟงั กช์ ันสรา้ งข้อมูลใหม่โดยจะสั่งใหเ้ ปดิ Modal มีโค้ดต่อดา้ นล่าง
handleNew = () => { 230
this.props.dispatch(resetStatus())
WorkShop WebApplication ดว้ ย React : 231
this.props.location.data = [] โค้ดต่อจากดา้ นบน
this.setState({ modalTitle: 'เพ่ิม' })
this.toggle();
}
//ฟังกช์ นั แกไ้ ขขอ้ มูล และสงั่ ให้เปิด Modal โดยสง่ ข้อมลู ไปแปะ๊ ให้กับฟอรม์ ดว้ ย
handleEdit = (id) => {
this.props.dispatch(resetStatus())
this.setState({ modalTitle: 'แกไ้ ข' })
this.props.dispatch(getLocation(id)).then(() => {
this.toggle()
})
}
//ฟงั กช์ นั บนั ทึกขอ้ มลู
handleSubmit = (values) => {
this.props.dispatch(saveLocation(values)).then(() => {
if (!this.props.locationSave.isRejected) {
this.toggle()
this.props.dispatch(loadLocations())
}
})
}
//ฟังก์ชนั ลบขอ้ มูล
handleDelete = (id) => {
confirmModalDialog({
show: true,
title: 'ยนื ยันการลบ',
message: 'คณุ ตอ้ งการลบขอ้ มลู น้ใี ชห่ รอื ไม่',
confirmLabel: 'ยนื ยัน ลบทนั ที!!',
onConfirm: () => this.props.dispatch(deleteLocation(id)).then(() => {
this.props.dispatch(loadLocations())
})
})
}
}
function mapStateToProps(state) {
return {
locations: state.locationReducers.locations,
location: state.locationReducers.location,
locationDelete: state.locationReducers.locationDelete,
locationSave: state.locationReducers.locationSave
}
}
export default connect(mapStateToProps)(Location)
231
WorkShop WebApplication ด้วย React : 232
จากโค้ดจะมี Component ที่เราจะตอ้ งไปสรา้ งอกี 2 ตวั ขา้ งลา่ งนี้
import LocationTable from ‘../components/Locations/LocationTable’
import LocationForm from ‘../components/Locations/LocationForm’
Component แสดงขอ้ มลู รายช่ือสถานท่แี บบ TABLE
สรา้ งไฟล์ใหมไ่ ว้ท่ี src/components/Locations/LocationTable.js แล้วพิมพโ์ คด้ ดา้ นล่างนี้
import React, { Component } from 'react'
import { Table, Button } from 'reactstrap';
//แสดงรายชอ่ื ขอ้ มูลสถานที่ แสดงแบบ HTML TABLE
class LocationTable extends Component {
render() {
//Destructuring คา่ props ทีส่ ง่ มาจาก src/pages/Location.js
const { data, buttonNew, buttonEdit, buttonDelete } = this.props
return (
<Table striped bordered hover>
<thead>
<tr>
<th width="160" className="text-center">รหสั </th>
<th>ช่อื สถานที<่ /th>
<th width="120" className="text-center">
<Button color="success" size="sm"
onClick={buttonNew}>เพิ่มข้อมูล</Button>
</th>
</tr>
</thead>
<tbody>
{/* loop ข้อมลู ทีไ่ ดร้ ับมา */}
{data && data.map(e => {
return (
<tr key={e.id}>
<td className="text-center">{e.code}</td>
<td>{e.name}</td>
<td className="text-center">
<Button color="secondary" size="sm"
onClick={() => buttonEdit(e.id)}>แก้ไข</Button>{' '}
<Button color="danger" size="sm"
onClick={() => buttonDelete(e.id)}>ลบ</Button>
</td>
</tr>
)
})}
</tbody>
</Table>
)
}
}
export default LocationTable
232
WorkShop WebApplication ด้วย React : 233
Component ฟอรม์ กรอกขอ้ มลู สถานท ่ี
ฟอรม์ กรอกข้อมูลสถานทีจ่ ะถกู นำ� ไปใชง้ านร่วมกบั Modal ทเ่ี ราสรา้ งไวใ้ นไฟล์ src/pages/Location.js โดยให้
สรา้ งไฟล์ใหมไ่ วท้ ี่ /src/components/Locations/LocationForm.js แลว้ พิมพ์โค้ดด้านล่างนี้
import React, { Component } from 'react'
import { Button, ModalBody, ModalFooter } from 'reactstrap';
import { Field, reduxForm } from 'redux-form';
import renderField from '../../Utils/renderFields'
class LocationForm extends Component {
componentDidMount() {
//เรยี กใชฟ้ งั ก์ชนั ในการก�ำหนด value ให้กบั textbox และ control ต่างๆ
this.handleInitialize()
}
//กำ� หนดค่า value ให้กับ textbox หรอื control ตา่ งๆ ในฟอรม์
//ถา้ เปน็ HTML ธรรมดากจ็ ะก�ำหนดเป็น value="xxx" แตส่ ำ� หรบั redux-form
//ตอ้ งใช้ initialize ถ้าเป็น redux-form v.6 ต้องประกาศใช้ initialize แต่ v.7 เรยี กใชไ้ ดเ้ ลย
handleInitialize() {
let initData = {
"code": '',
"name": '',
};
//ตรวจสอบกอ่ นว่ามี data.id หรือไม่
//ถ้าไมม่ แี สดงว่าเป็นการสรา้ งรายการใหม่
//ถา้ มแี สดงว่ามีการ get ข้อมูลสถานทีจ่ ึงเป็นการปรับปรุง
if (this.props.data.id) {
initData = this.props.data
}
this.props.initialize(initData);
}
render() {
//redux-form จะมี props ทช่ี อื่ handleSubmit เพือ่ ใช้ submit คา่
const { handleSubmit, locationSave } = this.props
return (
<div>
<ModalBody>
{/* ตรวจสอบวา่ มี err หรือไม่ */}
{locationSave.isRejected && <div className="alert alert-danger">{locationSave.data}</div>}
<Field name="code" component={renderField} type="text" label="รหัส" autoFocus />
<Field name="name" component={renderField} type="text" label="ช่อื สถานที่" />
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={handleSubmit(this.onSubmit)}>บันทึก</Button>{' '}
<Button color="secondary" onClick={this.toggle}>ยกเลิก</Button>
</ModalFooter>
มีโค้ดตอ่ ดา้ นล่าง 233
</div> WorkShop WebApplication ดว้ ย React : 234
) โค้ดต่อจากดา้ นบน
}
//ฟงั ก์ชนั น้เี รยี กใช้ props ชือ่ onToggle จาก src/pages/Location.js เพ่ือปิด Modal
toggle = () => {
this.props.onToggle()
}
//ฟงั กช์ ันส่งการคา่ การ submit โดยส่งใหฟ้ ังกช์ นั ชือ่ onSubmit ทไี่ ด้จาก props
onSubmit = (values) => {
this.props.onSubmit(values);
}
}
//validate ข้อมลู กอ่ น submit
function validate(values) {
const errors = {};
if (!values.code) {
errors.code = 'จ�ำเปน็ ตอ้ งกรอกรหัส !';
}
if (!values.name) {
errors.name = 'จำ� เป็นต้องกรอกช่อื สถานที่ !';
}
return errors;
}
const form = reduxForm({
form: 'LocationForm',
validate
})
//สงั เกตุว่าไมม่ กี ารใช้ connect เลยเพราะเราไม่ได้เป็นตวั จัดการ data โดยตรง
//แตส่ ่งส่ิงตา่ งผ่าน props ทไ่ี ดจ้ าก src/pages/Location.js
export default form(LocationForm)
เสรจ็ แล้วดผู ลงานท่ี browser ได้เลยครับ ลองกรอกข้อมลู ตา่ งๆ ลงไปดว้ ยนะครับ
234
WorkShop WebApplication ดว้ ย React : 235
หน้าจอแจ้งซอ่ ม
ส�ำหรบั การแจง้ ซ่อมจะมหี นา้ จอดว้ ยกันท้งั หมด 3 หนา้ จอคอื
- หน้าจอแสดงรายการแจง้ ซอ่ ม จะแสดงรายการแจง้ ซอ่ มโดยปุม่ แกไ้ ข และป่มุ ลบ จะแสดงเฉพาะรายการแจ้ง
ซ่อมทส่ี ถานะเปน็ รอซอ่ ม หากเปลย่ี นเปน็ สถานะ ก�ำลงั ด�ำเนินการ หรอื ซอ่ มเสร็จ จะแสดงปุม่ รายละเอยี ด แทน
- หน้าจอบันทึกการเพ่มิ /แก้ไขรายการแจง้ ซ่อม โดยวนั เวลาแจง้ ซอ่ มจะถูกบนั ทึกอัตโนมัติ
- หนา้ จอแสดงรายละเอียดงานแจ้งซอ่ ม (แสดงเมอื่ คลิกปุ่ม รายละเอียด บนหนา้ จอแสดงรายการแจ้งซ่อม)
1. สรา้ งไฟล์ src/redux/reducers/workReducers.js แลว้ พมิ พโ์ คด้ ดา้ นล่างน้ี
//กำ� หนดค่าเรม่ิ ตน้ ให้ state เช่น เช็ควา่ ข้อมลู ทดี่ งึ มา error หรือไมเ่ รากจ็ ะเชค็ จาก isRejected
//ซึ่งถา้ เราไม่กำ� หนด state เรม่ิ ตน้ ก็จะไมม่ ี object ช่อื isRejected ใหเ้ รียกใชง้ าน
const initialState = {
works: { data: null, isLoading: true, isRejected: false },
work: { data: null, isLoading: true, isRejected: false },
workDelete: { success: false, isLoading: true, isRejected: false },
workSave: { data: null, isLoading: true, isRejected: false },
}
export default (state = initialState, action) => {
switch (action.type) {
//เกบ็ state การดึงข้อมูลรายการแจ้งซ่อม
case 'LOAD_WORKS_PENDING':
return { ...state, works: { data: null, isLoading: true, isRejected: false } }
case 'LOAD_WORKS_SUCCESS':
return { ...state, works: { data: action.payload, isLoading: false, isRejected: false } }
case 'LOAD_WORKS_REJECTED':
return { ...state, works: { data: action.payload, isLoading: false, isRejected: true } }
มีโค้ดตอ่ ด้านล่าง
235
WorkShop WebApplication ดว้ ย React : 236
//เก็บ state การดงึ ข้อมลู รายการแจ้งซ่อม id ท่ีส่งไป โค้ดตอ่ จากด้านบน
case 'LOAD_WORK_PENDING':
return { ...state, work: { data: null, isLoading: true, isRejected: false } }
case 'LOAD_WORK_SUCCESS':
return { ...state, work: { data: action.payload, isLoading: false, isRejected: false } }
case 'LOAD_WORK_REJECTED':
return { ...state, work: { data: action.payload, isLoading: false, isRejected: true } }
//เกบ็ state การลบขอ้ มลู รายการแจ้งซอ่ ม
case 'DELETE_WORK_SUCCESS':
return { ...state, workDelete: { data: true, isLoading: false, isRejected: false } }
case 'DELETE_WORK_REJECTED':
return { ...state, workDelete: { data: action.payload, isLoading: false, isRejected: true } }
//เก็บ state สถานะการบันทกึ ขอ้ มูลแจง้ ซ่อม
case 'SAVE_WORK_SUCCESS':
return { ...state, workSave: { data: null, isLoading: false, isRejected: false} }
case 'SAVE_WORK_REJECTED':
return { ...state, workSave: { data: action.payload, isLoading: false, isRejected: true} }
default:
return state
}
}
2. แกไ้ ขไฟล์ src/redux/reducers/index.js เป็นโคด้ ดา้ นล่างนี้
import { combineReducers } from 'redux'
//redux-form จะทำ� การเกบ็ state และมี reducer ในตัวของมนั เอง
//ดงั นนั้ เวลาเราจะใชง้ าน redux-form เราต้องท�ำเหมือนว่ามันคอื reducer ตัวหน่งึ ดว้ ยครบั
import { reducer as formReducer } from 'redux-form'
import authReducers from './authReducers'
import userReducers from './userReducers'
import locationReducers from './locationReducers'
import workReducers from './workReducers'
const rootReducers = combineReducers({
form: formReducer, //ก�ำหนดช่อื reducer ไวว้ ่าชื่อ form นะครบั ตามคำ� แนะนำ� ของ redux-form
authReducers,
userReducers,
locationReducers,
workReducers,
})
export default rootReducers
236
WorkShop WebApplication ดว้ ย React : 237
3. สรา้ งไฟล์ src/redux/actions/workActions.js แล้วพมิ พด์ า้ นลา่ งน้ี
import axios from 'axios'
import config from '../../configure'
//ดึงเอา url ทีใ่ ช้ fetch data มาเก็บไว้ใน BASE_URL
const BASE_URL = config.BASE_URL
//ฟงั ก์ชันดึงขอ้ มลู รายการแจ้งซอ่ มโดยสง่ query ชอื่ repair เพ่อื นำ� ไป where
//โดยถ้าหากเป็น 0 จะแสดงทกุ รายการ แตถ่ า้ repair = 1 จะแสดงเฉพาะรายการ
//ท่มี ีสถานะเปน็ รอซอ่ ม และกำ� ลังดำ� เนินการ เพือน�ำรายการน้ไี ปแสดงในหนา้ จอ งานรอซอ่ ม
//ซ่งึ จะมองเหน็ ไดเ้ ฉพาะสิทธผ์ิ ดู้ ูแลระเบบ
export const loadWorks = (repair = 0) => {
return (dispatch) => {
//ก่อนดึงข้อมลู สั่ง dispatch ให้ reducer รู้วา่ กอ่ นเพ่ือจะแสดง loading
dispatch({ type: 'LOAD_WORKS_PENDING' })
return axios.get(`${BASE_URL}/works?repair=${repair}`, {
//ต้องสง่ heder ชื่อ authorization โดยสง่ token เขาไป
//เพ่อื บอกให้ server ร้วู า่ เราได้ signin ถูกต้องแลว้
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
//เมอ่ื ขอ้ มลู สง่ กลบั มาก็ส่ัง dispatch ให้ reducer รพู้ ร้อมสง่ payload
//เนื่องจากเราใช้ axios แทน fetch ดังน้ันขอ้ มลู ทส่ี ง่ มาจะอย่ใู น object ชื่อ data
//ทมี่ ี Array อยขู่ า้ งใน ดังนั้นน�ำไป data.map ได้เลยครับ
dispatch({ type: 'LOAD_WORKS_SUCCESS', payload: results.data })
}).catch(err => {
//กรณี error
dispatch({ type: 'LOAD_WORKS_REJECTED', payload: err.message })
})
}
}
//ฟงั ก์ชันดึงข้อมลู งานแจ้งซ่อมตาม id ทีส่ ง่
export const getWork = (id) => {
return (dispatch) => {
dispatch({ type: 'LOAD_WORK_PENDING' })
return axios.get(`${BASE_URL}/works/${id}`, {
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
dispatch({ type: 'LOAD_WORK_SUCCESS', payload: results.data })
}).catch(err => {
dispatch({ type: 'LOAD_WORK_REJECTED', payload: err.message })
})
}
}
//ฟังกช์ นั บนั ทกึ ขอ้ มลู แจ้งซ่อม โดยเราจะเชค็ วา่ เปน็ การเพิ่มข้อมูลใหม่ หรือปรับปรงุ ข้อมูล
export const saveWork = (values) => {
//ถา้ มี values.id แสดงวา่ เป็นการบันทึกการปรบั ปรงุ ขอ้ มูลจงึ ตอ้ งสง่ method put
//put จะไป match กับ route ฝงั่ server คือ app.put('/works/:id', requireAuth, works.update)
//แต่ถา้ ไมใ่ ช่ให้ส่ง method post เพือ่ เพม่ิ ขอ้ มลู ใหม่
//post จะไป match กับ route ฝ่งั server คอื app.post('/works', requireAuth, works.create)
มโี ค้ดต่อดา้ นลา่ ง
237
let _id = '' WorkShop WebApplication ดว้ ย React : 238
let _method = 'post' โค้ดตอ่ จากดา้ นบน
if (values.id) {
_id = values.id
_method = 'put'
}
return (dispatch) => {
//รูปแบบการใช้ axios อีกรูปแบบในการจะบุ method ทตี่ ้องการ
//ตอ้ งส่ง heder ช่อื authorization โดยสง่ token เขาไปดว้ ยครับ
return axios({
method: _method,
url: `${BASE_URL}/works/${_id}`,
data: values,
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
dispatch({ type: 'SAVE_WORK_SUCCESS' })
}).catch(err => {
dispatch({ type: 'SAVE_WORKS_REJECTED', payload: err.message })
})
}
}
//ฟงั กช์ ันบนั ทึกข้อมูลการปฏิบตั ิงานซ่อม ซ่งึ จะใชง้ านส�ำหรับผู้ดูแลระบบเทา่ นน้ั
export const saveRepair = (values) => {
return (dispatch) => {
return axios({
method: 'put',
url: `${BASE_URL}/works/${values.id}/repair`,
data: values,
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
dispatch({ type: 'SAVE_WORK_SUCCESS' })
}).catch(err => {
dispatch({ type: 'SAVE_WORKS_REJECTED', payload: err.message })
})
}
}
//ฟังก์ชนั ลบข้อมูลแจง้ ซ่อมตาม id ที่ส่งเขา้ มา
export const deleteWork = (id) => {
return (dispatch) => {
return axios.delete(`${BASE_URL}/works/${id}`, {
headers: { authorization: localStorage.getItem('token') }
}).then(results => {
dispatch({ type: 'DELETE_WORKS_SUCCESS' })
}).catch(err => {
dispatch({ type: 'DELETE_WORKS_REJECTED', payload: err.message })
})
}
}
238
WorkShop WebApplication ดว้ ย React : 239
4. แกไ้ ขไฟล์ src/pages/Work.js เป็นโคด้ ดา้ นล่างนี้
import React, { Component } from 'react'
import { connect } from 'react-redux'
//ใช้ความสามารถของ browserHistory ในการสงั่ เปลยี่ น url ไปยงั เพจที่ต้องการ
import { browserHistory } from 'react-router'
import { loadWorks, deleteWork } from '../redux/actions/workActions'
import { confirmModalDialog } from '../Utils/reactConfirmModalDialog'
import WorkTable from '../components/Works/WorkTable'
class Work extends Component {
//สั่ง dispach ฟงั ก์ชัน loadWorks
componentDidMount() {
this.props.dispatch(loadWorks())
}
render() {
const { works } = this.props
//แสดงขอ้ ความ error หาก isRejected เป็น true
if (works.isRejected) {
return <div>{works.data}</div>
}
//แสดงข้อความ Loading... หาก isLoading เป็น true
if (works.isLoading) {
return <div>Loading...</div>
}
return (
<div>
<h4>รายการแจ้งซอ่ ม</h4>
{/* Component WorkTable จะส่ง props ไป 4 ตัว */}
<WorkTable
data={works.data}
buttonEdit={this.handleEdit}
buttonDelete={this.handleDelete}
buttonDetail={this.handleDetail}
/>
</div>
)
}
//ฟงั ก์ชนั แสดงหนา้ จอรายละเอียดงานซ่อม
//โดยใช้ browserHistory สงั่ เปลย่ี นไปยัง route ทตี่ อ้ งการ
handleDetail = (id) => {
browserHistory.push(`/work/detail/${id}`)
}
มีโคด้ ตอ่ ดา้ นลา่ ง
239
//ฟังก์ชันแสดงหน้าจอกรอกข้อมลู งานซ่อมในโหลดแก้ไข WorkShop WebApplication ดว้ ย React : 240
//โดยใช้ browserHistory สัง่ เปลี่ยนไปยงั route ที่ตอ้ งการ โค้ดต่อจากดา้ นบน
handleEdit = (id) => {
browserHistory.push(`/work/update/${id}`)
}
//ฟังกช์ ันลบรายการแจ้งซอ่ ม
handleDelete = (id) => {
confirmModalDialog({
show: true,
title: 'ยนื ยนั การลบ',
message: 'คณุ ต้องการลบขอ้ มูลนใ้ี ชห่ รือไม่',
confirmLabel: 'ยืนยนั ลบทันท!ี !',
onConfirm: () => this.props.dispatch(deleteWork(id)).then(() => {
this.props.dispatch(loadWorks())
})
})
}
}
//map ขอ้ มลู จาก reducer เขา้ props
function mapStateToProps(state) {
return {
works: state.workReducers.works
}
}
//export component และ connect เข้ากับ redux
export default connect(mapStateToProps)(Work)
จากโคด้ จะมี Component ท่ีเราจะต้องไปสร้างอกี 1 ตัวขา้ งล่างน้ี มโี ค้ดต่อดา้ นล่าง
import WorkTable from ‘../components/Works/WorkTable’ 240
Component แสดงรายการแจ้งซ่อมแบบ TABLE
สรา้ งไฟลใ์ หม่ไว้ท่ี src/components/Works/WorkTable.js แล้วพมิ พโ์ ค้ดดา้ นล่างนี้
import React, { Component } from 'react'
import { Link } from 'react-router'
import { Table, Button } from 'reactstrap'
import moment from 'moment'
class WorkTable extends Component {
//ฟังกช์ ันส�ำหรับแสดงปมุ่ แกไ้ ข,ลบ หรือปมุ่ รายละเอยี ด
renderButton(data) {
if (data.status > 0) {
//ถ้าสถานะไมใ่ ช่ รอซ่อม (กำ� ลังด�ำเนินการ หรือซ่อมเสรจ็ )
WorkShop WebApplication ด้วย React : 241
return ( โคด้ ต่อจากด้านบน
<Button color="info" size="sm"
onClick={() => this.props.buttonDetail(data.id)}>รายละเอยี ด</Button>
)
} else {
//ถา้ เปน็ สถานะ รอซ่อม
return (
<div>
<Button color="secondary" size="sm"
onClick={() => this.props.buttonEdit(data.id)}>แก้ไข</Button>{' '}
<Button color="danger" size="sm"
onClick={() => this.props.buttonDelete(data.id)}>ลบ</Button>
</div>
)
}
}
render() {
//Destructuring ค่า props ท่ไี ดจ้ าก src/pages/Work.js
const { data } = this.props
return (
<Table striped bordered hover>
<thead>
<tr>
<th width="180" className="text-center">วนั ท่แี จง้ </th>
<th>ปญั หาท่ีแจ้ง</th>
<th width="140" className="text-center">สถานะ</th>
<th>สถานที<่ /th>
<th width="120" className="text-center">
<Link to={"work/new"}><Button color="success" size="sm">เพิม่ ขอ้ มูล</Button></Link>
</th>
</tr>
</thead>
<tbody>
{/* loop ข้อมูลทส่ี ง่ มาจาก props */}
{data && data.map(e => {
let statusText = 'รอซ่อม'
if (e.status === 1) {
statusText = 'กำ� ลังดำ� เนนิ การ'
} else if (e.status === 2) {
statusText = 'ซอ่ มเสรจ็ '
}
return (
<tr key={e.id}>
{/* ส่วนน้ใี ช้ moment เพือ่ แปลงวนั ท่ใี ห้เปน็ รูปแบบทต่ี อ้ งการ */}
<td e="text-center">{moment(e.doc_date).format('YYYY-MM-DD')} {e.doc_time}</td>
<td>{e.detail}</td>
<td className="text-center">{statusText}</td>
<td>{e.location_name}</td>
<td className="text-center">
{/* สงั่ แสดงปุม่ กด แก้ไข,ลบ หรือป่มุ รายละเอยี ด */}
มีโค้ดตอ่ ดา้ นลา่ ง
241
{this.renderButton(e)} WorkShop WebApplication ดว้ ย React : 242
</td> โค้ดต่อจากดา้ นบน
</tr>
)
})}
</tbody>
</Table>
)
}
}
export default WorkTable
สรา้ งฟอร์มบันทึกงานแจง้ ซ่อม 242
สรา้ งไฟล์ใหมไ่ ว้ท่ี src/pages/WorkFormUser.js แล้วพมิ พโ์ คด้ ดา้ นลา่ งนี้
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { browserHistory } from 'react-router'
import { Field, reduxForm } from 'redux-form';
import moment from 'moment'
import { Button } from 'reactstrap';
import { getWork, saveWork } from '../redux/actions/workActions'
import renderField from '../Utils/renderFields'
//สร้างฟลิ แบบ dropdown สำ� หรบั ใช้เลือกสถานท่ี
import renderLocation from '../Utils/renderLocations'
class WorkFormUser extends Component {
//โหลดขอ้ มลู ใส่ในฟอร์ม โดยเชค็ กอ่ นวา่ มกี ารส่ง id มาหรือไม่
//ถ้าส่ง id มาใหส้ งั่ dispatch get เอาขอ้ มลู งานซอ่ ม
componentDidMount() {
const workId = (this.props.params.id) ? this.props.params.id : 0
this.props.dispatch(getWork(workId)).then(() => {
this.handleInitialize()
})
}
handleInitialize() {
//การก�ำหนดคา่ default ใหก้ ับ dropdown (กำ� หนดหลอกเปน็ อะไรก็ได้ครบั )
let initData = {
"location_id": "foo",
};
if (this.props.work.data) {
initData = this.props.work.data
}
this.props.initialize(initData);
} มโี คด้ ต่อด้านลา่ ง
render() { WorkShop WebApplication ดว้ ย React : 243
const { handleSubmit, work } = this.props โคด้ ต่อจากด้านบน
const { data } = work
if (work.isRejected) {
return <div className="alert alert-danger">Error: {data}</div>
}
if (work.isLoading) {
return <div>Loading...</div>
}
//แปลงวนั ท่ีและเวลาเปน็ รูปแบบทต่ี อ้ งการไวค้ รบั
const datetime = (data) ? `${moment(data.doc_date).format('YYYY-MM-DD')} ${data.doc_time} ` : ''
//ก�ำหนดส่วนหวั หนา้ จอ โดยเชค็ ว่าถา้ data มขี อ้ มูลแสดงวา่ เแปน็ การแก้ไขรายการ
const caption = (data) ? 'แกไ้ ขรายการแจ้งซอ่ ม' : 'เพิ่มรายการแจ้งซอ่ ม'
return (
<div>
<h4>{caption}</h4>
<form>
<div className="form-group row">
<label className="col-sm-3 col-form-label">วนั ที่แจ้ง</label>
<div className="col-sm-9">
<input type="text" readOnly className="form-control-plaintext" value={datetime}/>
</div>
</div>
<Field name="location_id" component={renderLocation} label="สถานท่"ี />
<Field name="detail" component={renderField} label="ปัญหา" textarea />
<Field name="phone" component={renderField} label="โทรศพั ทต์ ิดต่อ" />
<hr />
<Button color="primary" onClick={handleSubmit(this.onSubmit)}>บันทกึ </Button>{' '}
<Button color="secondary" onClick={browserHistory.goBack}>ยกเลิก</Button>
</form>
</div>
)
}
//ฟังกช์ ันบันทกึ ขอ้่ มลู
onSubmit = (values) => {
//เม่ือบันทึกขอ้ มลู เสรจ็ สังให้ไปยงั route /work
this.props.dispatch(saveWork(values)).then(() => {
browserHistory.push('/work')
})
}
}
//validate ขอ้ มูล จดุ ส�ำคญั คือ values.location_id
//เพราะการ validate dropdown ต้องทำ� ขอ้ มูลหลอกไวก้ อ่ นครบั
function validate(values) {
const errors = {};
มโี ค้ดต่อด้านล่าง
243
if (values.location_id === "foo") { WorkShop WebApplication ดว้ ย React : 244
errors.location_id = 'ตอ้ งเลอื กสถานท่ี'; โค้ดต่อจากดา้ นบน
}
if (!values.detail) {
errors.detail = 'จ�ำเป็นต้องกรอกรายละเอยี ด';
}
if (!values.phone) {
errors.phone = 'จ�ำเป็นตอ้ งกรอกโทรศพั ท์';
}
return errors;
}
const form = reduxForm({
form: 'workUser',
validate
})
function mapStateToProps(state) {
return {
work: state.workReducers.work
}
}
export default connect(mapStateToProps)(form(WorkFormUser))
จากโค้ดจะมี Component ที่เราจะตอ้ งไปสร้างอกี 1 ตวั ข้างลา่ งน้ี มโี ค้ดตอ่ ด้านล่าง
import renderLocation from ‘../Utils/renderLocations’’ 244
Component แสดง DropDown เลือกรายช่อื สถานท ี่
สร้างไฟลใ์ หม่ไวท้ ่ี src/Utils/renderLocations.js แลว้ พมิ พโ์ คด้ ด้านล่างนี้
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { loadLocations } from '../redux/actions/locationActions'
//component น้ีสร้างคล้ายๆ กับ renderFields.js เพยี งแตแ่ สดงเป็น DropDown
class renderLocations extends Component {
//สั่งโหลดข้อมลู สถานท่ี
componentDidMount() {
this.props.dispatch(loadLocations())
}
render() {
const { input, label, meta: { touched, error } } = this.props
const { locations } = this.props
return (
<div className="form-group row">
<label className="col-sm-3 col-form-label">{label}</label>
<div className="col-sm-6">
WorkShop WebApplication ด้วย React : 245
โค้ดตอ่ จากดา้ นบน
<select {...input} className="form-control">
<option value="foo" disabled>===เลือกสถานท=ี่ ==</option>
{locations.data && locations.data.map(e => {
return (
<option key={e.id} value={e.id}>{e.code}@{e.name}</option>
)
})}
</select>
{touched && error && <small className="text-danger">{error}</small>}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
locations: state.locationReducers.locations
}
}
export default connect(mapStateToProps)(renderLocations)
สรา้ งหน้าจอแสดงรายละเอยี ดงานซ่อม
สรา้ งไฟล์ใหม่ src/pages/WorkDetail.js แลว้ พิมพโ์ คด้ ด้านล่างนล้ี งไปครับ
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { browserHistory } from 'react-router'
import moment from 'moment'
import { Button } from 'reactstrap';
import { getWork } from '../redux/actions/workActions'
import { getLocation } from '../redux/actions/locationActions'
class WorkUser extends Component {
//โหลดข้อมูลงานซอ่ ม และเพ่อื ความชวั ร์เราจึงเชค็ ก่อนว่ามี id ส่งเข้ามาหรอื ไม่
componentDidMount() {
const workId = (this.props.params.id) ? this.props.params.id : 0
this.props.dispatch(getWork(workId)).then(() => {
//เม่อื โหลดข้อมูลเสรจ็ ก็จะเรยี กข้อมลู สถานท่ีตาม id ท่ีได้จากงานซอ่ มเพอื่ แสดงชือ่ ของสถานท่ี
this.props.dispatch(getLocation(this.props.work.data.location_id))
})
}
มโี ค้ดต่อด้านลา่ ง
245
render() { WorkShop WebApplication ดว้ ย React : 246
const { work, location } = this.props โคด้ ตอ่ จากด้านบน
const { data } = work
if (work.isRejected) {
return <div className="alert alert-danger">Error: {data}</div>
}
if (location.isRejected) {
return <div className="alert alert-danger">Error: {location.data}</div>
}
if (work.isLoading || location.isLoading) {
return <div>Loading...</div>
}
//จดั การวนั ที่ เวลา ใหอ้ ยู่ในรูปแบบทตี่ อ้ งการ
const datetime = `${moment(data.doc_date).format('YYYY-MM-DD')} ${data.doc_time} `
return (
<div>
{/* แปลงสถานะเพ่อื บอกให้รวู้ า่ อยูใ่ นขน้ั ตอนไหน */}
<h4>รายละเอียดแจง้ ซอ่ ม : {(data.status === 1) ? 'ก�ำลังด�ำเนนิ การ' : 'ซ่อมเสรจ็ '}</h4>
<form>
{/* รูปแบบการจัดวาง layout กใ็ ช้ class ของ bootstrap 4 ครับ */}
<div className="form-group row">
<label className="col-sm-3 col-form-label">วันทีแ่ จง้ </label>
<div className="col-sm-9">
<input type="text" readOnly className="form-control-plaintext"
value={datetime} />
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label">สถานที่</label>
<div className="col-sm-9">
<input type="text" readOnly className="form-control-plaintext"
value={location.data.name} />
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label">ปัญหา</label>
<div className="col-sm-9">
<input type="text" readOnly className="form-control-plaintext"
value={data.detail} />
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label">โทรศัพทต์ ดิ ต่อ</label>
<div className="col-sm-9">
<input type="text" readOnly className="form-control-plaintext"
value={data.phone} />
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label">ผลการซอ่ ม</label>
มโี คด้ ตอ่ ดา้ นลา่ ง
246
WorkShop WebApplication ดว้ ย React : 247
<div className="col-sm-9"> โค้ดต่อจากดา้ นบน
{/* การแสดงข้อความถ้าเป็นคา่ Null จะมีปญั หา จึงต้องตรวจสอบด้วยนะครบั
ถ้าเป็นคา่ Null กใ็ หแ้ สดงเป็นค่าว่างๆ แตท่ ่ี control ข้างบนไมม่ กี ารตรวจสอบ
เพราะถกู บังคับใหก้ รอกข้อมลู มาตั้งแตต่ ้นแล้ว ดงั นนั้ มันกจ็ ะไม่ใช่ค่า Null อย่แู ลว้ ครบั */}
<input type="text" readOnly className="form-control-plaintext"
value={data.work_detail || ''} />
</div>
</div>
<hr />
<Button color="secondary" onClick={browserHistory.goBack}>กลบั </Button>
</form>
</div>
)
}
}
function mapStateToProps(state) {
return {
work: state.workReducers.work,
location: state.locationReducers.location
}
}
export default connect(mapStateToProps)(WorkUser)
ถึงตอนน้เี ราก็จะสามารถแสดงรายการแจง้ ซอ่ ม และดูเหมอื นวา่ จะสามารถ เพม่ิ หรอื แกไ้ ขงานซ่อมไดแ้ ลว้ แตค่ วาม
จรงิ เรายงั ไมส่ ามารถแสดงหน้าจอเหล่านน้ั ได้ เนอื่ งจากเรายังไม่ไดก้ ำ� หนด route ให้เปลย่ี นไปหนา้ จอเหลา่ น้นั
ก�ำหนด Route สำ� หรบั หนา้ จอฟอรม์ บันทกึ แจ้งซอ่ ม
แก้ไขไฟล์ src/routes.js เป็นโค้ดด้านลา่ งนี้ครับ
import App from './pages/App'
import Home from './pages/Home'
import RequireAuth from './pages/Auth/Authentication'
import RequireAuthAdmin from './pages/Auth/AuthenticationAdmin'
import Signin from './pages/Auth/Signin'
import Signout from './pages/Auth/Signout'
import Work from './pages/Work'
import WorkRepair from './pages/WorkRepair'
import User from './pages/User'
import Location from './pages/Location'
import WorkFormUser from './pages/WorkFormUser'
import WorkDetail from './pages/WorkDetail'
const routes = [{ มีโค้ดต่อดา้ นล่าง
path: '/',
component: App,
247
WorkShop WebApplication ดว้ ย React : 248
indexRoute: { component: Home }, โค้ดต่อจากดา้ นบน
childRoutes: [
{ path: 'signin', component: Signin },
{ path: 'signout', component: Signout },
{ path: 'work', component: RequireAuth(Work) },
{ path: 'work/new', component: RequireAuth(WorkFormUser) },
{ path: 'work/update/:id', component: RequireAuth(WorkFormUser) },
{ path: 'work/detail/:id', component: RequireAuth(WorkDetail) },
{ path: 'workrepair', component: RequireAuth(RequireAuthAdmin(WorkRepair)) },
{ path: 'user', component: RequireAuth(RequireAuthAdmin(User))},
{ path: 'location', component: RequireAuth(RequireAuthAdmin(Location))},
]
}]
export default routes
ถึงตอนนี้เราก็สามารถบนั ทกึ งานแจง้ ซ้อมได้เรยี บรอ้ ยแล้วครบั แต่ส่ิงหน่ึงท่เี รายังไมส่ ามารถทำ� ไดค้ อื การแสดง
หนา้ จอรายละเอียดแจ้งซ่อม เหมือนภาพด้านล่าง
สาเหตกุ เ็ พราะการท่ีปุ่ม รายละเอยี ด จะแสดงก็ต่อเมอ่ื ไม่ใช่สถานะรอซ่อม ซึ่งจะเกิดขน้ึ ไดก้ ต็ ่อเมื่อผใู้ ชง้ านสทิ ธ์ผิ ู้
ดแู ลระบบท�ำการบันทึกงานซอ่ ม (หรอื เราเข้าไปแก้สถานะเองในฐานขอ้ มูลก็ไดน้ ะครบั )
248
WorkShop WebApplication ด้วย React : 249
หนา้ จองานซอ่ ม
แสดงงานซ่อม แสดงเฉพาะรายการแจ้งซอ่ มท่ีสถานะเป็น รอซอ่ ม หรอื กำ� ลังด�ำเนินการ ซง่ึ หน้าจอนจ้ี ะแสดงให้เหน็
เฉพาะผ้ใู ชง้ านประเภท ผู้ดแู ลระบบ
ส�ำหรบั หนา้ จอนเ้ี ราไมต่ ้องสร้าง Action และ Reducer เนอ่ื งจากใชต้ ัวเดยี วกบั หน้าจอแจง้
1. แกไ้ ขไฟล์ src/pages/WorkRepair.js เปน็ โคด้ ดา้ นลา่ งนี้
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { browserHistory } from 'react-router'
import { loadWorks } from '../redux/actions/workActions'
import WorkTableRepair from '../components/Works/WorkTableRepair';
class WorkRepair extends Component {
//ส่ัง dispach ฟังกช์ ัน loadWorks โดยสง่ ค่า parameter ไปเป็น 1
//เพ่ือให้แสดงเฉพาะรายการท่ียงั ซอ่ มไม่เสร็จ
componentDidMount() {
this.props.dispatch(loadWorks(1))
}
render() {
const { works } = this.props
//แสดงข้อความ error หาก isRejected เปน็ true
if (works.isRejected) {
return <div>{works.data}</div>
}
return (
<div>
<h4>งานซ่อม</h4>
{works.isLoading && <div>Loading...</div>}
{/* Component WorkTableRepair จะสง่ props ไป 2 ตัว */}
มโี คด้ ตอ่ ด้านลา่ ง
249
<WorkTableRepair WorkShop WebApplication ดว้ ย React : 250
data={works.data} โคด้ ตอ่ จากดา้ นบน
buttonRepair={this.handleRepair}
/>
</div>
)
}
//ฟังกช์ นั แสดงหน้าจอบันทกึ การปฏิบตั ิงานซอ่ ม
//โดยใช้ browserHistory สงั่ เปลยี่ นไปยงั route ท่ีตอ้ งการ
handleRepair = (id) => {
browserHistory.push(`/work/repair/${id}`)
}
}
//map ขอ้ มลู จาก reducer เขา้ props
function mapStateToProps(state) {
return {
works: state.workReducers.works
}
}
//export component และ connect เข้ากับ redux
export default connect(mapStateToProps)(WorkRepair)
จากโค้ดจะมี Component ท่ีเราจะตอ้ งไปสร้างอกี 1 ตัวข้างลา่ งนี้
import WorkTableRepair from ‘../components/Works/WorkTableRepair’;
Component แสดงรายการงานซอ่ มแบบ TABLE
สรา้ งไฟลใ์ หม่ไว้ที่ src/components/Works/WorkTableRepair.js แลว้ พิมพโ์ ค้ดดา้ นล่างนี้
import React, { Component } from 'react'
import { Table, Button } from 'reactstrap'
import moment from 'moment'
class WorkTableRepair extends Component { มีโคด้ ต่อด้านล่าง
render() { 250
//Destructuring ค่า props ที่ได้จาก src/pages/WorkRpair.js
const { data, buttonRepair } = this.props
return (
<Table striped bordered hover>
<thead>
<tr>
<th width="180" className="text-center">วนั ทแี่ จง้ </th>
<th>ปญั หาทีแ่ จ้ง</th>
<th width="140" className="text-center">สถานะ</th>
<th>สถานท่<ี /th>
<th width="120" className="text-center"></th>