본문 바로가기

개발 관련 기타/ML

React) [주소 검색] 서울 빌라 적정 월세 구하기 react-daum-postcode (mobile, responsive) - part 1

기획:

1. figma 작성

- 기본 틀만 그림

- 기본 글자만 적음

- 1차적으로 모바일만

- 데스크탑은 모바일에서 조금 변형해서 구현 예정 (responsive)

2. 이미지 설명:

- title이 있음

- 각 각 input field

- 결과 보기는 button

- 전용 면적은 select bar. drag 시 평으로 자동 변환해서 옆에 적어줌 (modal)

- 층수, 건축년도는 select box (modal)

- 결과 보기 누르면 아래에 결과가 뜸

구현 설계:

- 구현 과정 나열:

1. create-react-app

2. title mui

3. textfield mui

4. react-daum-postcode 적용: 팝업 띄워서 주소 textfield 로 동까지 결과 넣어줄 예정

5. 결과 보기 button

6. 결과 보기 버튼 누를 시 현상 (part 2): part 2 에서 자세히 이어보겠습니다

 

구현 결과:

- 화면 스크린샷:

모바일
데스크탑

- 소스 코드:

import './App.css';

import React, {useState} from 'react';

import Grid from '@mui/material/Grid';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Typography, Container } from '@mui/material';
import Modal from '@mui/material/Modal';

import { useDaumPostcodePopup } from 'react-daum-postcode';

const MyTitle = () => {
  return (
    <Typography 
      variant="h3"
      color="secondary"
      align="center"
    >
      내 빌라의 적정 월세는? 
    </Typography>
  );
};

const Footer = () => {
  return (
    <Box sx={{
      position: 'fixed', 
      bottom: 0, 
      width: '100%', 
      backgroundColor: '#f0f0f0', 
      padding: '1rem 0',
    }}>
      <Container maxWidth="lg">
        <Typography variant="body1" align="center">
          Copyright © {new Date().getFullYear()} 엘앤에스프로퍼티
        </Typography>
      </Container>
    </Box>
  );
};

const SelectBox = (props) => {

  const handleChange = (event) => {
    props.setValue(event.target.value);
  };

  return (
    <>
      <FormControl sx={{ width: "100%"}}>
        <InputLabel id="demo-simple-select-label">{props.use}</InputLabel>
        <Select 
          variant="outlined"
          labelId="demo-simple-select-label"
          label={props.use}
          value={props.value}
          onChange={handleChange}
        >
          {props.myArray.map((item, index) => (
            <MenuItem value={item.value}>{item.label}</MenuItem>
          ))}
        </Select>
      </FormControl>
    </>
  );
};

const DepositSliderModal = (props) => {

  const handleChange = (event, newValue) => {
    props.setValue(newValue);
  };

  const handleClose = () => {
    props.setOpen(false);
  };

  return (
    <Modal open={props.open} onClose={handleClose}>
      <Box 
        sx={{ 
          position: 'absolute', 
          top: '50%', 
          left: '50%', 
          transform: 'translate(-50%, -50%)', 
          width: '80%',
          bgcolor: 'background.paper',
          border: '2px solid #000',
          boxShadow: 24,
          p: 4,
        }}
      >
        <p id="slider-in-modal">{props.use}</p>
        <Slider
          defaultValue={props.value}
          step={500}
          marks
          min={0}
          max={10000}
          value={props.value}
          onChange={handleChange}
          aria-label={props.use}
          color="secondary"
          valueLabelDisplay="on"
        />
        <Button onClick={handleClose}>닫기</Button>
      </Box>
    </Modal>
  );
};

const CustomSliderModal = (props) => {

  const [peong, setPeong] = useState(0);

  const handleChange = (event, newValue) => {
    props.setValue(newValue);
    setPeong((newValue / 3.305785).toFixed(1));
  };

  const handleClose = () => {
    props.setOpen(false);
  };

  return (
    <Modal open={props.open} onClose={handleClose}>
      <Box 
        sx={{ 
          position: 'absolute', 
          top: '50%', 
          left: '50%', 
          transform: 'translate(-50%, -50%)', 
          width: '80%',
          bgcolor: 'background.paper',
          border: '2px solid #000',
          boxShadow: 25,
          p: 4,
        }}
      >
        <Typography>
          {props.use}
        </Typography>
        <Slider
          defaultValue={props.value}
          min={0}
          max={100}
          value={props.value}
          onChange={handleChange}
          aria-label={props.use}
          color="secondary"
          valueLabelDisplay="on"
        />
        <Typography align="center">
          {peong} 평
        </Typography>
        <Button onClick={handleClose}>닫기</Button>
      </Box>
    </Modal>
  );
};

