노력이 좋아서

<step76>'react_고객관리 사이트 구현_step_final'

zoaseo 2022. 7. 8. 14:16

1) 

green_customer_client

components/

CreateCustomer.js

import React, { useState } from 'react';
import { Table, TableBody, TableRow, TableCell } from '@mui/material';
import PopupDom from './PopupDom';
import PopupPostCode from './PopupPostCode';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import { API_URL } from '../config/conf';

const CreateCustomer = () => {
    const navigate = useNavigate(); // 리다이렉션
    // 우편번호 관리하기
    const onAddData = (data) => {
        console.log(data);
        setFormData({
            ...formData,
            c_add: data.address,
        })
    }
    // 팝업창 상태 관리
    const [ isPopupOpen, setIsPopupOpen] = useState(false);
    // 팝업창 상태 true로 변경
    const openPostCode = () => {
        setIsPopupOpen(true);
    }
    // 팝업창 상태 false로 변경
    const closePostCode = () => {
        setIsPopupOpen(false);
    }
    const [ formData, setFormData ] = useState({
        c_name: "",
        c_phone: "",
        c_birth: "",
        c_gender: "",
        c_add: "",
        c_adddetail: "",
    })
    const onChange = (e) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value
        })
    }
    // 폼 submit 이벤트
    const onSubmit = (e) => {
        // form에 원래 연결된 이벤트를 제거
        e.preventDefault();
        console.log(formData);
        // 전화번호가 숫자인지 체크하기
        if(isNaN(formData.c_phone)){
            alert("전화번호는 숫자만 입력해주세요");
            setFormData({
                ...formData,
                c_phone: "",
            })
        }
        // input에 값이 있는지 체크하고
        // 입력이 다되어있으면 post전송
        else if(formData.c_name !== "" && formData.c_phone !== "" &&
        formData.c_birth !== "" && formData.c_gender !== "" &&
        formData.c_add !== "" && formData.c_adddetail !== ""){
            insertCustomer();
        }
    }
    function insertCustomer(){
        axios.post(`${API_URL}/addCustomer`,formData)
        .then((result)=>{
            console.log(result);
            navigate("/"); // 리다이렉션 추가
        })
        .catch(e=>{
            console.log(e);
        })
    }
    return (
        <div>
            <h2>신규 고객 등록하기</h2>
            <form onSubmit={onSubmit}>
                <Table>
                    <TableBody>
                        <TableRow>
                            <TableCell>이름</TableCell>
                            <TableCell>
                                <input name="c_name" type="text" 
                                value={formData.c_name}
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>연락처</TableCell>
                            <TableCell>
                                <input name="c_phone" type="text" 
                                value={formData.c_phone}
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>생년월일</TableCell>
                            <TableCell>
                                <input name="c_birth" type="date" 
                                value={formData.c_birth}
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>성별</TableCell>
                            <TableCell>
                                여성<input name="c_gender" type="radio" 
                                value="여성"
                                onChange={onChange}/>
                                남성<input name="c_gender" type="radio" 
                                value="남성"
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>주소</TableCell>
                            <TableCell>
                                <input name="c_add" type="text" 
                                value={formData.c_add}
                                onChange={onChange}/>
                                <input name="c_adddetail" type="text"
                                value={formData.c_adddetail}
                                onChange={onChange}/>
                                <button type="button" onClick={openPostCode}>우편번호 검색</button>
                                <div id="popupDom">
                                    {isPopupOpen && (
                                        <PopupDom>
                                            <PopupPostCode onClose={closePostCode}
                                            onAddData={onAddData}/>
                                        </PopupDom>
                                    )}
                                </div>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell colSpan={2}>
                                <button type="submit">등록</button>
                                <button type="reset">취소</button>
                            </TableCell>
                        </TableRow>
                    </TableBody>
                </Table>                
            </form>
        </div>
    );
};

export default CreateCustomer;

Customer.js

import React from 'react';
import { TableRow, TableCell } from '@mui/material';
import { Link } from 'react-router-dom';

const Customer = ({customer}) => {

    return (
        <TableRow>
            <TableCell>{customer.no}</TableCell>
            <TableCell><Link to={`/detailview/${customer.no}`}>{customer.name}</Link></TableCell>
            <TableCell>{customer.phone}</TableCell>
            <TableCell>{customer.birth}</TableCell>
            <TableCell>{customer.gender}</TableCell>
            <TableCell>{customer.add1}<br/>{customer.add2}</TableCell>
        </TableRow>
    );
};

export default Customer;

CustomerList.js

import React from 'react';
import { Table, TableBody, TableHead, TableCell, TableRow} from '@mui/material';
import Customer from './Customer';
import axios from 'axios';
import useAsync from '../customHook/useAsync';
import { API_URL } from '../config/conf';

async function getCustomers(){
    const response = await axios.get(`${API_URL}/customers`);
    return response.data;
}
const CustomerList = () => {
    const [ state ] = useAsync(getCustomers,[]);
    const { loading, data:customers, error } = state;
    if(loading) return <div>로딩중....</div>
    if(error) return <div>에러가 발생했습니다.</div>
    if(!customers) return <div>로딩중입니다.</div>

    return (
        <div>
            <h2>고객리스트</h2>
            <Table>
                <TableHead>
                    <TableRow>
                        <TableCell>번호</TableCell>
                        <TableCell>이름</TableCell>
                        <TableCell>연락처</TableCell>
                        <TableCell>생년월일</TableCell>
                        <TableCell>성별</TableCell>
                        <TableCell>주소</TableCell>
                    </TableRow>
                </TableHead>
                <TableBody>
                    {customers.map(customer=>(
                        <Customer key={customer.no} customer={customer}/>
                    ))}
                </TableBody>
            </Table>
        </div>
    );
};

export default CustomerList;

DetailCustomer.js

import React from 'react';
import { Table, TableBody, TableCell, TableRow } from '@mui/material';
import axios from 'axios';
import useAsync from '../customHook/useAsync';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { API_URL } from '../config/conf';
 
const DetailCustomer = () => {
    async function getCustomers(no){
        const response = await axios.get(`${API_URL}/detailview/${no}`);
        return response.data;
    }  
    const { no } = useParams();
    const navigate = useNavigate();
    const [ state ] = useAsync(()=>getCustomers(no),[no]);
    const { loading, data:customer, error } = state;

    // 삭제하기
    const onDelete = () => {
        axios.delete(`${API_URL}/delCustomer/${no}`)
        .then(result=>{
            console.log("삭제되었습니다.");
            navigate("/"); // 리다이렉션 추가
        })
        .catch(e=>{
            console.log(e);
        })
    }

    if(loading) return <div>로딩중.....</div>
    if(error) return <div>에러가 발생했습니다.</div>
    if(!customer) return <div>로딩중입니다.</div>
    return (
        <div>
            <h2>고객 상세 정보</h2>
            <Table>
                <TableBody>
                    <TableRow>
                        <TableCell>고객명</TableCell>
                        <TableCell>{customer.name}</TableCell>
                    </TableRow>
                    <TableRow>
                        <TableCell>연락처</TableCell>
                        <TableCell>{customer.phone}</TableCell>
                    </TableRow>
                    <TableRow>
                        <TableCell>생년월일</TableCell>
                        <TableCell>{customer.birth}</TableCell>
                    </TableRow>
                    <TableRow>
                        <TableCell>주소</TableCell>
                        <TableCell>{customer.add1}<br/>{customer.add2}</TableCell>
                    </TableRow>
                    <TableRow>
                        <TableCell colSpan={2}>
                            <button onClick={onDelete}>삭제</button>
                            <button><Link to={`/editCustomer/${no}`}>수정</Link></button>
                        </TableCell>
                    </TableRow>
                </TableBody>
            </Table>
        </div>
    );
};

export default DetailCustomer;

EditCustomer.js

import React, { useState, useEffect } from 'react';
import { Table, TableBody, TableRow, TableCell } from '@mui/material';
import PopupDom from './PopupDom';
import PopupPostCode from './PopupPostCode';
import axios from 'axios';
import { useParams, useNavigate } from 'react-router-dom';
import { API_URL } from '../config/conf';
import useAsync from '../customHook/useAsync';

const EditCustomer = () => {
    const navigate = useNavigate(); // 리다이렉션
    const { no } = useParams();
    const [ formData, setFormData ] = useState({
        c_name: "",
        c_phone: "",
        c_birth: "",
        c_gender: "",
        c_add: "",
        c_adddetail: "",
    })
    async function getCustomers(no){
        const response = await axios.get(`${API_URL}/detailview/${no}`);
        return response.data;
    }  
    const [ state ] = useAsync(()=>getCustomers(no),[no]);
    const { loading, data:customer, error } = state;
    useEffect(()=>{
        setFormData({
            c_name: customer? customer.name : "",
            c_phone: customer? customer.phone : "",
            c_birth: customer? customer.birth : "",
            c_gender: customer? customer.gender : "",
            c_add: customer? customer.add1 : "",
            c_adddetail: customer? customer.add2 : "",
        })
    },[customer])
    // 우편번호 관리하기
    const onAddData = (data) => {
        console.log(data);
        setFormData({
            ...formData,
            c_add: data.address,
        })
    }
    // 팝업창 상태 관리
    const [ isPopupOpen, setIsPopupOpen] = useState(false);
    // 팝업창 상태 true로 변경
    const openPostCode = () => {
        setIsPopupOpen(true);
    }
    // 팝업창 상태 false로 변경
    const closePostCode = () => {
        setIsPopupOpen(false);
    }
    const onChange = (e) => {
        const { name, value } = e.target;
        setFormData({
            ...formData,
            [name]: value
        })
    }
    // 폼 submit 이벤트
    const onSubmit = (e) => {
        // form에 원래 연결된 이벤트를 제거
        e.preventDefault();
        console.log(formData);
        // 전화번호가 숫자인지 체크하기
        if(isNaN(formData.c_phone)){
            alert("전화번호는 숫자만 입력해주세요");
            setFormData({
                ...formData,
                c_phone: "",
            })
        }
        // input에 값이 있는지 체크하고
        // 입력이 다되어있으면 post전송
        else if(formData.c_name !== "" && formData.c_phone !== "" &&
        formData.c_birth !== "" && formData.c_gender !== "" &&
        formData.c_add !== "" && formData.c_adddetail !== ""){
            updateCustomer();
        }
    }
    function updateCustomer(){
        axios.put(`${API_URL}/editCustomer/${no}`,formData)
        .then((result)=>{
            console.log(result);
            navigate("/"); // 리다이렉션 추가
        })
        .catch(e=>{
            console.log(e);
        })
    }
    if(loading) return <div>로딩중.....</div>
    if(error) return <div>페이지를 나타낼 수 없습니다.</div>
    if(!customer) return null;
    return (
        <div>
            <h2>고객 정보 수정하기</h2>
            <form onSubmit={onSubmit}>
                <Table>
                    <TableBody>
                        <TableRow>
                            <TableCell>이름</TableCell>
                            <TableCell>
                                <input name="c_name" type="text" 
                                value={formData.c_name}
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>연락처</TableCell>
                            <TableCell>
                                <input name="c_phone" type="text" 
                                value={formData.c_phone}
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>생년월일</TableCell>
                            <TableCell>
                                <input name="c_birth" type="date" 
                                value={formData.c_birth}
                                onChange={onChange}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>성별</TableCell>
                            <TableCell>
                                여성<input name="c_gender" type="radio" 
                                value="여성"
                                onChange={onChange}
                                checked={formData.c_gender === "여성" ? true : false}/>
                                남성<input name="c_gender" type="radio" 
                                value="남성"
                                onChange={onChange}
                                checked={formData.c_gender === "남성" ? true : false}/>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell>주소</TableCell>
                            <TableCell>
                                <input name="c_add" type="text" 
                                value={formData.c_add}
                                onChange={onChange}/>
                                <input name="c_adddetail" type="text"
                                value={formData.c_adddetail}
                                onChange={onChange}/>
                                <button type="button" onClick={openPostCode}>우편번호 검색</button>
                                <div id="popupDom">
                                    {isPopupOpen && (
                                        <PopupDom>
                                            <PopupPostCode onClose={closePostCode}
                                            onAddData={onAddData}/>
                                        </PopupDom>
                                    )}
                                </div>
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell colSpan={2}>
                                <button type="submit">등록</button>
                                <button type="reset">취소</button>
                            </TableCell>
                        </TableRow>
                    </TableBody>
                </Table>                
            </form>
        </div>
    );
};

export default EditCustomer;

Footer.js

import React from 'react';

const Footer = () => {
    return (
        <div id="footer">
            <h1>그린 고객 관리</h1>
            <p>
                대표자 : 김그린 | 사업자 등록번호 214-86-12345<br/>
                통신판매업 신고 : 강남 13711호 | 학원등록번호 : 강남 제 1104호<br/>
                주소 : 서울시 강남구 역삼동 강남빌딩 5층<br/>
                copyright (c) 2022 gitaAcademy
            </p>
        </div>
    );
};

export default Footer;

Header.js

import React from 'react';
import { Link } from 'react-router-dom';

const Header = () => {
    return (
        <div id="header">
            <h1>그린고객센터</h1>
            <ul>
                <li><Link to="/">고객리스트보기</Link></li>
                <li><Link to="/write">신규 고객 등록하기</Link></li>
                <li>고객 검색</li>
            </ul>
        </div>
    );
};

export default Header;

PopupDom.js

import ReactDom from 'react-dom';

const PopupDom = ({children}) => {
    const el = document.getElementById('popupDom');
    return ReactDom.createPortal(children, el);
};

export default PopupDom;

PopupPostCode.js

import React from 'react';
import DaumPostcode from "react-daum-postcode";
 
const PopupPostCode = (props) => {
	// 우편번호 검색 후 주소 클릭 시 실행될 함수, data callback 용
    const handlePostCode = (data) => {
        let fullAddress = data.address;
        let extraAddress = ''; 
        
        if (data.addressType === 'R') {
          if (data.bname !== '') {
            extraAddress += data.bname;
          }
          if (data.buildingName !== '') {
            extraAddress += (extraAddress !== '' ? `, ${data.buildingName}` : data.buildingName);
          }
          fullAddress += (extraAddress !== '' ? ` (${extraAddress})` : '');
        }
        console.log(data)
        console.log(fullAddress)
        console.log(data.zonecode)
        props.onAddData(data);
        
    }

    const postCodeStyle = {
        display: "block",
        position: "absolute",
        top: '50%',
        left: '50%',
        transform:'translate(-50%,-50%)',
        width: "600px",
        height: "600px",
        padding: "7px",
        border: "2px solid #666"
      };
 
    return(
        <div>
            <DaumPostcode style={postCodeStyle} onComplete={handlePostCode} /> 
            <button type='button' onClick={() => {props.onClose()}} className='postCode_btn'>입력</button>
        </div>
    )
}
 
export default PopupPostCode;

config/

conf.js

export const API_URL = "http://localhost:3001";

customHook/

useAsync.js

import { useReducer, useEffect } from 'react';
const initialState = {
    loading: false,
    data: null,
    error: null,
}

function reducer(state, action){
    switch(action.type){
        case "LOADING":
            return {
                ...initialState,
                loading: true,
            }
        case "SUCCESS":
            return {
                ...initialState,
                data: action.data,
            }
        case "ERROR":
            return {
                ...initialState,
                error: action.error,
            }
        default:
            return state;
    }
}
function useAsync(callback, deps=[]){
    const [ state, dispatch ] = useReducer(reducer, initialState);
    const fetchDate = async () => {
        dispatch({type: "LOADING"});
        try {
            const data = await callback();
            dispatch({
                type: "SUCCESS",
                data: data,
            })
        }
        catch(e) {
            dispatch({
                type: "ERROR",
                error: e,
            })
        }
    }
    useEffect(()=>{
        fetchDate();
        // eslint-disable-next-line
    },deps);
    return [ state, fetchDate ];
}
export default useAsync;

App.css

li { list-style: none; }
a { text-decoration: none; color: inherit; }
.App {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
}
#header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #ccc;
  margin-bottom: 50px;
}
#header ul {
  display: flex;
}
#header li {
  padding: 0 20px;
}
h1 {
  font-size: 24px;
}
#footer {
  padding-top: 50px;
}

App.js

import './App.css';
import CustomerList from './components/CustomerList';
import DetailCustomer from './components/DetailCustomer';
import Footer from './components/Footer';
import Header from './components/Header';
import { Route, Routes } from 'react-router-dom';
import CreateCustomer from './components/CreateCustomer';
import EditCustomer from './components/EditCustomer';
const customers = [
  {
    no: 1,
    name: "고객",
    phone: "01012345678",
    birth: "19920206",
    gender: "여성",
    add: "울산시 남구",
  },
  {
    no: 2,
    name: "그린",
    phone: "01012345678",
    birth: "19920206",
    gender: "남성",
    add: "울산시 동구",
  },
  {
    no: 3,
    name: "kh",
    phone: "01012345678",
    birth: "19920206",
    gender: "여성",
    add: "울산시 북구",
  },
]
function App() {
  return (
    <div className="App">
      <Header/>
      <Routes>
        <Route path="/" element={<CustomerList customers={customers}/>}/>
        <Route path="/detailview/:no" element={<DetailCustomer/>}/>
        <Route path="/write" element={<CreateCustomer/>}/>
        <Route path="/editCustomer/:no" element={<EditCustomer/>}/>
      </Routes>
      <Footer/>
    </div>
  );
}

export default App;

 

green_customer_server

index.js

const express = require("express");
const cors = require("cors");
const app = express();
const port = 3001;
const mysql = require("mysql");
const fs = require("fs");

const dbinfo = fs.readFileSync('./database.json');
// 받아온 json데이터를 객체형태로 변경 JSON.parse
const conf = JSON.parse(dbinfo);

// connection mysql연결 createConnection()
// connection.connect() 연결하기
// connection.end() 연결종료
// connection.query('쿼리문', callback함수)
// callback(error, result, result의 field정보)

const connection = mysql.createConnection({
    host: conf.host,
    user: conf.user,
    password: conf.password,
    port: conf.port,
    database: conf.database,
})
app.use(express.json());
app.use(cors());

// 서버실행
app.listen(port, ()=>{
    console.log("고객 서버가 돌아가고 있습니다.");
})

// app.get("경로", 함수)
// connection.query("쿼리문", 함수)

app.get('/customers', async (req,res)=>{
    // connection.connect();
    connection.query(
        "select * from customers_table",
        (err, rows, fields)=>{
            res.send(rows);     
        }
    )
    // connection.end();
})

app.get('/detailview/:no', async (req,res)=>{
    const params = req.params;
    const { no } = params;
    connection.query(
        `select * from customers_table where no=${no}`,
        (err, rows, fields)=>{
            res.send(rows[0]);
        }
    )
})

// addCustomer post 요청이 오면 처리  req => 요청하는 객체, res => 응답하는 객체

app.post('/addCustomer', async (req,res)=>{
    const body = req.body;
    const { c_name, c_phone, c_birth, c_gender, c_add, c_adddetail } = body;
    if(!c_name || !c_phone || !c_birth || !c_gender || !c_add || !c_adddetail) {
        res.send("모든 필드를 입력해주세요");
    }
    // insert into 테이블(컬럼1, 컬럼2, 컬럼3, ....) values(?,?,?,....)
    // query("쿼리",[값1,값2,값3,값4,값5,값6],함수)
    // insert into customers_table(name, phone, birth, gender, add1, add2)
    // values(?,?,?,?,?,?)
    connection.query(
        "insert into customers_table(name, phone, birth, gender, add1, add2) values(?,?,?,?,?,?)",
        [c_name, c_phone, c_birth, c_gender, c_add, c_adddetail],
        (err, rows, fields)=>{
            res.send(rows);
        }
    )
    // connection.query(
    //     `insert into customers_table (name, phone, birth, gender, add1, add2)
    //     values ('${c_name}','${c_phone}','${c_birth}','${c_gender}','${c_add}','${c_adddetail}')`,
    //     (err, rows, fields)=>{
    //         res.send(rows);
    //     }
    // )
})

// 삭제요청시 처리 /delCustomer/${no}
// delete from 테이블명 조건절
// delete from customers_table where no = no
app.delete('/delCustomer/:no', async (req,res)=>{
    const params = req.params;
    const { no } = params;
    console.log("삭제");
    connection.query(
        `delete from customers_table where no=${no}`,
        (err, rows, fields)=>{
            res.send(rows);
        }
    )
})

// 수정하기
// update 테이블이름 set 컬럼명 = 값 where no = 값
// update customers_table set name='', phone='', birth='', gender='', add1='', add2='' where no=
// http://localhost:3001/editcustomer/1
app.put('/editCustomer/:no', async (req,res)=>{
    // 파라미터 값을 가지고 있는 객체
    const params = req.params;
    const { no } = params;
    const body = req.body;
    const { c_name, c_phone, c_birth, c_gender, c_add, c_adddetail } = body;
    connection.query(
        `update customers_table
        set name='${c_name}', phone='${c_phone}', birth='${c_birth}', gender='${c_gender}', add1='${c_add}', add2='${c_adddetail}'
        where no = ${no}`,
        (err, rows, fields)=>{
            res.send(rows);
        }
    )
})
app.put('/updateCustomer/:no', (req,res)=>{
    const params = req.params;
    const { no } = params;
    const body = req.body;
    const { c_name, c_phone, c_birth, c_gender, c_add, c_adddetail } = body;
    connection.query(
        `update customers_table
        set name='${c_name}', phone='${c_phone}', birth='${c_birth}', gender='${c_gender}', add1='${c_add}', add2='${c_adddetail}'
        where no = ${no}`,
        (err, rows, fields)=>{
            res.send(rows);
        }
    )
})