Commit a45fda6a authored by Piyaphorn Arphornsri's avatar Piyaphorn Arphornsri

web สมบูรณ์

parent cfaa58ab
\chapter{บทนำ}
\section{ที่มาและเหตุผล }
เนื่องจากปัจจุบัน การดำเนินธุรกิจร้านเสริมสวยที่ให้บริการเสริมความงาม เช่น การทำผม ตัดผม ออกแบบทรงผม อบไอน้ำ เวลาผู้ใช้บริการมาใช้บริการโดยจะมาที่ร้านเลยโดยที่ไม่จองคิวพบว่าร้านที่มาใช้บริการมีลูกค้าเป็นจำนวนมาก อาจจะทำให้ต้องรอคิวนานหรือต้องเสียเวลามาใช้บริการในวันอื่นบางครั้งผู้ใช้บริการมีเบอร์ของร้านเสริมสวยก็จะโทรมา สอบถามคิวและจองคิว แต่ช่างติดทำผมให้ลูกค้าท่านอื่นก็ไม่สามารถรับโทรศัพท์ได้ผู้พัฒนาจึงมีแนวคิดว่าจะทำระบบการจอง คิวร้านเสริมสวยขึ้น เพื่อแก้ปัญหาการรอคิวนานและให้มีความทันสมัยตลอดจนสามารถรองรับการแสดงผลบนอุปกรณ์ สมาร์ทโฟนในปัจจุบัน ทำให้สามารถจองคิวหรือติดต่อสื่อสารในเรื่องของการจองคิวทำผมกับทางร้านได้สะดวกมากยิ่งขึ้น
เนื่องจากปัจจุบัน การดำเนินธุรกิจร้านเสริมสวยที่ให้บริการเสริมความงาม เช่น การทำผม ตัดผม ออกแบบทรงผม อบไอน้ำ โดยปกติผู้ใช้บริการจะมาที่ร้านเลยโดยที่ไม่จองคิว และพบว่าร้านที่มาใช้บริการมีลูกค้าเป็นจำนวนมาก จึงทำให้ผู้ใช้บริการต้องรอคิวนานหรือต้องเสียเวลามาใช้บริการในวันอื่น บางครั้งผู้ใช้บริการโทรมาสอบถามคิวและจองคิว แต่ช่างกำลังทำผมให้ลูกค้าท่านอื่นไม่สามารถรับโทรศัพท์ได้ ผู้พัฒนาจึงมีแนวคิดจะทำระบบการจองคิวร้านเสริมสวยขึ้น เพื่อแก้ปัญหาการรอคิวนานและสามารถรองรับการแสดงผลบนอุปกรณ์สมาร์ทโฟนในปัจจุบัน ทำให้สามารถจองคิวหรือติดต่อสื่อสารในเรื่องของการจองคิวทำผมกับทางร้านได้สะดวกมากยิ่งขึ้น
แนวทางการแก้ปัญหา จัดทำการพัฒนาเป็นเว็บแอปพิเคชัน ระบบจองคิวร้านเสริมสวยที่ถูกพัฒนาขึ้นเป็นเว็บแอปพิเคชัน จะช่วยเพิ่มระเบียบในการจัดการจองคิวให้เป็นระบบ ลดขั้นตอนการดำเนินงานที่ซับซ้อน ลดระยะเวลาในการดำเนินงาน ลดความผิดพลาดที่จะเกิดขึ้นในขั้นตอนการดำเนินงาน และช่วยเพิ่มประสิทธิภาพในการทำงาน
จากที่กล่าวมาข้างต้น ผู้พัฒนาจึงได้พัฒนาเว็บแอปพิเคชัน ระบบจองคิวร้านเสริมสวยขึ้นซึ่งช่วยในการจัดการจองคิวให้เป็นระบบ ลดขั้นตอนการดำเนินงานที่ซับซ้อน ลดระยะเวลาในการดำเนินงาน ลดความผิดพลาดที่จะเกิดขึ้นในขั้นตอนการดำเนินงาน และช่วยเพิ่มประสิทธิภาพในการทำงาน
\section{วัตถุประสงค์}
\begin{enumerate}
\item เพื่อออกแบบและพัฒนาเว็บแอปพลิเคชั่น จองคิวร้านเสริมสวย
\item เพื่อออกแบบและพัฒนาเว็บแอปพลิเคชั่นจองคิวร้านเสริมสวย
\item เพื่อแก้ปัญหาการรอคิวร้านเสริมสวย
\end{enumerate}
......@@ -69,7 +69,7 @@
\subsection{ซอฟต์แวร์ (Software)}
\begin{enumerate}
\item ReactJS เป็น JavaScript Framework โดยมีชุดคำสั่งและไลบารี่ (Library) ให้ใช้งานมากมาย
\item ReactJS เป็น JavaScript Framework โดยมีชุดคำสั่งและไลบารี่ (Library) ให้ใช้งานเป็นจำนวนมาก
\item Node.js คือ Cross Platform Runtime Environment หรือเรียกอีกอย่างว่า Backend Framework ใช้สำหรับเป็นเว็บเซิฟเวอร์ (Web Server) ซึ่งเขียนด้วยภาษา JavaScript
\item JavaScript เป็น ภาษาที่ใช้ในการพัฒนาเว็บ Environment)
\item Xampp เป็นโปรแกรม Apache web server ไว้จำลอง web server เพื่อทดสอบระบบระหว่างพัฒนา
......@@ -80,7 +80,7 @@
\newpage
\subsection{แผนการดำเนินการ}
ในการสร้างระบบแนะนำสถานที่ท่องเที่ยวในจังหวัดอุบลราชธานี ผู้พัฒนาได้แบ่งขั้นตอนการดำเนินงานไว้ด้วยกัน 8 ขั้นตอน ดังตารางที่ \ref{tab:ganttchart}
ในการสร้างระบบจองคิวร้านเสริมสวย ผู้พัฒนาได้แบ่งขั้นตอนการดำเนินงานไว้ด้วยกัน 8 ขั้นตอน ดังตารางที่ \ref{tab:ganttchart}
%\begin{landscape}
%\sffamily
......
\chapter{ทฤษฎีที่เกี่ยวข้อง}
ในบทนี้จะกล่าวถึงรายละเอียดเกี่ยวกับทฤษฎีและงานวิจัยที่เกี่ยวของกับการพัฒนาระบบจองคิวร้านเสริมสวย
โดยแบ่งเนื้อหาออกเป็น 2 ส่วน ได้แก่ ส่วนที่หนึ่งเป็นเนื้อหาพื้นฐานเกี่ยวกับทฤษฎีการเขียนโปรแกรมและเทคโนโลยีที่นำมาใช้ในการพัฒนาในหัวข้อที่ 2.1 - 2.6
ได้แก่ ความรู้พื้นฐานเกี่ยวกับ React Node.js JavaScript MySQL Visual studio code Google maps API
และในส่วนที่สองเป็นเนื้อหาเกี่ยวกับเว็ปแอพพลิเคชันที่เกี่ยวข้องกับโครงงานนี้เว็บแอพพลิเคชัน Gowabi
โดยแบ่งเนื้อหาออกเป็น 2 ส่วน ได้แก่ ส่วนที่หนึ่งเป็นเนื้อหาพื้นฐานเกี่ยวกับทฤษฎีการเขียนโปรแกรมและเทคโนโลยีที่นำมาใช้ในการพัฒนา
ได้แก่ ความรู้พื้นฐานเกี่ยวกับ React, Node.js, JavaScript, MySQL, Visual studio code และ Google maps API
\section{ความรู้พื้นฐานเกี่ยวกับ React}
React \cite{bib2} เป็น JavaScript Library ที่ถูกสร้างโดย Facebook ซึ่ง React ทำหน้าที่เป็นเพียง User Interface (UI) ที่สร้างมาจากพื้นฐานแนวความคิดแบบ Model View Controller (MVC)
React \cite{bib2} เป็น JavaScript Library ที่ถูกสร้างโดย Facebook ซึ่ง React ทำหน้าที่เป็นเพียง User Interface (UI) ที่สร้างมาจากพื้นฐานแนวความคิดแบบ Model View Controller (MVC)
React ทำหน้าที่เฉพาะส่วน View (จาก Model View Controller) เหมาะกับงาน Web Front-End ที่สามารถแบ่งออกเป็น Web Component ย่อยๆ โดยหลักการวิเคราะห์ควรแยกให้ย่อยที่สุดเท่าที่จะทำได้ ซึ่งสามารถแบ่ง Component ออกเป็น 2 รูปแบบ\cite{bib3} คือ
React ทำหน้าที่เฉพาะส่วน View (จาก Model View Controller) เหมาะกับงาน Web Front-End ที่สามารถแบ่งออกเป็น Web Component ย่อยๆ โดยหลักการวิเคราะห์ควรแยกให้ย่อยที่สุดเท่าที่จะทำได้ ซึ่งสามารถแบ่ง Component ออกเป็น 2 รูปแบบ คือ
\begin{itemize}
\item Container สำหรับบรรจุ Component หลักย่อยอื่นๆ ซึ่งไม่ควรมีการเก็บค่าใดๆ (สามารถทำหน้าที่เป็นตัวกลางในการส่งผ่านค่าได้) เน้นไปที่การจัด Layout
\item Container สำหรับบรรจุ Component หลักย่อยอื่นๆ ซึ่งไม่ควรมีการเก็บค่าใดๆ (สามารถทำหน้าที่เป็นตัวกลางในการส่งผ่านค่าได้) เน้นไปที่การจัด Layout
\item Web Component คือ ส่วนที่ต้อง interact กับผู้ใช้จริงๆ เช่น ช่องกรอกข้อมูล ลิสต์แสดงข้อมูล ลาเบล (Label) และ ปุ่ม เป็นต้น ซึ่งอาจมีการเก็บค่าบางค่าเอาไว้ที่ สเตท (State) เพื่อนำมาแสดงผล
\end{itemize}
การเขียน React จำเป็นต้องมีความรู้ใน 3 ประเด็น ได้แก่
\begin{itemize}
\item Component – ส่วนประกอบต่างๆ ในเว็บ จะถูกมองเป็น Component
......@@ -26,16 +29,16 @@ React ทำหน้าที่เฉพาะส่วน View (จาก Mod
\end{figure}
\subsection{React Life Cycle}
การเขียน render() ฟังก์ชันใน component \cite{bib5} นั้น ควรจะเขียนในแบบ pure function ซึ่งจะไม่มีการเปลี่ยน state และสร้าง side effect ต่อภายนอกทั้งสิ้น อย่างเช่น การ call extenal service แบบ Ajax request, Firebase calling เป็นต้น เพราะหน้าที่ของ render() มีแค่การ render UI เท่านั้น หากไม่สามารถทำสิ่งดังกล่าวภายใน render() แล้ว กิจกรรมเหล่านั้นจึงสามารถทำได้ที่ life cycle ของ React
การเขียน render() ฟังก์ชันใน component \cite{bib5} นั้น ควรจะเขียนในแบบ pure function ซึ่งจะไม่มีการเปลี่ยน state และสร้าง side effect ต่อภายนอกทั้งสิ้น อย่างเช่น การ call extenal service แบบ Ajax request เป็นต้น เพราะหน้าที่ของ render() มีแค่การ render UI เท่านั้น หากไม่สามารถทำสิ่งดังกล่าวภายใน render() แล้ว กิจกรรมเหล่านั้นจึงสามารถทำได้ที่ life cycle ของ React
ตลอดช่วงวงจรชีวิต \cite{bib6} สามารถควบคุมเหตุการณ์ต่างๆ ที่เกิดขึ้นในการแสดงผล UI การอัพเดทข้อมูล และการ re-rendering จนกระทังข้อมูลนั้นหายไป โดยที่ React ได้มีการเตรียมฟังก์ชันต่างๆ ไว้ สามารถอธิบายการทำงานของฟังก์ชันได้ ดังนี้
ตลอดช่วงวงจรชีวิต \cite{bib6} สามารถควบคุมเหตุการณ์ต่างๆ ที่เกิดขึ้นในการแสดงผล UI การอัพเดทข้อมูล และการ re-rendering จนกระทังข้อมูลนั้นหายไป โดยที่ React ได้มีการเตรียมฟังก์ชันต่างๆ ไว้ สามารถอธิบายการทำงานของฟังก์ชันได้ ดังนี้
\begin{itemize}
\item componentWillMount() : คุณสมบัติของ componentWillMount ไม่มีอะไรเกี่ยวกับการใช้งาน component เพราะยังไม่มีการ mount อะไรขึ้นมา โดยมีหน้าที่ คือ การกำหนดค่าเริ่มต้นสำหรับการใช้งาน
\item componentDidMount() : เกิดขึ้นเมื่อทำการ Mount เรียบร้อย พร้อมที่จะใช้งาน โดยปกติจะใช้ในการกำหนดค่าทุกอย่างที่ต้องใช้ DOM และรับข้อมูลที่ต้องการมาแสดงผล
\item componentWillReceiveProps(nextProps) : เมื่อ Component ทำงาน จนกระทังมี pro-ps ใหม่เข้ามา เพื่อทำการเปลี่ยนแปลงข้อมูล componentWillReceive Props จะถูกเรียก โดยมี nextProps เป็นตัวแปรที่ถูกส่งเข้ามา
\item componentWillReceiveProps(nextProps) : เมื่อ Component ทำงาน จนกระทังมี pro-ps ใหม่เข้ามา เพื่อทำการเปลี่ยนแปลงข้อมูล componentWillReceive Props จะถูกเรียก โดยมี nextProps เป็นตัวแปรที่ถูกส่งเข้ามา
\item shouldComponentUpdate(nextProps, nextState) : ถูกเรียกเมื่อ component มีการเปลี่ยนแปลงด้วย nextProps กับ nextState
\item componentWillUpdate(nextProps, nextState) : ถูกเรียกก่อนที่จะ render หลังจากได้รับค่าใหม่ของ props หรือ state คุณสมบัติของคล้ายกับ componentWillReveiveProps
\item componentDidUpdate(prevProps, prevState) : ถูกเรียกทันทีหลังจากเกิดการเปลี่ยนแปลงของ component แต่จะไม่ถูกเรียกตอนครั้งแรกที่ render โดยที่ componentDidUpd-ate สามารถใช้งานได้เหมือน componentDidMount
\item componentDidUpdate(prevProps, prevState) : ถูกเรียกทันทีหลังจากเกิดการเปลี่ยนแปลงของ component แต่จะไม่ถูกเรียกตอนครั้งแรกที่ render โดยที่ componentDidUpd-ate สามารถใช้งานได้เหมือน componentDidMount
\item componentWillUnmount() : ถูกเรียกก่อนที่ component ทำการ unmount และ destroy โดยปกติแล้วจะใช้เพื่อทำการรีเซ็ต (reset) ค่าต่างๆ
\end{itemize}
......@@ -44,19 +47,20 @@ React ทำหน้าที่เฉพาะส่วน View (จาก Mod
\section{ความรู้พื้นฐานเกี่ยวกับ Node.js}
Node.js \cite{bib7} เป็นภาษาที่ทำงานอยู่ในฝั่งเซิร์ฟเวอร์ (server) ซึ่ง syntax ที่ใช้ในการเขียนคือ JavaScript และเป็นภาษาที่ออกแบบมาให้ทำงานแบบ Event-Driven หรือทำงานเมื่อเกิดเหตุการณ์ตามที่กำหนดไว้ และการทำงานแบบ Asynchronous ซึ่งสามารถทำงานในลำดับต่อไปโดยที่ไม่ต้องรอให้งานก่อนหน้าเสร็จก่อนแล้วจึงทำงานขั้นต่อไป แต่ก็สามารถกำหนดให้ทำงานแบบ Synchronous ได้เช่นกัน โดยการกำหนด Callback เมื่องานแรกทำงานเสร็จแล้ว นอกจากนี้ Node.js นั้นจะใช้ Compiler จาก Google JavaScript Engine V8
ส่วนใหญ่จะนิยมใช้ node.js ในงานที่ทำเป็นเบื้องหลัง คือ งานที่ประมวลผลสั่งเซิร์ฟเวอร์ซึ่งเป็นงานที่อาจจะต้อง interface กับผู้ใช้ หรือไม่ต้อง interface กับผู้ใช้ ตัวอย่างงานที่ต้อง interface กับผู้ใช้ เช่น การทำตัวเองเป็น http server ในการดึงหน้าเว็บมาแสดงผลให้กับ user หรือว่า การเปิด socket เพื่อรับส่งข้อมูลกันระหว่างเซิร์ฟเวอร์กับผู้ใช้งาน เช่น ทำเป็นห้อง chat ทำเกม ทำระบบที่ป้อนข้อมูลเพื่อคำนวณผลลัพธ์ เป็นต้น ตัวอย่างงานที่ไม่ต้อง interface กับผู้ใช้ เช่น ทำ spider crawler เว็บ คือ การเปิดเว็บแล้วเก็บข้อมูลไปเรื่อยๆ หรือ โปรแกรมที่ รอรับค่าจาก streaming ต่างๆ เพื่อนำมาบันทึกไว้ ซึ่งการทำงานเหล่านี้ไม่จำเป็นต้อง interface กับผู้ใช้
ส่วนใหญ่จะนิยมใช้ node.js ในงานที่ทำเป็นเบื้องหลัง คือ งานที่ประมวลผลสั่งเซิร์ฟเวอร์ซึ่งเป็นงานที่อาจจะต้อง interface กับผู้ใช้ หรือไม่ต้อง interface กับผู้ใช้ ตัวอย่างงานที่ต้อง interface กับผู้ใช้ เช่น การทำตัวเองเป็น http server ในการดึงหน้าเว็บมาแสดงผลให้กับ user หรือว่า การเปิด socket เพื่อรับส่งข้อมูลกันระหว่างเซิร์ฟเวอร์กับผู้ใช้งาน เช่น ทำเป็นห้อง chat ทำเกม ทำระบบที่ป้อนข้อมูลเพื่อคำนวณผลลัพธ์ เป็นต้น ตัวอย่างงานที่ไม่ต้อง interface กับผู้ใช้ เช่น ทำ spider crawler เว็บ คือ การเปิดเว็บแล้วเก็บข้อมูลตลอดเวลา หรือ โปรแกรมที่รองรับค่าจาก streaming ต่างๆ เพื่อนำมาบันทึกไว้ ซึ่งการทำงานเหล่านี้ไม่จำเป็นต้อง interface กับผู้ใช้
node.js มีส่วนเสริมที่ชื่อว่า node package management (npm) ซึ่งเปรียบเหมือน google play ใน android หรือ app store ใน iOS ที่สามารถเอา package ที่คนอื่นเขียนเอาไว้แล้ว เพื่อแจกฟรี (free) มาต่อยอดเพื่อใช้ในงานของตนได้ โดยตัวอย่างที่ได้รับความนิยมจะเป็น underscore, async, request และ express เป็นต้น สำหรับการติดตั้ง ใช้คำสั่ง npm install ตามด้วยชื่อ package ที่ต้องการติดตั้ง \cite{bib8}
node.js มีส่วนเสริมที่ชื่อว่า node package management (npm) ซึ่งเปรียบเหมือน google play ในandroid หรือ app store ใน iOS ที่สามารถเอา package ที่คนอื่นเขียนเอาไว้แล้ว เพื่อแจกฟรี (free) มาต่อยอดเพื่อใช้ในงานของตนได้ โดยตัวอย่างที่ได้รับความนิยมจะเป็น underscore, async, request และ express เป็นต้น สำหรับการติดตั้ง ใช้คำสั่ง npm install ตามด้วยชื่อ package ที่ต้องการติดตั้ง \cite{bib8}
node.js มีการทำงานเป็น Asynchronous คือ การทำงานบางอย่างไม่ต้องรอให้บรรทัดนั้นทำงานเสร็จ เช่น ส่งคำสั่งไป query ข้อมูลจากฐานข้อมูล แล้วสามารถข้ามไปทำงานบรรทัดต่อไปโดยไม่ต้องรอผลจากฐานข้อมูล เมื่อการทำงานนั้นทำงานเสร็จจึงค่อยรอผลลัพธ์กลับมา ดังนั้นปัญหาจะเกิดทันที ถ้าการทำงานต่อไปนำผลลัพธ์จากคำสั่งก่อนหน้านั้นมาใช้ต่อ ซึ่งส่งผลให้เกิดการทำงานผิดพลาด เพราะผลลัพธ์ยังไม่ได้รับกลับมา
\subsection{node.js ทำงานแบบ event driven}
การทำงานของ node \cite{bib9} เรียกว่าเป็นการขับเคลื่อนด้วย event ต่างๆ ที่เกิดขึ้น ทำให้สามารถข้ามจาก event หนึ่งที่เสร็จแล้วไปยัง event อื่นได้ด้วยการสั่งงานต่อเนื่องกันไป หรือการสั่งให้ event หลาย event เริ่มทำงานในเวลาใกล้เคียงกัน ประโยชน์ที่ได้จาก event driven คือ การสั่งให้รอรับ event นั้นไปตลอดการณ์ โดยไม่เปลืองทรัพยากร เช่น การเชื่อมต่อไปยัง streaming channel ที่หนึ่ง ซึ่งอาจเป็น text หรือข้อมูลบางอย่าง เช่น ปริมาณน้ำฝน เอาไว้ หากต้นทางของ streaming ยังไม่มีข้อมูลส่งมา จะไม่เกิด event ใดๆ และ node.js จะรออยู่ แต่หากต้นทาง streaming มีข้อมูลมา node.js จะทำงานเพื่อตอบสนองต่อ event ที่เกิดขึ้นนั้นทันที สามารถแสดงการทำงานดังกล่าวได้ ดังรูปที่ \ref{Fig:event-driven}
การทำงานของ node \cite{bib9} เรียกว่าเป็นการจัดการด้วย event ต่างๆ ที่เกิดขึ้น ทำให้สามารถข้ามจาก event หนึ่งที่เสร็จแล้วไปยัง event อื่นได้ด้วยการสั่งงานต่อเนื่องกันไป หรือการสั่งให้ event หลาย event เริ่มทำงานในเวลาใกล้เคียงกัน ประโยชน์ที่ได้จาก event driven คือ การสั่งให้รองรับ event แบบถาวร โดยไม่เปลืองทรัพยากร เช่น การเชื่อมต่อไปยัง streaming channel ที่หนึ่ง ซึ่งอาจเป็น text หรือข้อมูลบางอย่าง เช่น ปริมาณน้ำฝนเอาไว้ หากต้นทางของ streaming ยังไม่มีข้อมูลส่งมา จะไม่เกิด event ใดๆ และ node.js จะรออยู่ แต่หากต้นทาง streaming มีข้อมูลมา node.js จะทำงานเพื่อตอบสนองต่อ event ที่เกิดขึ้นนั้นทันที สามารถแสดงการทำงานดังกล่าวได้ ดังรูปที่ \ref{Fig:event-driven}
\begin{figure}[H]
\centering
\includegraphics[scale=0.5]{Figures/2/unnamed}
\includegraphics[scale=0.9]{Figures/2/event-driven}
\caption{การทำงานแบบ event driven}{ที่มา: http://meewebfree.com/u/i/nodejs/\texttt{node\_js\_stage}.png}
\label{Fig:event-driven}
\end{figure}
จากรูปที่ \ref{Fig:event-driven} สามารถอธิบายการทำงานได้ดังนี้ การทำงานของ stage1 เมื่อเรียกใช้การทำงานของ stage2 แล้ว ไม่จำเป็นต้องรอให้ stage2 ทำงานเสร็จก่อน ซึ่ง stage1 สามารถเรียกการทำงานของ stage3 ได้เลยโดยไม่จำเป็นต้องรอการทำงานของ stage2
......@@ -65,16 +69,18 @@ node.js มีการทำงานเป็น Asynchronous คือ กา
\begin{itemize}
\item มีการทำงานแบบ Event-Driven และ Asynchronous
\item เหมาะกับการทำ Web แบบ Real time
\item ประหยัดทรัพยากร ในการทำงาน
\item ประหยัดทรัพยากรในการทำงาน
\item มีการประมวลผลที่รวดเร็ว
\end{itemize}
\section{ความรู้พื้นฐานเกี่ยวกับ JavaScript}
JavaScript \cite{bib12} คือ ภาษาคอมพิวเตอร์สำหรับการเขียนโปรแกรมบนระบบอินเทอร์เน็ต (Internet) ที่กำลังได้รับความนิยมอย่างสูง JavaScript เป็น ภาษาสคริปต์ (script) เชิงวัตถุ ที่เรียกกันว่า "สคริปต์" ซึ่งการใช้ JavaScript ในการสร้างและพัฒนาเว็บไซต์ (ใช้ร่วมกับ HTML) จะช่วยให้เว็บไซต์ดูมีการเคลื่อนไหว สามารถตอบสนองผู้ใช้งานได้มากขึ้น โดยมีวิธีการทำงานในลักษณะ "แปลความและดำเนินงานไปทีละคำสั่ง" (interpret) หรือเรียกว่า โปรแกรมเชิงวัตถุ (Object Oriented Programming) ที่มีเป้าหมายในการ ออกแบบและพัฒนาโปรแกรมในระบบอินเทอร์เน็ต สำหรับผู้เขียนด้วยภาษา HTML สามารถทำงานข้ามแพลตฟอร์มได้ โดยทำงานร่วมกับ ภาษา HTML และภาษา Java ได้ทั้งทางฝั่งไคลเอนต์ (client) และ ทางฝั่งเซิร์ฟเวอร์
JavaScript \cite{bib12} คือ ภาษาคอมพิวเตอร์สำหรับการเขียนโปรแกรมบนระบบอินเทอร์เน็ต (Int-
ernet) ที่กำลังได้รับความนิยมอย่างสูง JavaScript เป็น ภาษาสคริปต์ (script) เชิงวัตถุ ที่เรียกกันว่า สคริปต์ ซึ่งการใช้ JavaScript ในการสร้างและพัฒนาเว็บไซต์ (ใช้ร่วมกับ HTML) จะช่วยให้เว็บไซต์ดูมีการเคลื่อนไหว สามารถตอบสนองผู้ใช้งานได้มากขึ้น โดยมีวิธีการทำงานในลักษณะ แปลความและดำเนินงานไปทีละคำสั่ง (interpret) หรือเรียกว่า โปรแกรมเชิงวัตถุ (Object Oriented Programming) ที่มีเป้าหมายในการออกแบบและพัฒนาโปรแกรมในระบบอินเทอร์เน็ต สำหรับผู้เขียนด้วยภาษา HTML สามารถทำงานข้ามแพลตฟอร์มได้ โดยทำงานร่วมกับ ภาษา HTML และภาษา Java ได้ทั้งทางฝั่งไคลเอนต์ (client) และ ทางฝั่งเซิร์ฟเวอร์(server)
JavaScript ถูกพัฒนาขึ้นโดย บริษัท เน็ตสเคปคอมมิวนิเคชันส์ (Netscape Communications Corporation) โดยใช้ชื่อว่า Live Script ออกมาพร้อมกับ Netscape Navigator 2.0 เพื่อใช้สร้างเว็บเพจ (Web page) โดยติดต่อกับเซิร์ฟเวอร์แบบ Live Wire ต่อมาเน็ตสเคปได้ร่วมมือกับ บริษัทซันไมโครซิสเต็มส์ (Sun Microsystems, Inc) ปรับปรุงระบบของเบราว์เซอร์ (Browser) เพื่อให้สามารถติดต่อใช้งานกับภาษาจาวา (Java) ได้ และได้ปรับปรุง LiveScript ใหม่เมื่อ ปี พ.ศ. 2538 แล้วตั้งชื่อใหม่ว่า JavaScript ซึ่ง JavaScript ทำให้การสร้างเว็บเพจมีลูกเล่นต่างๆ มากมาย และยังสามารถโต้ตอบกับผู้ใช้ได้อย่างทันที เช่น การใช้เมาส์คลิก หรือ การกรอกข้อความในฟอร์ม เป็นต้น
JavaScript ถูกพัฒนาขึ้นโดย บริษัทเน็ตสเคปคอมมิวนิเคชันส์ (Netscape Communicati-
ons Corporation) โดยใช้ชื่อว่า Live Script ออกมาพร้อมกับ Netscape Navigator 2.0 เพื่อใช้สร้างเว็บเพจ (Web page) โดยติดต่อกับเซิร์ฟเวอร์แบบ Live Wire ต่อมาเน็ตสเคปได้ร่วมมือกับบริษัทซันไมโครซิสเต็มส์ (Sun Microsystems, Inc) ปรับปรุงระบบของเบราว์เซอร์ (Browser) เพื่อให้สามารถติดต่อใช้งานกับภาษาจาวา (Java) ได้ และได้ปรับปรุง LiveScript ใหม่เมื่อ ปี พ.ศ. 2538 แล้วตั้งชื่อใหม่ว่า JavaScript ซึ่ง JavaScript ทำให้การสร้างเว็บเพจมีลูกเล่นต่างๆ มากมาย และยังสามารถโต้ตอบกับผู้ใช้ได้อย่างทันที เช่น การใช้เมาส์คลิก หรือ การกรอกข้อความในฟอร์ม เป็นต้น
เนื่องจาก JavaScript ช่วยให้ผู้พัฒนา สามารถสร้างเว็บเพจได้ตรงกับความต้องการ และมีความน่าสนใจมากขึ้น ประกอบกับเป็นภาษาเปิด ที่ทุกคนสามารถนำไปใช้ได้ ดังนั้นจึงได้รับความนิยมเป็นอย่างสูง มีการใช้งานอย่างกว้างขวาง รวมทั้งได้ถูกกำหนดให้เป็นมาตรฐานโดย European Computer Manufacturer's Association (ECMA) การทำงานของ JavaScript จะต้องมีการแปลความคำสั่ง ซึ่งขั้นตอนนี้จะถูกจัดการโดยเบราว์เซอร์ (เรียกว่าเป็น client-side script) ดังนั้น JavaScript จึงสามารถทำงานได้เฉพาะบนเบราว์เซอร์ที่สนับสนุน ซึ่งปัจจุบันเบราว์เซอร์เกือบทั้งหมดสามารถสนับสนุน JavaScript แล้ว อย่างไรก็ดี สิ่งที่ต้องระวังคือ JavaScript มีการพัฒนาเป็นเวอร์ชันใหม่ๆ ออกมาด้วย ดังนั้น ถ้านำโค้ด (code) ของเวอร์ชันใหม่ ไปรันบนเบราว์เซอร์รุ่นเก่าที่ยังไม่สนับสนุน อาจจะทำให้เกิด error ได้
เนื่องจาก JavaScript ช่วยให้ผู้พัฒนาสามารถสร้างเว็บเพจได้ตรงกับความต้องการ และมีความน่าสนใจมากขึ้น ประกอบกับเป็นภาษาเปิด ที่ทุกคนสามารถนำไปใช้ได้ ดังนั้นจึงได้รับความนิยมเป็นอย่างสูง มีการใช้งานอย่างกว้างขวาง รวมทั้งได้ถูกกำหนดให้เป็นมาตรฐานโดย European Computer Manufacturer's Association (ECMA) การทำงานของ JavaScript จะต้องมีการแปลความคำสั่ง ซึ่งขั้นตอนนี้จะถูกจัดการโดยเบราว์เซอร์ (เรียกว่าเป็น client-side script) ดังนั้น JavaScript จึงสามารถทำงานได้เฉพาะบนเบราว์เซอร์ที่สนับสนุน ซึ่งปัจจุบันเบราว์เซอร์เกือบทั้งหมดสามารถสนับสนุน JavaScript แล้ว อย่างไรก็ดี สิ่งที่ต้องระวังคือ JavaScript มีการพัฒนาเป็นเวอร์ชันใหม่ๆ ออกมา ดังนั้น ถ้านำโค้ด (code) ของเวอร์ชันใหม่ ไปรันบนเบราว์เซอร์รุ่นเก่าที่ยังไม่สนับสนุน อาจจะทำให้เกิด error ได้
\subsection{ประโยชน์ของ JavaScript}
\begin{itemize}
\item JavaScript ทำให้สามารถใช้เขียนโปรแกรมแบบง่ายได้ โดยไม่ต้องพึ่งภาษาอื่น
......@@ -85,16 +91,16 @@ JavaScript ถูกพัฒนาขึ้นโดย บริษัท เ
\item JavaScript สร้าง Cookies (เก็บข้อมูลของผู้ใช้ในคอมพิวเตอร์ของผู้ใช้เอง) ได้
\end{itemize}
\subsection{ข้อดีและข้อเสียของ Java JavaScript}
JavaScript \cite{bib13} ทำงานบนเว็บบราวเซอร์ (client-side script) จึงไม่มีข้อจำกัดว่าจะใช้เซิร์ฟเวอร์แบบไหนก็ตาม เพราะ JavaScript ทำงานเฉพาะในเครื่องของผู้ใช้งานเท่านั้น ซึ่งต่างกับภาษาสคริปต์อื่น เช่น PHP , ASP, JSP หรือ Perl ซึ่งต้องประมวลผลและทำงานที่เครื่องเซิร์ฟเวอร์ (server-side script) จึงจำเป็นต้องใช้บนเซิร์ฟเวอร์ ที่สนับสนุนภาษาเหล่านี้เท่านั้นจึงจะสามารถใช้งาน server-side script ได้ แต่อย่างไรก็ตาม จากลักษณะการทำงานที่กล่าวมาก็ทำให้ JavaScript มีข้อจำกัด กล่าวคือคือไม่สามารถรับและส่งข้อมูลต่างๆ กับเซิร์ฟเวอร์โดยตรง เช่น การอ่านไฟล์จากเซิร์ฟเวอร์ เพื่อนำมาแสดงบนเว็บเพจ หรือรับข้อมูลจากผู้ชม เพื่อนำไปเก็บบนเซิร์ฟเวอร์ เป็นต้น ดังนั้นงานลักษณะนี้ จึงยังคงต้องอาศัยภาษา server-side script อยู่ (ความจริงมี JavaScript ที่ทำงานบนเซิร์ฟเวอร์เช่นกัน ซึ่งต้องอาศัยเซิร์ฟเวอร์ที่สนับสนุนโดยเฉพาะเช่นกัน แต่ไม่เป็นที่นิยมนัก)
JavaScript \cite{bib13} ทำงานบนเว็บบราวเซอร์ (client-side script) จึงไม่มีข้อจำกัดว่าจะใช้เซิร์ฟเวอร์แบบไหน เพราะ JavaScript ทำงานเฉพาะในเครื่องของผู้ใช้งานเท่านั้น ซึ่งต่างกับภาษาสคริปต์อื่น เช่น PHP, ASP, JSP หรือ Perl ซึ่งต้องประมวลผลและทำงานที่เครื่องเซิร์ฟเวอร์ (server-side script) จึงจำเป็นต้องใช้บนเซิร์ฟเวอร์ที่สนับสนุนภาษาเหล่านี้เท่านั้น จึงจะสามารถใช้งาน server-side script ได้ แต่อย่างไรก็ตาม จากลักษณะการทำงานที่กล่าวมาก็ทำให้ JavaScript มีข้อจำกัด กล่าวคือคือไม่สามารถรับและส่งข้อมูลต่างๆ กับเซิร์ฟเวอร์โดยตรง เช่น การอ่านไฟล์จากเซิร์ฟเวอร์ เพื่อนำมาแสดงบนเว็บเพจ หรือรับข้อมูลจากผู้ชม เพื่อนำไปเก็บบนเซิร์ฟเวอร์ เป็นต้น ดังนั้นงานลักษณะนี้ จึงยังคงต้องอาศัยภาษา server-side script อยู่ (ความจริงมี JavaScript ที่ทำงานบนเซิร์ฟเวอร์เช่นกัน ซึ่งต้องอาศัยเซิร์ฟเวอร์ที่สนับสนุนโดยเฉพาะเช่นกัน แต่ไม่เป็นที่นิยมนัก)
นักพัฒนาเว็บส่วนใหญ่จึงนิยมใช้ JavaScript ร่วมกับ ภาษา Server Script เพื่อทำการส่งข้อมูลระหว่าง เซิร์ฟเวอร์กับเครื่องของผู้ใช้งาน ซึ่งทำให้การแสดงผลของหน้าเว็บมีความสวยงามและราบรื่นมากยิ่งขึ้น
นักพัฒนาเว็บส่วนใหญ่จึงนิยมใช้ JavaScript ร่วมกับภาษา Server Script เพื่อทำการส่งข้อมูลระหว่าง เซิร์ฟเวอร์กับเครื่องของผู้ใช้งาน ซึ่งทำให้การแสดงผลของหน้าเว็บมีความสวยงามและราบรื่นมากยิ่งขึ้น
\section{ความรู้พื้นฐานเกี่ยวกับ MySQL}
MySQL \cite{bib15} เป็นระบบจัดการฐานข้อมูลเชิงสัมพันธ์ (Relational Database Management System : RDBMS) ตัวหนึ่ง ซึ่งเป็นที่นิยมกันมากในปัจจุบัน โดยเฉพาะอย่างยิ่งในโลกของอินเตอร์เน็ต สาเหตุเพราะว่า MySQL เป็นฟรีแวร์ ทางด้านของฐานข้อมูลที่มีประสิทธิภาพสูง เป็นทางเลือกใหมจากผลิตภัณฑ์ระบบจัดการฐานข้อมูลในปัจจุบัน ที่มักจะเป็นการผูกขาดของผลิตภัณฑ์เพียงไม่กี่ตัว นักพัฒนาระบบฐานขอมูลที่เคยใช้ MySQL ต่างยอมรับในความสามารถความรวดเร็ว การรองรับจํานวนผู้ใช้ และขนาดของข้อมูลจํานวนมหาศาล ทั้งยังสนับสนุนการใช้งานบนระบบปฏิบัติการมากมาย ไม่ว่าจะเป็น Unix , OS/2 , Mac , OS หรือ Windows ก็ตาม นอกจากนี้ MySQL ยังสามารถใช้ งานร่วมกับ Web Development Platform ทั้งหลาย ไม่ว่าจะเป็น C, C++, Java, Perl, PHP, Python, Tcl หรือ ASP ดังนั้น MySQL จึงได้รับความนิยมอย่างมากในปัจจุบัน และมีแนวโน้มสูงยิ่งขึ้นต่อไปในอนาคต
MySQL \cite{bib15} เป็นระบบจัดการฐานข้อมูลเชิงสัมพันธ์ (Relational Database Management System : RDBMS) ตัวหนึ่ง ซึ่งเป็นที่นิยมกันมากในปัจจุบัน โดยเฉพาะอย่างยิ่งในโลกของอินเตอร์เน็ต สาเหตุเพราะว่า MySQL เป็นฟรีแวร์ ทางด้านของฐานข้อมูลที่มีประสิทธิภาพสูง เป็นทางเลือกใหม่จากผลิตภัณฑ์ระบบจัดการฐานข้อมูลในปัจจุบัน ที่มักจะเป็นการผูกขาดของผลิตภัณฑ์เพียงไม่กี่ตัว นักพัฒนาระบบฐานขอมูลที่เคยใช้ MySQL ต่างยอมรับในความสามารถความรวดเร็ว การรองรับจํานวนผู้ใช้ และขนาดของข้อมูลจํานวนมหาศาล ทั้งยังสนับสนุนการใช้งานบนระบบปฏิบัติการจำนวนมาก ไม่ว่าจะเป็น Unix , OS/2 , Mac , OS หรือ Windows นอกจากนี้ MySQL ยังสามารถใช้งานร่วมกับ Web Development Platform ได้หลากหลาย เช่น C, C++, Java, Perl, PHP, Python, Tcl หรือ ASP ดังนั้น MySQL จึงได้รับความนิยมอย่างมากในปัจจุบัน และมีแนวโน้มสูงยิ่งขึ้นต่อไปในอนาคต
\subsection{โครงสร้างของ MySQL}
โครงสร้างภายในของ MySQL \cite{bib14} คือ การออกแบบการทํางานในลักษณะของ Client Server นั่นเอง ซึ่งประกอบด้วย 2 ส่วน คือ ส่วนของผู้ให้บริการ (Server) และ ส่วนของผู้ใช้บริการ (Client) โดยในแต่ละส่วนจะมีโปรแกรมสําหรับการทํางานตามหน้าที่ของโปรแกรมนั้น ส่วนของผู้ให้บริการ (Server) จะเป็นส่วนที่ทําหน้าที่บริหารจัดการระบบฐานข้อมูล (MySQL Server) และเป็นที่จัดเก็บข้อมูลทั้งหมด ข้อมูลที่เก็บไว้นี้มีข้อมูลที่จําเป็นสําหรับการทํางานกับระบบฐานข้อมูลและข้อมูลที่เกิดจากการที่ผู้ใช้แต่ละคนสร้างขึ้นมา ส่วนของผู้ใช้บริการ (Client) คือ ส่วนที่ผู้ใช้ใช้งาน โดยโปรแกรมสําหรับใช้งานในส่วนนี้ได้แก่ MySQL , Client , Access . Web Development เป็นต้น
โครงสร้างภายในของ MySQL \cite{bib14} เป็นการทํางานในลักษณะของ Client Server นั่นเอง ซึ่งประกอบด้วย 2 ส่วน คือ ส่วนของผู้ให้บริการ (Server) และ ส่วนของผู้ใช้บริการ (Client) โดยในแต่ละส่วนจะมีโปรแกรมสําหรับการทํางานตามหน้าที่ของโปรแกรมนั้น ส่วนของผู้ให้บริการ (Server) จะเป็นส่วนที่ทําหน้าที่บริหารจัดการระบบฐานข้อมูล (MySQL Server) และเป็นที่จัดเก็บข้อมูลทั้งหมด ข้อมูลที่เก็บไว้นี้มีข้อมูลที่จําเป็นสําหรับการทํางานกับระบบฐานข้อมูลและข้อมูลที่เกิดจากการที่ผู้ใช้แต่ละคนสร้างขึ้นมา ส่วนของผู้ใช้บริการ (Client) คือ ส่วนที่ผู้ใช้ใช้งาน โดยโปรแกรมสําหรับใช้งานในส่วนนี้ได้แก่ MySQL , Client , Access , Web Development เป็นต้น
\subsection{หลักการทํางานในลักษณะ Client Server}
......@@ -107,9 +113,9 @@ MySQL \cite{bib15} เป็นระบบจัดการฐานข้อ
\end{itemize}
\subsection{การทำงานของโปรแกรมของ MySQL}
MySQL ถือเป็นระบบจัดการฐานข้อมูล (Database Management System : DBMS) ฐานข้อมูลมีลักษณะเป็นโครงสร้างของการเก็บรวบรวมข้อมูล การที่จะเพิ่มเติม เข้าถึงหรือประมวลผลข้อมูลที่เก็บในฐานข้อมูลจำเป็นจะต้องอาศัยระบบจัดการ ฐานข้อมูล ซึ่งจะทำหน้าที่เป็นตัวกลางในการจัดการกับข้อมูลในฐานข้อมูลทั้งสำหรับการ ใช้งานเฉพาะ และรองรับการทำงานของแอพพลิเคชั่น (Application) ที่ต้องการใช้งานข้อมูลในฐานข้อมูล เพื่อให้ได้รับความสะดวกในการจัดการกับข้อมูลจำนวนมาก MySQL ทำหน้าที่เป็นทั้งตัวฐานข้อมูลและระบบจัดการฐานข้อมูล ดังนี้
MySQL ถือเป็นระบบจัดการฐานข้อมูล (Database Management System : DBMS) ฐานข้อมูลมีลักษณะเป็นโครงสร้างของการเก็บรวบรวมข้อมูล การที่จะเพิ่มเติม เข้าถึง หรือประมวลผลข้อมูลที่เก็บในฐานข้อมูลจำเป็นจะต้องอาศัยระบบจัดการฐานข้อมูล ซึ่งจะทำหน้าที่เป็นตัวกลางในการจัดการกับข้อมูลในฐานข้อมูลทั้งสำหรับการใช้งานเฉพาะ และรองรับการทำงานของแอพพลิเคชั่นที่ต้องการใช้งานข้อมูลในฐานข้อมูล เพื่อให้ได้รับความสะดวกในการจัดการกับข้อมูลจำนวนมาก MySQL ทำหน้าที่เป็นทั้งตัวฐานข้อมูลและระบบจัดการฐานข้อมูล ดังนี้
\begin{itemize}
\item MySQL เป็นระบบจัดการฐานข้อมูลแบบ relational ฐานข้อมูลแบบ relational จะทำการเก็บข้อมูลทั้งหมดในรูปแบบของตารางแทนการเก็บข้อมูลทั้งหมดลงในไฟล์ เพียงไฟล์เดียว ทำให้ทำงานได้รวดเร็วและมีความยืดหยุ่น นอกจากนั้น แต่ละตารางที่เก็บข้อมูลสามารถเชื่อมโยงเข้าหากันทำให้สามารถรวมหรือจัด กลุ่มข้อมูลได้ตามต้องการ โดยอาศัยภาษา SQL ที่เป็นส่วนหนึ่งของโปรแกรม MySQL ซึ่งเป็นภาษามาตรฐานในการเข้าถึงฐานข้อมูล
\item MySQL เป็นระบบจัดการฐานข้อมูลแบบ relational โดยฐานข้อมูลแบบ relational จะทำการเก็บข้อมูลทั้งหมดในรูปแบบของตารางแทนการเก็บข้อมูลทั้งหมดลงในไฟล์เพียงไฟล์เดียว ทำให้ทำงานได้รวดเร็วและมีความยืดหยุ่น นอกจากนั้น แต่ละตารางที่เก็บข้อมูลสามารถเชื่อมโยงเข้าหากันทำให้สามารถรวมหรือจัดกลุ่มข้อมูลได้ตามต้องการ โดยอาศัยภาษา SQL ที่เป็นส่วนหนึ่งของโปรแกรม MySQL ซึ่งเป็นภาษามาตรฐานในการเข้าถึงฐานข้อมูล
\item MySQL แจกจ่ายให้ใช้งานแบบ Open Source นั่นคือ ผู้ใช้งาน MySQL ทุกคนสามารถใช้งานและปรับแต่งการทำงานได้ตามต้องการ สามารถดาวน์โหลดโปรแกรม MySQL ได้จากอินเทอร์เน็ตและนำมาใช้งานโดยไม่มีค่าใช้จ่าย
\end{itemize}
......@@ -127,8 +133,8 @@ MySQL ถือเป็นระบบจัดการฐานข้อม
\end{enumerate}
\section{ความรู้เกี่ยวกับ Visual Studio Code}
วิชวล สตูดิโอโค้ด (Visual Studio Code หรือ VSCode) \cite{vscode} เป็นโปรแกรม Code Editor ที่ใช้ในการแก้ไขและปรับแต่งโค้คถูกพัฒนาโดยค่ายไมโครซอฟท์(Microsoft) มีการพัฒนาออกมาในรูปแบบของ OpenSource
จึงสามารถนำมาใช้งานได้แบบไม่ต้องเสียค่าใช้จ่าย Visual Studio Code เหมาะสำหรับนักพัฒนาโปรแกรมที่ต้องการใช้งานข้ามแพลตฟอร์ม (Cross-platform) โดยจะรองรับการใช้งานทั้งบนระบบปฏิบัติการ Windows,macOS และ Linux
วิชวล สตูดิโอโค้ด (Visual Studio Code หรือ VSCode) \cite{vscode} เป็นโปรแกรม Code Editor ที่ใช้ในการแก้ไขและปรับแต่งโค้คถูกพัฒนาโดยไมโครซอฟท์ (Microsoft) มีการพัฒนาออกมาในรูปแบบของ OpenSource
จึงสามารถนำมาใช้งานได้แบบไม่ต้องเสียค่าใช้จ่าย Visual Studio Code เหมาะสำหรับนักพัฒนาโปรแกรมที่ต้องการใช้งานข้ามแพลตฟอร์ม (Cross-platform) โดยจะรองรับการใช้งานทั้งบนระบบปฏิบัติการ Windows, macOS และ Linux
ซึ่งภาษาที่ Visual Studio Code รองรับการทำงานซึ่งมีมากกว่า 30 ภาษาโปรแกรม เช่น C++, C, CSS, Dockerfile, HTML, JavaScript, JSON, Less, Markdown, PHP, Python, Sass, TypeScript,
Node.js และ Java เป็นต้น
......@@ -154,21 +160,5 @@ Google Maps API \cite{maps} เป็นชุด API ของ Google สำห
\label{Fig:2-maps}
\end{figure}
\newpage
\section{เอกสารและงานวิจัยที่เกี่ยวข้อง}
\subsection{เว็บแอพพลิเคชัน Gowabi}
Gowabi\cite{gowabi} เป็นเว็บไซต์และแอปพลิเคชันที่ให้บริการเกี่ยวกับการค้นหาและจองคิวร้านเสริมสวย มีฟังก์ชันการทำงานพื้นฐานอันได้แก่ การค้นหา การจองคิว ดูข้อมูล เป็นต้น
\subsection{ข้อแตกต่างระหว่างเว็บแอปพลิเคชั่น Gowabi กับเว็บของโครงงาน}
เว็บแอปพลิเคชั่นของ Gowabi ยังไม่มีฟังก์ชันเลือกรายการ และช่างในการจองคิว ผู้พัฒนาจึงได้ทำฟังก์ชันเลือกรายหารและะช่างที่เหมาะสมตามรสนิยมของผู้ใช้เพิ่มลงในเว็บของโครงงาน
\begin{figure}[H]
\centering
\includegraphics[width=14cm]{Figures/2/gowabi}
\caption{หน้าแรกของเว็บไซต์ Gowabi}{ที่มา :https://www.gowabi.com}
\label{Fig:2-wognai}
\end{figure}
......@@ -2,7 +2,7 @@
การวิเคราะห์และออกแบบระบบก่อนดำเนินการจริงเป็นอีกหนึ่งขั้นตอนที่มีความสำคัญมาก เพราะการวิเคราะห์และออกแบบระบบนั้นเป็นการกระทำที่ทำให้ผู้พัฒนาเห็นรายละเอียดส่วนย่อยของงานทั้งหมด เพิ่มประสิทธิภาพในการวางแผน การทำงาน และยังช่วยลดปัญหาที่อาจจะเกิดขึ้นในระหว่างพัฒนา เพื่อให้ระบบมีความสมบูรณ์มากยิ่งขึ้น เนื่องจากการวิเคราะห์และออกแบบระบบนั้นจะช่วยให้ให้บริการ จัดการทรัพยากรได้อย่างคุ้มค่าและตรงตามความต้องการของระบบ
การวิเคราะห์และออกแบบระบบ การจองคิวร้านเสริมสวย ในบทนี้จะแบ่งออกเป็น 6 ข้นตอนเพื่อให้เห็นการดำเนินงานอย่างมีระบบ ในหัวข้อแรกจะนำเสนอภาพรวมของระบบ ก่อนจะนำเสนอเอกสารแสดงความต้องการของระบบซึ่งจะทำให้เห็นที่มาของเพจต่าง ๆ ในขั้นตอนของการออกแบบในหัวข้อที่สาม ส่วนหัวข้อที่เหลือจะแสดงแผนภาพการการทำงานของระบบโดยใช้ UML diagram ซึ่งประกอบไปด้วย Use Case, Class และ Sequence Diagram เพื่อแสดงรายละเอียดของระบบก่อนนำไปเขียนคำสั่งด้วยภาษาโปรแกรมในบทต่อไป
การวิเคราะห์และออกแบบระบบ การจองคิวร้านเสริมสวย ในบทนี้จะแบ่งออกเป็น 6 ขั้นตอนเพื่อให้เห็นการดำเนินงานอย่างมีระบบ ในหัวข้อแรกจะนำเสนอภาพรวมของระบบ ก่อนจะนำเสนอเอกสารแสดงความต้องการของระบบซึ่งจะทำให้เห็นที่มาของหน้าของระบบ ในขั้นตอนของการออกแบบในหัวข้อที่สาม ส่วนหัวข้อที่เหลือจะแสดงแผนภาพการการทำงานของระบบโดยใช้ UML diagram ซึ่งประกอบไปด้วย Use Case diagram, Class diagram และ Sequence Diagram เพื่อแสดงรายละเอียดของระบบก่อนนำไปเขียนคำสั่งด้วยภาษาโปรแกรมในบทต่อไป
\begin{enumerate}[label=3.\arabic*]
\item โครงสร้างภาพรวมของระบบ (System Architecture) เป็นการออกแบบภาพรวมและเทคโนโลยีของระบบ
......@@ -15,9 +15,9 @@
\end{enumerate}
\section{โครงสร้างภาพรวมของระบบ}
ความหมายของ System Architecture \cite{architecture} หมายถึง กรอบโครงสร้างของระบบที่อธิบายความสัมพันธ์ขององค์ประกอบต่าง ๆ ไปจนถึงขั้นการเชื่อมต่อกันของระบบย่อยต่าง ๆ โดยจัดกลุ่มองค์ประกอบไว้ในหลาย ๆ ลักษณะเพื่อให้ผู้เกี่ยวข้อง (Stakeholder) จากพื้นฐานสาขาอาชีพที่แตกต่าง กันสามารถทำความเข้าใจได้ง่าย เช่น การจัดแบ่งองค์ประกอบตามลักษณะการทำงานของระบบ (functional components) เป็นต้น
System Architecture \cite{architecture} หมายถึง กรอบโครงสร้างของระบบที่อธิบายความสัมพันธ์ขององค์ประกอบต่าง ๆ ไปจนถึงขั้นการเชื่อมต่อกันของระบบย่อยต่าง ๆ โดยจัดกลุ่มองค์ประกอบไว้ในหลาย ๆ ลักษณะเพื่อให้ผู้เกี่ยวข้อง (Stakeholder) จากพื้นฐานสาขาอาชีพที่แตกต่าง กันสามารถทำความเข้าใจได้ง่าย เช่น การจัดแบ่งองค์ประกอบตามลักษณะการทำงานของระบบ (functional components) เป็นต้น
การออกแบบ System architecture แสดงภาพรวมและเทคโนโลยีของระบบกองทุนเงินให้กู้ยืมเพื่อการศึกษา คณะวิทยาศาสตร์ มหาวิทยาลัยอุบลราชธานี มีรายละเอียดดังรูปที่ \ref{Fig:architecture}
การออกแบบ System architecture แสดงภาพรวมและเทคโนโลยีของระบบจองคิวร้านเสริมสวย มีรายละเอียดดังรูปที่ \ref{Fig:architecture}
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{Figures/3/architecture/architecture}
......@@ -28,11 +28,11 @@
จากรูปที่ \ref{Fig:architecture} สามารถอธิบายโครงสร้างและเทคโนโลยีของระบบโดยแบ่งเป็น 3 ส่วนหลัก ดังนี้
\begin{enumerate}
\item Database
ระบบใช้บริการฐานข้อมูลแบบ MySQL ของไฟร์เบสชื่อ Relational Database Management System : RDBMS
ระบบใช้บริการฐานข้อมูลแบบ MySQL ของไฟร์เบส(Firebase) ชื่อ Relational Database Management System (RDBMS)
\item Server
กระบวนการทำงานในส่วนของเซิฟเวอร์ (server) แบ่งเป็น 2 ส่วนได้แก่
\begin{itemize}
\item ชุดเผยแพร่สำหรับการพัฒนาเว็บไซต์ซึ่งในที่นี้ใช้ Node.jsและExpress ในการพัฒนา
\item ชุดเผยแพร่สำหรับการพัฒนาเว็บไซต์ซึ่งในที่นี้ใช้ Node.js และ Express ในการพัฒนา
\item ชุดบริการ phpmyadmin Api ใช้สำหรับการทำงานกับบริการต่าง ๆ ของ MySLQ
\end{itemize}
\item Client
......@@ -79,7 +79,7 @@
\begin{enumerate}
\item เว็บแอปพลิเคชัน
\begin{itemize}[label={--}]
\item ใช้โปรโตคอล (Protocol) แบบ HTTPS (Hypertext Transfer Protocol Secure) ในการสื่อสารที่ช่วยรักษาความสมบูรณ์ถูกต้องของข้อมูลผู้ใช้และเก็บข้อมูลไว้เป็นความลับระหว่างคอมพิวเตอร์ของผู้ใช้กับเว็บไซต์
\item ใช้โปรโตคอล (Protocol) แบบ Hypertext Transfer Protocol Secure(HTTPS) ในการสื่อสารที่ช่วยรักษาความสมบูรณ์ถูกต้องของข้อมูลผู้ใช้และเก็บข้อมูลไว้เป็นความลับระหว่างคอมพิวเตอร์ของผู้ใช้กับเว็บไซต์
\item รองรับการใช้งานบนเว็บบราวน์เซอร์และสมาร์ทโฟน
\end{itemize}
......@@ -109,7 +109,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอข้อมูลร้าน}
\label{Fig:Shop}
\end{figure}
จากภาพที่ \ref{Fig:Shop} แสดงหน้าจอข้อมูลรายละเอียดภายในร้าน
จากภาพที่ \ref{Fig:Shop} แสดงหน้าจอข้อมูลรายละเอียดภายในร้าน เพื่ออำนวยความสะดวกต่อผู้ใช้งาน ในรายละเอียดร้านได้รวบรวมข้อมูลรวมถึง การดูรายการ การจองคิว ดูผลงานร้าน ดูผลงานช่าง และดูรีวิว
\newpage
\item การออกแบบหน้าจอการจองคิว
\begin{figure}[H]
......@@ -118,7 +118,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอการจองคิว}
\label{Fig:Shopgueue}
\end{figure}
จากภาพที่ \ref{Fig:Shopgueue} แสดงหน้าจอการจองคิวร้านเสริมสวย ทั้งนี้ผู้ที่มีสิทธิ์ในการจองคิวมีเพียงผู้ใช้งานเท่านั้น
จากภาพที่ \ref{Fig:Shopgueue} แสดงหน้าจอการจองคิวร้านเสริมสวย เมื่อผู้ใช้กดปุ่มจองคิวในหน้าหลักหรือหน้ารายละเอียดร้าน ระบบจะแสดง dialog การจองคิว ส่วนการจองคิว จะมีข้อมูลวันที่ รายการ ช่าง และเวลา ที่ใช้ในการจองคิว
\newpage
\item การออกแบบหน้าจอดูรายการ
\begin{figure}[H]
......@@ -127,7 +127,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอดูรายการ}
\label{Fig:Shoplist}
\end{figure}
จากภาพที่ \ref{Fig:Shoplist} แสดงหน้าจอรายการที่ให้บริการประจำร้าน
จากภาพที่ \ref{Fig:Shoplist} แสดงหน้าจอรายการที่ให้บริการประจำร้าน เมื่อผู้ใช้งานกดปุ่มรายการที่หน้ารายละเอียดร้าน ระบบจะแสดง dialog รายการ เพื่อให้ผู้ใช้งานดูรายการประจำร้าน
\newpage
\item การออกแบบหน้าจอเขียนรีวิว
\begin{figure}[H]
......@@ -136,7 +136,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเขียนรีวิว}
\label{Fig:Shopreview}
\end{figure}
จากภาพที่ \ref{Fig:Shopreview} แสดงหน้าจอการเขียนรีวิว ทั้งนี้ผู้ที่มีสิทธิ์ในการอัพโหลดเอกสารมีเพียงผู้ใช้งานเท่านั้น
จากภาพที่ \ref{Fig:Shopreview} แสดงหน้าจอการเขียนรีวิว เมือ่ผู้ใช้กดปุ่มเขียนรีวิวในหน้ารายละเอียดร้าน ระบบจะสดง dialog เพื่อให้ผู้ใช้งานเขียนรีวิว
\newpage
\item การออกแบบหน้าจอลงทะเบียน
\begin{figure}[H]
......@@ -145,7 +145,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอลงทะเบียน}
\label{Fig:Register}
\end{figure}
จากภาพที่ \ref{Fig:Register} แสดงหน้าจอการลงทะเบียนของผู้ใช้บริการ เพื่อใช้ในการเข้าสู่ระบบ
จากภาพที่ \ref{Fig:Register} แสดงหน้าจอการลงทะเบียนของผู้ใช้บริการ เพื่อให้ผู้ใช้ลงทะเบียนขอใช้ระบบ โดยจะมีข้มูล ชื่อ-นามสกุล อีเมลล์ รหัสผ่าน ยืนยันรหัสผ่าน ที่อยู่ และ เบอร์โทร
\item การออกแบบหน้าจอเข้าสู่ระบบ
\begin{figure}[H]
\centering
......@@ -153,11 +153,11 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเข้าสู่ระบบ}
\label{Fig:Login}
\end{figure}
จากภาพที่ \ref{Fig:Login} แสดงหน้าจอการเข้าสู่ระบบของผู้ใช้โดยผู้ใช้จำเป็นต้องกรอกข้อมูลอีเมลและรหัสผ่านเพื่อเข้าใช้งานระบบ
จากภาพที่ \ref{Fig:Login} แสดงหน้าจอการเข้าสู่ระบบของผู้ใช้ โดยผู้ใช้จำเป็นต้องกรอกข้อมูลอีเมลและรหัสผ่านเพื่อเข้าใช้งานระบบ
\end{itemize}
\newpage
\item เจ้าของร้าน \\
User Interface เจ้าของร้าน จะออกแบบให้สะดวกต่อการใช้งาน
User Interface เจ้าของร้าน แสดงดังภาพ \ref{Fig:Shopprofile} - \ref{Fig:Shoppic}
\begin{itemize}
\item การออกแบบหน้าจอโปรไฟล์ร้าน
......@@ -167,7 +167,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอโปรไฟล์ร้าน}
\label{Fig:Shopprofile}
\end{figure}
จากภาพที่ \ref{Fig:Shopprofile} แสดงหน้าจอโปรไฟล์บนเว็บแอปพลิเคชัน เพื่อดูข้อมูลของเจ้าของร้าน
จากภาพที่ \ref{Fig:Shopprofile} แสดงหน้าจอโปรไฟล์บนเว็บแอปพลิเคชัน เมื่อเจ้าของร้านเข้าสู่ระบบ ระบบจะแสดงหน้าโปรไฟล์ เพื่อให้เจ้าของร้านดูข้อมูลโปรไฟล์ และอัพโหลดรูปภาพ
\newpage
\item การออกแบบหน้าจอเพิ่มข้อมูลร้าน
\begin{figure}[H]
......@@ -176,7 +176,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเพิ่มข้อมูลร้าน}
\label{Fig:Datashop}
\end{figure}
จากภาพที่ \ref{Fig:Datashop} แสดงหน้าจอเพิ่มข้อมูลร้าน เพื่อให้เจ้าของร้านเพิ่มข้อมูลร้าน
จากภาพที่ \ref{Fig:Datashop} แสดงหน้าจอเพิ่มข้อมูลร้าน เมื่อเจ้าของร้านกดเมนูข้อมูลร้าน ระบบจะแสดงหน้าข้อมูลร้าน เพื่อให้เจ้าของร้านทำการดูข้อมูลร้าน เพิ่มข้อมูลร้าน แก้ไขข้อมูลร้าน
\newpage
\item การออกแบบหน้าจอเพิ่มรายการ
\begin{figure}[H]
......@@ -185,7 +185,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเพิ่มรายการ}
\label{Fig:List}
\end{figure}
จากภาพที่ \ref{Fig:List} แสดงหน้าจอเพิ่มข้อมูลรายการของร้าน เพื่อให้เจ้าของร้านเพิ่มข้อมูลรายการที่ให้บริการประจำร้าน
จากภาพที่ \ref{Fig:List} แสดงหน้าจอเพิ่มข้อมูลรายการของร้าน เมื่อเจ้าของร้านกดเมนูข้อมูลรายการ ระบบจะแสดงหน้าข้อมูลรายการ เพื่อให้เจ้าของร้านทำการดูข้อมูลรายการ เพิ่มข้อมูลรายการ แก้ไขข้อมูลรายการ และลบข้อมูลรายการ
\item การออกแบบหน้าจอเพิ่มข้อมูลช่าง
\begin{figure}[H]
......@@ -194,8 +194,8 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเพิ่มข้อมูลช่าง}
\label{Fig:Qely}
\end{figure}
จากภาพที่ \ref{Fig:Qely} แสดงหน้าจอข้อมูลช่าง เพื่อเพิ่มและอสดงข้อมูลช่างประจำร้าน
\newpage
จากภาพที่ \ref{Fig:Qely} แสดงหน้าจอข้อมูลช่าง เจ้าของร้านกดที่เมนูข้อมูลช่าง ระบบจะแสดงหน้าข้อมูลช่าง เพื่อให้เจ้าของร้านดูข้อมูลช่าง เพิ่มข้อมูลช่าง และลบข้อมูลช่าง
\item การออกแบบหน้าจอดูการจองคิว
\begin{figure}[H]
\centering
......@@ -203,7 +203,7 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอดูการจองคิว}
\label{Fig:ShopQely}
\end{figure}
จากภาพที่ \ref{Fig:ShopQely} แสดงหน้าจอการจองคิว เพื่อให้เจ้าของร้านดูข้อมูลการจองคิวจากผู้ใช้งาน
จากภาพที่ \ref{Fig:ShopQely} แสดงหน้าจอการจองคิว เมื่อเจ้าของร้านกดที่เมนูดูการจองคิว ระบบจะแสดงหน้าข้อมูลการจองคิว
\item การออกแบบหน้าจอเพิ่มรูปภาพ
\begin{figure}[H]
\centering
......@@ -211,12 +211,12 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเพิ่มรูปภาพ}
\label{Fig:Shoppic}
\end{figure}
จากภาพที่ \ref{Fig:Shoppic} แสดงหน้าจอเพิ่มรูปภาพ เพื่ออัปโหลดรุปภาพเฉพาะเจ้าของร้าน
จากภาพที่ \ref{Fig:Shoppic} แสดงหน้าจอเพิ่มรูปภาพ เมื่อเจ้าของร้านกดเมนูเพิ่มรูปภาพ ระบบจะแสดงหน้าเพิ่อมรูปภาพ เพื่อให้เจ้าของร้านเพิ่มรูปภาพและลบรูอภาพ
\end{itemize}
\newpage
\item เจ้าของร้าน \\
User Interface เจ้าของร้าน จะออกแบบให้สะดวกต่อการใช้งาน
User Interface เจ้าของช่าง แสดงดังภาพ \ref{Fig:Bprofile} - \ref{Fig:Bpic}
\begin{itemize}
\item การออกแบบหน้าจอโปรไฟล์ช่าง
......@@ -226,7 +226,8 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอโปรไฟล์ช่าง}
\label{Fig:Bprofile}
\end{figure}
จากภาพที่ \ref{Fig:Bprofile} แสดงหน้าจอโปรไฟล์บนเว็บแอปพลิเคชัน เพื่อดูข้อมูลของช่าง
จากภาพที่ \ref{Fig:Bprofile} แสดงหน้าจอโปรไฟล์บนเว็บแอปพลิเคชัน เมื่อช่างเข้าสู่ระบบ ระบบจะแสดงหน้าโปรไฟล์ เพื่อให้ช่างดูข้อมูลโปรไฟล์ และอัพโหลดรูปภาพ
\newpage
\item การออกแบบหน้าจอดูตารางงาน
\begin{figure}[H]
\centering
......@@ -234,8 +235,8 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอดดูตารางงาน}
\label{Fig:Work}
\end{figure}
จากภาพที่ \ref{Fig:Work} แสดงหน้าจอตารางงาน เฉพาะช่าง
\newpage
จากภาพที่ \ref{Fig:Work} แสดงหน้าจอตารางงาน เมื่อช่างกดเมนูตารางงาน ระบบจะแสดงหน้าตารางงาน เพื่อให้ช่างดูข้อมูลตารางงาน
\item การออกแบบหน้าจอเพิ่มรูปภาพผลงานช่าง
\begin{figure}[H]
\centering
......@@ -243,7 +244,8 @@ User Interface Design ของระบบการจองคิวร้า
\caption{หน้าจอเพิ่มรูปภาพผลงานช่าง}
\label{Fig:Bpic}
\end{figure}
จากภาพที่ \ref{Fig:Bpic} แสดงหน้าจอเพิ่มรูปภาพ เพื่ออัพโหลดรุปภาพผลงานเฉพาะช่าง
จากภาพที่ \ref{Fig:Bpic} แสดงหน้าจอเพิ่มรูปภาพ เมื่อช่างกดเมนูภาพผลงาน ระบบจะแสดงหน้าเพิ่อมรูปภาพ เพื่อให้ช่างเพิ่มรูปภาพและลบรูอภาพ
\end{itemize}
\end{enumerate}
\newpage
......@@ -265,7 +267,7 @@ User Interface Design ของระบบการจองคิวร้า
\raisebox{-\totalheight}{\includegraphics[width=0.3\textwidth]{Figures/table/use-case/4}}
& \setstretch{1.5} {กรอบสี่เหลี่ยมแสดงถึงขอบเขตของระบบโดยแสดงชื่อระบบภายในหรือด้านบนกรอกสี่เหลี่ยม Use case อยู่ภายในกรอบสี่เหลี่ยม และ actor อยู่ภายนอกกรอบสี่เหลี่ยม} \\ \hline
\raisebox{-\totalheight}{\includegraphics[width=0.3\textwidth]{Figures/table/use-case/5}}
& \setstretch{1.5} {ความสัมพันธ์แบบ <<includes>> แสดงว่า Use case หนึ่งดำเนินการตามขั้นตอนของ Use case อื่น โดยแทนด้วยสัลักษณ์ลูกศรเส้นประ ซึ่ง Use case ที่หางลูกศรเรียกใช้งาน Use case ที่หัวลูกศรทุกครั้งที่มีการทำงาน} \\ \hline
& \setstretch{1.5} {ความสัมพันธ์แบบ <<includes>> แสดงว่า Use case หนึ่งดำเนินการตามขั้นตอนของ Use case อื่น โดยแทนด้วยสัลักษณ์ลูกศรเส้นประ ซึ่ง Use case ที่หางลูกศรเรียกใช้งาน Use case ที่หัวลูกศรทุกครั้งที่มีการทำงาน} \\ \hline
\raisebox{-\totalheight}{\includegraphics[width=0.3\textwidth]{Figures/table/use-case/6}}
& \setstretch{1.5} {ความสัมพันธ์แบบ <<extend>> แสดงว่า Use case หนึ่งดำเนินการตามขั้นตอนของ Use case อื่น โดยแทนด้วยสัญลักษณ์ลูกศรเส้นประ ซึ่ง Use case ที่หัวลูกศรเรียกใช้งาน Use case ที่หางลูกศร แต่การใช้งานไม่จำเป็นต้องเกิดขึ้นทุกครั้งขึ้นอยู่กับเงื่อนไขระหว่างการทำงาน} \\ \hline
\end{tabular}
......@@ -295,7 +297,7 @@ User Interface Design ของระบบการจองคิวร้า
จองคิวร้านเสริมสวย & ผุ้ใช้บริการสามารถจองคิวร้านเสริมสวยได้โดยจำเป็นต้องเข้าสู่ระบบก่อน และจำเป็นต้อง เลือก วันที่ รายการ ช่าง เวลา \\ \hline
เพิ่ม แก้ไข และลบรายการ & ใช้เจ้าของร้านเพื่อ เพิ่ม แก้ไขหรือลบข้อมูลรายการ \\ \hline
post ภาพผลงานทั้งหมดของร้านเสริมสวย & ใช้สำหรับเจ้าของร้านเพื่อ เพิ่ม post รูปภาพผลงานของร้าน\\ \hline
เพิ่มแก้ไขและลบข้อมูลร้าน & ใช้สำหรับเจ้าของร้านเพื่อ เพิ่ม แก้ไขหรือลบข้อมูลร้าน \\ \hline
เพิ่มแก้ไขข้อมูลร้าน & ใช้สำหรับเจ้าของร้านเพื่อ เพิ่ม แก้ไขหรือลบข้อมูลร้าน \\ \hline
......@@ -509,16 +511,16 @@ User Interface Design ของระบบการจองคิวร้า
\end{table}
\begin{table}[H]
\centering
\caption{Use Case เพิ่มแก้ไขและลบข้อมูลร้าน}
\caption{Use Case เพิ่มแก้ไขข้อมูลร้าน}
\label{tab:usecase}
\resizebox{\totalheight}{!}{\textwidth}{%
\begin{tabular}{|c|p{10cm}|}
\hline
\multicolumn{1}{|c|}{\textbf{Use Case Title : เพิ่มแก้ไขและลบข้อมูลร้าน}} & \multicolumn{1}{c|}{\textbf{Use case Id : 13 }} \\ \hline
\multicolumn{1}{|c|}{\textbf{Use Case Title : เพิ่มแก้ไขข้อมูลร้าน}} & \multicolumn{1}{c|}{\textbf{Use case Id : 13 }} \\ \hline
\multicolumn{2}{|l|}{Primary Actor :เจ้าของร้าน} \\ \hline
\multicolumn{2}{|l|}{Stakeholder Actor : -} \\ \hline
\multicolumn{2}{|p{\linewidth}|}{Main Flow : เจ้าของร้าน เพิ่ม แก้ไขและลบข้อมูลร้าน} \\ \hline
\multicolumn{2}{|p{\linewidth}|}{Exceptional Flow ที่ 1 : หากเจ้าหน้าที่ไม่เชื่อมต่ออินเทอร์เน็ต จะไม่สามารถ เพิ่ม แก้ไขและลบข้อมูลร้านได้} \\ \hline
\multicolumn{2}{|p{\linewidth}|}{Main Flow : เจ้าของร้าน เพิ่ม แก้ไขข้อมูลร้าน} \\ \hline
\multicolumn{2}{|p{\linewidth}|}{Exceptional Flow ที่ 1 : หากเจ้าหน้าที่ไม่เชื่อมต่ออินเทอร์เน็ต จะไม่สามารถ เพิ่ม แก้ไขข้อมูลร้านได้} \\ \hline
\end{tabular}%
}
\end{table}
......@@ -635,7 +637,7 @@ User Interface Design ของระบบการจองคิวร้า
\newpage
%IMAGE of class
Class Diagram แสดงความสัมพันธ์ในรูปแบบต่างๆ ระหว่างคลาสของแอปพลิเคชันระบบกองทุนเงินให้กู้ยืมเพื่อการศึกษา คณะวิทยาศาสตร์ มหาวิทยาลัยอุบลราชธานี อธิบายได้ตามภาพที่ \ref{Fig:classD} ดังต่อไปนี้
Class Diagram แสดงความสัมพันธ์ในรูปแบบต่างๆ ระหว่างคลาสของเว็บแอปพลิเคชันระบบจองคิวร้านเสริมสวย อธิบายได้ตามภาพที่ \ref{Fig:classD} ดังต่อไปนี้
\begin{sidewaysfigure}
\begin{figure}[H]
......@@ -662,18 +664,12 @@ User Interface Design ของระบบการจองคิวร้า
& \setstretch{1.5} {คลาส Register จะถูกเรียกใช้งานทุกครั้งเมื่อผู้ใช้เปิดเว็บแอปพลิเคชัน โดยวัตถุประสงค์การทำงานของคลาสคือ เพื่อใช้ในการลงทะเบียนขอใช้เว็บแอปพลิเคชัน} \\ \hline
\raisebox{-\totalheight}{Login}
& \setstretch{1.5} {คลาส Login เป็นคลาสที่ใช้เพื่อให้ผู้ใช้ที่ได้ลงทะเบียนกับระบบเข้าสู่ระบบเพื่อใช้งานบริการต่าง ๆ จากระบบ} \\ \hline
\raisebox{-\totalheight}{User}
& \setstretch{1.5} {คลาส User เป็นคลาสที่ใช้เก็บข้อมูลจากการลงทะเบียน} \\ \hline
\raisebox{-\totalheight}{BookingController}
& \setstretch{1.5} {คลาส BookingController เป็นคลาสที่ใช้เพิ่มข้อมูลช่าง} \\ \hline
\raisebox{-\totalheight}{Beautician}
& \setstretch{1.5} {คลาส Beautician เป็นคลาสที่ใช้เพิ่มข้อมูลช่าง} \\ \hline
\raisebox{-\totalheight}{Shop}
& \setstretch{1.5} {คลาส Shop เป็นคลาสที่ใช้จัดการการทำงาน Shop} \\ \hline
\raisebox{-\totalheight}{EditShop}
& \setstretch{1.5} {คลาส EditShop เป็นคลาสที่ใช้ในการจัดการการ update ข้อมูลร้าน} \\ \hline
\raisebox{-\totalheight}{List}
& \setstretch{1.5} {คลาส List เป็นคลาสที่ใช้จัดการการทำงานของรายการ} \\ \hline
\raisebox{-\totalheight}{EditList}
& \setstretch{1.5} {คลาส EditList เป็นคลาสที่ใช้จัดการการ update ข้อมูลรายการ} \\ \hline
\raisebox{-\totalheight}{Review}
& \setstretch{1.5} {คลาส Review เป็นคลาสที่ใช้จัดการการรีวิวของผู้ใช้งาน} \\ \hline
\raisebox{-\totalheight}{Booking}
......@@ -769,25 +765,34 @@ User Interface Design ของระบบการจองคิวร้า
\begin{figure}[H]
\centering
\includegraphics[width=0.99\columnwidth]
{Figures/3/Sequence/EditList}
\caption{Sequence Diagram การแก้ไขรายการ}
\label{Fig:Sequence-Editlist}
{Figures/3/Sequence/List}
\caption{Sequence Diagram การจัดการรายการ}
\label{Fig:Sequence-list}
\end{figure}
\end{sidewaysfigure}
\newpage
จากภาพที่ \ref{Fig:Sequence-Editlist} สามารถอธิบายแผนภาพ Sequence Diagram การแก้ไขรายการ ได้ดังนี้ เมื่อเจ้าของร้านทำการ login เสร็จ เจ้าของร้านกดที่สัญลักษณ์แก้ไขระบบจะแสดงช่องให้กรอกข้อมูล และเมื่อเจ้าของร้านกรอกข้อมูลที่ต้องการแก้ไขเสร็จแล้วกดเครื่องหมายถูก ระบบจะทำการเรียกใช้เมธอด updateList() ที่คลาส Editlist ระบบจะทำการส่งข้อมูลแบบ put ไปยังคลาส api:Server ด้วยเมธอด list จะทำการ update ข้อมูลลงฐานข้อมูล ระบบจะทำการแจ้งเตือนแก้ไขข้อมูลสำเร็จและแสดงข้อมูลแก้ไขออกทางหน้าจอ
จากภาพที่ \ref{Fig:Sequence-list} สามารถอธิบายแผนภาพ Sequence Diagram การจัดการรายการ ได้ดังนี้ เมื่อเจ้าของร้านทำการ login เสร็จ
เจ้าของร้านกดปุ่มบวก ระบบจะแสดงช่องกรอกข้อมูล เจ้าของร้านทำการกรอกข้อมูลแล้วกดยืนยัน
ระบบจะทำการเรียกใช้เมธอด onAddList() ที่คลาส List ระบบจะทำการส่งข้อมูลแบบ post ไปยังคลาส api:Server ด้วยเมธอด list จะทำการ insert ข้อมูลลงฐานข้อมูล ระบบจะทำการแจ้งเตือนเพิ่มขข้อมูลสำเร็จและแสดงข้อมูลออกทางหน้าจอ
เมื่อเจ้าของร้านกดปุ่มแก้ไข กรอกข้อมูลที่ต้องการแก้ไขเสร็จแล้วกดเครื่องหมายถูก ระบบจะทำการเรียกใช้เมธอด updateList() ที่คลาส List ระบบจะทำการส่งข้อมูลแบบ put ไปยังคลาส api:Server ด้วยเมธอด list จะทำการ update ข้อมูลลงฐานข้อมูล ระบบจะทำการแจ้งเตือนแก้ไขข้อมูลสำเร็จและแสดงข้อมูลแก้ไขออกทางหน้าจอ
เมื่อเจ้าของร้านกดปุ่มลบ ระบบจะทำการเรียกใช้เมธอด ondeleteList() ที่คลาส List ระบบจะทำการส่งข้อมูลแบบ delete ไปยังคลาส api:Server ด้วยเมธอด list จะทำการ delete จากฐานข้อมูล ระบบจะทำการแจ้งเตือนลบสำเร็จออกทางหน้าจอง
\begin{sidewaysfigure}
\begin{figure}[H]
\centering
\includegraphics[width=0.8\columnwidth]
{Figures/3/Sequence/Editshop}
\caption{Sequence Diagram การแก้ไขข้อมูลร้าน }
\label{Fig:Sequence-editshop}
{Figures/3/Sequence/Shop}
\caption{Sequence Diagram การจัดการข้อมูลร้าน }
\label{Fig:Sequence-shop}
\end{figure}
\end{sidewaysfigure}
\newpage
จากภาพที่ \ref{Fig:Sequence-editshop} สามารถอธิบายแผนภาพ Sequence Diagram การแก้ไขข้อมูลร้าน ได้ดังนี้ เเมื่อเจ้าของร้านทำการ login เสร็จ เจ้าของร้านกดที่สัญลักษณ์แก้ไขระบบจะแสดงช่องให้กรอกข้อมูล และเมื่อเจ้าของร้านกรอกข้อมูลที่ต้องการแก้ไขเสร็จแล้วกดยืนยัน ระบบจะทำการเรียกใช้เมธอด updateShop() ที่คลาส EditShop ระบบจะทำการส่งข้อมูลแบบ put ไปยังคลาส api:Server ด้วยเมธอด shop จะทำการ update ข้อมูลลงฐานข้อมูล ระบบจะทำการแจ้งเตือนแก้ไขข้อมูลสำเร็จและแสดงข้อมูลแก้ไขออกทางหน้าจอ
จากภาพที่ \ref{Fig:Sequence-shop} สามารถอธิบายแผนภาพ Sequence Diagram การจัดการข้อมูลร้าน ได้ดังนี้ เเมื่อเจ้าของร้านทำการ login เสร็จ เจ้าของร้านทำการกรอกข้อมูลแล้วกดยืนยัน
ระบบจะทำการเรียกใช้เมธอด onAddShop() ที่คลาส Shop ระบบจะทำการส่งข้อมูลแบบ post ไปยังคลาส api:Server ด้วยเมธอด shop จะทำการ insert ข้อมูลลงฐานข้อมูล ระบบจะทำการแจ้งเตือนเพิ่มขข้อมูลสำเร็จและแสดงข้อมูลออกทางหน้าจอ
เมื่อต้องการแก้ไขให้กรอกข้อมูลและกดยืนยัน ระบบจะทำการเรียกใช้เมธอด updateShop() ที่คลาส Shop ระบบจะทำการส่งข้อมูลแบบ put ไปยังคลาส api:Server ด้วยเมธอด shop จะทำการ update ข้อมูลลงฐานข้อมูล ระบบจะทำการแจ้งเตือนแก้ไขข้อมูลสำเร็จและแสดงข้อมูลแก้ไขออกทางหน้าจอ
\begin{sidewaysfigure}
\begin{figure}[H]
......@@ -857,7 +862,7 @@ User Interface Design ของระบบการจองคิวร้า
shops & ข้อมูลร้าน & ตารางเก็บข้อมูลร้าน \\ \hline
Lists & ข้อมูลรายการ & ตารางเก็บข้อมูลรายการ \\ \hline
Bookings & ข้อมูลการจอง & ตารางเก็บข้อมูการจองคิว \\ \hline
Beauticians & ข้อมูลช่าง & ตารางเก็บข้อมูช่าง \\ \hline
Beauticians & ข้อมูลช่าง & ตารางเก็บข้อมูช่าง \\ \hline
Reviews & ข้อมูลรีวิว & ตารางเก็บข้อมูลรีวิว \\ \hline
Userimages & ข้อมูลรูปภาพผู้ใช้ & ตารางเก็บข้อมูลรูปภาพผู้ใช้งาน \\ \hline
Shopimages & ข้อมูลรูปภาพร้าน & ตารางเก็บข้อมูลรูปภาพร้าน \\ \hline
......@@ -903,7 +908,7 @@ User Interface Design ของระบบการจองคิวร้า
detail & text & รายละเอียด & & \\ \hline
lat & double & ตำแหน่ง x & & \\ \hline
lng& double & ตำแหน่ง y & &\\ \hline
facebook & text & เฟบุ๊ค & &\\ \hline
facebook & text & เฟบุ๊ค & &\\ \hline
userId & int(11) & รหัสผู้ใช้ &FK & Users\\ \hline
type & text & บทบาท & &\\ \hline
\end{tabular}
......
\chapter{การพัฒนาระบบ}
หลังจากที่ได้มีการเตรียมความพร้อมสำหรับการพัฒนาในด้านต่าง ไม่ว่าจะเป็นที่มาและความสำคัญของปัญหา เทคโนโลยีที่มีความเหมาะสมกับระบบ และการออกแบบระบบการทำงานรวมไปถึงโครงสร้างของข้อมูล ในบทนี้จะเป็นการพูดถึงการสร้างระบบที่ได้มีการออกแบบไว้ในบทที่แล้วจะถูกนำเสนอในบทนี้ โดยการพัฒนาระบบแบ่งได้เป็นส่วนต่าง ๆ ดังนี้
หลังจากที่ได้มีการเตรียมความพร้อมสำหรับการพัฒนาในด้านต่างๆ ไม่ว่าจะเป็นที่มาและความสำคัญของปัญหา เทคโนโลยีที่มีความเหมาะสมกับระบบ และการออกแบบระบบการทำงานรวมไปถึงโครงสร้างของข้อมูล ในบทนี้จะเป็นการพูดถึงการสร้างระบบที่ได้มีการออกแบบไว้ในบทที่แล้ว โดยการพัฒนาระบบแบ่งได้เป็นส่วนต่าง ๆ ดังนี้
\section{การพัฒนาเว็บแอปพลิเคชัน}
การพัฒนาระบบจองคิวร้านเสริมสวยสำหรับเว็บแอปพลิเคชันนั้นวัตถุประสงค์หลังเพื่อสร้างความสะดวกต่อการกำงานของผู้ใช้บริการและเจ้าของร้านอันเนื่องมาจากข้อจำกัดบางประการหากใช้ระบบทำงานบนอุปกรณ์สมาร์ทโฟนเพียงอย่างเดียว โดยตัวเว็บแอปพลิเคชันนี้ถูกพัฒนาขึ้นด้วย React.js มีรายละเอียดการทำงานดังนี้
การพัฒนาระบบจองคิวร้านเสริมสวยสำหรับเว็บแอปพลิเคชันนั้นวัตถุประสงค์หลังเพื่อสร้างความสะดวกต่อการทำงานของผู้ใช้บริการ เจ้าของร้าน และช่าง โดยเว็บแอปพลิเคชันนี้ถูกพัฒนาขึ้นด้วย React.js มีรายละเอียดการทำงานดังนี้
\subsection{การเชื่อมต่อ Cloud SQLstore}bsection,
\subsection{การเชื่อมต่อ Cloud SQLstore}
ในการเชื่อมต่อเว็บแอปพลิเคชันกับ mySQL เพื่อใช้บริการต่างๆ ของ mySQL ทำได้ดังนี้
4.1.1.1การเชื่อมต่อ Nodes กับ MySQl
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
module.exports = {
......@@ -25,6 +26,8 @@ module.exports = {
\item บรรทัดที่ 1 เป็นการส่งออกโมดูลเพื่อใช้งานในไฟล์อื่น
\item บรรทัดที่ 2 - 6 เป็นการตั้งค่าระบุตัวตนเพื่อใช้งาน my SQL
\end{itemize}
\newpage
4.1.1.2 การ connect database
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
const Sequelize = require('sequelize');
......@@ -57,7 +60,8 @@ module.exports = db
\item บรรทัดที่ 5 - 17 เป็นการตั้งค่า host เพื่อนำไปใช้งาน
\item บรรทัดที่ 19 เป็นการส่งออกโมดูลเพื่อใช้งานในไฟล์อื่น
\end{itemize}
\newpage
4.1.1.3 การกำหนด api
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
const express = require("express");
......@@ -95,8 +99,9 @@ console.log("Express server listening on port " + port);
\item บรรทัดที่ 6 เป็นการกำหนด port เพื่อใช้เชื่อ api ระหว่าง backend กับ fontend
\item บรรทัดที่ 7 เป็นการประกาศตัวแปล เพื่อเรียกใช้งาน package ตามบรรทัดที่ 1
\end{itemize}
\newpage
\subsection{โครงสร้างของการสร้างหน้าเข้าสู่ระบบ}
4.1.2.1 การสร้าง form กรอกข้อมูล
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
......@@ -133,12 +138,9 @@ console.log("Express server listening on port " + port);
}}
onChange={this.handleChange}/>
</div>
<div>
<Button variant="contained" color="primary"
<div><Button variant="contained" color="primary"
disableElevation>
เข้าสู่ระบบ
</Button>
</div>
เข้าสู่ระบบ</Button></div>
\end{lstlisting}}
\caption{การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเข้าสู่ระบบ loginPage.js}
......@@ -150,9 +152,9 @@ console.log("Express server listening on port " + port);
\item บรรทัดที่ 2-17 เป็นสร้างช่องกรอกข้อมูลอีเมล (e-mail) จากผู้ใช้
\item บรรทัดที่ 18-34 เป็นสร้างช่องกรอกข้อมูลรหัสผ่าน (password) จากผู้ใช้
\item บรรทัดที่ 34-40 สร้างปุ่มเข้าสู่ระบบ
\item บรรทัดที่ 34-37 สร้างปุ่มเข้าสู่ระบบ
\end{itemize}
4.1.2.2 การสร้าง axios เพื่อเพิ่มข้อมูลจากหน้าเว็บลงฐานข้อมูล
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
......@@ -195,7 +197,7 @@ alert("e-mail หรือรหัสผ่านไม่ถูกต้อง
\item บรรทัดที่ 14-24 เป็นการเช็คบทบาทของ user
\item บรรทัดที่ 25-28 เป็นการแจ้งเตือน error
\end{itemize}
4.1.2.3 การสร้างลอจิก(logic)การเข้าสู่ระบบ
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.login = async (req, res) => {
......@@ -233,6 +235,7 @@ exports.login = async (req, res) => {
\end{itemize}
\subsection{โครงสร้างของการสร้างหน้าหลัก}
4.1.3.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าหลัก
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
componentDidMount = async () => {
......@@ -247,7 +250,7 @@ componentDidMount = async () => {
};
\end{lstlisting}}
\caption{การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าข่าวสาร HomePage.JS}
\caption{การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าหลัก HomePage.JS}
\label{Fig:Home1}
\end{figure}
จากภาพที่ \ref{Fig:Home1} โครงสร้างของการสร้างหน้าจอข้อมูลร้าน สามารถอธิบายการทำงานได้ดังนี้
......@@ -259,6 +262,7 @@ componentDidMount = async () => {
\item บรรทัดที่ 8 เป็นการ set ค่าใน State
\end{itemize}
\newpage
4.1.3.2 การสร้างลอจิก(logic)ของหน้าหลัก
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.getShop = async (req, res) => {
......@@ -275,7 +279,7 @@ exports.getShop = async (req, res) => {
\end{lstlisting}}
\caption{การสร้างลอจิก(logic)ของหน้าข่าวสาร Shopcontroller.js}
\caption{การสร้างลอจิก(logic)ของหน้าหลัก Shopcontroller.js}
\label{Fig:HomeshopJs}
\end{figure}
จากภาพที่ \ref{Fig:HomeshopJs} โครงสร้างลอจิกของหน้าข่าวสาร สามารถอธิบายการทำงานได้ดังนี้
......@@ -285,6 +289,7 @@ exports.getShop = async (req, res) => {
\end{itemize}
\newpage
\subsection{โครงสร้างของการสร้างหน้าดูรายละเอียดร้าน}
4.1.4.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้ารายละเอียดร้าน
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
componentDidMount = async () => {
......@@ -329,7 +334,7 @@ componentDidMount = async () => {
\item บรรทัดที่ 12-19 เป็นการกำหนดช่วงเวลาที่จะให้แสดง
\item บรรทัดที่ 20-24 เป็นการ set ค่าใน state
\end{itemize}
4.1.4.2 การสร้างลอจิกของหน้าดูรายละเอียดของร้าน
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.getShopId = async (req, res) => {
......@@ -357,6 +362,7 @@ exports.getShopId = async (req, res) => {
\end{itemize}
\subsection{โครงสร้างของการสร้างหน้าโปรไฟล์}
4.1.5.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าโปรไฟล์
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
componentDidMount = async () => {
......@@ -382,6 +388,8 @@ componentDidMount = async () => {
\item บรรทัดที่ 7-9 เป็นการ set ค่า ใน state
\end{itemize}
\newpage
4.1.5.2 การสร้างลอจิกของหน้าโปรไฟล์
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.getUserById = async (req, res) => {
......@@ -411,14 +419,14 @@ exports.getUserById = async (req, res) => {
\end{itemize}
\newpage
\subsection{โครงสร้างของการสร้างหน้าเพิ่มข้อมูลร้าน}
4.1.6.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเพิ่มข้อมูลร้าน
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
<div className="row center">>
<img src={shop.image} width="60%"/>
<div className="row center">
<Button variant="outlined" color="primary">
เพิ่มรูปภาพร้าน
</Button>
เพิ่มรูปภาพร้าน</Button>
</div>
<div className="col s12 m12 l12">
<div ClassName="row">
......@@ -462,6 +470,7 @@ exports.getUserById = async (req, res) => {
\item บรรทัดที่ 16-18 ปุ่มยกเลิก
\item บรรทัดที่ 19-35 ปุ่มตกลง เมื่อกดปุ่มระบบจะบันทึกข้อมูลลงฐานข้อมูล
\end{itemize}
4.1.6.2 การสร้างลอจิกของหน้าเพิ่มข้อมูลร้าน
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.addShop = async (req, res) => {
......@@ -476,7 +485,7 @@ exports.addShop = async (req, res) => {
}
};
\end{lstlisting}}
\caption{การสร้างลอจิกของหน้าเพิ่มข้อมูล shopcontroller.js}
\caption{การสร้างลอจิกของหน้าเพิ่มข้อมูลร้าน shopcontroller.js}
\label{Fig:AddShop}
\end{figure}
จากภาพที่ \ref{Fig:AddShop} โครงสร้างลอจิกของหน้าเพิ่มข้อมูลร้าน สามารถอธิบายการทำงานได้ดังนี้
......@@ -485,15 +494,14 @@ exports.addShop = async (req, res) => {
\item บรรทัดที่ 2-6 เป็นคำสั่งที่ใช้เพิ่มข้อมูลลงฐานข้อมูล
\item บรรทัดที่ 7-10 เป็นการแสดง error เมื่อเพิ่มข้อมูลไม่ได้
\end{itemize}
\newpage
\subsection{โครงสร้างของการสร้างหน้าเพิ่มรายการ}
4.1.7.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเพิ่มรายการ
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
<div className="row">
<MaterialTable title="รายการ" columns={columns} data={data}
options={{
selection: false,
}}
options={{ selection: false,}}
editable={{
onRowAdd: (newData) =>
new Promise((resolve) => {
......@@ -502,8 +510,7 @@ exports.addShop = async (req, res) => {
let data = [...prevState.data];
data = [newData, ...data];
return { ...prevState, data };
})
}),
}) }),
onRowUpdate: (newData, oldData) =>
new Promise((resolve) => {
setTimeout(() => {
......@@ -514,8 +521,7 @@ exports.addShop = async (req, res) => {
data[data.indexOf(oldData)] = newData;
return { ...prevState, data };
});
}
}, 600);}),
} }, 600);}),
onRowDelete: (oldData) =>
new Promise((resolve) => {
setTimeout(() => {
......@@ -527,17 +533,18 @@ exports.addShop = async (req, res) => {
}); }, 600); }),}} />
</div>
\end{lstlisting}}
\caption{การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้ารายการ List.js}
\caption{การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเพิ่มรายการ List.js}
\label{Fig:ListJS}
\end{figure}
จากภาพที่ \ref{Fig:ListJS} โครงสร้างของการสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้ารายการ สามารถอธิบายการทำงานได้ดังนี้
\begin{itemize}[label={--}]
\item บรรทัดที่ 1-5 เป็นการแสดงตาราง
\item บรรทัดที่ 6-15 เป็นการเพิ่มข้อมูลในตาราง
\item บรรทัดที่ 16-27 เป็นการแก้ไขในตาราง
\item บรรทัดที่ 28-37 เป็นการลบข้อมูลในตาราง
\item บรรทัดที่ 1-3 เป็นการแสดงตาราง
\item บรรทัดที่ 4-12 เป็นการเพิ่มข้อมูลในตาราง
\item บรรทัดที่ 13-23 เป็นการแก้ไขในตาราง
\item บรรทัดที่ 23-33 เป็นการลบข้อมูลในตาราง
\end{itemize}
4.1.7.2 การสร้างลอจิกของหน้าเพิ่มรายการ
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.addlist = async (req, res) => {
......@@ -557,7 +564,7 @@ exports.addlist = async (req, res) => {
}
};
\end{lstlisting}}
\caption{การสร้างลอจิกของหน้ารายการ listcontroller.js}
\caption{การสร้างลอจิกของหน้าเพิ่มรายการ listcontroller.js}
\label{Fig:ListShop}
\end{figure}
จากภาพที่ \ref{Fig:ListShop} โครงสร้างลอจิกของหน้ารายการ สามารถอธิบายการทำงานได้ดังนี้
......@@ -566,8 +573,9 @@ exports.addlist = async (req, res) => {
\item บรรทัดที่ 2-11 เป็นการเพิ่มข้อมูลลงในฐานข้อมูล
\item บรรทัดที่ 12-15 เป็นคำสั่งแสดง error เมื่อไม่สามารถเพิ่มข้อมูลได้
\end{itemize}
\newpage
\subsection{โครงสร้างของการสร้างหน้าการจองคิว}
4.1.8.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าดูคิวจอง
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
<div className="row center">
......@@ -605,6 +613,8 @@ exports.addlist = async (req, res) => {
\item บรรทัดที่ 1-15 เป็นการสร่้างวันที่แบบคีบอร์ดเพื่อเลือกวันที่ในการแสดงข้อมูล
\item บรรทัดที่ 16-25 ตารางแสดงข้อมูลจองคิว
\end{itemize}
\newpage
4.1.8.2 การสร้างลอจิกของหน้าดูการจองคิว
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.getBooking = async (req, res) => {
......@@ -627,8 +637,9 @@ exports.getBooking = async (req, res) => {
\item บรรทัดที่ 2-9 เป็นการดึงข้อมูลจากฐานข้มูล
\end{itemize}
\newpage
\subsection{โครงสร้างของการสร้างหน้าเพิ่มข้อมูลช่าง}
4.1.9.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเพิ่มข้อมูลช่าง
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
<TextField
......@@ -639,8 +650,7 @@ exports.getBooking = async (req, res) => {
label="เบอร์โทร"
autoComplete="Phone number"
variant="outlined"
onChange={this.handleChange}
/>
onChange={this.handleChange}/>
<Button
color="primary"
autoFocus
......@@ -653,8 +663,7 @@ exports.getBooking = async (req, res) => {
name: name,
address: address,
tel: tel,
role: "2",
})
role: "2", })
const Beautician = axios
.post("http://localhost:9000/api/beautician/add", {
email: email,
......@@ -662,29 +671,24 @@ exports.getBooking = async (req, res) => {
address: address,
tel: tel,
shopID: shops.id
})
.then(response => {
}).then(response => {
console.log("สร้างผู้ใช้สำเร็จ", response);
this.handleClose();
})
.catch(error => {
}).catch(error => {
console.log(error);
});
} else {
alert("password ไม่ถูกต้อง");
}
}}
> ยืนยัน </Button>
} else {alert("password ไม่ถูกต้อง");} }} > ยืนยัน </Button>
\end{lstlisting}}
\caption{การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเพิ่มข้อมูลช่าง beautician.js}
\label{Fig:Beautician}
\end{figure}
จากภาพที่ \ref{Fig:Beautician} โครงสร้างของการสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าเพิ่มข้อมูลช่าง สามารถอธิบายการทำงานได้ดังนี้
\begin{itemize}[label={--}]
\item บรรทัดที่ 1-10 เป็นตัวอย่างการสร้าง form เพื่อกรอกข้อมูล
\item บรรทัดที่ 11-44 เป็นปุ่มยืนยัน และเพิ่มข้อมูลลงฐานข้อมูล
\item บรรทัดที่ 1-9 เป็นตัวอย่างการสร้าง form เพื่อกรอกข้อมูล
\item บรรทัดที่ 10-36 เป็นปุ่มยืนยัน และเพิ่มข้อมูลลงฐานข้อมูล
\end{itemize}
4.1.9.2 การสร้างลอจิกของหน้าสร้างกำหนดการเพิ่มข้อมูลช่าง
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
exports.add = async (req, res) => {
......@@ -716,6 +720,7 @@ exports.add = async (req, res) => {
\end{itemize}
\subsection{โครงสร้างของการสร้างหน้าอัพโหลดรูปภาพ}
4.1.10.1 การสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าอัพโหลดรูปภาพ
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
<Dialog open={open} onClose={this.handleClose}
......@@ -727,11 +732,6 @@ exports.add = async (req, res) => {
id="outlined-file-input" label="เพิ่มรูปภาพ"
type="file" autoComplete="current-password"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>),}}
onChange={(e) => {
this.setState({
file: e.target.files[0],
......@@ -764,10 +764,11 @@ exports.add = async (req, res) => {
จากภาพที่ \ref{Fig:AddimageJS} โครงสร้างของการสร้างหน้าจอส่วนติดต่อผู้ใช้ของหน้าอัพโหลดรูปภาพ สามารถอธิบายการทำงานได้ดังนี้
\begin{itemize}[label={--}]
\item บรรทัดที่ 1-4 สร้าง Dialog เพื่อเพิ่มอัพโหลดรูปภาพ
\item บรรทัดที่ 5-19 สร้าง form สำหรับอัพโหลดรูปภาพ
\item บรรทัดที่ 20-39 เป็นปุ่มเพื่อเพิ่มข้อมูลลงฐานข้อมูล
\item บรรทัดที่ 5-15 สร้าง form สำหรับอัพโหลดรูปภาพ
\item บรรทัดที่ 16-34 เป็นปุ่มเพื่อเพิ่มข้อมูลลงฐานข้อมูล
\end{itemize}
4.10.2 การสร้างลอจิกของหน้าสร้างกำหนดการอัพโหลดรูปภาพ
\begin{figure}[H]
{\setstretch{1.0}\begin{lstlisting}
const router = require('express-promise-router')();
......
\chapter{การทดสอบระบบ}
การทดสอบการทำงานของเว็บไซต์ โดยทำการทดสอบในลักษณะ Black-box Testing \cite{blackbox} หรือ Data-Driven testing ซึ่งเป็นการเทสแบบที่ไม่สนใจโปรเซส (Process) การทำงานภายในของโปรแกรมว่าทำงานอย่างไร แต่จะเน้นไปที่ Input และ Result ที่ได้มากกว่าว่าการทำงานต่าง ๆ ถูกต้องตามความต้องการ (Requirement) หรือไม่ ซึ่งการทดสอบการใช้งานเว็บแอปพลิเคชัน ได้ผลดังนี้
การทดสอบการทำงานของเว็บไซต์ โดยทำการทดสอบในลักษณะ Black-box Testing \cite{blackbox} หรือ Data-Driven testing ซึ่งเป็นการทดสอบแบบที่ไม่สนใจกระบวนการทำงานภายใน (Process) การทำงานภายในของโปรแกรมว่าทำงานอย่างไร แต่จะเน้นไปที่ Input และ Result ที่ได้มากกว่าว่าการทำงานต่าง ๆ ถูกต้องตามความต้องการ (Requirements) หรือไม่ ซึ่งการทดสอบการใช้งานเว็บแอปพลิเคชัน ได้ผลดังนี้
\section{การทดสอบการใช้งานเว็บแอปพลิเคชัน}
\subsection{การทดสอบการทำงานของผู้ใช้งาน}
\begin{itemize}
\item{การทดสอบหน้ารายละเอียดร้าน}
ในการแสดงผลหน้าจอรายละเอียดร้านนั้นจะประกอบไปด้วย ข้อมูลร้าน แผนที่ร้าน รายการร้าน การจองคิว รีวิว ผลการทดสอบดังตารางที่ \ref{tab:Shop}
\item{การทดสอบหน้าหลัก}
ในการแสดงผลหน้าจอหน้าหลักนั้นจะประกอบไปด้วย การเข้าสู่ระบบ การลงทะเบียน การค้นหา การดูรายละเอียดร้าน และการจองคิว ผลการทดสอบดังตารางที่ \ref{tab:Home}
\begin{table}[H]
\caption{ผลการทดสอบหน้ารายละเอียดร้าน}
\caption{ผลการทดสอบหน้าหลัก}
\centering
\label{tab:Shop}
\label{tab:Home}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้ารายละเอียดร้าน}
& \setstretch{1.0}{กดที่รูปภาพร้าน}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอร้านพร้อมทั้งแสดงรายการร้านทั้งหมด} \\ \cline{2-3}
& \setstretch{1.0}{กดที่แผ่นที่}
& \setstretch{1.0}{ระบบแสดงผลแผ่นที่} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มรายการ}
& \setstretch{1.0}{ระบบแสดงผลรายการ} \\ \cline{2-3}
\setstretch{1.0}{หน้าหลัก}
& \setstretch{1.0}{กดปุ่มLogin}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอเข้าสู่ระบบ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มRegister}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอลงทะเบียน} \\ \cline{2-3}
& \setstretch{1.0}{ปุ่มค้นหา}
& \setstretch{1.0}{กรอกข้อมูลและกดปุ่มค้นหา ระบบจะแสดงร้านที่ผู้ใช้ค้นหา} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มจองคิว}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอจองคิว} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มจเขียนรีวิว}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอเขียนรีวิว} \\ \hline
& \setstretch{1.0}{ระบบแสดง model เพื่อให้ผู้ใช้ทำการจอจองคิว} \\ \cline{2-3}
& \setstretch{1.0}{กดที่รูปภาพ}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอรายละเอียดร้าน} \\ \hline
\end{tabular}
\end{table}
\newpage
\item{การทดสอบหน้าเจ้าของร้าน} ในการแสดงผลหน้าจอเจ้าของร้านนั้นจะประกอบไปด้วย โปรไฟล์ ข้อมูลร้าน รายการ ข้อมูลช่าง ดูการจองคิว เพิ่มรูปภาพ ผลการทดสอบดังตารางที่ \ref{tab:datashop}
\item{การทดสอบหน้ารายละเอียดร้าน} ในการแสดงผลหน้าจอรายละเอียดร้านนั้นจะประกอบไปด้วย โปรไฟล์ ข้อมูลร้าน รายการ ข้อมูลช่าง ดูการจองคิว เพิ่มรูปภาพ ผลการทดสอบดังตารางที่ \ref{tab:shop}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าเจ้าของร้าน}
\caption{ผลการการทดสอบหน้ารายละเอียดร้าน}
\centering
\label{tab:datashop}
\label{tab:shop}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอเจ้าของร้าน}
& \setstretch{1.0}{กดปุ่มเมนูโปรไฟล์}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอโปรไฟล์ ทั้งแสดงรูปภาพโปรไฟล์} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูข้อมูลร้าน}
& \setstretch{1.0}{ระบบแสดงหน้าจอข้อมูลร้านทั้งหมด พร้แมทั้งแสดงรูปภาพร้าน} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูรายการ}
& \setstretch{1.0}{ระบบแสดงหน้ารายการ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูข้อมูลช่าง}
& \setstretch{1.0}{ระบบแสดงหน้าข้อมูลช่าง} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูดูการจองคิว}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอการจองคิว} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูเฟิ่มรูปภาพ}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอการเพิ่มรูปภาพ} \\ \hline
\setstretch{1.0}{หน้าจอรายละเอียดร้าน}
& \setstretch{1.0}{กดปุ่มรายการ}
& \setstretch{1.0}{ระบบแสดง model เพื่อให้ผู้ใช้งานดูรายการร้าน} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มจองคิว}
& \setstretch{1.0}{ระบบแสดง model เพื่อให้ผู้ใช้งานทำการจองคิว} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแผนที่}
& \setstretch{1.0}{ระบบแสดงแผ่นที่ขนาดใหญ่} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่ม facebook}
& \setstretch{1.0}{ระบบแสดงหน้า facebook ของร้าน} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเขียนรีวิว}
& \setstretch{1.0}{ระบบแสดง model เพื่อให้ผู้ใช้ทำการเขียนรีวิว} \\ \hline
\end{tabular}
\end{table}
\end{itemize}
\subsection{การทดสอบการทำงานของเจ้าของร้าน}
\begin{itemize}
\item{การทดสอบโปรไฟล์}
ในการแสดงผลหน้าจอโปรไฟล์นั้นจะประกอบไปด้วย การเพิ่มรูปภาพ การแก้ไขข้อมูล ผลการทดสอบดังตารางที่ \ref{tab:Profile}
\begin{table}[H]
\caption{ผลการทดสอบโปรไฟล์}
\centering
\label{tab:Profile}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{โปรไฟล์}
& \setstretch{1.0}{กดปุ่มเพิ่มรูปภาพ}
& \setstretch{1.0}{ระบบ model เพื่อให้เจ้าของร้านเพิ่มรูปภาพโปรไฟล์} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไขรูปภาพ}
& \setstretch{1.0}{ระบบ model เพื่อให้เจ้าของร้านแก้ไขรูปภาพโปรไฟล์} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไข}
& \setstretch{1.0}{ระบบจะทำการแก้ไขข้อมูล} \\ \hline
\end{tabular}
\end{table}
\newpage
\item{การทดสอบหน้าช่าง} ในการแสดงผลหน้าจอเจ้าของร้านนั้นจะประกอบไปด้วย โปรไฟล์ ตารางงาน เพิ่มรูปภาพ ผลการทดสอบดังตารางที่ \ref{tab:Work}
\item{การทดสอบหน้าข้อมูลร้าน} ในการแสดงผลหน้าจอข้อมูลร้านนั้นจะประกอบไปด้วย การเพิ่มรูปภาพ การแก้ไขรูปภาพ การเพิ่มข้อมูล การแก้ไขข้อมูล ผลการทดสอบดังตารางที่ \ref{tab:Datashop}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าข้อมูลร้าน}
\centering
\label{tab:Datashop}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอข้อมูลร้าน}
& \setstretch{1.0}{กดปุ่มเพิ่มรูปภาพ}
& \setstretch{1.0}{ระบบ model เพื่อให้เจ้าของร้านเพิ่มรูปภาพข้อมูลร้าน} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไขรูปภาพ}
& \setstretch{1.0}{ระบบ model เพื่อให้เจ้าของร้านแก้ไขรูปภาพข้อมูลร้าน} \\ \cline{2-3}
& \setstretch{1.0}{ปุ่มยกเลิก}
& \setstretch{1.0}{ระบบจะทำการยกเลิกการเพิ่มข้อมูล} \\ \cline{2-3}
& \setstretch{1.0}{ยืนยัน}
& \setstretch{1.0}{ระบบจะทำการยืนยันการเพิ่มข้อมูล} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไข}
& \setstretch{1.0}{ระบบจะทำการแก้ไขข้อมูล} \\ \hline
\end{tabular}
\end{table}
\item{การทดสอบหน้ารายการ} ในการแสดงผลหน้าจอรายการนั้นจะประกอบไปด้วย การเพิ่มรายการ การค้นหารายการ การแก้ไขรายการ การลบรายการ ผลการทดสอบดังตารางที่ \ref{tab:List}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าเจ้าของร้าน}
\caption{ผลการการทดสอบหน้ารายการ}
\centering
\label{tab:Work}
\label{tab:List}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอเจ้าของร้าน}
& \setstretch{1.0}{กดปุ่มเมนูโปรไฟล์}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอโปรไฟล์ ทั้งแสดงรูปภาพโปรไฟล์} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูตารางงาน}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอตารางงาน} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มเมนูเฟิ่มรูปภาพ}
& \setstretch{1.0}{ระบบแสดงผลหน้าจอการเพิ่มรูปภาพ} \\ \hline
\setstretch{1.0}{หน้าจอรายการ}
& \setstretch{1.0}{กดปุ่มบวก}
& \setstretch{1.0}{ระบบแสดงช่องให้เพิ่มรายการ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไข}
& \setstretch{1.0}{ระบบแสดงช่องแก้ไขรายการ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มค้นหา}
& \setstretch{1.0}{ระบบแสดงผลการค้นหารายการ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มลบ}
& \setstretch{1.0}{ระบบแสดงการลบรายการ} \\ \hline
\end{tabular}
\end{table}
\newpage
\item{การทดสอบหน้าข้อมูลช่าง} ในการแสดงผลหน้าจอข้อมูลช่างนั้นจะประกอบไปด้วย การเพิ่มข้อมูลช่าง การค้นหาข้อมูลช่าง การลบข้อมูลช่าง ผลการทดสอบดังตารางที่ \ref{tab:bue}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าข้อมูลช่าง}
\centering
\label{tab:bue}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอข้อมูลช่าง}
& \setstretch{1.0}{กดปุ่มบวก}
& \setstretch{1.0}{ระบบแสดงช่องให้เพิ่มข้อมูลช่าง} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มค้นหา}
& \setstretch{1.0}{ระบบแสดงผลการค้นหข้อมูลช่าง} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มลบ}
& \setstretch{1.0}{ระบบแสดงการลบข้อมูลช่าง} \\ \hline
\end{tabular}
\end{table}
\item{การทดสอบหน้าดูคิวจอง} ในการแสดงผลหน้าจอดูคิวจองนั้นจะประกอบไปด้วย การเลือกวันที่ ผลการทดสอบดังตารางที่ \ref{tab:qely}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าดูคิวจอง}
\centering
\label{tab:qely}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอดูคิวจอง}
& \setstretch{1.0}{กดเลือกวันที่}
& \setstretch{1.0}{ระบบแสดงข้อมูลการจองคิวตามวันที่ที่เลือก}
\\ \hline
\end{tabular}
\end{table}
\item{การทดสอบหน้าเพิ่มรูปภาพร้าน} ในการแสดงผลหน้าจอเพิ่มรูปภาพร้านนั้นจะประกอบไปด้วย การเพิ่มรูปภาพร้าน การลบรูปภาพร้าน ผลการทดสอบดังตารางที่ \ref{tab:Shoppic}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าเพิ่มรูปภาพร้าน}
\centering
\label{tab:Shoppic}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอเพิ่มรูปภาพร้าน}
& \setstretch{1.0}{กดปุ่มบวก}
& \setstretch{1.0}{ระบบแสดง model เพื่อให้เจ้าของร้านเพิ่มรูปภาพ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มลบ}
& \setstretch{1.0}{ระบบแสดงการลบรูปภาพ} \\ \hline
\end{tabular}
\end{table}
\end{itemize}
\newpage
\subsection{การทดสอบการทำงานของช่าง}
\begin{itemize}
\item{การทดสอบโปรไฟล์}
ในการแสดงผลหน้าจอโปรไฟล์นั้นจะประกอบไปด้วย การเพิ่มรูปภาพ การแก้ไขข้อมูล ผลการทดสอบดังตารางที่ \ref{tab:BProfile}
\begin{table}[H]
\caption{ผลการทดสอบโปรไฟล์}
\centering
\label{tab:BProfile}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{โปรไฟล์}
& \setstretch{1.0}{กดปุ่มเพิ่มรูปภาพ}
& \setstretch{1.0}{ระบบ model เพื่อให้ช่างเพิ่มรูปภาพโปรไฟล์} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไขรูปภาพ}
& \setstretch{1.0}{ระบบ model เพื่อให้ช่างแก้ไขรูปภาพโปรไฟล์} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มแก้ไข}
& \setstretch{1.0}{ระบบจะทำการแก้ไขข้อมูล} \\ \hline
\end{tabular}
\end{table}
\item{การทดสอบหน้าตารางงาน} ในการแสดงผลหน้าจอตารางงานนั้นจะประกอบไปด้วย การเลือกวันที่ ผลการทดสอบดังตารางที่ \ref{tab:work}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าตารางงาน}
\centering
\label{tab:work}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอตารางงาน}
& \setstretch{1.0}{กดเลือกวันที่}
& \setstretch{1.0}{ระบบแสดงข้อมูลการจองคิวตามวันที่ที่เลือก}
\\ \hline
\end{tabular}
\end{table}
\item{การทดสอบหน้าเพิ่มรูปภาพผลงานช่าง} ในการแสดงผลหน้าจอเพิ่มรูปภาพผลงานช่างร้านนั้นจะประกอบไปด้วย การเพิ่มรูปภาพร้าน การลบรูปภาพร้าน ผลการทดสอบดังตารางที่ \ref{tab:Bpic}
\begin{table}[H]
\caption{ผลการการทดสอบหน้าเพิ่มรูปภาพผลงานช่าง}
\centering
\label{tab:Bpic}
\begin{tabular}{ | p{4.5cm} | p{4.5cm} | p{4.5cm} | }
\hline
% {\setstretch{1.0} }
{\multicolumn{1}{c}{\centering การทำงาน}} &
{\multicolumn{1}{c}{\centering เงื่อนไขการทดสอบ}} & {\multicolumn{1}{c}{\centering ผลการทดสอบ}} \\ \hline
\setstretch{1.0}{หน้าจอเพิ่มรูปภาพผลงานช่าง}
& \setstretch{1.0}{กดปุ่มบวก}
& \setstretch{1.0}{ระบบแสดง model เพื่อให้ช่างเพิ่มรูปภาพ} \\ \cline{2-3}
& \setstretch{1.0}{กดปุ่มลบ}
& \setstretch{1.0}{ระบบแสดงการลบรูปภาพ} \\ \hline
\end{tabular}
\end{table}
\end{itemize}
\chapter{สรุปและข้อเสนอแนะ}
การดำเนินโครงงานเพื่อพัฒนาระบบจองคิวร้านเสริมสวยนี้ พบว่าระบบสามารถทำงานได้ตามที่วิเคราะห์และออกแบบไว้ แต่ก็พบปัญหาและอุปสรรคระหว่างการพัฒนา ในบทนี้ผู้พัฒนาจึงขอสรุปความสามารถของระบบ ชี้แจงปัญหาและอุปสรรค พร้อมเสนอแนวทางในการพัฒนาระบบจองคิวร้านเสริมสวย ต่อ ตามลำดับ
การดำเนินโครงงานเพื่อพัฒนาระบบจองคิวร้านเสริมสวยนี้ พบว่าระบบสามารถทำงานได้ตามที่วิเคราะห์และออกแบบไว้ แต่ก็พบปัญหาและอุปสรรคระหว่างการพัฒนา ในบทนี้ผู้พัฒนาจึงขอสรุปความสามารถของระบบ ชี้แจงปัญหาและอุปสรรค พร้อมเสนอแนวทางในการพัฒนาระบบจองคิวร้านเสริมสวย ตามลำดับ
\section{สรุปความสามารถของระบบ}
ระบบจองคิวร้านเสริมสวย เว็บแอปพลิเคชันสามารถสรุปความสามารถที่ระบบทำได้ดังนี้
......@@ -51,11 +51,13 @@
\section{แนวทางการพัฒนาต่อ}
\begin{enumerate}
\item การพัฒนาช่องทางการติดต่อ
\item เจ้าของร้านสามารถยืนยันการจองคิวได้
\item การพัฒนาเป็นแอปพลิเคชัน
\item การเพิ่มข้อมูลโปรดมชั่น
\item การค้นหาร้านตามวันและเวลาว่างของร้าน
\item การพัฒนาช่องทางการติดต่อ ให้ผู้ใช้บริการสามารถติดต่อกับเจ้าของร้านได้
\item เจ้าของร้านสามารถยืนยันการจองคิวได้ เจ้าของร้านสามารถยืนยัน
\item ดูประวัติการจองคิว ผู้ใช้บริการสมารถดูประวัติการจองคิวของตัวเองได้
\item การพัฒนาเป็นแอปพลิเคชัน พัฒนาเป็บแอปพลิเคชันสำหรับ Android และ Ios
\item การเพิ่มข้อมูลโปรโมชั่น เจ้าของร้านสามารถเพิ่มโปรโมชั่นของร้านได้
\item แจ้งเตือนการจองคิว แจ้งเตือนการจองคิวบอกผู้ใช้บริการถึงสถานะการจองคิว
\item การเปลี่ยนวิธีการเลือกช่าง เลือกร้านและรายการ
\end{enumerate}
......
......@@ -77,7 +77,7 @@
\CommitteeBlockAdvisor
\CommitteeBlockCoAdvisor
\CommitteeBlock{กรรมการ}{อาจารย์ วาโย ปุยะติ}
\CommitteeBlock{กรรมการ}{อาจารย์ วาสนา เหง้าเกษ }
\CommitteeBlock{กรรมการ}{ดร.เกรียงศักดิ์ ตรีประพิณ}
}
%%-------------------------------------------------------------------------------
......
Document/Latex/Figures/3/DB/ER.png