const MyInputField = (props) => {

  const postcodeOpen = useDaumPostcodePopup();

  const [open, setOpen] = useState(false);
  const [depositOpen, setDepositOpen] = useState(false);

  const handleComplete = (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})` : '';
    }
    props.setValue(fullAddress);
  };

  const handleClick = () => {
    if (props.use === '주소')
      postcodeOpen({ onComplete: handleComplete });
    else if (props.use === '전용면적 (㎡)') {
      setOpen(true);
    } else if (props.use === '보증금 (만원)') {
      setDepositOpen(true);
    }
  };

  const handleChange = (event) => {
  };

  let readOnly = false;
  if (props.use === '주소' || props.use === '전용면적 (㎡)' || props.use === '보증금 (만원)') readOnly = true;

  let defaultValue = "";

  return (
    <>
      <TextField
        label={props.use}
        variant="outlined"
        value={props.value}
        fullWidth
        aria-label={props.use}
        size="small"
        color="secondary"
        onClick={handleClick} 
        onChange={handleChange}
        InputProps={{
          readOnly: readOnly,
        }}
      />
      <CustomSliderModal use={props.use} open={open} setOpen={setOpen} value={props.value} setValue={props.setValue} />
      <DepositSliderModal use={props.use} open={depositOpen} setOpen={setDepositOpen} value={props.value} setValue={props.setValue} />
    </>
  );
};

const theme = createTheme({
  palette: {
    secondary: {
      main: '#1976D2',
    },
    secondary: {
      main: '#F57C00',
    },
  },
});

function App() {
  const [addr, setAddr] = useState();
  const [size, setSize] = useState();
  const [deposit, setDeposit] = useState();
  const [floor, setFloor] = useState();
  const [year, setYear] = useState();

  const floorArray = [ {"label" : "지하1층", "value" : "지하1층"}, {"label" : "1층", "value" : "1층"}, {"label" : "2층", "value" : "2층"}, {"label" : "3층", "value" : "3층"}, {"label" : "4층", "value" : "4층"}, {"label" : "5층", "value" : "5층"}, {"label" : "6층", "value" : "6층"} ];

  const currentYear = new Date().getFullYear();
  const yearArray = Array.from({length: 100}, (_, i) => ({
    label: currentYear - i,
    value: currentYear - i,
  }))

  const handleSubmit = () => {
  // 
  };

  return (
    <ThemeProvider theme={theme}>
      <Grid container spacing={3}>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={2} mb={0}>
            <MyTitle />
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={1} mb={0} maxWidth={600} sx={{ display: 'block', margin: '0 auto' }}>
            <MyInputField use="주소" value={addr} setValue={setAddr} />
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={1} mb={0} maxWidth={600} sx={{ display: 'block', margin: '0 auto' }}>
            <MyInputField use="보증금 (만원)" value={deposit} setValue={setDeposit} />
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={1} mb={0} maxWidth={600} sx={{ display: 'block', margin: '0 auto' }}>
            <MyInputField use="전용면적 (㎡)" value={size} setValue={setSize} />
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={1} mb={0} maxWidth={600} sx={{ display: 'block', margin: '0 auto' }}>
            <SelectBox use="층수" value={floor} setValue={setFloor} myArray={floorArray} />
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={1} mb={0} maxWidth={600} sx={{ display: 'block', margin: '0 auto' }}>
            <SelectBox use="건축년도" value={year} setValue={setYear} myArray={yearArray} />
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={12}>
          <Box m={0.5}>
            <Button sx={{ display: 'block', margin: '0 auto' }} variant="contained" color="secondary" onClick={handleSubmit}>계산하기</Button>
          </Box>
        </Grid>
      </Grid>
      <Footer />
    </ThemeProvider>
  );
}

export default App;