728x90
index.tsx
/* eslint-disable react-hooks/exhaustive-deps */
/***
* 매장 상세 (CEO) 페이지
*/
import React, { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import Layout from "example/containers/Layout";
import ImageUploader, { ImageFile } from "../../../components/ImageUploader";
import { ImgModel, CodeModel } from "@apis/common";
import { getFormattedDate } from "utils/util";
import Dialog, {
DialogProps,
initDialog,
showDlg,
} from "../../../components/Dialog";
import {
getAllOption,
getStore,
updateStore,
getImages,
insertImages,
delImage,
InfoModel,
} from "@apis/store";
import PhoneNumberInput from "../../../components/PhoneNumberInput";
function StoreDetail() {
const [editable, setEditable] = useState(true);
const [allOptions, setAllOptions] = useState<CodeModel[]>([]);
const [data, setData] = useState<InfoModel>({} as InfoModel);
const mapElement = useRef(null);
const [initImages, setInitImages] = useState<ImageFile[]>();
let imageFiles: ImageFile[] = [];
const [deletedImages, setDelImages] = useState<ImageFile[]>([]);
const [dlgProps, setDlgProps] = useState<DialogProps>({} as DialogProps);
initDialog(setDlgProps);
// on page change, load new sliced data
// here you would make another server request for new data
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
// 상점 데이터 대입이 완료 되면 지도초기화 & 상점 위치 설정
initMap();
}, [data]);
async function fetchData() {
// 상점에 세팅 가능한 전체 옵션 목록 조회
let response = await getAllOption();
if (response.status == 200) setAllOptions(response.data);
// 상점 정보 조회
response = await getStore();
if (response.status != 200) return;
setData(response.data); // 상점 정보 세팅
response = await getImages();
if (response.status == 200) {
// 상점 첨부 이미지 조회
const fileList = response.data.dataList as ImgModel[];
const initList = fileList.map(
({ apndFileId, urlPath }) =>
({
fileId: apndFileId,
src: urlPath?.startsWith("/") ? urlPath : "/" + urlPath,
} as ImageFile)
);
setInitImages(initList); // 초기 이미지 세팅
}
}
function initMap() {
// 지도 영역 생성
if (!window.naver || !data) return;
let lat = 37.29618649320232;
let lng = 127.01751553242397;
if (data.lat && data.lng) {
lat = data.lat;
lng = data.lng;
}
const mapOptions = {
center: new window.naver.maps.LatLng(lat, lng),
tileSpare: 10,
zoom: 15,
};
const map = new window.naver.maps.Map(
mapElement.current as any,
mapOptions
);
// 목표 위치에 마크 표시하기
const marker = new window.naver.maps.Marker({
position: new window.naver.maps.LatLng(lat, lng),
map: map,
});
// 마크 클릭 하면 표시 될 상세 창 만들기
const infoWindow = new window.naver.maps.InfoWindow({
content: "<div><b>" + data.name + "</b><br>" + data.addr1 + "</div>",
});
marker.addListener("click", function () {
const shouldOpenInfoWindow = !infoWindow.getMap();
if (shouldOpenInfoWindow) {
infoWindow.open(map, marker);
} else {
infoWindow.close();
}
});
// 자동으로 마크 클릭 효과(상세 창 표시)
window.naver.maps.Event.trigger(marker, "click");
}
function handleCheckboxChange(opt: CodeModel, isChecked: boolean) {
if (isChecked) {
// 체크됐을 때: optionList에 opt 추가
const updatedOptionList = data.storeOptionList
? [...data.storeOptionList, opt]
: [opt];
setData({ ...data, storeOptionList: updatedOptionList });
} else {
// 체크 해제됐을 때: optionList에서 opt 제거
const updatedOptionList = data.storeOptionList
? data.storeOptionList.filter((item) => item.cdC !== opt.cdC)
: [];
setData({ ...data, storeOptionList: updatedOptionList });
}
}
// ImageUploader의 조작에 대한 핸들러 함수들
function onSelectImages(files: ImageFile[]) {
imageFiles = files;
}
function onRemoveImage(file: ImageFile) {
// 이미 디비에 등록 된 상점 사진의 경우는 디비에서의 삭제 처리까지 진행
if (file.fileId) setDelImages((prev) => [...prev, file]);
}
async function onUpdate() {
if (
!data.name ||
!data.tel ||
!data.intro ||
!data.openTime ||
!data.payInfo ||
!data.parkInfo
) {
showDlg("필수 값이 입력되지 않았습니다.<br><br> (편의시설 외 모든 항목이 필수)<br><br> 다시 확인해 주세요.");
return;
}
const formData = new FormData();
imageFiles.forEach((img) => img.org && formData.append("files", img.org));
console.log("update data = ", data);
let result: boolean = (await updateStore(data)).status === 200;
if (result) {
deletedImages.forEach((file) =>
delImage({ apndFileId: file.fileId } as ImgModel)
);
}
if (result) result &&= (await insertImages(formData)).status === 200;
if (result) {
showDlg("상점 데이터 저장을 완료했습니다.");
fetchData(); // 데이터 다시 로드
} else {
showDlg("상점 데이터 저장에 실패하였습니다.");
}
}
return (
<Layout>
<div className="content">
<div className="page-title">매장관리</div>
<div className="box">
<table className="write-table">
<colgroup>
<col style={{ width: "200px" }} />
<col />
</colgroup>
<tbody>
<tr>
<th>매장명</th>
<td>
<div className="input-group">
<input
type="text"
value={data.name || ""}
readOnly={!editable}
onChange={(e) =>
setData({ ...data, name: e.target.value })
}
/>
{data.idx && !data.name && (
<div style={{ color: "red" }}>필수 항목</div>
)}
</div>
</td>
</tr>
<tr>
<th>매장 연락처</th>
<td>
<div className="input-group">
<PhoneNumberInput value={data.tel ? data.tel.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3') : ""} onValueChange={(value) => setData({ ...data, tel: value })} readOnly={!editable} />
{data.idx && !data.tel && (
<div style={{ color: "red" }}>필수 항목</div>
)}
</div>
</td>
</tr>
<tr>
<th>매장주소</th>
<td>
<div className="input-btn-group flex-start">
<input type="text" disabled value={data.addr1 || ""} />
<button className="btn btn-border">검색</button>
</div>
<div
ref={mapElement}
className="mt-2"
style={{ border: "1px solid black", height: "300px" }}
></div>
</td>
</tr>
<tr>
<th>매장 소개</th>
<td>
<div className="input-group">
<textarea
value={data.intro || ""}
readOnly={!editable}
onChange={(e) =>
setData({ ...data, intro: e.target.value })
}
></textarea>
{data.idx && !data.intro && (
<div style={{ color: "red" }}>필수 항목</div>
)}
</div>
</td>
</tr>
<tr>
<th>운영 시간</th>
<td>
<div className="input-group">
<textarea
value={data.openTime || ""}
readOnly={!editable}
onChange={(e) =>
setData({ ...data, openTime: e.target.value })
}
></textarea>
{data.idx && !data.openTime && (
<div style={{ color: "red" }}>필수 항목</div>
)}
</div>
</td>
</tr>
<tr>
<th>이용 요금</th>
<td>
<div className="input-group">
<textarea
value={data.payInfo || ""}
readOnly={!editable}
onChange={(e) =>
setData({ ...data, payInfo: e.target.value })
}
></textarea>
{data.idx && !data.payInfo && (
<div style={{ color: "red" }}>필수 항목</div>
)}
</div>
</td>
</tr>
<tr>
<th>
편의시설
<br />및 서비스
</th>
<td>
<div className="radio-check-list row">
<ul>
{allOptions.map((opt, idx) => (
<li key={`options${idx + 1}`}>
<div className="checkbox">
<input
type="checkbox"
name="options"
id={`options${idx + 1}`}
checked={
data.storeOptionList
? data.storeOptionList.some(
(item) => item.cdC === opt.cdC
)
: false
}
disabled={!editable}
onChange={(e) => {
handleCheckboxChange(opt, e.target.checked);
}}
/>
<label htmlFor={`options${idx + 1}`}>
{opt.cdNm}
</label>
</div>
</li>
))}
</ul>
</div>
</td>
</tr>
<tr>
<th>주차 안내</th>
<td>
<div className="input-group">
<textarea
value={data.parkInfo || ""}
readOnly={!editable}
onChange={(e) =>
setData({ ...data, parkInfo: e.target.value })
}
></textarea>
{data.idx && !data.parkInfo && (
<div style={{ color: "red" }}>필수 항목</div>
)}
</div>
</td>
</tr>
<tr>
<th>
매장 사진
<br />
(최대 10장)
</th>
<td>
<ImageUploader
initImages={initImages}
onSelected={onSelectImages}
onRemove={onRemoveImage}
/>
</td>
</tr>
<tr>
<th>마지막 저장일시</th>
<td>
<div className="input-group">
<label>{getFormattedDate(data.updateDt, true, true)}</label>
</div>
</td>
</tr>
</tbody>
</table>
<ul className="submit-btn">
<li>
<button className="btn btn-green" onClick={onUpdate}>
저장하기
</button>
</li>
</ul>
</div>
</div>
<Dialog props={dlgProps} />
</Layout>
);
}
export default StoreDetail;
'Frond-end > React' 카테고리의 다른 글
[React] 엄청 간단한 카카오 로그인 구현 (0) | 2024.03.06 |
---|---|
[React] 글과 이미지가 같이 있는 글 저장해서 보여주는 방법 (0) | 2024.03.02 |
[React] 좋아요 적용 (0) | 2024.02.27 |
[React] 리액트에서 텍스트 줄바꿈(개행)하는 방법 (whiteSpace: "pre-wrap") (0) | 2024.02.26 |
[React] datepicker 적용해보기 (0) | 2024.02.21 |