import { lwCds, type comCodeState } from "@/app/store/common/code"; import { filePickedBlob, filePickedBlobArray, filePickedBlobNumStyle, type ReturnType, type SelectDataType, type SheetParameter, } from "@common/business_components/ui"; import { Button } from "@common/components/ui"; import moment from "moment"; import type { RefObject } from "react"; import toast from "react-hot-toast"; export const getCity = (val: string, sggCd: SelectDataType[]) => { let data: any; if (val !== "0") { data = sggCd .filter((item) => String(item.val).substring(0, 2) === val) .map((item) => ({ val: item.val, text: item.text, // 한국어 텍스트 })); } else { data = sggCd.map((item) => ({ val: item.val, text: item.text, // 한국어 텍스트 })); } data.unshift({ val: "0", text: "전체" }); return data; }; // cd text 넣기 export const tableData = (data: any, code: comCodeState) => { const chCode = code.code; return data?.map( (item: { sggCd: string; ctpvCd: string; reuseBuilding: { lawTrgtYn: string; bdstUsgCd: string; grntchBdstCertGrd: string; }[]; }) => { let returnData = {}; const sggText = chCode.sggCd.find( (cd) => cd.val === String(item.sggCd).trim() )?.text; const cityText = chCode.cityCd.find( (cd) => cd.val === String(item.ctpvCd).trim() )?.text; if (item?.reuseBuilding?.length > 0) { const bdsgUsgText = chCode?.bdstTotalUsgType?.find( (cd) => cd.val === item?.reuseBuilding[0]?.bdstUsgCd )?.text; const lawTrgtYn = item?.reuseBuilding[0]?.lawTrgtYn; const grntchBdstCertGrdText = chCode?.bdstGrntchClass?.find( (cd) => cd.val === item?.reuseBuilding[0]?.grntchBdstCertGrd )?.text; returnData = { ...item, sggText: sggText ?? "없음", cityText: cityText ?? "없음", bdsgUsgText: bdsgUsgText, lawTrgtYn: lawTrgtYn, grntchBdstCertGrdText: grntchBdstCertGrdText, }; } else { returnData = { ...item, sggText: sggText ?? "없음", cityText: cityText ?? "없음", }; } return returnData; } ); }; export const excelDownload = ( excelList: [][], fileNm: string, sampleFile: string, headerRange = "A1:V3", writeLine = 4 ) => { if (excelList.length === 0) return; fetch(`/${sampleFile}`, {}).then(async (response) => { if (!response.ok) { throw Error(response.statusText); } const isTemplateOnlyDownload = excelList.length === 1 && Array.isArray(excelList[0]) && excelList[0].length === 0; if (isTemplateOnlyDownload) { const blob = await response.blob(); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = fileNm.endsWith(".xlsx") ? fileNm : `${fileNm}.xlsx`; link.click(); URL.revokeObjectURL(link.href); return; } // 받은 파일 바인너리 처리 const blobRet = await response.arrayBuffer(); // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 await filePickedBlob(blobRet, excelList, fileNm, writeLine, headerRange); }); }; export const excelDownloadNumStyle = ( fileNm: string, sampleFile: string, sheetArray: SheetParameter[] ) => { // if (excelList.length === 0) return; fetch(`/${sampleFile}`, {}).then(async (response) => { // excel Tamplate을 읽는다. if (!response.ok) { throw Error(response.statusText); } // 받은 파일 바인너리 처리 const blobRet = await response.arrayBuffer(); // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 await filePickedBlobNumStyle(blobRet, fileNm, sheetArray); }); }; export const excelArrayDownload = ( fileNm: string, sampleFile: string, sheetArray: SheetParameter[] ) => { if (!sheetArray) return; fetch(`/${sampleFile}`, {}).then(async (response) => { if (!response.ok) { throw Error(response.statusText); } // 받은 파일 바인너리 처리 const blobRet = await response.arrayBuffer(); // 버퍼에 있는 엑셀데이터 전달 후 json으로 변환 await filePickedBlobArray(blobRet, fileNm, sheetArray); }); }; // onchange 텍스트를 숫자로 and ,제거 export const handleCommaNumberChange = ( e: React.ChangeEvent, onChange: (value: number | "") => void ) => { const raw = e.target.value.replace(/,/g, ""); if (!/^\d*$/.test(raw)) return; onChange(raw === "" ? "" : Number(raw)); }; export const handleOnlyNumberChange = ( e: React.ChangeEvent, onChange: (value: number | "") => void ) => { // 콤마 제거 후 숫자와 소수점만 남기기 const cleaned = e.target.value.replace(/,/g, "").replace(/[^0-9.]/g, ""); // 숫자 및 소수점 외 입력 차단 if (!/^\d*\.?\d*$/.test(cleaned)) return; onChange(cleaned === "" ? "" : Number(cleaned)); }; // onchange 텍스트를 숫자로 and ,제거 export const handleMinusNumberChange = ( e: React.ChangeEvent, onChange: (value: number | "") => void ) => { const raw = e.target.value.replace(/,/g, ""); if (!/^\d*$/.test(raw)) return; onChange(raw === "" ? -1 : Number(raw)); }; // 숫자입력시 , 삽입 export const formatCommaNumber = ( value: number | string | undefined | null ): string => { if (value === undefined || value === null || value === "") return ""; const num = Number(value); // 숫자로 변환할 수 없는 값이 들어올 경우 빈 문자열을 반환하여 예상치 못한 "NaN" 출력을 방지합니다. if (isNaN(num)) { return ""; } return num.toLocaleString(); }; //내가 쓰는 값이 숫자이냐 아니냐 export const isNumeric = (value: string) => { if (value && value.length > 0) { return value?.trim() !== "" && !isNaN(Number(value)); } else { return false; } }; export const getCityOrSgg = (ref: RefObject) => { let con; if (ref.current != null) if (ref.current.sigunValue != "0") { con = ref.current.sigunValue; } else { con = ref.current.sidoValue; } return con; }; export const safeDate = (value: any): Date | undefined => { const date = new Date(value); return isNaN(date.getTime()) ? undefined : date; }; const currentYear = new Date().getFullYear(); export const yearOptions: { val: string; text: string }[] = Array.from( { length: currentYear - 1969 }, (_, i) => ({ val: String(currentYear - i), text: `${currentYear - i}년`, }) ); export const getdmdMatFlag = (flag: string | undefined | null) => { const cleanFlag = (flag ?? "").trim(); switch (cleanFlag) { case "Sub": return "검토"; case "Matched": return "매칭완료"; case "Checked": return "검토완료"; default: return "신청"; } }; export const formatPhoneNumber = (tel: string) => { const onlyNums = tel.replace(/[^0-9]/g, ""); if (onlyNums.length === 10) { return onlyNums.replace(/(\d{2})(\d{4})(\d{4})/, "$1-$2-$3"); } else if (onlyNums.length === 11) { return onlyNums.replace(/(\d{3})(\d{4})(\d{4})/, "$1-$2-$3"); } else { return tel; // 유효하지 않으면 빈 문자열 } }; export const confirmToast = ( content: string, saveFunc: () => void, contentClass?: string ) => { toast( (t) => (
{content}
), { id: "global-confirm-toast", // 중복 토스트 방지 duration: Infinity, // 자동 사라짐 방지 } ); }; export const showConfirmToast = (message: string) => { toast.custom( (t) => (
{message}
{" "}
), { duration: Infinity } ); // 수동 닫기 }; // yyyy-mm-dd 형태로 포맷 export const formatYyyymmdd = (val: string | undefined) => { if (!val) return ""; if (/^\d{8}$/.test(val)) { return moment(val, "YYYYMMDD").format("YYYY-MM-DD"); } const m = moment(val); return m.isValid() ? m.format("YYYY-MM-DD") : val; }; /** * 문자열 숫자(콤마 포함 가능)를 반올림 처리해 반환. * 숫자가 아닌 경우(예: "확인불가")는 원래 값을 그대로 반환. * * @param value 입력 문자열 * @param digits 소수점 자릿수 (기본 0 = 정수 반올림) */ export const formatArchArea = (value: string, digits: number = 0) => { // "확인불가" 등 숫자 아님 → 그대로 반환 const cleaned = value.replace(/,/g, ""); const num = parseFloat(cleaned); if (isNaN(num)) { return value; } // 반올림 (digits 자릿수까지) const rounded = num.toFixed(digits); // 다시 천 단위 콤마 찍어서 반환 return Number(rounded).toLocaleString(); }; export const codeToText = (val: string, codeName: string) => { if (val === "" || val === undefined) { return ""; } return lwCds[codeName]?.find((cd) => cd.val === val)?.text; }; export const textToCode = (text: string, codeName: string) => { if (text === "" || text === undefined) { return ""; } return lwCds[codeName]?.find((cd) => cd.text === text)?.val; }; // export const handleLaeSeCdChange = ( // laeSeCd: string, // cityCd: SelectDataType[], // sggCd: SelectDataType[] // ): { isCityCd: SelectDataType[]; isSggCd: SelectDataType[] } => { // if (laeSeCd.length !== 5 && cityCd.length > 0) { // const city = // laeSeCd === "0" ? cityCd : cityCd.filter((item) => item.val === laeSeCd); // const sgg = sggCd.filter((item) => item.val.slice(0, 2) === laeSeCd); // sgg.unshift({ text: "전체", val: "0" }); // return { isCityCd: city, isSggCd: sgg }; // } else if (laeSeCd.length === 5 && cityCd.length > 0 && sggCd.length > 0) { // const city = cityCd.filter((item) => item.val === laeSeCd.slice(0, 2)); // const sgg = sggCd.filter((item) => item.val === laeSeCd); // sgg.unshift({ text: "전체", val: "0" }); // return { isCityCd: city, isSggCd: sgg }; // } // // 기본 반환값 (조건에 안 맞을 때) // return { isCityCd: [], isSggCd: [] }; // }; export function handleLaeSeCdChange( laeSeCd: string, cityCd: SelectDataType[] = [], sggCd: SelectDataType[] = [] ): { isCityCd: SelectDataType[]; isSggCd: SelectDataType[] } { const isAll = laeSeCd === "0"; // 전체 const isSgg = laeSeCd.length === 5; // 시군구 코드 const prefix = isAll ? "" : laeSeCd.slice(0, 2); // 시 코드(앞 2자리) // 시 리스트: 전체면 전부, SGG 선택이면 해당 시만, 시 선택이면 그 시만 const isCityCd = isAll ? cityCd : isSgg ? cityCd.filter(city => city.val === prefix) : cityCd.filter(city => city.val === laeSeCd); // 시군구 리스트: 전체면 비움(+ 전체만), 아니면 prefix로 필터 const sggInCity = isAll ? [] : sggCd.filter(s => s.val.slice(0, 2) === prefix); // 선택된 SGG가 있으면 맨 위로(옵션) const sggOrdered = isSgg ? [...sggInCity].sort((a, b) => a.val === laeSeCd ? -1 : b.val === laeSeCd ? 1 : 0 ) : sggInCity; return { isCityCd, isSggCd: [{ text: "전체", val: "0" }, ...sggOrdered], }; }