254 KB | W: | H:

Document/Latex/Figures/3/DB/ER.png

254 KB | W: | H:

Document/Latex/Figures/3/DB/ER.png
Document/Latex/Figures/3/DB/ER.png
Document/Latex/Figures/3/DB/ER.png
Document/Latex/Figures/3/DB/ER.png
  • 2-up
  • Swipe
  • Onion skin
Document/Latex/Figures/3/UIWeb/bpic.png

46.2 KB | W: | H:

Document/Latex/Figures/3/UIWeb/bpic.png

22.2 KB | W: | H:

Document/Latex/Figures/3/UIWeb/bpic.png
Document/Latex/Figures/3/UIWeb/bpic.png
Document/Latex/Figures/3/UIWeb/bpic.png
Document/Latex/Figures/3/UIWeb/bpic.png
  • 2-up
  • Swipe
  • Onion skin
Document/Latex/Figures/3/UIWeb/list.png

51.2 KB | W: | H:

Document/Latex/Figures/3/UIWeb/list.png

23.7 KB | W: | H:

Document/Latex/Figures/3/UIWeb/list.png
Document/Latex/Figures/3/UIWeb/list.png
Document/Latex/Figures/3/UIWeb/list.png
Document/Latex/Figures/3/UIWeb/list.png
  • 2-up
  • Swipe
  • Onion skin
