728x90
반응형
 
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (barRef.current && !barRef.current.contains(event.target as Node)) {
setBarOpen(false);
}
}
if (barOpen) {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}
}, [barRef])
728x90
반응형
728x90
반응형

xlsx 모듈 설치

$npm install xlsx

 

@breif xlsx 모듈추출

const xlsx = require( "xlsx" );

 

@breif 가상의 엑셀파일을 생성

const book = xlsx.utils.book_new();

 

 

 

첫번째 방법 (aoa_to_sheet)

const doctors = xlsx.utils.aoa_to_sheet( [

      [ "학과", "직급", "이름", "나이" ]

    , [ "흉부외과", "병원장", "주전", "67" ]

    , [ "흉부외과", "교수", "천명태", "52" ]

    , [ "흉부외과", "치프", "도재학", "39" ]

    , [ "소아외과", "레지던트", "장겨울", "29" ]

    , [ "산부인과", "레지던트", "추민하", "34" ]

    , [ "산부인과", "레지던트", "명은원", "28" ]

    , [ "신경외과", "교수", "민기준", "55" ]

    , [ "신경외과", "치프", "용석민", "33" ]

    , [ "신경외과", "레지던트", "안치홍", "38" ]

    , [ "신경외과", "레지던트", "허선빈", "31" ]

    , [ "응급의학과", "조교수", "봉광현", "40" ]

    , [ "응급의학과", "펠로우", "배준희", "31" ]

] );

 

넓이 지정

doctors["!cols"] = [

      { wpx : 130 }   // A열

    , { wpx : 100 }   // B열

    , { wpx : 80 }    // C열

    , { wch : 60 }    // D열

]

 

xlsx.utils.book_append_sheet( book, doctors, "DOCTOR" );

 


 

두번째 방법 (json_to_sheet)

const nurses = xlsx.utils.json_to_sheet( [

      { A : "학과", B : "직급", C : "이름", D : "나이" }

    , { A : "흉부외과", B : "PA간호사", C : "소이현", D : "33" }

    , { A : "소아외과", B : "PA간호사", C : "한현희", D : "29" }

    , { A : "산부인과", B : "분만실간호사", C : "한한승주현희", D : "41" }

    , { A : "산부인과", B : "PA간호사", C : "은선진", D : "36" }

    , { A : "간담췌외과", B : "수간호사", C : "송수빈", D : "45" }

    , { A : "간담췌외과", B : "병동간호사", C : "이영하", D : "35" }

    , { A : "간담췌외과", B : "병동간호사", C : "김재환", D : "28" }

    , { A : "간담췌외과", B : "PA간호사", C : "국해성", D : "32" }

    , { A : "간담췌외과", B : "이식코디네이터", C : "함덕주", D : "37" }

    , { A : "신경외과", B : "PA간호사", C : "황재신", D : "39" }

    , { A : "응급의학과", B : "응급실간호사", C : "선우희수", D : "26" }

], { header : ["A", "B", "C", "D"], skipHeader : true } );

 

넓이 지정

nurses["!cols"] = [

      { wpx : 130 }   // A열

    , { wpx : 100 }   // B열

    , { wpx : 80 }    // C열

    , { wch : 60 }    // D열

]

xlsx.utils.book_append_sheet( book, nurses, "NURSES" );

 

 


마지막 엑셀파일을 생성하고 저장한다.

xlsx.writeFile( book, "dramatis_personae.xlsx" ); 

728x90
반응형
728x90
반응형

회사에서 캘린더 안에 원하는 스케쥴을 추가하고 싶은 상황이 생겼다.

 

npm 날짜 관련 모듈이 생각보다 많다. react-day-picker, react-datepicker, react-fullcalendar 등..

날짜 자체만 선택하는 경우에는 쓸 수 있는 모듈은 엄청나게 많지만

 

FullCalendar

내가 원하는 날짜를 선택하고 그 날짜에 스케줄을 추가하는 모듈을 fullcalendar 보다 좋은 모듈을 못 봤다.

 

이미 있는 것을 쓰는것은 참 편하지만 고객이 필요로 하는 정보를 기반으로 커스터마이징을 할 땐 결국 모듈의 힘을 빌리면 한계가 있다. 나중에 복잡해지면 오히려 편하자고 쓴 것 때문에 더 스트레스 받게된다..

그래서 그냥 만들자 해서 진짜 만들게 되었다.

 

먼저 Next 앱을 설치한다.

$ npx create-next-app --typescript calendar

 

캘린더를 만드는데 필요한 모듈을 설치한다.

$ npm i moment --save

 

 

일반적으로 우리가 생각하는 캘린더의 기본은 맨 위에 현재 연도와 월이 표시되어 있고,

