[React] 고객관리 사이트 구현 (Client)

2022. 7. 7. 12:34·Stack/React

 

green_customer_client

Header

Footer

CustomerList

Customer

DetailCustomer

 

- 라이브러리 설치

axios

react-router-dom

react-daum-postcode

material-ui

https://mui.com/material-ui/getting-started/installation/

 

Installation - Material UI

Install Material UI, the world's most popular React UI framework.

mui.com

npm install react-router-dom@6
npm install axios
npm install react-daum-postcode
npm install @mui/material @emotion/react @emotion/styled

 

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>
        {/* props로 위에 {customers}를 CustomerList로 전달 */}
        <Route path="/" element={<CustomerList customers={customers}/>} />
        <Route path="/detailview/:no" element={<DetailCustomer/>} />
        <Route path="/write" element={<CreateCustomer/>} />
        <Route path="/edit/:no" element={<EditCustomer/>} />
      </Routes>
      <Footer/>
    </div>
  );
}

export default App;

 

components/CustomerList.js

// components/Customer에게 props 전달해주기

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

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;

 

components/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;

 

config/contansts.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 {
                loading: true,
                data: null,
                error: null
            };
        case "SUCCESS":
            return {
                loading: false,
                data: action.data,
                error: null
            }
        case "ERROR":
            return {
                loading: false,
                data: null,
                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

 

components/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;

 

components/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;

 

components/DetailCustomer.js

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

async function getCustomer(no) {
    const response = await axios.get(`${API_URL}/detailview/${no}`);
    return response.data;
}

const DetailCustomer = ( ) => {
    const navigate = useNavigate();
    const { no } = useParams();
    const [state] = useAsync(()=>getCustomer(no), [no]);
    const { loading, data: customer, error} = state;
    
    // 삭제하기
    const onDelete = () => {
        axios.delete(`${API_URL}/detailview/${no}`)
        .then(result=> {
            console.log('삭제되었습니다.')
            navigate('/');
        })
        .catch(err=> {
            console.log(err);
        })
    }

    if(loading) return <div>로딩중입니다...</div>
    if(error) return <div>에러가 발생했습니다.</div>
    if(!customer) return null;
    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.gender}</TableCell>
                    </TableRow>
                    <TableRow>
                        <TableCell>주소</TableCell>
                        <TableCell>{customer.add1}{customer.add2}</TableCell>
                    </TableRow>
                    <TableRow>
                        <TableCell colSpan={2}>
                            <button onClick={onDelete}>삭제</button>
                            <button><Link to={`/edit/${no}`}>수정</Link></button>
                        </TableCell>
                    </TableRow>
                </TableBody>
            </Table>
        </div>
    );
};

export default DetailCustomer;

 

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 { API_URL } from '../config/contansts';
import { useNavigate } from 'react-router-dom';

const CreateCustomer = () => {
    // 우편번호 관리하기
    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 onSumbit = (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();
        } else {
            alert('모든 항목을 기입해주세요');
        }
    }
    const navigate = useNavigate();
    function insertCustomer(){
        // axios.post(`${API_URL}/customers`, {
        //     name: formData.c_name,
        //     phone: formData.c_phone,
        //     birth: formData.c_birth,
        //     gender: formData.c_gender,
        //     add1: formData.c_add,
        //     add2: formData.c_adddetail
        // })
        axios.post(`${API_URL}/addCustomer`, formData)
        .then((result)=>{
            console.log(result)
            navigate("/");
        })
        .catch((e)=> {
            console.log(e);   
        })
    }
    return (
        <div>
            <h2>신규 고객 등록하기</h2>
            <form onSubmit={onSumbit}>
                <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;

 

components/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 useAsync from '../customHook/useAsync';
import { API_URL } from '../config/contansts';
import { useParams, useNavigate } from 'react-router-dom';

async function getCustomer(no) {
    const response = await axios.get(`${API_URL}/detailview/${no}`);
    return response.data;
}

const EditCustomer = () => {
    const { no } = useParams();
    const [ formData, setFormData ] = useState({
        c_name: "",
        c_phone: "",
        c_birth: "",
        c_gender: "",
        c_add: "",
        c_adddetail: ""
    })  
  
    // const [state] = useAsync(()=>getCustomer(no), [no]);
    const [state] = useAsync(()=>getCustomer(no), [no]);
    const { loading, data: customer, error} = state;
    useEffect(()=>{
        setFormData({
            // 앞에는 인풋의 네임값(c_add), 뒤에는 db 컬럼값(customer.add1)
            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 navigate = useNavigate(); 
    // console.log(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 onSumbit = (e) => {
        // form에 원래 연결된 이벤트를 제거
        e.preventDefault();
        // console.log(formData);
        // 전화번호가 숫자인지 체크하기
        if(isNaN(formData.c_phone)){
            alert('전화번호는 숫자만 입력해주세요');
            setFormData({
                ...formData,
                c_phone: ""
            })
        } 
        // input에 값이 있는지 체크하고
        // 입력이 다되어있으면 post전송
        else if( formData.c_name.value !== "" && formData.c_phone.value !== "" && 
        formData.c_birth.value !== "" && formData.c_gender.value !=="" &&
        formData.c_add.value !== "" && formData.c_adddetail.value !== "" ){
            updateCustomer();
        } else {
            alert('모든 항목을 기입해주세요');
        }
    }
    // 수정하기
    function updateCustomer() {
        axios.put(`${API_URL}/edit/${no}`, formData)
        .then(result=> {
            console.log('수정되었습니다.')
            navigate('/');
        })
        .catch(err=> {
            console.log(err);
        })
    }

    if(loading) return <div>로딩중입니다...</div>
    if(error) return <div>에러가 발생했습니다.</div>
    if(!customer) return null;
    // console.log(customer.name);
return (
    <div>
        <h2>고객 정보 수정하기</h2>
        <form onSubmit={onSumbit}>
            <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;

 

components/PopupDom.js

import  ReactDOM  from "react-dom";

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

export default PopupDom;

 

components/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;

 

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

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;
}

'Stack > React' 카테고리의 다른 글

[React] git clone 한 뒤, 오류 없이 npm start 하는 방법  (0) 2022.07.09
[React] 고객관리 사이트 구현 (Server)  (0) 2022.07.07
[React] useMemo / useCallback  (0) 2022.07.05
[React] Lamp 쇼핑몰 구현하기 8 (깃허브 주소 및 최종 코드)  (0) 2022.07.05
[React] Lamp 쇼핑몰 구현하기 7 / 배포  (0) 2022.07.05
'Stack/React' 카테고리의 다른 글
  • [React] git clone 한 뒤, 오류 없이 npm start 하는 방법
  • [React] 고객관리 사이트 구현 (Server)
  • [React] useMemo / useCallback
  • [React] Lamp 쇼핑몰 구현하기 8 (깃허브 주소 및 최종 코드)
7ingout
7ingout
  • 7ingout
    Hello, 7ingout world!
    7ingout
  • 전체
    오늘
    어제
    • 분류 전체보기 (205)
      • Project (5)
      • Stack (173)
        • React (40)
        • JavaScript (50)
        • TypeScript (14)
        • HTML (11)
        • CSS (31)
        • Spring (9)
        • PHP (15)
        • SQL (3)
        • Python (0)
      • ETC (9)
      • Design (13)
        • Illustrator (6)
        • Photoshop (7)
      • Articloid (4)
        • 7ingout (4)
  • 공지사항

    • ☻
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
7ingout
[React] 고객관리 사이트 구현 (Client)
상단으로

티스토리툴바