2023.08.25 - [JS/React] - "나의 일지"프로젝트(5)_handsontable, 파이어스토어 데이터 베이스
리포트에 필요한 조건으로는
글 제목, 글 작성시간, 글 내용, 수정여부(false로 시작)가 꼭 필요하다
글 제목을 setDoc에 전달해줄려면
상태 저장을 위해 useState를 사용 기본 값은 "" 로 둔다
import React, { useState } from "react";
const [title, setTitle] = useState("");
그 다음, 제목 입력에 onChange 이벤트에 이벤트타겟의 값을 `setTitle()`에 전달해준다
const handleTitleChange = (e) => {
setTitle(e.target.value);
};
//.
//.
//.
<input
type="text"
placeholder="제목"
onChange={handleTitleChange}
required
className=" max-w-[300px] "
/>
템플릿 넣어주기
제목 옆에 체크박스와 라벨을 넣어주고 useTemplete 값에 따라 체크박스 아이콘을 설정해준다.
<input
type="checkbox"
id="useTemplete"
className="hidden"
onChange={hadleuseTemplete}
/>
<label htmlFor="useTemplete" className="flex items-center gap-2">
<p>템플릿 사용</p>
{useTemplete ? (
<AiFillCheckSquare className="text-blue-700" />
) : (
<AiOutlineCheckSquare />
)}
</label>
또한 체크박스에 onChange 이벤트에 함수를 전달해주어 표에 데이터를 넣어주자!
const [data, setData] = useState();
const hadleuseTemplete = () => {
hot = hotRef.current.hotInstance;
const data = hot.getData();
if (!useTemplete) {
if (window.confirm("템플릿을 사용하겠습니까?")) {
const templete = [
"날짜",
"분류",
"요청자",
"내용",
"작업자",
"전달방식",
"관련 파일명",
];
console.log(Array(templete).concat(data));
setData(Array(templete).concat(data));
setUseTemplete(true);
}
} else {
if (window.confirm("템플릿을 사용 취소 하겠습니까?")) {
setData(data.slice(1));
setUseTemplete(false);
}
}
};
템블릿 체크가 안되어 있으면(false) 데이터를 수정하는데
hot.getData()로 불러온 데이터는 배열안에 배열들을 반환하니 templete를 Array 함수로 감싸고 concat 함수를 이용해서 붙여주어 getData()의 인자로 넣어주어 데이터를 수정한다.
만약 체크가 되어있으면(true: 템플릿 취소)
hot.getData()로 데이터로 불러와 템플릿영역만 slice() 함수를 사용해 제외하고 setData()의 인자로 전달해준다.
hottable에는 데이터(전달해주면 테이블에 표시)에 데이터를 전달하도록 해놓았다
<HotTable
id="hot"
data={data && data}
colHeaders={colHeaders}
rowHeaders={true}
manualColumnMove={true}
fixedColumnsStart={window.innerWidth > 1024 ? 1 : 0}
className="htCenter htMiddle z-0"
colWidths={colWidths()}
rowHeights={`${(window.innerHeight - 200) / 10}`}
licenseKey="non-commercial-and-evaluation"
readOnly={readOnly ? readOnly : false}
ref={hotRef}
contextMenu={true}
manualColumnResize={true}
dropdownMenu={true}
columnSorting={true}
width={"100%"}
afterColumnSort={exclude}
// for non-commercial use only
/>
이제 필요한 내용들을 담아서 파이어베이스 파이어스토어에 전달해주자!
제목과 테이블을 form 태그안에 담아서 form의 `onSubmit 이벤트`에 저장하는 역활을 하는 `saveClickCallback`함수를 전달해준다.
데이터 전달 방법
writePage.jsx
const saveClickCallback = async (e) => {
e.preventDefault();
hot = hotRef.current.hotInstance;
console.log(hot.getData());
let data = JSON.stringify(hot.getData());
await addReport(data, user, title).then((reportId) => {
setTitle("");
navigate(`/reports/${reportId}`);
});
};
getData() 받아온 데이터는 Array of Array 형태이기 때문에 `JSON.stringify()`로 json 문자열로 변환해주어서 전달해준다.
addReport 에는 데이터, 작성한 유저의 정보, 제목을 넘겨준다.
firestore.js
export async function addReport(data, user, title) {
const reportId = uuidv1();
await setDoc(doc(db, "reports", reportId), {
data: data,
userId: user.uid,
createdAt: serverTimestamp(),
title: title,
reportId: reportId,
fix: false,
writer: user.displayName,
});
return reportId;
}
serverTimestamp란?
`serverTimestamp()` 는 구글에서 설명하길 "문서의 필드를 서버 업데이트 수신 시점을 추적하는 서버 타임스탬프로 설정할 수 있습니다."
라고 한다. 작성시점을 `new Date()`를 통해서 전달하게 되면 네트워크 지연시 문제가 발생한다. new Date()는 요청때 값이 생성이 되어서 서버에 데이터가 저장된 시점과 달라질 수 있다. 즉 시스템 시계는 클라언트 서버 간에 다를 수 있기에 동기화하고 , 정확한 시간 정보가 필요하기 때문에 서버의 현재 시간을 나타내는 serverTimestamp()를 이용해주었다.
사용방법
- 문서 생성시
import { setDoc, serverTimestamp } from "firebase/firestore";
export async function addReport() {
const reportId = uuidv1();
await setDoc(doc(db, "reports", reportId), {
createdAt: serverTimestamp()
});
}
- 문서 업데이트
import { updateDoc, serverTimestamp } from "firebase/firestore";
export async function updateReport(data, title, reportId) {
const reportRef = doc(db, "reports", reportId);
await updateDoc(reportRef, {
createdAt: serverTimestamp()
});
}
글 작성 페이지 전체 코드
writePage.jsx
return (
<div className="w-full">
<form onSubmit={saveClickCallback}>
<div className="flex flex-col gap-2 grow p-4 xl:w-full w-screen">
<div className="flex gap-2 w-full flex-col xl:flex-row items-start xl:items-end">
<input
type="text"
placeholder="제목"
onChange={handleTitleChange}
required
className=" max-w-[300px] "
/>
<div>
<input
type="checkbox"
id="useTemplete"
className="hidden"
onChange={hadleuseTemplete}
/>
<label htmlFor="useTemplete" className="flex items-center gap-2">
<p>템플릿 사용</p>
{useTemplete ? (
<AiFillCheckSquare className="text-blue-700" />
) : (
<AiOutlineCheckSquare />
)}
</label>
</div>
</div>
<div className="min-h-[120vw] xl:min-h-[30vw] w-full overflow-hidden reportTable relative">
<HotTableOption colHeaders={true} data={data} hotRef={hotRef} />
</div>
<div className="flex gap-4">
<button type="submit" className="btn_default">
save
</button>
</div>
</div>
</form>
</div>
);
결과
전달된 데이터