이번 달이 아닌 날짜는 회색계열로 표시, 오늘 날짜는 유니크하게 표시하게 되어있다.

 

날짜 한 개마다 원하는 속성을 넣어주어 쉽게 커스터마이징 할 수 있는 캘린더를 만들어보자.

 

 

일단 index.tsx 파일을 깨끗하게 비우고 필요한 상태들을 선언해준다.

index.tsx

import moment from 'moment'
import type { NextPage } from 'next'
import { useEffect, useState } from 'react'
import styles from '../styles/Home.module.css'

const Home: NextPage = () => {

  const [getMoment, setMoment] = useState(moment()); // 오늘

  const today = getMoment; // 오늘

  const todayFirstWeek = today.clone().startOf('month').week(); // 이번달의 첫째 주
  const todayLastWeek = today.clone().endOf('month').week() === 1 ? 53 : today.clone().endOf('month').week(); // 이번달의 마지막 주

  const [dayArray, setDayArray] = useState<any>([]); // 날짜들이 가지는 상태값들 모아둔 배열


  return (
    <div className={styles.container}>
      
    </div>
  )
}

export default Home

 

moment()를 useState에 넣고 today에 할당시켜 오늘 날짜를 가지게 하고

이번달의 첫째 주, 마지막 주의 데이터를 moment를 이용해 가져온다.

 

또한 날짜들의 데이터가 담길 배열을 선언해준다.

 

그 다음 useEffect()를 추가하여 배열 안에 이번 달의 날짜 내용을 담아준다.

useEffect(() => {

    // 고유 인덱스 번호
    let Index = 0;

    // 이번 달 배열에 담기 
    let week = todayFirstWeek;
    let result = []; // 이번 달 배열
    for (week; week <= todayLastWeek; week++) {
      let weekArray: any[] = []; // 주 별로 배열에 담음
      for (var i = 0; i < 7; i++) { // 7번 반복(일주일)
        let days = today.clone().startOf('year').week(week).startOf('week').add(i, 'day'); // 그날의 시간 정보
        if (moment().format('YYYYMMDD') === days.format('YYYYMMDD')) { // 현재날짜일 경우
          weekArray.push({
            Index: i,
            day: days.format('YYYY-MM-DD'),
            work: "",
            tardy: "",
            css: {
              color: "",
              bgColor: "lightpink",
              display: "",
            },
            func: {
              onClick: true,
            }
          });
        }
        else if (days.format('MM') !== today.format('MM')) { // 현재 월이 아닐 경우 (클릭옵션 주지 않기 or dispaly none)
          weekArray.push({
            Index: i,
            day: days.format('YYYY-MM-DD'),
            work: "",
            tardy: "",
            css: {
              color: "",
              bgColor: "white",
              display: "",
            },
            func: {
              onClick: true,
            }
          });
        }
        else {
          weekArray.push({
            Index: i,
            day: days.format('YYYY-MM-DD'),
            work: "",
            tardy: "",
            css: {
              color: "",
              bgColor: "lightgrey",
              display: "",
            },
            func: {
              onClick: true,
            }
          });
        }
        Index++; // monthIndex 값을 1씩 증가
      }
      result.push(weekArray);
    }

    setDayArray(result);
  }, []);

주석에 표기한 바와 같이 현재 날짜와 이번 달 날짜가 아닌 날짜들은 좀 다른 데이터를 넣어주었다.

이 상태로 서버를 실행하고 브라우저 콘솔로 배열을 확인해보자

 

날짜들에 대한 데이터가 잘 담긴 것을 확인할 수 있다. 

사실상 스케줄러를 제외한 캘린더 구현은 이 데이터들을 매핑해주는 것으로 끝이다.

728x90
반응형
728x90
반응형

React에서 상태 관리를 하다 보면 JSON 안에 JSON이 있는 형태의 Object가 있을 수 있고, 

JSON 안에 JSON 데이터를 setState로 해당 요소만 변경하려고 할 때가 있다.

 

상태 관리 방법이 매우 다양하지만 ( useReducer를 컴포넌트 안에 쓸 경우 나 리덕스 미들웨어를 쓰는 등 )

나 같은 경우에는 먼저 컴포넌트 함수 안에 상태를 useState만 써서 몰아서 다 채워 넣고 나중에 중복 사용이 많아지면

그때 미들웨어로 정리하고 싶었기 때문에 이런 상황이 생겼고, 물론 아는 사람은 있을테지만 아무리 구글링을 해봐도 도대체 방법이 나오질 않아서 찾아내게 되었다.

 

예를 들어,

const [state,setState] = useState(animal);

const animal = {
      cat : {
          name: "",
          age: 0
      },
      dog : {
          name: "",
          age: 0
      }
};

 