Document/Latex/Figures/3/UIWeb/qely.png

57.6 KB | W: | H:

Document/Latex/Figures/3/UIWeb/qely.png

29.8 KB | W: | H:

Document/Latex/Figures/3/UIWeb/qely.png
Document/Latex/Figures/3/UIWeb/qely.png
Document/Latex/Figures/3/UIWeb/qely.png
Document/Latex/Figures/3/UIWeb/qely.png
  • 2-up
  • Swipe
  • Onion skin
Document/Latex/Figures/3/UIWeb/work.png

51.2 KB | W: | H:

Document/Latex/Figures/3/UIWeb/work.png

26.3 KB | W: | H:

Document/Latex/Figures/3/UIWeb/work.png
Document/Latex/Figures/3/UIWeb/work.png
Document/Latex/Figures/3/UIWeb/work.png
Document/Latex/Figures/3/UIWeb/work.png
  • 2-up
  • Swipe
  • Onion skin
<mxfile userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36" version="8.5.12" editor="www.draw.io" type="device"><diagram id="120ff508-374b-bf88-8e7a-aaa9685ebcdc" name="Page-1">5VlLk+I2EP41PoayJD+PGGayqUqqpjKH7B4F1oBrbcuxxcDk16eFJduSzYZQwLK1cxmp1Xp9/fVDxiGL4vBrTavtHzxluYPd9OCQpYMx8nAA/6Tko5WE2G0FmzpLlVIveM3+YUqo1XZZyhpDUXCei6wyhWtelmwtDBmta7431d54bu5a0Q0bCV7XNB9L/8pSsW2lEQ57+SeWbbZ6ZxTE7ciKrr9uar4r1X4OJm/Hv3a4oHotddFmS1O+H4jIk0MWNeeibRWHBcslthq2dt7zidHu3DUrxTkT1LHfab5j+sTHc4kPjcV+mwn2WtG17O/B3g5JtqLIoYegSZuqtcBbdmCwavLGS/FMiyyXtv/E8ncmsjWFAbUVqwU7nDwu6kAAcjFeMFF/gMp+aAWF3XZggU5IleU33dz+9tBQAJxAD0+gEeSwRbKCxkYcL9EK5C0NnIK/d1wP/NIcGT0HBexVh36wW+XJdZLImS91w1eNBKlGDJZxdTuxR6Ol3UiwrTwneqhbx9MS3ZjHeqjViZ1ooSW6kcR6SCtHcy2J9DqhbnSIAdgtRiZuIB5gaVENiCFMdt2ETGoCJoozKkghv+0OuEY0JYZcI9egGvlBHK8L6SZWsTvCyp/yS/8qfumPwFoyIS8Pm3Gx53U6Ag9WgGQBneQ/YLwlal5gMcwbw4Ymw9k1UItGqM3XgtcjqCAHVbK5K/JWgSTy1oBC/jtdsfyFN5nIeAkqKy4EL0AhlwNJl+wWPJfzln2669eY59lGzhXcQp7vRJ6VbNHlcPfG5vBNa2B/wuEnjBFcwRjayEN/t8N0F/pl3vitrHYyeNJColaummoqYD4Ey0lo4krcO7JcV5oDYF8FhfILajF5xgJq08eEzU4/eAo2bwK28BqwTRR+io+j5B7p5B5huxLoCxhkVgtdSQPK3qgywTbl1RagrGuVKBnVKmRUojzbs/o6ZDkx6yFp4FlRqcsZ96ABmQhLOuaocm3Yy8p1vpP+pIQbQ2W6pmMpvKxUt+QlM5FlZTqX7zXo8oqVE2mjSzkpbbayzjnOO5F/1AYn7cUOmfgsM83MV70v3UEAusGQ7H5ROam9k7zIt20M9+a7es3MkC9ovWFi6HbffN4gbf+a5VRk7+aeU8ZWy73w7Pgk0eEliExe2dG2PauaNXwiWgt5rhXeQ99cqL3gaKEj97o7nkfHcVX8J2sqXjaPGsPR93Re7yd13oHrDrz189CT+7ELnTceO2/Lzrs4L4ksn4ssnzvXee2FvPh2zhv+LzrCU5/JMu1qbGzgKsLi44ChasJRS31whMf93fKNG2ODthGJVP+F1RnAzGpN7btkonuSGfkz36JzOLuQ0JCNZihGgRcGbkgCK8kRL55FLmiFhMSx7TRX5PoZ303Pjp1q8CQRT7OrY8o47GnmIYN1xPo7QcHLaKY/OBgFT3A3msXWc8ouU85lGA7sese9FY28qfL7/jR6EKq4340ql2ZXZGVXjG8WcTz0kxZ7s9gNDWIiPzwZ+9BVWaq/LQ1Z2n6Lvk/etL6rYXJpzrQjGroWTaHb/1bZqvc/CJOnfwE=</diagram></mxfile>
\ No newline at end of file
Document/Latex/Figures/3/usecase.png