이런 식의 데이터가 있을 때 cat의 name만 바꾸고자 할 경우에 이런 방법을 쓸 수 있다.

 

setState({...state,['cat'] : {...state.cat, ['name'] : "초코"} })

 

별거 아닌거 같은데 이상하게 검색해도 내용이 안나오는게 은근히 많다..

 

728x90
반응형
728x90
반응형

다음과 같이 환경설정을 해주자.

 

MariaDB [(none)]> set global interactive_timeout = 180;
MariaDB [(none)]> set global wait_timeout = 180;
MariaDB [(none)]> set global max_connections = 2000;

 

- DB 설정 JSON

var db_info = {
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PW,
    port: process.env.DB_PORT,
    database: process.env.DB_DATABASE,
    connectionLimit: 300
}

 

- connection.query 함수를 쓸 경우 

var express = require('express');
var router = express.Router();

var mysql = require('mysql');
var { db_info } = require('../config/database');
const connection = mysql.createConnection(db_info);
728x90
반응형
728x90
반응형
// 쿠키 생성
res.cookie('userid', userid); // 응답 객체에 쿠키를 생성한다.

// 쿠키삭제
res.clearCookie('userid'); // 응답 객체에 쿠키를 삭제한다.

// 쿠키 조회
req.cookies["userid"] // 요청 객체에서 쿠키를 조회한다



// 쿠키 생성할 때 옵션 주기
res.cookie('userid', userid, { maxAge: 60*60*1000, httpOnly: true, path:'/' });

//maxAge : 만료 시간을 밀리초 단위로 설정

//expires : 만료 날짜를 시간으로 설정

//path : cookie의 경로 default “/“

//domain : 도메인 네임

//secure : https에서만 cookie 사용

//httpOnly : 웹서버를 통해서만 cookie 접근
728x90
반응형
728x90
반응형

MariaDB timeout 설정

 

설정 : mysql에서 아래 내용을 입력해준다. ( 최대값 설정 )

set global connect_timeout = 100
set session wait_timeout = 2147483

set session interactive_timeout = 31536000;

 

설정값 확인

show variables like '%timeout%';
show global variables like '%timeout%';

728x90
반응형
728x90
반응형

전 포스트까지 Create, Find api를 만들었다. 이번엔 마지막으로 수정과 삭제를 구현해보겠다.

 

1. Update 라우터를 만들어준다.

routes/index.js

...

router.patch('/update', async (req, res, next) => {
    const query = req.query; // 클라이언트에서 보낸 query
    const where = JSON.parse(query.where); // 수정할 튜플 검색 (ex. {"name" : "teepo"})
    const content = JSON.parse(query.content); // 수정할 내용 (ex. {"email" : "teepo3@co.kr"})

    Users.update(content, { where }) // 검색 조건이 있을 경우 조건에 맞게 데이터를 수정한다.
        .then((result) => {
            console.log("수정 성공: ", result);
            return res.send({ success: true });
        })
        .catch((err) => {
            console.log("수정 Error: ", err);
            return res.send({ success: false });
        });
});

...

 

2. postman으로 확인해본다. ( email이 teepo@co.kr 인 튜플의 email을 teepo3@co.kr로 바꾸어본다. )

 

3. HeidiSQL로 확인해본다.

이메일이 정상적으로 바뀌었다.

 

 

4. Delete 라우터를 만든다.

routes/index.js

...

router.delete('/delete', async (req, res, next) => {
    const query = req.query; // 클라이언트에서 보낸 query
    Users.destroy({ where: query }) // 검색 조건이 있을 경우 조건에 맞게 데이터를 삭제한다.
        .then((result) => {
            console.log("삭제 성공: ", result);
            return res.send({ success: true });
        })
        .catch((err) => {
            console.log("삭제 Error: ", err);
            return res.send({ success: false });
        });
});

...

 

5. postman으로 확인해본다. ( email이 teepo2@co.kr 인 튜플을 삭제한다. )

 

6. HeidiSQL로 확인해본다.

 

 

여기까지 우리는 NodeJS 백엔드로 Sequelize를 사용한 CRUD API를 구현해보았다.

728x90
반응형

'DB > MySQL' 카테고리의 다른 글

MySQL | Connection 문제 해결  (0) 2021.12.21
MySOL | MariaDB timeout 설정  (0) 2021.12.20
MySQL | CRUD with Node.js | Find  (0) 2021.12.08
MySQL | CRUD with Node.js | Create  (0) 2021.12.08
MySQL | CRUD with Node.js | Sequelize  (0) 2021.12.07
728x90
반응형

전 포스트에서 클라이언트에서 쓰일 Create API를 만들었다. 

이번엔 나머지 findALL과 find메소드를 사용해서 전체조회, name에 따른 내용 조회를 구현해보겠다.

 

1. 이름은 같지만 나머지 내용이 다른 튜플을 하나 더 생성해준다.

 

이제 이름이 teepo인 튜플 2개가 생성되었다.

HeidiSQL 결과

 

2. 전체조회, 튜플 한 개 조회 라우터를 추가로 만들어준다.

 

routes/index.js

const express = require('express');
const router = express.Router();
const { Users } = require('../sequelize/models');

// 유저 생성 api
router.post('/create', async (req, res, next) => {
    const userbody = req.body; // 클라이언트로 부터 생성할 user 정보를 받는다.
    Users.create({
        email: userbody.email,
        password: userbody.password,
        name: userbody.name,
        phone: userbody.phone
    })
        .then((result) => {
            console.log("저장 성공: ", result);
            return res.send({ success: true, result });
        })
        .catch((err) => {
            console.log("저장 Error: ", err);
            return res.send({ success: false });
        });
});

// 유저 전체 조회 api
router.get('/findall', async (req, res, next) => {
    const query = req.query; // 클라이언트에서 보낸 query
    Users.findAll({ where: query }) // 검색 조건이 있을 경우 조건에 맞게 조회한다.
        .then((result) => {
            console.log("전체조회 성공: ", result);
            return res.send({ success: true, result });
        })
        .catch((err) => {
            console.log("전체조회 Error: ", err);
            return res.send({ success: false });
        });
});

// 유저 한 개 조회 api
router.get('/findone', async (req, res, next) => {
    const query = req.query; // 클라이언트에서 보낸 query
    Users.findOne({ where: query }) // 검색 조건이 있을 경우 조건에 맞게 제일 오래된 데이터를 조회한다.
        .then((result) => {
            console.log("조회 성공: ", result);
            return res.send({ success: true, result });
        })
        .catch((err) => {
            console.log("조회 Error: ", err);
            return res.send({ success: false });
        });
});


module.exports = router;

 

2-1. findall api로 먼저 조건없이 전체조회를 해본다.

 

2-2. findall api로 name이 teepo인 조건으로 조회한다.

 

2-3. findall api로 email이 teepo@co.kr인 조건으로 조회한다.

 

2-4. findone api로 조건없이 전체조회를 해본다.

 

2-5. findone api로 name이 teepo인 조건으로 조회한다.

 

2-6. findone api로 email이 teepo@co.kr인 조건으로 조회한다.

 

결과를 보면 알 수 있듯이 findAll 메소드로 검색을 하면 배열 안에 담기는 것을 볼 수 있고,

findOne 메소드로 검색을 하면 json형태로 한 개의 데이터만 담기는 것을 볼 수 있다.

 

 

728x90
반응형
728x90
반응형

전 포스트에서 만든 Users 모델을 이용해서 클라이언트에서 사용할 API를 구성해보자.

 

 

1. 먼저 클라이언트에서 전송한 json 파일을 읽기 위해 express json을 추가하고 루트 라우터 경로를 설정해준다.

server/index.js

const express = require('express'); //express를 설치했기 때문에 가져올 수 있다.
const { sequelize } = require('./sequelize/models');

const app = express();
app.use(express.urlencoded({extended:false}));
app.use(express.json());

sequelize.sync({ force: false })
.then(() => {
    console.log('데이터베이스 연결 성공');
})
.catch((err) => {
    console.error(err);
});

// index router
app.use('/', require('./routes'));


app.listen(3001);

 

2. 설정해준 것과 같이 routes 폴더를 만들고 index.js 파일을 만들어준다.

server/routes/index.js

const express = require('express');
const router = express.Router();
const { Users } = require('../sequelize/models');

router.post('/create', async (req, res, next) => {
    const userbody = req.body; // 클라이언트로 부터 생성할 user 정보를 받는다.
    Users.create({
        email: userbody.email,
        password: userbody.password,
        name: userbody.name,
        phone: userbody.phone
    })
    .then((result) => {
        console.log("저장 성공: ", result);
        return res.send({ success: true, result });
    })
    .catch((err) => {
        console.log("저장 Error: ", err);
        return res.send({ success: false });
    });
})


module.exports = router;

 

클라이언트로부터 데이터를 생성할 유저의 데이터를 받고 성공하면 클라이언트에 결과값을 반환하도록 하였다.

 

3. postman으로 데이터가 잘 생성되는지 확인해보자.

 

4. 콘솔에서 결과값을 확인해보자.

 

 

5. 성공적으로 튜플이 생성되었다. 이번엔 HeIdiSQL로 확인해보자.

 

 

입력한 값이 정상적으로 DB에 추가된 것을 확인할 수 있다.

728x90
반응형

+ Recent posts