362 KB | W: | H:

Document/Latex/Figures/3/usecase.png

368 KB | W: | H:

Document/Latex/Figures/3/usecase.png
Document/Latex/Figures/3/usecase.png
Document/Latex/Figures/3/usecase.png
Document/Latex/Figures/3/usecase.png
  • 2-up
  • Swipe
  • Onion skin
\begin{thaiabstract}
การเสริมสวยเป็นที่นิยมและเป็นธุรกิจที่ได้รับความนิยมจากลูกค้าเป็นจำนวนมากในปัจจุบัน โดยปกติเมื่อลูกค้าต้องการใช้บริการจะมาที่ร้านโดยไม่ได้นัดหมาย ซึ่งปัญหาคือร้านมีลูกค้าที่ใช้บริการอยู่ในขณะนั้นทำให้ต้องผู้ที่เข้ามาโดยไม่ได้นัดต้องรอคิวหรือถ้ามีลูกค้ากำลังรอรับบริการอยู่เป็นจำนวนมากอาจทำให้ต้องมาใช้บริการในวันอื่นแทน แม้ในบางกรณีที่ลูกค้าโทรมาสอบถามเพื่อทำการจองคิวล่วงหน้า แต่ช่างไม่สะดวกรับโทรศัพท์เนื่องจากกำลังให้บริการลูกค้าคนอื่นอยู่
ดังนั้นผู้พัฒนาจึงมีแนวคิดสร้างเว็บแอปพลิเคชันระบบการจองคิวร้านเสริมสวยขึ้น เพื่อช่วยจัดการปัญหาดังกล่าว ระบบนี้ถูกพัฒนาด้วย React framework , material ui , nodejs และ MySql โดยระบบสามารถให้ลูกค้าทำการจองคิว ดูคิวว่างของร้านเสริมสวย หาตำแหน่งของร้าน ดูข้อมูลทั่วไปของร้าน และเขียนรีวิวติชมได้ ในส่วนของเจ้าของร้าน สามารถเพิ่มข้อมูลทั่วไปของร้าน และจัดการการจองคิวของร้านเสริมสวยได้ ระบบการจองคิวรองรับการแสดงผลบนอุปกรณ์สมาร์ทโพนและเว็บบราวเซอร์
ดังนั้นผู้พัฒนาจึงมีแนวคิดสร้างเว็บแอปพลิเคชันระบบการจองคิวร้านเสริมสวยขึ้น เพื่อช่วยจัดการปัญหาดังกล่าว ระบบนี้ถูกพัฒนาด้วย React framework material ui nodejs และ MySql โดยระบบสามารถให้ลูกค้าทำการจองคิว ดูคิวว่างของร้านเสริมสวย หาตำแหน่งของร้าน ดูข้อมูลทั่วไปของร้าน และเขียนรีวิวติชมได้ ในส่วนของเจ้าของร้าน สามารถเพิ่มข้อมูลทั่วไปของร้าน และจัดการการจองคิวของร้านเสริมสวยได้ ระบบการจองคิวรองรับการแสดงผลบนอุปกรณ์สมาร์ทโพนและเว็บบราวเซอร์
ระบบที่พัฒนาขึ้นจะช่วยอำนวยความสะดวกในการนัดหมายล่วงหน้า ลดการรอคิวของผู้ใช้บริการ และช่วยให้การจองคิวมีระเบียบมากขึ้น
......
\begin{acknowledgements} %TODO update here!
การพัฒนาโครงงานระบบจองคิวร้านเสริมสวย สำเร็จลุล่วงได้ด้วยความกรุณาแลความช่วยเหลือจากหลายๆ ท่าน ข้าพเจ้าขอขอพระคุณทุกท่าน ที่มีส่วนร่วมในการพัฒนาโครงงานนี้
ขอขอบพระคุณอาจารย์ ดร ทศพร จูฉิม อาจารย์ที่ปรึกษาโครงงานที่ได้แนะนำทฤษฎีและแนวทางในแก้ปัญหาต่าง ๆ ที่เกิดขึ้นระหว่างการพัฒนาระบบ อีกครั้งยังคอยตรวจสอบความก้าวหน้าของการทำงานเป็นระยะ ๆ รวมทั้งสร้างกำลังใจให้ผู้พัฒนาอยู่เสมอ
ขอขอบพระคุณอาจารย์ ดร.ทศพร จูฉิม อาจารย์ที่ปรึกษาโครงงานที่ได้แนะนำทฤษฎีและแนวทางในแก้ปัญหาต่าง ๆ ที่เกิดขึ้นระหว่างการพัฒนาระบบ อีกครั้งยังคอยตรวจสอบความก้าวหน้าของการทำงานเป็นระยะ ๆ รวมทั้งสร้างกำลังใจให้ผู้พัฒนาอยู่เสมอ
ขอบพระคุณอาจารย์ประจำสาขาวิทยาการคอมพิวเตอร์ อาจารย์ประจำภาควิชาคณิตศาสตร์ สถิติ และคอมพิวเตอร์ และอาจารย์ในคณะวิทยาศาสตร์ทุก ๆ ท่าน ที่คอยให้คำแนะนำ อบรมสั่งสอน และคอยช่วยเหลือข้าพเจ้าในการศึกษาตลอดมาขอบคุณเจ้าหน้าที่และบุคลากรของคณะวิทยาศาสตร์ ที่ได้อำนวยความสะดวกทางด้านอุปกรณ์และเครื่องมือต่าง ๆ
......@@ -15,5 +15,5 @@
\begin{flushright}
นางสาวปิยพร อาภรศรี
\\
17 มีนาคม 62
17 มีนาคม 63
\end{flushright}
\chapter{การติดตั้งเครื่องมือที่ใช้พัฒนาโปรแกรม}
การติดตั้งเครื่องมือที่ใช้ในการพัฒนาระบบสหกิจศึกษา คณะวิทยาศาสตร์ มหาวิทยาลัยอุบลราชธานี มีโปรแกรมที่จำเป็นในการพัฒนาระบบดังต่อไปนี้
การติดตั้งเครื่องมือที่ใช้ในการพัฒนาระบบจองคิดร้านเสริมสวย มีโปรแกรมที่จำเป็นในการพัฒนาระบบดังต่อไปนี้
\begin{itemize}
\item การติดตั้ง Node.js
\item การติดตั้ง React.js
......@@ -22,7 +22,7 @@
\label{Fig:nodeInstall2}
\end{figure}
\item แสดงหน้าต่างตอนรับของ Node.js ให้กด Next แสดงดังรูปที่ \ref{Fig:nodeInstall3}
\item แสดงหน้าต่างตอนรับของ Node.js ให้กดปุ่ม Next แสดงดังรูปที่ \ref{Fig:nodeInstall3}
\begin{figure}[H]
\centering
\includegraphics[width=10cm]{Figures/7/3}
......@@ -30,7 +30,7 @@
\label{Fig:nodeInstall3}
\end{figure}
\newpage
\item แสดงหน้าต่างข้อตกลงในการใช้ Node.js ให้เลือกช่อง I accept the terms in the License Agreement และกด Next แสดงดังรูปที่ \ref{Fig:nodeInstall4}
\item แสดงหน้าต่างข้อตกลงในการใช้ Node.js ให้เลือกช่อง I accept the terms in the License Agreement และกดปุ่ม Next แสดงดังรูปที่ \ref{Fig:nodeInstall4}
\begin{figure}[H]
\centering
\includegraphics[width=10cm]{Figures/7/4}
......@@ -46,7 +46,7 @@
\label{Fig:nodeInstall5}
\end{figure}
\newpage
\item แสดงหน้าต่างสำหรับติดตั้ง Node.js ให้กด Install เพื่อทำงานติดตั้ง แสดงดังรูปที่ \ref{Fig:nodeInstall6}
\item แสดงหน้าต่างสำหรับติดตั้ง Node.js ให้กดปุ่ม Install เพื่อทำงานติดตั้ง แสดงดังรูปที่ \ref{Fig:nodeInstall6}
\begin{figure}[H]
\centering
\includegraphics[width=10cm]{Figures/7/6}
......@@ -65,15 +65,17 @@
\caption{คำสั่งสำหรับติดตั้ง React.js}
\label{Fig:reactinstall}
\end{figure}
\newpage
\section{การติดตั้ง Visual Studio Code}
\begin{enumerate}
\item สามารถดาวน์โหลด Visual Studio Code ได้ที่ https://code.visualstudio.com/download ดังแสดงในรูปที่ \ref{Fig:vscode}
\begin{figure}[H]
\includegraphics[width=\columnwidth]{Figures/prepareation/vscode}
\centering
\includegraphics[width=0.7\columnwidth]{Figures/prepareation/vscode}
\caption{หน้าเว็บดาวน์โหลด Visual Studio Code}
\label{Fig:vscode}
\end{figure}
\newpage
\item เมื่อเปิดตัวติดตั้งขึ้นมาแล้ว จะแสดงหน้าจอ Welcome to the Visual Studio Code Setup Wizard ให้กดปุ่ม Next เพื่อเริ่มกระบวนการติดตั้ง ดังแสดงในรูปที่ \ref{Fig:vsi1}
\begin{figure}[H]
\centering
......@@ -81,16 +83,16 @@
\caption{หน้าต่างต้อนรับของ Visual Studio Code}
\label{Fig:vsi1}
\end{figure}
\item หลังจากนั้นจะแสดงหน้าต่างข้อตกลงการใช้งาน Visual Studio Code ทำการติ๊กที่ I accept the areement แล้วกด Next ดังแสดงในรูปที่ \ref{Fig:vsi2}
\newpage
\item หลังจากนั้นจะแสดงหน้าต่างข้อตกลงการใช้งาน Visual Studio Code ทำการเลือกที่ I accept the areement แล้วกดปุ่ม Next ดังแสดงในรูปที่ \ref{Fig:vsi2}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/prepareation/vsi2}
\caption{หน้าต่างข้อตกลงการใช้งาน Visual Studio Code}
\label{Fig:vsi2}
\end{figure}
\newpage
\item จากนั้นจะแสดงหน้าต่างที่จัดเก็บไฟล์ต่างๆ ของ Visual Studio Code ทำการกด Next ดังแสดงในรูปที่ \ref{Fig:vsi3}
\item จากนั้นจะแสดงหน้าต่างที่จัดเก็บไฟล์ต่างๆ ของ Visual Studio Code ทำการกดปุ่ม Next ดังแสดงในรูปที่ \ref{Fig:vsi3}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/prepareation/vsi3}
......@@ -98,27 +100,27 @@
\label{Fig:vsi3}
\end{figure}
\item จากนั้นจะแสดงหน้าต่างการจัดการซอร์ดคัทของ Visual Studio Code ทำการกด Next ดังแสดงในรูปที่ \ref{Fig:vsi4}
\item จากนั้นจะแสดงหน้าต่างการจัดการซอร์ดคัทของ Visual Studio Code ทำการกดปุ่ม Next ดังแสดงในรูปที่ \ref{Fig:vsi4}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/7/vsi4}
\includegraphics[width=0.7\columnwidth]{Figures/prepareation/vsi4}
\caption{หน้าต่างการจัดการซอร์ดคัท ของ Visual Studio Code}
\label{Fig:vsi4}
\end{figure}
\newpage
\item จากนั้นแสดงหน้าต่างเริ่มทำการติดตั้งทำการกด Next ดังแสดงในรูปที่ \ref{Fig:vsi5}
\item จากนั้นแสดงหน้าต่างเริ่มทำการติดตั้งกดปุ่ม Next ดังแสดงในรูปที่ \ref{Fig:vsi5}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/prepareation/vsi5}
\caption{หน้าต่างเริ่มทำการติดตั้งทำการกด ของ Visual Studio Code}
\caption{หน้าต่างเริ่มทำการติดตั้งของ Visual Studio Code}
\label{Fig:vsi5}
\end{figure}
\item จากนั้นจะแสดงหน้าต่างเมื่อเข้าโปรแกรมหลังติตั้งเสร็จ ดังแสดงในรูปที่ \ref{Fig:vsi6}
\item จากนั้นจะแสดงหน้าต่างเมื่อเข้าโปรแกรมหลังติตั้งเสร็จ ดังแสดงในรูปที่ \ref{Fig:vsi6}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/prepareation/vsi6}
\caption{ หน้าต่างเมื่อเข้าโปรแกรมหลังติตั้งเสร็จ ของ Visual Studio Code}
\caption{ หน้าต่างเมื่อเข้าโปรแกรมหลังติตั้งเสร็จ ของ Visual Studio Code}
\label{Fig:vsi6}
\end{figure}
\end{enumerate}
......@@ -16,7 +16,7 @@
\item หมายเลข 2 คือ ปุ่มลงทะเบียนสำหรับผู้ใช้งานทั่วไป
\item หมายเลข 3 คือ ปุ่มลงทะเบียนสำหรับเจ้าของร้าน
\item หมายเลข 4 คือ ฟอร์มสำหรับค้นหาร้าน
\item หมายเลข 5 คือ คลิ๊กที่รูปภาพเพื่อนเปิดหน้าต่างรายละเอียดร้าน
\item หมายเลข 5 คือ เลือกที่รูปภาพเพื่อเปิดหน้าต่างรายละเอียดร้าน
\item หมายเลข 6 คือ ปุ่มสำหรับจองคิว
\end{itemize}
\item เมื่อผู้ใช้งานกดที่ปุ่ม Register ระบบจะทำการแสดงหน้าต่างลงทะเบียน ดังแสดงในรูปที่ \ref{Fig:register}
......@@ -142,7 +142,7 @@
\end{itemize}
\item เมื่อเจ้าของร้านคลิ๊กที่เมนูข้อมูลร้าน ระบบจะแสดงหน้าข้อมูลร้าน ดังแสดงในรูปที่ \ref{Fig:datashop}
\item เมื่อเจ้าของร้านเลือกที่เมนูข้อมูลร้าน ระบบจะแสดงหน้าข้อมูลร้าน ดังแสดงในรูปที่ \ref{Fig:datashop}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/7/Manual/datashop}
......@@ -158,7 +158,7 @@
\end{itemize}
\item เมื่อเจ้าของร้านคลิ๊กที่เมนูรายการ ระบบจะแสดงหน้ารายการ ดังแสดงในรูปที่ \ref{Fig:list}
\item เมื่อเจ้าของร้านเลือกที่เมนูรายการ ระบบจะแสดงหน้ารายการ ดังแสดงในรูปที่ \ref{Fig:list}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/7/Manual/list}
......@@ -184,7 +184,7 @@
\item หมายเลข 1 คือ กดปุ่มเพื่อเพิ่มข้อมูลช่าง
\end{itemize}
\item เมื่อเจ้าของร้านคลิ๊กที่เมนูดูคิวจอง ระบบจะแสดงหน้าข้อมูลการจองคิว ดังแสดงในรูปที่ \ref{Fig:shopqely}
\item เมื่อเจ้าของร้านเลือกที่เมนูดูคิวจอง ระบบจะแสดงหน้าข้อมูลการจองคิว ดังแสดงในรูปที่ \ref{Fig:shopqely}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/7/Manual/shopqely}
......@@ -196,7 +196,7 @@
\item หมายเลข 1 คือ เลือกวันที่ที่ต้องการดูการจองคิว
\end{itemize}
\item เมื่อเจ้าของร้านคลิ๊กที่เมนูเพิ่มรูปภาพ ดังแสดงในรูปที่ \ref{Fig:shoppic}
\item เมื่อเจ้าของร้านเลือกที่เมนูเพิ่มรูปภาพ ดังแสดงในรูปที่ \ref{Fig:shoppic}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/7/Manual/shoppic}
......@@ -237,7 +237,7 @@
\end{itemize}
\item เมื่อช่างคลิ๊กที่เมนูตารางงาน ระบบจะแสดงหน้าดูตารางงาน ดังแสดงในรูปที่\ref{Fig:work}
\item เมื่อช่างเลือกที่เมนูตารางงาน ระบบจะแสดงหน้าดูตารางงาน ดังแสดงในรูปที่\ref{Fig:work}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\columnwidth]{Figures/7/Manual/work}
......@@ -250,7 +250,7 @@
\end{itemize}
\item เมื่อช่างคลิ๊กที่เมนูภาพผลงาน ระบบจะแสดงหน้าเพิ่มผลงานช่าง ดังแสดงในรูปที่ \ref{Fig:bpic}
\item เมื่อช่างเลือกที่เมนูภาพผลงาน ระบบจะแสดงหน้าเพิ่มผลงานช่าง ดังแสดงในรูปที่ \ref{Fig:bpic}
\begin{figure}[H]
\centering
\includegraphics[width=0.5\columnwidth]{Figures/7/Manual/bpic}
......
......@@ -2,12 +2,12 @@
\justify
ชื่อ-สกุล: นางสาวปิยพร อาภรศรี \\
รหัสประจำตัวนักศึกษา: 59110440259\\
วัดเกิด: 12 06 2540\\
วัดเกิด: 12 มิ.ย. 2540\\
ที่อยู่ที่สามารถติดต่อได้: 7 ม.1 ต.หนองบก อ.เหล่าเสือโก้ก จ.อุบลราชธานี 34000\\
เบอร์โทรศัพท์: (+66) 99 468 2013\\
อิเมลล์: piyaphorn.ar.59@ubu.ac.th\\
ระดับมัธยมต้น: โรงเรียนหกสิบพรรษาวิทยาคมอุบลราชธานี จังหวัดอุบลราชธานี\\
ระดับมัธยมปลาย: โรงเรียนหกสิบพรรษาวิทยาคมอุบลราชธานี จังหวัดอุบลราชธานี\\
ระดับอุดมศึกษา: ภาควิชาคณิตศาสตร์ สถิติ และคอมพิวเตอร์ สาขาวิทยาการ คอมพิวเตอร์ คณะวิทยาศาสตร์ มหาวิทยาลัยอุบลราชธานี
ระดับอุดมศึกษา: ภาควิชาคณิตศาสตร์ สถิติ และคอมพิวเตอร์ สาขาวิทยาการคอมพิวเตอร์ คณะวิทยาศาสตร์ มหาวิทยาลัยอุบลราชธานี
\end{biography}}
......@@ -167,4 +167,15 @@
url = {https://goo.gl/6ZhGQo},
note = {12 พฤษภาคม 2561}
}
@misc{blackbox,
author = {{Atthaboon S.}},
title = {BLACK-BOX TESTING STRATEGY
},
url = {http://everybitsconsult.com/blog/2015/06/22/black-box-testing.html},
date = {},
month = {},
year = {2555},
note = {20 พฤษภาคม 2561}
}
const Beautician = require("../../models/beautician");
const config = require("../../config");
exports.add = async (req, res) => {
try {
......@@ -34,15 +35,31 @@ exports.getbeauticianShopId = async (req, res) => {
});
}
};
exports.deletelist = async (req, res) => {
exports.getbeauticianName = async (req, res) => {
try {
const listId = req.params.listId;
await List.destroy({
const name = req.params.name;
let beautician = await Beautician.findAll({
where: {
id: listId
name:name
}
});
console.log('beautician', beautician)
res.status(200).send(beautician);
} catch (err) {
res.send({
error: err.message
});
}
};
exports.deletebeautician = async (req, res) => {
try {
const beauticianId = req.params.id;
await Beautician.destroy({
where: {
id: beauticianId,
},
});
res.status(200).send("success");
} catch (err) {
console.log(err);
......
......@@ -15,13 +15,72 @@ exports.addbooking = async (req, res) => {
exports.getBooking = async (req, res) => {
try {
let booking = await Booking.findAll();
res.status(200).send("success");
res.status(200).send("booking");
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
exports.getBookingShopid = async (req, res) => {
try {
const shop = req.params.shop;
let booking = await Booking.findAll({
where: {
shop: shop,
},
});
res.status(200).send(booking);
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
exports.getBookingDate = async (req, res) => {
try {
const date = req.params.date;
let booking = await Booking.findAll({
where: {
date: date,
},
});
res.status(200).send(booking);
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
exports.searchBooking= async (req, res) => {
const date = req.body.date;
console.log("name", name);
if (name !== undefined) {
console.log("called1");
var condition = name ? { name: { [Op.like]: `%${name}%` } } : null;
const datasearch = await Booking.findAll({
where: condition,
});
res.status(200).send(datasearch);
} else {
console.log("called2");
const datasearch = await Booking.findAll({
});
res.status(200).send(datasearch);
}
};
exports.getBookingbeautician = async (req, res) => {
try {
const beautician= req.params.beautician;
let booking = await Booking.findAll({
where: {
beautician: beautician,
},
});
res.status(200).send(booking);
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
......@@ -81,7 +81,7 @@ exports.getListshop = async (req, res) => {
}
};
exports.updateList = async (req, res) => {
exports.updateListId = async (req, res) => {
try {
const listData = req.body;
const id = req.params.id;
......
......@@ -15,7 +15,7 @@ exports.addreview = async (req, res) => {
exports.getReview = async (req, res) => {
try {
let review = await Review.findAll();
res.status(200).send("success");
res.status(200).send(review);
} catch (err) {
console.log(err);
res.sendStatus(401);
......@@ -39,13 +39,13 @@ exports.deleteReview = async (req, res) => {
exports.getReviewShopid = async (req, res) => {
try {
const shop = req.params.shop;
let review = await Review.findOne({
const shop= req.params.shop;
let review = await Review.findAll({
where: {
id: shop
shop: shop
}
});
res.status(200).send("success");
res.status(200).send(review);
} catch (err) {
console.log(err);
res.sendStatus(401);
......
const db = require('../../db');
const Op = db.Sequelize.Op;
const Shop = require("../../models/shop");
exports.addShop = async (req, res) => {
......@@ -40,9 +44,28 @@ exports.deleteshop = async (req, res) => {
exports.getShopId = async (req, res) => {
try {
const shopId = req.params.id;
let shop = await Shop.findOne({
if(Shop){
let shop = await Shop.findOne({
where: {
id: shopId,
},
});
res.status(200).send(shop);
}else{
res.status(200).send();
}
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
exports.getShopBytype = async (req, res) => {
try {
const type = req.params.type;
let shop = await Shop.findAll({
where: {
id: shopId,
type: type,
},
});
res.status(200).send(shop);
......@@ -51,7 +74,6 @@ exports.getShopId = async (req, res) => {
res.sendStatus(401);
}
};
exports.updateShop = async (req, res) => {
try {
const shopData = req.body;
......@@ -68,7 +90,22 @@ exports.getUserId = async (req, res) => {
const userId = req.params.userId;
let response = await Shop.findOne({
where: {
userId: userId,
userId: userId,
},
});
res.status(200).send(response);
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
exports.getType = async (req, res) => {
try {
const type = req.params.type;
let response = await Shop.findOne({
where: {
type: type
},
});
res.status(200).send(response);
......@@ -77,28 +114,56 @@ exports.getUserId = async (req, res) => {
res.sendStatus(401);
}
};
exports.updateuserId = async (req, res) => {
exports.updateshopId = async (req, res) => {
try {
const shopData = req.body;
const userId = req.params.userId;
await Shop.update(shopData, { where: { userId: userId } });
const id = req.params.id;
await Shop.update(shopData, { where: { id: id } });
res.status(200).send({ status: "done" });
console.log(shopData);
} catch (err) {
console.log(err);
res.sendStatus(401);
}
};
exports.searchShop= async (req, res) => {
exports.searchShop = async (req, res) => {
const name = req.body.name;
console.log('name', name);
if(name !== undefined) {
console.log('called1');
var condition = name ? { name: { [Op.like]: `%${name}%` } } : null;
const datasearch = await Shop.findAll({
where: condition
})
res.status(200).send(datasearch)
}
}
console.log("name", name);
if (name !== undefined) {
console.log("called1");
var condition = name ? { name: { [Op.like]: `%${name}%` } } : null;
const datasearch = await Shop.findAll({
where: condition,
});
res.status(200).send(datasearch);
} else {
console.log("called2");
const datasearch = await Shop.findAll({
});
res.status(200).send(datasearch);
}
};
// exports.searchType = async (req, res) => {
// const type = req.body.type;
// console.log("name", type);
// if (type !== undefined) {
// console.log("called1");
// var condition = type ? { name: { [Op.like]: `%${name}%` } } : null;
// const datasearch = await Shop.findAll({
// where: condition,
// });
// res.status(200).send(datasearch);
// } else {
// console.log("called2");
// const datasearch = await Shop.findAll({
// });
// res.status(200).send(datasearch);
// }
// };
const Sequelize = require("sequelize");
const db = require("../../db");
const User = require('../user')
const BeauticianImage = require("../beauticianImg")
module.exports = db.sequelize.define("beauticians", {
const Beautician = db.sequelize.define("beauticians", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
......@@ -29,4 +29,5 @@ module.exports = db.sequelize.define("beauticians", {
});
Beautician.hasMany(BeauticianImage, { foreignKey: 'image_id' });
module.exports = Beautician;
\ No newline at end of file
const Sequelize = require("sequelize");
const db = require("../../db");
module.exports = db.sequelize.define("beauticianImages", {
id: {
const BeauticianImages = db.sequelize.define("beauticianImages", {
image_id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
autoIncrement: true,
},
beautician_id:{
beauticians_id: {
type: Sequelize.INTEGER,
primaryKey: true
primaryKey: true,
},
image: {
type: Sequelize.BLOB
type: Sequelize.BLOB,
},
userId: {
type: Sequelize.INTEGER,
references: "users",
referencesKey: "id"
referencesKey: "id",
},
type: {
type: Sequelize.STRING
}
type: Sequelize.STRING,
},
});
module.exports = BeauticianImages;
......@@ -8,9 +8,8 @@ module.exports = db.sequelize.define("bookings", {
autoIncrement: true
},
name: {
type: Sequelize.INTEGER,
references: 'users',
referencesKey: 'id'
type: Sequelize.STRING
},
shop: {
type: Sequelize.INTEGER,
......@@ -18,14 +17,11 @@ module.exports = db.sequelize.define("bookings", {
referencesKey: 'id'
},
list: {
type: Sequelize.INTEGER,
references: 'lists',
referencesKey: 'id'
type: Sequelize.TEXT
},
beautician:{
type:Sequelize.INTEGER,
references: 'users',
referencesKey: 'id'
type:Sequelize.TEXT
},
date: {
type: Sequelize.DATE
......
......@@ -22,9 +22,8 @@ module.exports = db.sequelize.define("reviews", {
references: 'shops',
referencesKey: 'id'
},
userID: {
type: Sequelize.INTEGER,
references: 'users',
referencesKey: 'id'
name: {
type: Sequelize.STRING
}
});
const Sequelize = require("sequelize");
const db = require("../../db");
const ShopImage = require("../shopimage");
const WorkImage = require("../workimage")
const Shop = db.sequelize.define("shops", {
id: {
......@@ -58,5 +58,5 @@ const WorkImage = require("../workimage")
});
Shop.hasMany(ShopImage, { foreignKey: 'image_id' });
// Shop.hasMany(ShopImage, { foreignKey: 'image_id' });
module.exports = Shop;
\ No newline at end of file
const Sequelize = require("sequelize");
const db = require("../../db");
const User =require("../user")
const ShopImage = db.sequelize.define("shopimages", {
const ShopImage = db.sequelize.define("shopimages", {
image_id: {
type: Sequelize.INTEGER,
......
......@@ -11,10 +11,6 @@ const WorkImage = db.sequelize.define("workimages", {
type: Sequelize.INTEGER,
primaryKey: true,
},
// user_id: {
// type: Sequelize.INTEGER,
// primaryKey: true,
// },
image: {
type: Sequelize.BLOB,
},
......
......@@ -11,6 +11,6 @@ router.get("/getUserById/:id", authController.getUserById);
router.post("/register", authController.register);
router.post("/login", authController.login);
router.get('/currentuser', authController.currentUser);
// router.put('/updateProfile/:id', authController.updateProfile)
module.exports = router;
......@@ -6,7 +6,7 @@ const BeauticianController = require("../../controllers/beautician");
router.post("/add", BeauticianController.add);
router.get("/getbeauticianShopId/:shopID", BeauticianController.getbeauticianShopId);
router.get("/getbeauticianName/:name", BeauticianController.getbeauticianName);
router.delete("/deletebeautician/:id", BeauticianController.deletebeautician);
module.exports = router;
......@@ -6,7 +6,8 @@ const bookingController = require("\../../controllers/booking");
router.post("/add", bookingController.addbooking);
router.get('/all', bookingController.getBooking);
router.get('/getBookingShopid/:shop',bookingController.getBookingShopid)
router.get('/getBookingbeautician/:beautician',bookingController.getBookingbeautician)
router.get('/getBookingDate/:date', bookingController.getBookingDate)
router.post('/searchBooking',bookingController.searchBooking)
module.exports = router;
......@@ -9,7 +9,7 @@ router.get('/all', listController.getList);
router.delete('/delete/:listId',listController.deletelist);
router.get('/getListuser/:userId',listController.getListuser);
router.get("/getListshop/:shop", listController.getListshop)
router.put('/updateList/id',listController.updateList);
router.put('/updateListId/:id',listController.updateListId);
module.exports = router;
......@@ -7,7 +7,7 @@ const reviewController = require("../../controllers/review");
router.post("/add", reviewController.addreview);
router.get('/all', reviewController.getReview);
router.delete('/delete/:reviewId', reviewController.deleteReview);
router.get('/getReviewShopid/:id', reviewController.getReviewShopid);
router.get('/getReviewShopid/:shop', reviewController.getReviewShopid);
router.put('/updateReview/id', reviewController.updateReview);
......
......@@ -9,6 +9,8 @@ router.delete("/delete/:shopId", shopController.updateShop);
router.get("/getShopId/:id", shopController.getShopId);
router.put("/updateshop/:id", shopController.updateShop);
router.get("/getUserId/:userId", shopController.getUserId);
router.put("/updateuserId/:userId", shopController.updateuserId);
router.get("/getShopBytype/:type", shopController.getShopBytype)
router.put("/updateshopId/:id", shopController.updateshopId);
router.post("/searchShop", shopController.searchShop)
// router.get("/getType/:type", shopController.getType)
module.exports = router;
......@@ -27,12 +27,16 @@ router.get('/file/shop/:shopId', async (req, res) => {
shop_id: req.params.shopId
}
})
const urlimg = existingImage.image.toString('base64');
const dataimg = {
url: urlimg,
type: existingImage.type
if (existingImage){
const urlimg = existingImage.image.toString('base64');
const dataimg = {
url: urlimg,
type: existingImage.type
}
res.status(200).send(dataimg);
}else{
res.status(200).send();
}
res.status(200).send(dataimg);
})
router.put('/file/shop/:shopId', storage.single('images') ,async (req, res) => {
const editImage = req.file;
......
......@@ -5,15 +5,16 @@ const BeauticianImage = require("../../models/beauticianImg");
const User = require("../../models/user");
router.post(
"/upload/beautician/:beauticianId",storagebeautician.single("images"),async (req, res) => {
"/upload/beautician/:beauticianId",
storagebeautician.single("images"),
async (req, res) => {
// console.log(req.body.shopId);
const images = req.file;
const shop_id = req.body.shopId;
const beautician_id = req.params.beauticianId;
// console.log(req);
await BeauticianImage.create({
beautician_id: req.params.beauticianId,
beauticians_id: req.params.beauticianId,
image: images.buffer,
type: images.mimetype,
});
......@@ -21,17 +22,21 @@ router.post(
}
);
//get file
router.get('/file/beautician/:beauticianId', async (req, res) => {
const existingImage = await ShopImage.findAll({
where: {
beautician_id: req.params.beauticianId
}
})
const urlimg = existingImage.image.toString('base64');
const dataimg = {
router.get("/file/beautician/:beauticianId", async (req, res) => {
const existingImage = await BeauticianImage.findAll({
where: {
beautician_id: req.params.beauticianId,
},
});
if (existingImage) {
const urlimg = existingImage.image.toString("base64");
const dataimg = {
url: urlimg,
type: existingImage.type
type: existingImage.type,
};
res.status(200).send(dataimg);
} else {
res.status(200).send();
}
res.status(200).send(dataimg);
})
});
module.exports = router;
......@@ -22,17 +22,23 @@ router.post('/upload/user/:userId', storageprofile.single('images'), async (req,
})
//get file
router.get('/file/user/:userId', async (req, res) => {
const existingImage = await ShopImage.findOne({
const existingImage = await UserImage.findOne({
where: {
user_id: req.params.userId
}
})
const urlimg = existingImage.image.toString('base64');
const dataimg = {
url: urlimg,
type: existingImage.type
if (existingImage){
const urlimg = existingImage.image.toString('base64');
const dataimg = {
url: urlimg,
type: existingImage.type
}
res.status(200).send(dataimg);
}else{
res.status(200).send();
}
res.status(200).send(dataimg);
})
router.put('/file/user/:userId', storageprofile.single('images') ,async (req, res) => {
const editImage = req.file;
......@@ -40,7 +46,7 @@ router.put('/file/user/:userId', storageprofile.single('images') ,async (req, r
image: editImage.buffer
}
const userId = req.params.shopId;
const userId = req.params.userId;
await UserImage.update(
profileimage,
......
const router = require("express-promise-router")();
const storageshop = require("../../multer");
const WorkImage = require("../../models/workimage");
const Shop = require("../../models/shop");
const router = require('express-promise-router')();
const storageshop = require('../../multer');
const WorkImage = require('../../models/workimage');
const Shop = require('../../models/shop')
router.post('/upload/work/:shopId', storageshop.single('images'), async (req, res) => {
router.post(
"/upload/work/:shopId",
storageshop.single("images"),
async (req, res) => {
// console.log(req.body.shopId);
const images = req.file;
const shop_id = req.body.shopId
const shop_id = req.body.shopId;
// const user_id = req.body.userId
// console.log(req);
await WorkImage.create({
shop_id: req.params.shopId,
// user_id: req.params.userId,
image: images.buffer,
type: images.mimetype
});
res.send('success')
})
await WorkImage.create({
shop_id: req.params.shopId,
// user_id: req.params.userId,
image: images.buffer,
type: images.mimetype,
});
res.send("success");
}
);
//get file
router.get('/file/shop/:shopId', async (req, res) => {
const existingImage = await ShopImage.findAll({
where: {
shop_id: req.params.shopId
}
})
const urlimg = existingImage.image.toString('base64');
const dataimg = {
router.get("/file/shop/:shopId", async (req, res) => {
const existingImage = await WorkImage.findAll({
where: {
shop_id: req.params.shopId,
},
});
console.log(existingImage);
let dataimg=[]
for (let i = 0; i < existingImage.length;i++) {
const urlimg = existingImage[i].image.toString("base64");
dataimg.push = {
url: urlimg,
type: existingImage.type
type: existingImage[i].type,
};
res.status(200).send(dataimg);
}
res.status(200).send(dataimg);
})
});
module.exports = router;
......@@ -7,8 +7,8 @@ const port = process.env.PORT || 9000;
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use("/api/auth", router.auth);
app.use("/api/list", router.list);
app.use("/api/shop", router.shop);
......
......@@ -8821,6 +8821,11 @@
"object.assign": "^4.1.0"
}
},
"keycode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
"integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
},
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
......@@ -11853,6 +11858,16 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.4.tgz",
"integrity": "sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA=="
},
"react-event-listener": {
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz",
"integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==",
"requires": {
"@babel/runtime": "^7.2.0",
"prop-types": "^15.6.0",
"warning": "^4.0.1"
}
},
"react-github-fork-ribbon": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/react-github-fork-ribbon/-/react-github-fork-ribbon-0.6.0.tgz",
......@@ -12006,6 +12021,85 @@
"workbox-webpack-plugin": "4.3.1"
}
},
"react-swipeable-views": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.13.9.tgz",
"integrity": "sha512-WXC2FKYvZ9QdJ31v9LjEJEl1bA7E4AcaloTkbW0uU0dYf5uvv4aOpiyxubvOkVl1a5L2UAHmKSif4TmJ9usrSg==",
"requires": {
"@babel/runtime": "7.0.0",
"prop-types": "^15.5.4",
"react-swipeable-views-core": "^0.13.7",
"react-swipeable-views-utils": "^0.13.9",
"warning": "^4.0.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
"requires": {
"regenerator-runtime": "^0.12.0"
}
},
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"react-swipeable-views-core": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.13.7.tgz",
"integrity": "sha512-ekn9oDYfBt0oqJSGGwLEhKvn+QaqMGTy//9dURTLf+vp7W5j6GvmKryYdnwJCDITaPFI2hujXV4CH9krhvaE5w==",
"requires": {
"@babel/runtime": "7.0.0",
"warning": "^4.0.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
"requires": {
"regenerator-runtime": "^0.12.0"
}
},
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"react-swipeable-views-utils": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.13.9.tgz",
"integrity": "sha512-QLGxRKrbJCbWz94vkWLzb1Daaa2Y/TZKmsNKQ6WSNrS+chrlfZ3z9tqZ7YUJlW6pRWp3QZdLSY3UE3cN0TXXmw==",
"requires": {
"@babel/runtime": "7.0.0",
"keycode": "^2.1.7",
"prop-types": "^15.6.0",
"react-event-listener": "^0.6.0",
"react-swipeable-views-core": "^0.13.7",
"shallow-equal": "^1.2.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
"requires": {
"regenerator-runtime": "^0.12.0"
}
},
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
......@@ -14260,6 +14354,14 @@
"makeerror": "1.0.x"
}
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"watchpack": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
......
......@@ -29,6 +29,7 @@
"react-redux": "^7.2.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"react-swipeable-views": "^0.13.9",
"recompose": "^0.30.0",
"redux": "^4.0.5"
},
......
......@@ -15,6 +15,7 @@ import SpaIcon from '@material-ui/icons/Spa';
import FaceIcon from '@material-ui/icons/Face';
import RemoveRedEyeIcon from '@material-ui/icons/RemoveRedEye';
import PanToolIcon from '@material-ui/icons/PanTool';
import axios from "axios";
const styles = theme => ({
root: {
......@@ -47,6 +48,30 @@ const styles = theme => ({
});
class Dashboard extends Component {
state = {
shops: [],
};
componentDidMount = async () => {
const response = await axios.get("http://localhost:9000/api/shop/all");
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
// console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
console.log("response", response.data);
this.setState({
shops: response.data,
// data: result,
});
};
render() {
const { classes, history, tab, handleChangeTab } = this.props;
const badgeOrder = 5;
......@@ -69,12 +94,35 @@ class Dashboard extends Component {
>
<Tab
label="สปาและนวด"
value={tab.newValue}
icon={
<Badge
className={classes.margin}
//badgeContent={badgeOrder}
color="secondary"
onClick={e => {
e.preventDefault();
axios
.get(`http://localhost:9000/api/shop/getType/${tab.newValue}`)
.then(async response => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
this.setState({shops : response.data}
)
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
// alert("ค้นหาไม่สำเร็จ");
});
}}
>
<SpaIcon/>
......@@ -84,11 +132,35 @@ class Dashboard extends Component {
/>
<Tab
label="ผิวหน้า"
value={tab.newValue}
icon={
<Badge
className={classes.margin}
//badgeContent={badgeUser}
//badgeContent={badgeOrder}
color="secondary"
onClick={e => {
e.preventDefault();
axios
.get(`http://localhost:9000/api/shop/getType/${tab.newValue}`)
.then(async response => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
this.setState({shops : response.data}
)
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
// alert("ค้นหาไม่สำเร็จ");
});
}}
>
<FaceIcon />
</Badge>
......@@ -97,11 +169,35 @@ class Dashboard extends Component {
/>
<Tab
label="ขนตาและคิ้ว"
value={tab.newValue}
icon={
<Badge
className={classes.margin}
//badgeContent={badgeCredit}
//badgeContent={badgeOrder}
color="secondary"
onClick={e => {
e.preventDefault();
axios
.get(`http://localhost:9000/api/shop/getType/${tab.newValue}`)
.then(async response => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
this.setState({shops : response.data}
)
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
// alert("ค้นหาไม่สำเร็จ");
});
}}
>
<RemoveRedEyeIcon />
</Badge>
......@@ -110,11 +206,35 @@ class Dashboard extends Component {
/>
<Tab
label="ทำเล็บ"
value={tab.newValue}
icon={
<Badge
className={classes.margin}
//badgeContent={badgeCredit}
//badgeContent={badgeOrder}
color="secondary"
onClick={e => {
e.preventDefault();
axios
.get(`http://localhost:9000/api/shop/getType/${tab.newValue}`)
.then(async response => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
this.setState({shops : response.data}
)
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
// alert("ค้นหาไม่สำเร็จ");
});
}}
>
<PanToolIcon />
</Badge>
......@@ -123,11 +243,35 @@ class Dashboard extends Component {
/>
<Tab
label="ชาลอน"
value={tab.newValue}
icon={
<Badge
className={classes.margin}
//badgeContent={badgeCredit}
//badgeContent={badgeOrder}
color="secondary"
onClick={e => {
e.preventDefault();
axios
.get(`http://localhost:9000/api/shop/getType/${tab.newValue}`)
.then(async response => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
this.setState({shops : response.data}
)
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
// alert("ค้นหาไม่สำเร็จ");
});
}}
>
<FaceIcon />
</Badge>
......@@ -136,11 +280,35 @@ class Dashboard extends Component {
/>
<Tab
label="แต่งหน้าทำผม"
value={tab.newValue}
icon={
<Badge
className={classes.margin}
//badgeContent={badgeCredit}
//badgeContent={badgeOrder}
color="secondary"
onClick={e => {
e.preventDefault();
axios
.get(`http://localhost:9000/api/shop/getType/${tab.newValue}`)
.then(async response => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
console.log(resposeimgshop);
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
}
this.setState({shops : response.data}
)
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
// alert("ค้นหาไม่สำเร็จ");
});
}}
>
<FaceIcon />
</Badge>
......
......@@ -7,7 +7,7 @@ import {
DialogContent,
DialogTitle,
TextField,
InputAdornment
InputAdornment,
} from "@material-ui/core";
import { withRouter } from "react-router-dom";
......@@ -16,8 +16,9 @@ import Fab from "@material-ui/core/Fab";
import AddIcon from "@material-ui/icons/Add";
import AddPhotoAlternateIcon from "@material-ui/icons/AddPhotoAlternate";
import MaterialTable from "material-table";
const styles = theme => ({
import axios from "axios";
import { connect } from "react-redux";
const styles = (theme) => ({
root: {
height: 150,
flexGrow: 1,
......@@ -25,51 +26,47 @@ const styles = theme => ({
// The position fixed scoping doesn't work in IE 11.
// Disable this demo to preserve the others.
"@media all and (-ms-high-contrast: none)": {
display: "none"
}
display: "none",
},
},
modal: {
display: "flex",
padding: theme.spacing(1),
alignItems: "center",
justifyContent: "center"
justifyContent: "center",
},
fab: {
position: "absolute",
bottom: theme.spacing(2),
right: theme.spacing(2)
}
right: theme.spacing(2),
},
});
class Databeautician extends Component {
class AddImage extends Component {
state = {
open: false,
columns: [
{ title: "รูปภาพ", field: "img" }
],
data: [
{ img: "Mehmet"}
]
columns: [{ title: "รูปภาพ", field: "img" }],
data: [],
file: null,
beautician: [],
};
handleClickOpen = () => {
this.setState({
open: true
open: true,
});
};
handleClose = () => {
this.setState({
open: false
open: false,
});
};
handleCloseAndSave = () => {
//Save to db
this.setState(prevState => {
this.setState((prevState) => {
const data = [...prevState.data];
const newData = {
img: "Mehmet",
};
data.push(newData);
return { ...prevState, data: data, open: false };
......@@ -79,61 +76,56 @@ class Databeautician extends Component {
// open: false
// });
};
componentDidMount = () => {
let { pathname } = this.props.location;
pathname = pathname.substring(1, pathname.length);
// console.log('pathname', pathname)
// componentDidMount = () => {
// let { pathname } = this.props.location;
// pathname = pathname.substring(1, pathname.length);
// // console.log('pathname', pathname)
// };
componentDidMount = async () => {
const { location, userInfo } = this.props;
const { beautician } = this.state;
const response = await axios.get(
`http://localhost:9000/api/beautician/getbeauticianName/${userInfo.name}`
);
console.log("response.data",response.data);
if (response.data)
this.setState({
beautician: response.data,
// data: response.data
});
;
};
render() {
const { classes } = this.props;
const { open, columns, data } = this.state;
const { open, columns, data, file, beautician } = this.state;
return (
<div>
<MaterialTable
title="ภาพผลงาน"
title="รูปภาพร้าน"
columns={columns}
data={data}
options={{
selection: false
selection: false,
}}
editable={{
// onRowAdd: newData =>
// new Promise(resolve => {
// setTimeout(() => {
// resolve();
// this.setState(prevState => {
// const data = [...prevState.data];
// data.push(newData);
// return { ...prevState, data };
// });
// }, 600);
// }),
// onRowUpdate: (newData, oldData) =>
// new Promise(resolve => {
// setTimeout(() => {
// resolve();
// if (oldData) {
// this.setState(prevState => {
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
// });
// }
// }, 600);
// }),
onRowDelete: oldData =>
new Promise(resolve => {
onRowDelete: (oldData) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
this.setState(prevState => {
this.setState((prevState) => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
})
}),
}}
/>
<div className="row">
......@@ -154,7 +146,6 @@ class Databeautician extends Component {
>
<DialogTitle id="alert-dialog-title">{"เพิ่มรูปภาพ"}</DialogTitle>
<DialogContent>
<TextField
className={classes.margin}
id="outlined-file-input"
......@@ -167,7 +158,12 @@ class Databeautician extends Component {
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>
)
),
}}
onChange={(e) => {
this.setState({
file: e.target.files[0],
});
}}
/>
</DialogContent>
......@@ -176,7 +172,30 @@ class Databeautician extends Component {
ยกเลิก
</Button>
<Button
onClick={this.handleCloseAndSave}
onClick={() => {
const { location, userInfo } = this.props;
const { beautician } = this.state;
const formData = new FormData();
console.log(file);
formData.append("images", file);
formData.append("beauticians_id", beautician.id);
// formData.append('userId', userInfo.id)
axios
.post(
`http://localhost:9000/api/storagebeautician/upload/beautician/${beautician.id}`,
formData
)
.then((response) => {
console.log("เพิ่มรูปภาพสำเร็จ", response);
alert("เพิ่มรูปภาพสำเร็จ");
this.handleClose();
})
.catch((error) => {
console.log(error);
});
}}
color="primary"
autoFocus
>
......@@ -189,5 +208,13 @@ class Databeautician extends Component {
);
}
}
const mapStateToProps = (state) => ({
userInfo: state.user,
});
const mapDispatchToProps = (dispatch) => {};
export default compose(withStyles(styles), withRouter)(Databeautician);
export default connect(
mapStateToProps,
mapDispatchToProps
)(compose(withStyles(styles), withRouter)(AddImage));
......@@ -129,7 +129,7 @@ class ResponsiveDrawer extends Component {
</ListItemIcon>
<ListItemText primary="ตารางงาน" />
</ListItem>
<ListItem
{/* <ListItem
button
onClick={() => {
this.handleChangePath("addimage");
......@@ -140,7 +140,7 @@ class ResponsiveDrawer extends Component {
<MailIcon />
</ListItemIcon>
<ListItemText primary="ภาพผลงาน" />
</ListItem>
</ListItem> */}
......
......@@ -6,7 +6,18 @@ import {
InputAdornment,
TextField,
Button,
Fab
Fab,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
FormControl,
TextareaAutosize,
Paper,
CardMedia,
MenuItem,
Snackbar,
} from "@material-ui/core";
import AccountCircle from "@material-ui/icons/AccountCircle";
import MailOutlineIcon from "@material-ui/icons/MailOutline";
......@@ -16,32 +27,93 @@ import EditIcon from "@material-ui/icons/Edit";
import { connect } from "react-redux";
import PhotoCameraIcon from "@material-ui/icons/PhotoCamera";
import Avatar from "react-avatar";
import AddAPhotoIcon from "@material-ui/icons/AddAPhoto";
import AddPhotoAlternateIcon from "@material-ui/icons/AddPhotoAlternate";
import axios from "axios";
const styles = theme => ({
const styles = (theme) => ({
margin: {
margin: theme.spacing(1)
}
margin: theme.spacing(1),
},
});
class Profile extends Component {
state = {
user: ""
user: [],
file: null,
open: false,
openupdate: false,
edit: false,
};
handleClickOpen = () => {
this.setState({
open: true,
});
};
handleClose = () => {
this.setState({
open: false,
});
};
handleClickEdit = () => {
this.setState({
edit: true,
});
};
handleCloseEdit = () => {
this.setState({
edit: false,
});
};
handleClickOpenupdate = () => {
this.setState({
openupdate: true,
});
};
handleCloseupdate = () => {
this.setState({
openupdate: false,
});
};
handleChange = (event) => {
let { user } = this.state;
console.log("name : ", event.target.name);
console.log("value : ", event.target.value);
user[event.target.name] = event.target.value;
this.setState({
user,
});
};
componentDidMount = async () => {
const { location, userInfo } = this.props;
// const id = userInfo.id;
const response = await axios.get(
`http://localhost:9000/api/auth/getUserById/${userInfo.id}`
);
console.log(response.data);
this.setState({
user: response.data
});
console.log(userInfo);
const resposeimguser = await axios.get(
`http://localhost:9000/api/storageprofile/file/user/${userInfo.id}`
);
if(resposeimguser.data){
response.data.image =
"data:" + resposeimguser.data.type + ";base64," + resposeimguser.data.url;
console.log(resposeimguser);
}
console.log(response.data);
if (response.data)
this.setState({
user: response.data,
});
};
render() {
const { classes, userInfo } = this.props;
const { user } = this.state;
const { user, file, shop, open, openupdate, edit } = this.state;
return (
<div className="row center">
<div className="row mt-2 mb-2">
......@@ -49,26 +121,173 @@ class Profile extends Component {
</div>
<br />
<div className="row mt-2 mb-2">
<div className="col s12 m6 l4">
<div className="col s12 m12 l4">
<div className="row center">
<br />
<Avatar
skypeId="sitebase"
src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQZeSPYraiTcEz6JxyXy8ITsaGldAqDjX6860m7eCpYhYeQDQkL"
src={user.image}
size="200"
round={true}
/>
</div>
<div className="row center">
<Button variant="outlined" color="primary">
<PhotoCameraIcon />
แก้ไขรูปโปรไฟล์
</Button>
{user.image ? (
<Button
variant="outlined"
onClick={this.handleClickOpenupdate}
color="primary"
>
<PhotoCameraIcon />
แก้ไขรูปภาพร้าน
</Button>
) : (
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
<AddAPhotoIcon />
เพิ่มรูปภาพร้าน
</Button>
)}
<Dialog
open={open}
onClose={this.handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"เพิ่มรูปภาพ"}
</DialogTitle>
<DialogContent>
<TextField
className={classes.margin}
id="outlined-file-input"
label="เพิ่มรูปภาพ"
type="file"
autoComplete="current-password"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>
),
}}
onChange={(e) => {
this.setState({
file: e.target.files[0],
});
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
ยกเลิก
</Button>
<Button
onClick={() => {
const { location, userInfo } = this.props;
const formData = new FormData();
console.log(file);
formData.append("images", file);
// formData.append("shop_id", shop.id);
formData.append("userId", userInfo.id);
axios
.post(
`http://localhost:9000/api/storageprofile/upload/user/${userInfo.id}`,
formData
)
.then((response) => {
console.log("เพิ่มรูปภาพสำเร็จ", response);
alert("เพิ่มรูปภาพสำเร็จ");
this.handleClose();
})
.catch((error) => {
console.log(error);
});
}}
color="primary"
autoFocus
>
ยืนยัน
</Button>
</DialogActions>
</Dialog>
<Dialog
open={openupdate}
onClose={this.handleCloseupdate}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"แก้ไขรูปภาพ"}
</DialogTitle>
<DialogContent>
<TextField
className={classes.margin}
id="outlined-file-input"
label="แก้ไขรูปภาพ"
type="file"
autoComplete="current-password"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>
),
}}
onChange={(e) => {
this.setState({
file: e.target.files[0],
});
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseupdate} color="primary">
ยกเลิก
</Button>
<Button
onClick={() => {
const { location, userInfo } = this.props;
const formData = new FormData();
console.log(file);
formData.append("images", file);
// formData.append("shop_id", shop.id);
formData.append("userId", userInfo.id);
axios
.put(
`http://localhost:9000/api/storageprofile/file/user/${userInfo.id}`,
formData
)
.then((response) => {
console.log("แก้ไขรรูปภาพสำเร็จ", response);
alert("แกไขรูปภาพสำเร็จ");
this.handleCloseupdate();
})
.catch((error) => {
console.log(error);
});
}}
color="primary"
autoFocus
>
ยืนยัน
</Button>
</DialogActions>
</Dialog>
</div>
</div>
<div className="col s12 m6 l8">
<br />
<div ClassName="row">
<div className="col s12 m12 l8">
<div ClassName="row mt-5">
<TextField
className={classes.margin}
id="name"
......@@ -81,7 +300,7 @@ class Profile extends Component {
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
)
),
}}
/>
......@@ -97,7 +316,7 @@ class Profile extends Component {
<InputAdornment position="start">
<MailOutlineIcon />
</InputAdornment>
)
),
}}
/>
</div>
......@@ -116,7 +335,7 @@ class Profile extends Component {
<InputAdornment position="start">
<BusinessIcon />
</InputAdornment>
)
),
}}
/>
......@@ -133,38 +352,147 @@ class Profile extends Component {
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
)
),
}}
/>
</div>
<br></br>
<div className="row">
{/* <div className="row mt-5">
<div className={classes.root}>
<Button variant="contained" color="secondary">
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button variant="contained" color="primary">
ตกลง
</Button>
&nbsp; &nbsp; &nbsp;
<Fab color="secondary" aria-label="edit">
<Fab
color="secondary"
aria-label="edit"
onClick={this.handleClickEdit}
>
<EditIcon />
</Fab>
</div>
</div>
<Dialog
open={edit}
onClose={this.handleCloseEdit}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"แก้ไขข้อมูลโปรไฟล์"}
</DialogTitle>
<DialogContent>
<div ClassName="row">
<TextField
className={classes.margin}
id="name"
name="name"
value={user.name}
label="ชื่อ-นามสกุล"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="email"
name="email"
value={user.email}
label="E_mail"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MailOutlineIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="address"
name="address"
value={user.address}
label="ที่อยู่"
autoComplete="address"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<BusinessIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="tel"
name="tel"
value={user.tel}
label="เบอร์โทร"
autoComplete="Phone number"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
</div>
</DialogContent>
<DialogActions>
<Button
variant="contained"
color="secondary"
onClick={this.handleCloseEdit}
>
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button
variant="contained"
color="primary"
onClick={() => {
axios
.put("http://localhost:9000/api/auth/updateProfile/:id", {
...user,
})
.then((response) => {
console.log("แก้ไขข้อมูลสำเร็จ", response);
alert("แก้ไขข้อมูลสำเร็จ");
this.handleCloseEdit();
})
.catch((error) => {
console.log(error);
});
}}
>
ตกลง
</Button>
</DialogActions>
</Dialog> */}
{/* </div>
</div> */}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
userInfo: state.user
const mapStateToProps = (state) => ({
userInfo: state.user,
});
const mapDispatchToProps = dispatch => {};
const mapDispatchToProps = (dispatch) => {};
export default connect(
mapStateToProps,
......
......@@ -8,7 +8,9 @@ import {
KeyboardDatePicker,
} from '@material-ui/pickers';
import MaterialTable from "material-table";
import axios from "axios";
import { connect } from "react-redux";
const styles = theme => ({
root: {
height: 150,
......@@ -40,19 +42,13 @@ class Work extends Component {
columns: [
{ title: "ชื่อคนจอง", field: "name" },
{ title: "รายการจอง", field: "list" },
{ title: "วันที่จอง" , field: "date"},
{ title: "เวลาที่จอง", field: "time" },
{ title: "ช่างที่จอง", field: "booking" }
],
data: [
{
name: "Benz Piyaphorn",
list: "อบไอน้ำ",
time: "12:00",
booking: "ปิยพร อาภรศรี"
}
{ title: "ช่างที่จอง", field: "beautician" }
],
data: [],
selectedDate : new Date()
};
handleChangeDate = newDate => {
......@@ -90,10 +86,24 @@ class Work extends Component {
// open: false
// });
};
componentDidMount = () => {
let { pathname } = this.props.location;
pathname = pathname.substring(1, pathname.length);
// console.log('pathname', pathname)
componentDidMount = async () => {
const { location, userInfo } = this.props;
const { beautician } = this.state;
const response = await axios.get(
`http://localhost:9000/api/beautician/getbeauticianName/${userInfo.name}`
);
const responsebeautician = await axios.get(
`http://localhost:9000/api/booking/getBookingbeautician/${userInfo.name}`
);
console.log("response.data",response.data);
if (response.data)
this.setState({
beautician: response.data,
data: responsebeautician.data
});
};
render() {
......@@ -102,7 +112,7 @@ class Work extends Component {
const { open, columns, data, selectedDate } = this.state;
return (
<div>
<div className="row center">
{/* <div className="row center">
<KeyboardDatePicker
disableToolbar
variant="inline"
......@@ -116,7 +126,7 @@ class Work extends Component {
"aria-label": "change date"
}}
/>
</div>
</div> */}
<div className="row">
<MaterialTable
title="ตารางงาน"
......@@ -125,43 +135,7 @@ class Work extends Component {
options={{
selection: false
}}
editable={{
// onRowAdd: newData =>
// new Promise(resolve => {
// setTimeout(() => {
// resolve();
// this.setState(prevState => {
// const data = [...prevState.data];
// data.push(newData);
// return { ...prevState, data };
// });
// }, 600);
// }),
// onRowUpdate: (newData, oldData) =>
// new Promise(resolve => {
// setTimeout(() => {
// resolve();
// if (oldData) {
// this.setState(prevState => {
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
// });
// }
// }, 600);
// }),
// onRowDelete: oldData =>
// new Promise(resolve => {
// setTimeout(() => {
// resolve();
// this.setState(prevState => {
// const data = [...prevState.data];
// data.splice(data.indexOf(oldData), 1);
// return { ...prevState, data };
// });
// }, 600);
// })
}}
/>
</div>
</div>
......@@ -169,4 +143,14 @@ class Work extends Component {
}
}
export default compose(withStyles(styles), withRouter)(Work);
const mapStateToProps = (state) => ({
userInfo: state.user,
});
const mapDispatchToProps = (dispatch) => {};
export default connect(
mapStateToProps,
mapDispatchToProps
)(compose(withStyles(styles), withRouter)(Work));
......@@ -108,13 +108,13 @@ class Databeautician extends Component {
};
componentDidMount = async () => {
const { location, userInfo } = this.props;
const { shops } = this.state;
const response = await axios.get(
`http://localhost:9000/api/shop/getUserId/${userInfo.id}`
);
const responsedata = await axios.get(
`http://localhost:9000/api/beautician/getbeauticianShopId/${shops.id}`
`http://localhost:9000/api/beautician/getbeauticianShopId/${response.data.id}`
);
console.log(response.data);
console.log(responsedata.data);
......@@ -152,16 +152,22 @@ class Databeautician extends Component {
}}
editable={{
onRowDelete: (oldData) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
this.setState((prevState) => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
}),
new Promise((resolve) => {
setTimeout(() => {
axios.delete(
`http://localhost:9000/api/beautician/deletebeautician/${oldData.id}`
);
console.log("ลบข้อมูลช่างสำเร็จ");
alert("ลบข้อมูลช่างสำเร็จ");
resolve();
this.setState((prevState) => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
}),
}}
/>
<div className="row">
......@@ -332,6 +338,7 @@ class Databeautician extends Component {
})
.then((response) => {
console.log("สร้างผู้ใช้สำเร็จ", response);
alert("เพิ่มข้อมูลช่างสำเร็จ");
this.handleClose();
})
.catch((error) => {
......
......@@ -20,7 +20,7 @@ import {
Paper,
CardMedia,
MenuItem,
Snackbar
Snackbar,
} from "@material-ui/core";
import AccountCircle from "@material-ui/icons/AccountCircle";
import AddPhotoAlternateIcon from "@material-ui/icons/AddPhotoAlternate";
......@@ -31,103 +31,114 @@ import EditIcon from "@material-ui/icons/Edit";
import PhotoCameraIcon from "@material-ui/icons/PhotoCamera";
import AddAPhotoIcon from "@material-ui/icons/AddAPhoto";
import PostAddIcon from "@material-ui/icons/PostAdd";
import DetailsIcon from '@material-ui/icons/Details';
import PlaceIcon from '@material-ui/icons/Place';
import FacebookIcon from '@material-ui/icons/Facebook';
import MergeTypeIcon from '@material-ui/icons/MergeType';
import DetailsIcon from "@material-ui/icons/Details";
import PlaceIcon from "@material-ui/icons/Place";
import FacebookIcon from "@material-ui/icons/Facebook";
import MergeTypeIcon from "@material-ui/icons/MergeType";
import AddIcon from "@material-ui/icons/Add";
import axios from "axios";
import { connect } from "react-redux";
const styles = theme => ({
const styles = (theme) => ({
margin: {
margin: theme.spacing(1)
margin: theme.spacing(1),
},
root: {
"& .MuiTextField-root": {
margin: theme.spacing(1),
marginTop: theme.spacing(2),
width: "100%"
}
}
width: "100%",
},
},
});
const currencies = [
{
value: "1",
label: " กรุณาเลือกประเภท "
label: " กรุณาเลือกประเภท ",
},
{
value: "สปาและนวด",
label: "สปาและนวด"
label: "สปาและนวด",
},
{
value: "ผิวหน้า",
label: "ผิวหน้า"
label: "ผิวหน้า",
},
{
value: "ขนตาและคิ้ว",
label: "ขนตาและคิ้ว"
label: "ขนตาและคิ้ว",
},
{
value: "ทำเล็บ",
label: "ทำเล็บ"
label: "ทำเล็บ",
},
{
value: "ชาลอน",
label: "ชาลอน"
label: "ชาลอน",
},
{
value: "แต่งหน้าทำผม",
label: "แต่งหน้าทำผม"
}
label: "แต่งหน้าทำผม",
},
];
class Datashop extends Component {
state = {
currency: "1",
shop: {
name: "",
nameeng: "",
timeopen: "",
timeclose: "",
tel: "",
address: "",
detail: "",
images: "",
lat: "",
lng: "",
facebook: "",
type: ""
},
shop: {},
showButton: true,
file: null,
promotion: {detail:""},
promotion: { detail: "" },
open: false,
openupdate: false
openupdate: false,
add: false,
edit: false,
};
handleClickOpen = () => {
this.setState({
open: true
open: true,
});
};
handleClose = () => {
this.setState({
open: false
open: false,
});
};
handleClickAdd = () => {
this.setState({
add: true,
});
};
handleCloseAdd = () => {
this.setState({
add: false,
});
};
handleClickEdit = () => {
this.setState({
edit: true,
});
};
handleCloseEdit = () => {
this.setState({
edit: false,
});
};
handleClickOpenupdate = () => {
this.setState({
openupdate: true
openupdate: true,
});
};
handleCloseupdate = () => {
this.setState({
openupdate: false
openupdate: false,
});
};
handleChange = event => {
handleChange = (event) => {
let { shop } = this.state;
console.log("name : ", event.target.name);
console.log("value : ", event.target.value);
......@@ -135,31 +146,50 @@ class Datashop extends Component {
shop[event.target.name] = event.target.value;
this.setState({
shop
shop,
});
};
componentDidMount = async () => {
const { location, userInfo } = this.props;
const { shop } = this.state
// const id = location.state.id;
const response = await axios.get(
`http://localhost:9000/api/shop/getUserId/${userInfo.id}`
);
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data.id}`)
// console.log(resposeimgshop);
const responsepromotion = await axios.get(
`http://localhost:9000/api/promotion/getuserId/${userInfo.id}`
const resposeimgshop = await axios.get(
`http://localhost:9000/api/storage/file/shop/${response.data.id}`
);
response.data.image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
if (response.data) this.setState({ shop: response.data });
if (responsepromotion.data) this.setState({ promotion: responsepromotion.data });
console.log(response.data);
// console.log(resposeimgshop);
if (resposeimgshop.data) {
response.data.image =
"data:" +
resposeimgshop.data.type +
";base64," +
resposeimgshop.data.url;
}
if (response.data){
this.setState({
shop: response.data
});
}
};
render() {
const { classes, userInfo } = this.props;
const { currency, shop, promotion, open, file ,openupdate} = this.state;
const {
currency,
shop,
promotion,
open,
file,
openupdate,
add,
edit,
} = this.state;
return (
<div className="row center">
<div className="row mt-2 mb-2">
......@@ -168,24 +198,33 @@ class Datashop extends Component {
<br />
<div className="row mt-2 mb-2">
<div className="col s12 m12 l12">
<div className="row center">
<h3>ข้อมูลทั่วไป</h3>
</div>
<div className="row center">
<br />
<img src={shop.image} width="60%"/>
<img src={shop.image} width="60%" />
</div>
<div className="row center">
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
<AddAPhotoIcon />
เพิ่มรูปภาพร้าน
</Button>
{shop.image ? (
<Button
variant="outlined"
onClick={this.handleClickOpenupdate}
color="primary"
>
<PhotoCameraIcon />
แก้ไขรูปภาพร้าน
</Button>
) : (
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
<AddAPhotoIcon />
เพิ่มรูปภาพร้าน
</Button>
)}
<Dialog
open={open}
onClose={this.handleClose}
......@@ -207,11 +246,11 @@ class Datashop extends Component {
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>
)
),
}}
onChange={e => {
onChange={(e) => {
this.setState({
file: e.target.files[0]
file: e.target.files[0],
});
}}
/>
......@@ -225,21 +264,22 @@ class Datashop extends Component {
const { location, userInfo } = this.props;
const formData = new FormData();
console.log(file);
formData.append('images', file)
formData.append('shop_id', shop.id)
formData.append("images", file);
formData.append("shop_id", shop.id);
// formData.append('userId', userInfo.id)
axios
.post(
`http://localhost:9000/api/storage/upload/shop/${shop.id}`,formData)
.then(response => {
`http://localhost:9000/api/storage/upload/shop/${shop.id}`,
formData
)
.then((response) => {
console.log("เพิ่มรูปภาพสำเร็จ", response);
alert("เพิ่มรูปภาพสำเร็จ");
this.handleClose();
})
.catch(error => {
.catch((error) => {
console.log(error);
});
}}
......@@ -250,11 +290,7 @@ class Datashop extends Component {
</Button>
</DialogActions>
</Dialog>
&nbsp; &nbsp; &nbsp;
<Button variant="outlined" onClick={this.handleClickOpenupdate} color="primary">
<PhotoCameraIcon />
แก้ไขรูปภาพร้าน
</Button>
<Dialog
open={openupdate}
onClose={this.handleCloseupdate}
......@@ -276,11 +312,11 @@ class Datashop extends Component {
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>
)
),
}}
onChange={e => {
onChange={(e) => {
this.setState({
file: e.target.files[0]
file: e.target.files[0],
});
}}
/>
......@@ -294,21 +330,22 @@ class Datashop extends Component {
const { location, userInfo } = this.props;
const formData = new FormData();
// console.log(file);
formData.append('images', file)
formData.append('shop_id', shop.id)
formData.append("images", file);
formData.append("shop_id", shop.id);
// // formData.append('userId', userInfo.id)
axios
.put(
`http://localhost:9000/api/storage/file/shop/${shop.id}`,formData)
.then(response => {
console.log("แก้ไขรรูปภาพสำเร็จ", response);
`http://localhost:9000/api/storage/file/shop/${shop.id}`,
formData
)
.then((response) => {
console.log("แก้ไขรูปภาพสำเร็จ", response);
alert("แกไขรูปภาพสำเร็จ");
this.handleCloseupdate();
})
.catch(error => {
.catch((error) => {
console.log(error);
});
}}
......@@ -335,9 +372,8 @@ class Datashop extends Component {
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
......@@ -351,9 +387,8 @@ class Datashop extends Component {
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
......@@ -368,11 +403,10 @@ class Datashop extends Component {
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="timeclose"
......@@ -386,9 +420,8 @@ class Datashop extends Component {
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
......@@ -403,9 +436,8 @@ class Datashop extends Component {
<InputAdornment position="start">
<BusinessIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
......@@ -420,11 +452,10 @@ class Datashop extends Component {
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="detail"
......@@ -438,9 +469,8 @@ class Datashop extends Component {
<InputAdornment position="start">
<DetailsIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
......@@ -455,11 +485,10 @@ class Datashop extends Component {
<InputAdornment position="start">
<PlaceIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
<TextField
className={classes.margin}
id="lng"
name="lng"
......@@ -472,9 +501,8 @@ class Datashop extends Component {
<InputAdornment position="start">
<PlaceIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
......@@ -489,86 +517,532 @@ class Datashop extends Component {
<InputAdornment position="start">
<FacebookIcon />
</InputAdornment>
)
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="type"
name="type"
value={shop.type}
select
label="ประเภทของร้าน*"
onChange={this.handleChange}
SelectProps={{
native: true
}}
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MergeTypeIcon/>
</InputAdornment>
)
}}
onChange={this.handleChange}
>
{currencies.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
</div>
<br></br>
<div className="row">
<div className="row mt-5">
<div className={classes.root}>
<Button variant="contained" color="secondary">
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button
variant="contained"
color="primary"
onClick={() => {
axios
.post("http://localhost:9000/api/shop/add", {
...shop,
userId: userInfo.id
})
.then(response => {
console.log("เพิ่มข้อมูลร้านสำเร็จ", response);
alert("กรอกข้อมูลไม่ครบ");
})
.catch(error => {
console.log(error);
});
}}
{shop.id ? (
<Fab
color="secondary"
aria-label="edit"
onClick={this.handleClickEdit}
>
<EditIcon />
</Fab>
) : (
<Fab
aria-label={"add"}
className={classes.fab}
color="primary"
onClick={this.handleClickAdd}
>
<AddIcon />
</Fab>
)}
<Dialog
open={add}
onClose={this.handleCloseAdd}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
ตกลง
</Button>
&nbsp; &nbsp; &nbsp;
<Fab color="secondary" aria-label="edit">
<EditIcon />
</Fab>
<DialogTitle id="alert-dialog-title">
{"เพิ่มข้อมูลร้าน"}
</DialogTitle>
<DialogContent>
<TextField
className={classes.margin}
id="name"
name="name"
value={shop.name}
label="ชื่อร้าน*"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="nameeng"
name="nameeng"
value={shop.nameeng}
label="Name*"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="timeopen"
name="timeopen"
value={shop.timeopen}
label="เวลาเปิดร้าน*"
autoComplete="timeopen"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="timeclose"
name="timeclose"
value={shop.timeclose}
label="เวลาปิดร้าน*"
autoComplete="timeclose"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="tel"
name="tel"
value={shop.tel}
label="เบอร์โทรร้าน*"
autoComplete="tel"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<BusinessIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="address"
name="address"
value={shop.address}
label="ที่อยู่ร้าน*"
autoComplete="address"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="detail"
name="detail"
value={shop.detail}
label="รายละเอียดร้าน*"
autoComplete="detail"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<DetailsIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="lat"
name="lat"
value={shop.lat}
label="lat*"
autoComplete="lat"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PlaceIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="lng"
name="lng"
value={shop.lng}
label="lng*"
autoComplete="lng"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PlaceIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="facebook"
name="facebook"
value={shop.facebook}
label="Facebook"
autoComplete="address"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FacebookIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="type"
name="type"
value={shop.type}
select
label="ประเภทของร้าน*"
SelectProps={{
native: true,
}}
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MergeTypeIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
>
{currencies.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
</DialogContent>
<DialogActions>
<Button
variant="contained"
color="secondary"
onClick={this.handleCloseAdd}
>
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button
variant="contained"
color="primary"
onClick={() => {
axios
.post("http://localhost:9000/api/shop/add", {
...shop,
userId: userInfo.id,
})
.then((response) => {
console.log("เพิ่มข้อมูลร้านสำเร็จ", response);
alert("เพิ่มข้อมูลร้านสำเร็จ");
this.handleCloseAdd();
})
.catch((error) => {
console.log(error);
alert("กรอกข้อมูลไม่ครบ");
});
}}
>
ตกลง
</Button>
</DialogActions>
</Dialog>
<Dialog
open={edit}
onClose={this.handleCloseEdit}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"แก้ไขข้อมูลร้าน"}
</DialogTitle>
<DialogContent>
<TextField
className={classes.margin}
id="name"
name="name"
value={shop.name}
label="ชื่อร้าน*"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="nameeng"
name="nameeng"
value={shop.nameeng}
label="Name*"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="timeopen"
name="timeopen"
value={shop.timeopen}
label="เวลาเปิดร้าน*"
autoComplete="timeopen"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="timeclose"
name="timeclose"
value={shop.timeclose}
label="เวลาปิดร้าน*"
autoComplete="timeclose"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="tel"
name="tel"
value={shop.tel}
label="เบอร์โทรร้าน*"
autoComplete="tel"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<BusinessIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="address"
name="address"
value={shop.address}
label="ที่อยู่ร้าน*"
autoComplete="address"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="detail"
name="detail"
value={shop.detail}
label="รายละเอียดร้าน*"
autoComplete="detail"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<DetailsIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="lat"
name="lat"
value={shop.lat}
label="lat*"
autoComplete="lat"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PlaceIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="lng"
name="lng"
value={shop.lng}
label="lng*"
autoComplete="lng"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PlaceIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="facebook"
name="facebook"
value={shop.facebook}
label="Facebook"
autoComplete="address"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<FacebookIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="type"
name="type"
value={shop.type}
select
label="ประเภทของร้าน*"
onChange={this.handleChange}
SelectProps={{
native: true,
}}
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MergeTypeIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
>
{currencies.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
</DialogContent>
<DialogActions>
<Button
variant="contained"
color="secondary"
onClick={this.handleCloseEdit}
>
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button
variant="contained"
color="primary"
onClick={() => {
const { shop } = this.state;
axios
.put(
`http://localhost:9000/api/shop/updateshopId/${shop.id}`,
{
...shop,
}
)
.then((response) => {
console.log("แก้ไขข้อมูลร้านสำเร็จ", response);
alert("แก้ไขข้อมูลสำเร็จ");
this.handleCloseEdit();
})
.catch((error) => {
console.log(error);
});
}}
>
ตกลง
</Button>
</DialogActions>
</Dialog>
</div>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
userInfo: state.user
const mapStateToProps = (state) => ({
userInfo: state.user,
});
const mapDispatchToProps = dispatch => {};
const mapDispatchToProps = (dispatch) => {};
export default connect(
mapStateToProps,
......
......@@ -173,7 +173,7 @@ class ResponsiveDrawer extends Component {
</ListItemIcon>
<ListItemText primary="ดูคิวจอง" />
</ListItem>
<ListItem
{/* <ListItem
button
onClick={() => {
this.handleChangePath("addimage");
......@@ -184,7 +184,7 @@ class ResponsiveDrawer extends Component {
<AddAPhotoIcon />
</ListItemIcon>
<ListItemText primary="เพิ่มรูปภาพ" />
</ListItem>
</ListItem> */}
</List>
</div>
);
......
......@@ -95,7 +95,7 @@ class List extends Component {
};
render() {
const { classes } = this.props;
const { classes ,userInfo} = this.props;
const { columns, data, shop, userId, lists } = this.state;
return (
......@@ -111,7 +111,7 @@ class List extends Component {
editable={{
onRowAdd: (newData) =>
new Promise((resolve) => {
const { userInfo } = this.props;
newData = { ...newData, shop: shop.id, userId: shop.userId };
axios
......@@ -132,12 +132,14 @@ class List extends Component {
});
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve) => {
setTimeout(() => {
newData = { ...newData, shop: shop, userId: userId };
newData = { ...newData, shop: shop.id, userId: shop.userId };
axios
.post(`http://localhost:9000/api/list/updateList/${oldData.id}`, newData)
.put(`http://localhost:9000/api/list/updateListId/${oldData.id}`, newData)
.then((response) => {
console.log("แก้ไข list สำเร็จ", response);
alert("แก้ไขรายการสำเร็จ");
......
......@@ -8,7 +8,9 @@ import {
KeyboardDatePicker
} from "@material-ui/pickers";
import MaterialTable from "material-table";
import axios from "axios";
import { connect } from "react-redux";
const styles = theme => ({
root: {
height: 150,
......@@ -38,19 +40,13 @@ class Managequeue extends Component {
columns: [
{ title: "ชื่อคนจอง", field: "name" },
{ title: "รายการจอง", field: "list" },
{ title: "วันที่จอง" , field: "date"},
{ title: "เวลาที่จอง", field: "time" },
{ title: "ช่างที่จอง", field: "booking" }
],
data: [
{
name: "Benz Piyaphorn",
list: "อบไอน้ำ",
time: "12:00",
booking: "ปิยพร อาภรศรี"
}
{ title: "ช่างที่จอง", field: "beautician" }
],
data: [],
selectedDate: new Date()
};
handleChangeDate = newDate => {
......@@ -79,10 +75,29 @@ class Managequeue extends Component {
// open: false
// });
};
componentDidMount = () => {
let { pathname } = this.props.location;
pathname = pathname.substring(1, pathname.length);
componentDidMount = async () => {
// console.log('pathname', pathname)
const { location, userInfo } = this.props;
const {selectedDate} =this.state
// const id = location.state.id;
const response = await axios.get(
`http://localhost:9000/api/shop/getUserId/${userInfo.id}`
);
const responsebooking = await axios.get(
`http://localhost:9000/api/booking/getBookingShopid/${response.data.id}`
);
// if (responsebooking.data){
// const responsebookingdate = await axios.get(
// `http://localhost:9000/api/booking/getBookingShopid/${selectedDate}`
// );
// console.log(responsebookingdate.data);
// }
if (response.data) this.setState({ shop: response.data });
if (responsebooking .data) this.setState({ data: responsebooking .data });
};
render() {
......@@ -91,7 +106,7 @@ class Managequeue extends Component {
const { open, columns, data, selectedDate } = this.state;
return (
<div>
<div className="row center">
{/* <div className="row center">
<KeyboardDatePicker
disableToolbar
variant="inline"
......@@ -104,8 +119,25 @@ class Managequeue extends Component {
KeyboardButtonProps={{
"aria-label": "change date"
}}
onClick={(e) => {
e.preventDefault();
axios
.post("http://localhost:9000/api/booking/searchBooking", {
name: selectedDate,
})
.then(async (response) => {
this.setState({ booking: response.data });
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
alert("ค้นหาไม่สำเร็จ");
});
}}
/>
</div>
</div> */}
<div className="row">
<MaterialTable
title="คิวจอง"
......@@ -122,4 +154,14 @@ class Managequeue extends Component {
}
}
export default compose(withStyles(styles), withRouter)(Managequeue);
const mapStateToProps = (state) => ({
userInfo: state.user,
});
const mapDispatchToProps = (dispatch) => {};
export default connect(
mapStateToProps,
mapDispatchToProps
)(compose(withStyles(styles), withRouter)(Managequeue));
......@@ -12,11 +12,8 @@ import {
DialogContent,
DialogContentText,
DialogTitle,
FormControl,
TextareaAutosize,
Paper,
CardMedia,
MenuItem,
......@@ -41,18 +38,51 @@ const styles = (theme) => ({
class Profile extends Component {
state = {
user: "",
user: [],
file: null,
open: false
open: false,
openupdate: false,
edit: false,
};
handleClickOpen = () => {
this.setState({
open: true
open: true,
});
};
handleClose = () => {
this.setState({
open: false
open: false,
});
};
handleClickEdit = () => {
this.setState({
edit: true,
});
};
handleCloseEdit = () => {
this.setState({
edit: false,
});
};
handleClickOpenupdate = () => {
this.setState({
openupdate: true,
});
};
handleCloseupdate = () => {
this.setState({
openupdate: false,
});
};
handleChange = (event) => {
let { user } = this.state;
console.log("name : ", event.target.name);
console.log("value : ", event.target.value);
user[event.target.name] = event.target.value;
this.setState({
user,
});
};
componentDidMount = async () => {
......@@ -61,18 +91,29 @@ class Profile extends Component {
const response = await axios.get(
`http://localhost:9000/api/auth/getUserById/${userInfo.id}`
);
// const resposeimguser = await axios.get(`http://localhost:9000/api/storageprofile/file/user/${userInfo.id}`)
// // console.log(resposeimgshop);
// response.data.image = ('data:' + resposeimguser.data.type+';base64,'+resposeimguser.data.url)
console.log(response.data);
this.setState({
user: response.data,
});
console.log(userInfo);
const resposeimguser = await axios.get(
`http://localhost:9000/api/storageprofile/file/user/${userInfo.id}`
);
if(resposeimguser.data){
response.data.image =
"data:" + resposeimguser.data.type + ";base64," + resposeimguser.data.url;
console.log(resposeimguser);
}
console.log(response.data);
if (response.data)
this.setState({
user: response.data,
});
};
render() {
const { classes, userInfo } = this.props;
const { user, file ,shop,open} = this.state;
const { user, file, shop, open, openupdate, edit } = this.state;
return (
<div className="row center">
<div className="row mt-2 mb-2">
......@@ -85,21 +126,33 @@ class Profile extends Component {
<br />
<Avatar
skypeId="sitebase"
src={user.image}
src={user.image}
size="200"
round={true}
/>
</div>
<div className="row center">
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
<AddAPhotoIcon />
เพิ่มรูปภาพร้าน
</Button>
{user.image ? (
<Button
variant="outlined"
onClick={this.handleClickOpenupdate}
color="primary"
>
<PhotoCameraIcon />
แก้ไขรูปภาพร้าน
</Button>
) : (
<Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
<AddAPhotoIcon />
เพิ่มรูปภาพร้าน
</Button>
)}
<Dialog
open={open}
onClose={this.handleClose}
......@@ -142,7 +195,7 @@ class Profile extends Component {
formData.append("images", file);
// formData.append("shop_id", shop.id);
formData.append('userId', userInfo.id)
formData.append("userId", userInfo.id);
axios
.post(
......@@ -165,16 +218,76 @@ class Profile extends Component {
</Button>
</DialogActions>
</Dialog>
&nbsp; &nbsp; &nbsp;
<Button variant="outlined" color="primary">
<PhotoCameraIcon />
แก้ไขรูปภาพร้าน
</Button>
<Dialog
open={openupdate}
onClose={this.handleCloseupdate}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"แก้ไขรูปภาพ"}
</DialogTitle>
<DialogContent>
<TextField
className={classes.margin}
id="outlined-file-input"
label="แก้ไขรูปภาพ"
type="file"
autoComplete="current-password"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AddPhotoAlternateIcon />
</InputAdornment>
),
}}
onChange={(e) => {
this.setState({
file: e.target.files[0],
});
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseupdate} color="primary">
ยกเลิก
</Button>
<Button
onClick={() => {
const { location, userInfo } = this.props;
const formData = new FormData();
console.log(file);
formData.append("images", file);
// formData.append("shop_id", shop.id);
formData.append("userId", userInfo.id);
axios
.put(
`http://localhost:9000/api/storageprofile/file/user/${userInfo.id}`,
formData
)
.then((response) => {
console.log("แก้ไขรรูปภาพสำเร็จ", response);
alert("แกไขรูปภาพสำเร็จ");
this.handleCloseupdate();
})
.catch((error) => {
console.log(error);
});
}}
color="primary"
autoFocus
>
ยืนยัน
</Button>
</DialogActions>
</Dialog>
</div>
</div>
<div className="col s12 m12 l8">
<br />
<div ClassName="row">
<div ClassName="row mt-5">
<TextField
className={classes.margin}
id="name"
......@@ -243,23 +356,132 @@ class Profile extends Component {
}}
/>
</div>
<br></br>
<div className="row">
{/* <div className="row mt-5">
<div className={classes.root}>
<Button variant="contained" color="secondary">
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button variant="contained" color="primary">
ตกลง
</Button>
&nbsp; &nbsp; &nbsp;
<Fab color="secondary" aria-label="edit">
<Fab
color="secondary"
aria-label="edit"
onClick={this.handleClickEdit}
>
<EditIcon />
</Fab>
</div>
</div>
<Dialog
open={edit}
onClose={this.handleCloseEdit}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"แก้ไขข้อมูลโปรไฟล์"}
</DialogTitle>
<DialogContent>
<div ClassName="row">
<TextField
className={classes.margin}
id="name"
name="name"
value={user.name}
label="ชื่อ-นามสกุล"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="email"
name="email"
value={user.email}
label="E_mail"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MailOutlineIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="address"
name="address"
value={user.address}
label="ที่อยู่"
autoComplete="address"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<BusinessIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
<TextField
className={classes.margin}
id="tel"
name="tel"
value={user.tel}
label="เบอร์โทร"
autoComplete="Phone number"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<ContactPhoneIcon />
</InputAdornment>
),
}}
onChange={this.handleChange}
/>
</div>
</DialogContent>
<DialogActions>
<Button
variant="contained"
color="secondary"
onClick={this.handleCloseEdit}
>
ยกเลิก
</Button>{" "}
&nbsp; &nbsp; &nbsp;
<Button
variant="contained"
color="primary"
onClick={() => {
axios
.put("http://localhost:9000/api/auth/updateProfile/:id", {
...user,
})
.then((response) => {
console.log("แก้ไขข้อมูลสำเร็จ", response);
alert("แก้ไขข้อมูลสำเร็จ");
this.handleCloseEdit();
})
.catch((error) => {
console.log(error);
});
}}
>
ตกลง
</Button>
</DialogActions>
</Dialog> */}
{/* </div>
</div> */}
</div>
</div>
</div>
......
......@@ -13,7 +13,7 @@ import {
Divider,
IconButton,
InputAdornment,
TextField
TextField,
} from "@material-ui/core";
import AppBra from "../../components/AppBra";
import { withRouter } from "react-router-dom";
......@@ -32,7 +32,9 @@ import Rating from "@material-ui/lab/Rating";
import MaterialTable from "material-table";
import moment from "moment";
import "moment/locale/th";
const styles = theme => ({
import { handleClickOpenQ, handleCloseQ } from "../ShopPage";
import { connect } from "react-redux";
const styles = (theme) => ({
root: {
// padding: "2px 4px",
display: "flex",
......@@ -41,90 +43,90 @@ const styles = theme => ({
"& .MuiTextField-root": {
margin: theme.spacing(1),
marginTop: theme.spacing(2),
width: "100%"
}
width: "100%",
},
},
margin: {
margin: theme.spacing(1)
margin: theme.spacing(1),
},
input: {
marginLeft: theme.spacing(1),
flex: 1
flex: 1,
},
iconButton: {
padding: 10
padding: 10,
},
divider: {
height: 28,
margin: 4
margin: 4,
},
modal: {
display: "flex",
padding: theme.spacing(1),
alignItems: "center",
justifyContent: "center"
}
justifyContent: "center",
},
});
const currencieslist = [
{
value: "1",
label: " กรุณาเลือกรายการ "
label: " กรุณาเลือกรายการ ",
},
{
value: "2",
label: "อบไอน้ำ 30 นาที"
label: "อบไอน้ำ 30 นาที",
},
{
value: "3",
label: "นวดหน้า 60 นาที "
label: "นวดหน้า 60 นาที ",
},
{
value: "4",
label: "สระได 20 นาที"
label: "สระได 20 นาที",
},
{
value: "5",
label: "ทำเล็บ 60 นาที"
label: "ทำเล็บ 60 นาที",
},
{
value: "6",
label: "ยืดผมถาวร 120 นาที"
label: "ยืดผมถาวร 120 นาที",
},
{
value: "7",
label: "ทำสีผม 60 นาที"
}
label: "ทำสีผม 60 นาที",
},
];
const currencies = [
{
value: "1",
label: " กรุณาเลือกช่าง "
label: " กรุณาเลือกช่าง ",
},
{
value: "2",
label: "ช่าง ก"
label: "ช่าง ก",
},
{
value: "3",
label: "ช่าง ข"
label: "ช่าง ข",
},
{
value: "4",
label: "ช่าง ค"
label: "ช่าง ค",
},
{
value: "5",
label: "ช่าง ง"
label: "ช่าง ง",
},
{
value: "6",
label: "ช่าง จ"
label: "ช่าง จ",
},
{
value: "7",
label: "ช่าง"
}
label: "ช่าง",
},
];
class HomePage extends Component {
state = {
......@@ -136,26 +138,32 @@ class HomePage extends Component {
shops: [],
columns: [{ title: "เวลา", field: "time" }],
data: [],
shopName: null,
datalist: [],
beautician: [],
booking: {
list: "",
beautician: "",
},
};
handleClickOpen = () => {
this.setState({
open: true
open: true,
});
};
handleClose = () => {
this.setState({
open: false
open: false,
});
};
ClickOpen = () => {
this.setState({
list: true
list: true,
});
};
Close = () => {
this.setState({
list: false
list: false,
});
};
......@@ -164,36 +172,76 @@ class HomePage extends Component {
pathname = pathname.substring(1, pathname.length);
// console.log('pathname', pathname)
const tab =
pathname === "spa"
pathname === "สปาและนวด"
? 0
: pathname === "tab2"
: pathname === "ผิวหน้า"
? 1
: pathname === "tab3"
: pathname === "ขนตาและคิ้ว"
? 2
: pathname === "tab4"
: pathname === "ทำเล็บ"
? 3
: pathname === "tab5"
: pathname === "ซาลอน"
? 4
: pathname === "tab6"
: pathname === "แต่หน้าทำผม"
? 5
: 0;
// 7
// const result = tab.filter((tab) => {
// return (
// const response = await axios.get("http://localhost:9000/api/shop/all");
// );
// })
const response = await axios.get("http://localhost:9000/api/shop/all");
const {response2,responsedata }="";
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`)
// console.log(resposeimgshop);
const resposeimgshop = await axios.get(
`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`
);
// console.log(resposeimgshop);
response.data[i].image =
"data:" +
resposeimgshop.data.type +
";base64," +
resposeimgshop.data.url;
const response2 = await axios.get(
`http://localhost:9000/api/list/getListshop/${response.data[i].id}`
);
const responsedata = await axios.get(
`http://localhost:9000/api/beautician/getbeauticianShopId/${response.data[i].id}`
);
this.setState({
response.data[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
datalist: response2.data,
beautician: responsedata.data,
});
}
// const result="";
// for (let i = 0; i < response.data.length; i++) {
// let { timeopen, timeclose } = response[i].data;
// timeopen = timeopen.split(":");
// timeclose = timeclose.split(":");
// let dateClose = new Date();
// dateClose.setHours(parseInt(timeclose[0]));
// dateClose.setMinutes(parseInt(timeclose[1]));
// result = this.calculate(moment(dateClose), timeopen);
// }
console.log("response", response.data);
this.setState({
tab: tab,
shops: response.data,
// data: result,
// datalist: response2.data,
// beautician: responsedata.data,
});
};
calculate = (endTime, timeopen) => {
let timeStops = [];
......@@ -218,71 +266,124 @@ class HomePage extends Component {
handleChangeTab = (event, newValue) => {
const tab =
newValue === 0
? "spa"
? "สปาและนวด"
: newValue === 1
? "tab2"
? "ผิวหน้า"
: newValue === 2
? "tab3"
? "ขนตาและคิ้ว"
: newValue === 3
? "tab4"
? "ทำเล็บ"
: newValue === 4
? "tab5"
? "ซาลอน"
: newValue === 5
? "tab6"
: "spa";
? "แต่งหน้าทำผม"
: "สปาและนวด";
this.setState({ tab: newValue });
// this.props.history.push(`/${tab}`);
};
handleChangeDate = newDate => {
handleChangeDate = (newDate) => {
console.log("newDate: ", newDate);
this.setState({
date: newDate
date: newDate,
});
};
handleChange = (event) => {
let { shopName } = this.state;
console.log("name : ", event.target.name);
console.log("value : ", event.target.value);
shopName = [event.target.name] = event.target.value;
this.setState({
shopName,
});
};
render() {
const { classes } = this.props;
const { classes ,userInfo} = this.props;
// const tab = this.state.tab;
// หรือ
const { tab, date, open, list, value, rating, shops, columns,
data, } = this.state;
const {
tab,
date,
open,
list,
value,
rating,
shops,
columns,
data,
shopName,
datalist,
beautician,
booking,
} = this.state;
return (
<div>
<AppBra tab={tab} handleChangeTab={this.handleChangeTab} />
{/* <AppBra tab={tab} handleChangeTab={this.handleChangeTab} /> */}
<div className="row mt-5 ">
<div className="row mt-2 mb-2 ">
<div className="row ">
<center>
<Paper component="form" className={classes.root}>
<InputBase
className={classes.input}
inputProps={{ "aria-label": "ค้นหาร้าน" }}
name="shopName"
value={shopName}
onChange={this.handleChange}
/>
<IconButton
type="submit"
className={classes.iconButton}
aria-label="search"
onClick={(e) => {
e.preventDefault();
axios
.post("http://localhost:9000/api/shop/searchShop", {
name: shopName,
})
.then(async (response) => {
for (let i = 0; i < response.data.length; i++) {
const resposeimgshop = await axios.get(
`http://localhost:9000/api/storage/file/shop/${response.data[i].id}`
);
console.log(resposeimgshop);
response.data[i].image =
"data:" +
resposeimgshop.data.type +
";base64," +
resposeimgshop.data.url;
}
this.setState({ shops: response.data });
console.log("ค้นหาสำเร็จ", response);
// alert("ค้นหาสำเร็จ");
})
.catch((error) => {
console.log(error);
alert("ค้นหาไม่สำเร็จ");
});
}}
>
<SearchIcon />
</IconButton>
</Paper>
</center>
</div>
</div>
{shops.map(shop => (
{shops.map((shop) => (
<div className="row mt-5 mb-2 ">
<div className="col s12 m6 l6">
<div className="row mt-3 mb-2 center">
<img
<img
src={shop.image}
width="60%"
onClick={() => {
this.props.history.push("/ShopPage", { id: shop.id});
this.props.history.push("/ShopPage", { id: shop.id });
}}
/>
</div>
......@@ -298,28 +399,26 @@ class HomePage extends Component {
<div className="row mt-2 mb-2">{shop.address}</div>
<div className="row mt-3 mb-2">{shop.detail}</div>
<div className="row mt-3 mb-2">
{shop.detail}
</div>
<div className="row mt-3 mb-2">
<div className={classes.root}>
{/* <div className={classes.root}>
<Rating
name="size-large"
defaultValue={3.5}
size="large"
readOnly
/>
</div>
</div> */}
</div>
<div className="row center">
<Button
{/* <Button
variant="outlined"
color="primary"
onClick={this.handleClickOpen}
>
จองคิว
</Button>
</Button> */}
<Dialog
open={open}
fullWidth={true}
......@@ -336,7 +435,7 @@ class HomePage extends Component {
<div className="col s12 m5">
<div
style={{
maxWidth: "fit-content"
maxWidth: "fit-content",
}}
>
<DatePicker
......@@ -354,22 +453,24 @@ class HomePage extends Component {
<Typography>
วันที่ที่เลือกคือ{" "}
{format(new Date(date), "dd MMMM yyyy", {
locale: th
locale: th,
})}
</Typography>
<div className="mt-2">
<Typography>กรุณาเลือกข้อมูลการจอง</Typography>
<div className="row mt-2">
<div className="col">
<TextField
className={classes.margin}
id="list"
name="list"
value={booking.list}
select
label="เลือกรายการ"
label="เลือกรายการ*"
onChange={this.handleChange}
SelectProps={{
native: true
native: true,
}}
variant="outlined"
InputProps={{
......@@ -377,15 +478,15 @@ class HomePage extends Component {
<InputAdornment position="start">
<ListIcon />
</InputAdornment>
)
),
}}
>
{currencieslist.map(option => (
{datalist.map((datalist) => (
<option
key={option.value}
value={option.value}
key={datalist.id}
value={datalist.name}
>
{option.label}
{datalist.name}
</option>
))}
</TextField>
......@@ -393,13 +494,14 @@ class HomePage extends Component {
<div className="col">
<TextField
className={classes.margin}
id="list"
name="list"
id="beautician"
name="beautician"
value={booking.beautician}
select
label="เลือกช่าง"
label="เลือกช่าง*"
onChange={this.handleChange}
SelectProps={{
native: true
native: true,
}}
variant="outlined"
InputProps={{
......@@ -407,21 +509,21 @@ class HomePage extends Component {
<InputAdornment position="start">
<AssignmentIndIcon />
</InputAdornment>
)
),
}}
>
{currencies.map(option => (
{beautician.map((beautician) => (
<option
key={option.value}
value={option.value}
key={beautician.id}
value={beautician.name}
>
{option.label}
{beautician.name}
</option>
))}
</TextField>
</div>
<div className="col s12 mt-2">
<MaterialTable
<MaterialTable
title=""
columns={columns}
data={data}
......@@ -434,14 +536,14 @@ class HomePage extends Component {
search: false,
showTitle: false,
toolbar: false,
rowStyle: rowData => ({
rowStyle: (rowData) => ({
backgroundColor:
this.state.selectedRow &&
this.state.selectedRow.tableData.id ===
rowData.tableData.id
? "#EEE"
: "#FFF"
})
: "#FFF",
}),
}}
/>
</div>
......@@ -451,9 +553,9 @@ class HomePage extends Component {
</div>
</DialogContent>
<DialogActions>
<Button
<Button
variant="outlined"
onClick={this.handleCloseQ}
onClick={this.handleClose}
color="secondary"
>
ยกเลิก
......@@ -461,7 +563,29 @@ class HomePage extends Component {
<Button
variant="outlined"
color="primary"
onClick={this.handleSaveQ}
onClick={() => {
axios
.post("http://localhost:9000/api/booking/add", {
// ...booking,
name: userInfo.name,
shop: shop.id,
list: booking.list,
beautician: booking.beautician,
date: new Date(date),
// time: selectedRow.time,
name: userInfo.name,
})
.then((response) => {
console.log("ทำการจองคิวสำเร็จ", response);
alert("ทำการจองคิวสำเร็จ");
})
.catch((error) => {
console.log(error);
alert("คุณยังไม่ได้ทำการ login ");
this.props.history.push("/LoginPage");
});
this.handleClose();
}}
>
ยืนยัน
</Button>
......@@ -477,4 +601,14 @@ class HomePage extends Component {
}
}
export default compose(withStyles(styles), withRouter)(HomePage);
const mapStateToProps = (state) => ({
userInfo: state.user,
});
const mapDispatchToProps = (dispatch) => {};
export default connect(
mapStateToProps,
mapDispatchToProps
)(compose(withStyles(styles), withRouter)(HomePage));
......@@ -35,15 +35,18 @@ import ListIcon from "@material-ui/icons/List";
import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
import axios from "axios";
import { connect } from "react-redux";
import { red } from "@material-ui/core/colors";
import MaterialTable from "material-table";
import moment from "moment";
import FacebookIcon from "@material-ui/icons/Facebook";
import "moment/locale/th";
const styles = (theme) => ({
root: {
minWidth: 275,
height: 150,
height: 200,
flexGrow: 1,
maxheight: 345,
transform: "translateZ(0)",
// The position fixed scoping doesn't work in IE 11.
// Disable this demo to preserve the others.
......@@ -220,11 +223,7 @@ class ShopPage extends Component {
// open: true,
open: false,
shop: null,
review: {
topic: null,
message: null,
point: null,
},
review: [],
columns: [{ title: "เวลา", field: "time" }],
columnslist: [
{ title: "ชื่อรายการ", field: "name" },
......@@ -235,6 +234,13 @@ class ShopPage extends Component {
data: [],
selectedRow: null,
datalist: [],
beautician: [],
booking: {
list: "",
beautician: "",
},
beauticianimg: [],
shopimg: [],
};
handleClickOpenreview = () => {
......@@ -276,14 +282,16 @@ class ShopPage extends Component {
};
handleChange = (event) => {
let { review } = this.state;
let { review, booking } = this.state;
console.log("name : ", event.target.name);
console.log("value : ", event.target.value);
review[event.target.name] = event.target.value;
booking[event.target.name] = event.target.value;
this.setState({
review,
booking,
});
};
......@@ -292,19 +300,44 @@ class ShopPage extends Component {
this.setState({
date: newDate,
});
};
};
componentDidMount = async () => {
const { location, userInfo } = this.props;
const id = location.state.id;
const { beauticianimg } = this.state;
const response1 = await axios.get(
`http://localhost:9000/api/shop/getShopId/${id}`
);
const response2 = await axios.get(
`http://localhost:9000/api/list/getListshop/${id}`
`http://localhost:9000/api/list/getListshop/${id}`
);
const responsereview = await axios.get(
`http://localhost:9000/api/review/getReviewShopid/${response1.data.id}`
);
const resposeimgshop = await axios.get(
`http://localhost:9000/api/storage/file/shop/${response1.data.id}`
);
// const resposeimgwork = await axios.get(
// `http://localhost:9000/api/storageshop/file/shop/${response1.data.id}`
// );
// for (let i = 0; i < resposeimgwork.length; i++) {
// resposeimgwork[i].image = ('data:' + resposeimgshop.data.type+';base64,'+resposeimgshop.data.url)
// }
// console.log(resposeimgshop);
const responsedata = await axios.get(
`http://localhost:9000/api/beautician/getbeauticianShopId/${response1.data.id}`
);
response1.data.image =
"data:" + resposeimgshop.data.type + ";base64," + resposeimgshop.data.url;
console.log("response", response1.data);
console.log("response", response2.data);
console.log("response", responsereview.data);
console.log("response", responsedata.data);
let { timeopen, timeclose } = response1.data;
timeopen = timeopen.split(":");
timeclose = timeclose.split(":");
......@@ -312,14 +345,19 @@ class ShopPage extends Component {
dateClose.setHours(parseInt(timeclose[0]));
dateClose.setMinutes(parseInt(timeclose[1]));
const result = this.calculate(moment(dateClose), timeopen);
// tmp = [a,b,c]
// if(x === b){
// remove b from tmp
// }
// render tmp
this.setState({
shop: response1.data,
data: result,
datalist: response2.data
datalist: response2.data,
beautician: responsedata.data,
review: responsereview.data,
// shopimg:resposeimgwork.data
});
};
calculate = (endTime, timeopen) => {
......@@ -357,11 +395,15 @@ class ShopPage extends Component {
datalist,
selectedRow,
columnslist,
beautician,
booking,
shopimg,
beauticianimg,
} = this.state;
return (
<div>
<AppBar />
{/* <AppBar /> */}
{shop && (
<div className="row">
......@@ -374,13 +416,10 @@ class ShopPage extends Component {
{shop.address}
</div>
</div>
<div c>
<div>
<div className="col s12 m6 l8">
<div className="row center">
<img
src="https://www.smeleader.com/wp-content/uploads/2018/05/%E0%B9%81%E0%B8%9F%E0%B8%A3%E0%B8%99%E0%B9%84%E0%B8%8A%E0%B8%AA%E0%B9%8C%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%99%E0%B9%80%E0%B8%AA%E0%B8%A3%E0%B8%B4%E0%B8%A1%E0%B8%AA%E0%B8%A7%E0%B8%A2-%E0%B8%A3%E0%B8%A7%E0%B8%A1%E0%B9%81%E0%B8%9A%E0%B8%A3%E0%B8%99%E0%B8%94%E0%B9%8C%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%99%E0%B8%8B%E0%B8%B2%E0%B8%A5%E0%B8%AD%E0%B8%99-%E0%B8%AA%E0%B8%B2%E0%B8%99%E0%B8%9D%E0%B8%B1%E0%B8%99%E0%B8%98%E0%B8%B8%E0%B8%A3%E0%B8%81%E0%B8%B4%E0%B8%88%E0%B8%97%E0%B8%A3%E0%B8%87%E0%B8%9C%E0%B8%A1.jpg"
width="90%"
></img>
<img src={shop.image} width="90%"></img>
</div>
<div className="row center">
<Button
......@@ -473,6 +512,7 @@ class ShopPage extends Component {
className={classes.margin}
id="list"
name="list"
value={booking.list}
select
label="เลือกรายการ*"
onChange={this.handleChange}
......@@ -491,7 +531,7 @@ class ShopPage extends Component {
{datalist.map((datalist) => (
<option
key={datalist.id}
value={datalist.id}
value={datalist.name}
>
{datalist.name}
</option>
......@@ -501,8 +541,9 @@ class ShopPage extends Component {
<div className="col">
<TextField
className={classes.margin}
id="list"
name="list"
id="beautician"
name="beautician"
value={booking.beautician}
select
label="เลือกช่าง*"
onChange={this.handleChange}
......@@ -518,12 +559,12 @@ class ShopPage extends Component {
),
}}
>
{currencies.map((option) => (
{beautician.map((beautician) => (
<option
key={option.value}
value={option.value}
key={beautician.id}
value={beautician.name}
>
{option.label}
{beautician.name}
</option>
))}
</TextField>
......@@ -569,111 +610,35 @@ class ShopPage extends Component {
<Button
variant="outlined"
color="primary"
onClick={this.handleSaveQ}
onClick={() => {
axios
.post("http://localhost:9000/api/booking/add", {
// ...booking,
name: userInfo.name,
shop: shop.id,
list: booking.list,
beautician: booking.beautician,
date: new Date(date),
time: selectedRow.time,
name: userInfo.name,
})
.then((response) => {
console.log("ทำการจองคิวสำเร็จ", response);
alert("ทำการจองคิวสำเร็จ");
})
.catch((error) => {
console.log(error);
alert("คุณยังไม่ได้ทำการ login ");
this.props.history.push("/LoginPage");
});
this.handleCloseQ();
}}
>
ยืนยัน
</Button>
</DialogActions>
</Dialog>
</div>
</div>
<div className="col s12 m6 l4">
<div className="row">
<center>
<div style={{ height: "50vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{
key: "AIzaSyBJWuQmrf6UgrkGbMJF6-m1GwTZrazBFBo",
}}
defaultCenter={{
lat: shop.lat * 1,
lng: shop.lng * 1,
}}
defaultZoom={16}
>
<Marker
lat={shop.lat * 1}
lng={shop.lng * 1}
text={`My Marker2`}
/>
</GoogleMapReact>
</div>
</center>
</div>
<div className="row">
<h4>ที่อยู่ :&nbsp;{shop.address}</h4>
</div>
<div className="row">
<h4>เบอร์โทร : {shop.tel}</h4>
<h4>เวลาเปิด : {shop.timeopen} .</h4>
<h4>เวลาปิด : {shop.timeclose} .</h4>
</div>
</div>
</div>
<div className="row mt-2 mb-2">
<div className="col s12 m5 l7">
<div className="row center">
<h4>ผลงานช่าง ปิยพร อาภรศรี</h4>
<div className="col s12 m12 l6 mb-5">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSAaEt_unwBuDLyqCP_bW3PBawVHkjZrNq-F3u7mAKtSjmBTrHE"></img>
</div>
<div className="col s12 m12 l6 mb-5">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSAaEt_unwBuDLyqCP_bW3PBawVHkjZrNq-F3u7mAKtSjmBTrHE"></img>
</div>
</div>
</div>
<div className="col s12 m1 l1"></div>
<div className="col s12 m6 l4">
<div className="row">
<Card className={classes.root} variant="outlined">
<CardHeader
avatar={
<Avatar aria-label="recipe" className={classes.avatar}>
R
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title="Shrimp and Chorizo Paella"
subheader="September 14, 2016"
/>
<div className="row center">
<div className={classes.root}>
<Rating
name="size-large"
defaultValue={3.5}
size="small"
readOnly
/>
</div>
</div>
<CardMedia
className={classes.media}
image="/static/images/cards/paella.jpg"
title="Paella dish"
/>
<CardContent>
<Typography
variant="body2"
color="textSecondary"
component="p"
>
This impressive paella is a perfect party dish and a fun
meal to cook together with your guests. Add 1 cup of
frozen peas along with the mussels, if you like.
</Typography>
</CardContent>
</Card>
</div>
<div className="row right">
&nbsp; &nbsp; &nbsp;
<Button
variant="contained"
color="primary"
......@@ -771,36 +736,30 @@ class ShopPage extends Component {
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleCloserevie}
color="primary"
>
<Button onClick={this. handleClosereview } color="primary">
ยกเลิก
</Button>
<Button
onClick={() => {
if(userInfo.id){
axios
axios
.post("http://localhost:9000/api/review/add", {
...review,
shop: shop.id,
userID: userInfo.id,
name: userInfo.name,
})
.then((response) => {
console.log("เขียนรีวิวสำเร็จ", response);
alert("เขียนรีวิวสำเร็จ");
this. handleClosereview ();
})
.catch((error) => {
console.log(error);
alert("คุณยังไม่ได้ทำการ login ");
this.props.history.push("/LoginPage");
// this.handleCloserevie();
// alert("คุณยังไม่ได้ทำการ login ");
// this.props.history.push("/LoginPage");
});
}
else{
alert("คุณยังไม่ได้ทำการ login ");
this.props.history.push("/LoginPage");
}
// this.handleCloserevie();
}}
color="primary"
>
......@@ -808,8 +767,109 @@ class ShopPage extends Component {
</Button>
</DialogActions>
</Dialog>
{/* <div className="row center mt-5">
<h4>ผลงานร้าน {shop.name}</h4>
{shopimg.image.map(shopimg=> (
<div className="col s12 m12 l6 mb-5">
<img src={shopimg.image}></img>
</div>
))}
</div> */}
{/* {beautician.map((beautician) => (
<div className="row center mt-5">
<h4>ผลงานช่าง {beautician.name}</h4>
<div className="col s12 m12 l6 mb-5">
<img src={beautician.image}></img>
</div>
</div>
))} */}
</div>
</div>
<div className="col s12 m6 l4">
<div className="row">
<center>
<div style={{ height: "50vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{
key: "AIzaSyBJWuQmrf6UgrkGbMJF6-m1GwTZrazBFBo",
}}
defaultCenter={{
lat: shop.lat * 1,
lng: shop.lng * 1,
}}
defaultZoom={16}
>
<Marker
lat={shop.lat * 1}
lng={shop.lng * 1}
text={`My Marker2`}
/>
</GoogleMapReact>
</div>
</center>
</div>
<div className="row">
<h4>ที่อยู่ :&nbsp;{shop.address}</h4>
</div>
<div className="row">
<h4>เบอร์โทร : {shop.tel}</h4>
<h4>เวลาเปิด : {shop.timeopen} .</h4>
<h4>เวลาปิด : {shop.timeclose} .</h4>
<Button
variant="contained"
color="primary"
disableElevation
href={shop.facebook}
>
<FacebookIcon />
</Button>
</div>
</div>
<div className="col s12 m8 l8">
{review.map((review) => (
<div className="row">
<Card className={classes.root} variant="outlined">
<CardHeader
avatar={
<Avatar
aria-label="recipe"
className={classes.avatar}
>
{review.name.slice(0, 1)}
</Avatar>
}
title={review.name}
subheader={review.updatedAt}
/>
<CardContent>
<Typography>
<div className="row center">
<Rating
name="size-large"
defaultValue={review.point}
size="small"
readOnly
/>
</div>
<b>{review.topic}</b> : {review.message}
</Typography>
</CardContent>
</Card>
</div>
))}
</div>
<div className="col s12 m4 l4">
</div>
</div>
</div>
)}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment