Merge commit 'b42d518ecf3605c250aadbd9eb4f79e259aeb708' into iipbs/dm/dev

This commit is contained in:
jsyang
2026-04-08 11:02:53 +09:00
30 changed files with 645 additions and 444 deletions
@@ -1,4 +1,8 @@
"use client";
/**
* 재이용실적등록 > 지도점검결과등록
*/
"use client";
import { useWrStore } from "@/app/store";
import { btnClass } from "@/app/store/common/code";
@@ -43,25 +47,25 @@ import { useTranslation } from "react-i18next";
export default function Page() {
const {
auth,
code,
rainChkgdn,
rswtChkgdn,
rainChkInit,
rainChkDetilUpdate,
rainChkDelete,
rainChkExcelDownload,
rainChkHeadChange,
rainChkNmSearch,
isRainSttn,
rswtChkgdnInit,
rswtChkNmSearch,
rswtChkPolInit,
rswtChkDetilsUpdate,
rswtChkDelete,
rswtrChkExcelDownload,
rswtChkHeadChange,
getLaeSeCdCheck
auth, // 사용자 권한 및 로그인 사용자 기준 정보
code, // 공통 코드 및 지역 코드 정보
rainChkgdn, // 빗물이용시설 지도점검 화면 상태
rswtChkgdn, // 중수도시설 지도점검 화면 상태
rainChkInit, // 빗물이용시설 지도점검 목록 초기 조회
rainChkDetilUpdate, // 빗물이용시설 지도점검 상세 수정 저장
rainChkDelete, // 빗물이용시설 지도점검 선택 건 삭제
rainChkExcelDownload, // 빗물이용시설 지도점검 엑셀 양식/데이터 조회
rainChkHeadChange, // 빗물이용시설 테이블 헤더 선택 상태 변경
rainChkNmSearch, // 빗물이용시설명으로 목록 조회
isRainSttn, // 빗물이용시설 상세/선택 상태 초기화
rswtChkgdnInit, // 중수도시설 지도점검 목록 초기 조회
rswtChkNmSearch, // 중수도시설명으로 목록 조회
rswtChkPolInit, // 중수도 시설구분 변경 시 조회조건 초기화
rswtChkDetilsUpdate, // 중수도시설 지도점검 상세 수정 저장
rswtChkDelete, // 중수도시설 지도점검 선택 건 삭제
rswtrChkExcelDownload, // 중수도시설 지도점검 엑셀 양식/데이터 조회
rswtChkHeadChange, // 중수도시설 테이블 헤더 선택 상태 변경
getLaeSeCdCheck // 사용자 관할 기관 코드 조회
} = useWrStore((state) => {
return state;
});
@@ -83,12 +87,13 @@ export default function Page() {
String(new Date().getFullYear())
);
const useInitRef = useRef(false);
// laeSeCd가 아직 준비되기 전 초기 렌더에서 rainChkInit, rswtChkgdnInit 같은 초기 조회 로직이 먼저 도는 것을 막는다.
const useInitRef = useRef(false); //초기 마운트가 한 번 끝났는지
useEffect(() => {
getLaeSeCdCheck();
useInitRef.current = true;
useInitRef.current = true; // getLaeSeCdCheck()를 호출한 뒤 true로 바뀜
}, []);
@@ -111,8 +116,8 @@ export default function Page() {
} else { // 중수도
if (fcltNm) {
rswtChkNmSearch(
rainPageNation,
rainPageSize,
rswtPageNation,
rswtPageSize,
searchYear,
laeSeCd,
fcltNm
@@ -145,7 +150,7 @@ export default function Page() {
if (fcltNm) {
rswtChkNmSearch(
pageNo,
rainPageSize,
rswtPageSize,
searchYear,
laeSeCd,
fcltNm
@@ -1,4 +1,7 @@
"use client";
/**
* 재이용실적등록 > 하수재이용실적등록
*/
"use client";
import { useWrStore } from "@/app/store";
import { btnClass } from "@/app/store/common/code";
import { excelToPbnPrfData } from "@/app/store/prfmncReg/pbnstPrfmncReg/excel";
@@ -15,7 +18,7 @@ import {
yearOptions,
} from "@/components/lib/func/common";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { InputTableBody } from "@/components/lib/func/TableData";
import { pbnstTableBody } from "@/components/lib/func/TableData";
import PbnPrfExcelModal from "@/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal";
import {
ContentsDiv,
@@ -355,7 +358,7 @@ export default function PrfmncPrfmncReg() {
{headMakeprfmnc(monthHeadList, [])}
</TableHeader>
<TableBody key={prfmnc.pageKey + "pageKey"}>
{InputTableBody(
{pbnstTableBody(
inputData ?? [],
monthHeadList,
setInputData,
@@ -6,7 +6,7 @@ import { initValInUsage, initValOutUsage, subTotalObj, totalObj, type FcPrfType,
import { excelPrfmncSttnParsing } from "@/app/store/prfmncSttn/pbnstPrfmncSttn/excel";
import { excelDownloadNumStyle, yearOptions } from "@/components/lib/func/common";
import { type SheetParameter } from "@common/business_components/ui";
import { InputTableBody } from "@/components/lib/func/TableData";
import { pbnstTableBody } from "@/components/lib/func/TableData";
import { CHART_FORM, ContentsDiv, MainChart, ScrollArea, SearchBaseForm, SearchInput, SelectBox, UiTable, type ChartConfig, type ReturnType } from "@common/business_components";
import ListDiv from "@common/business_components/ui/list_div";
import { Label, Table, TableBody, TableHeader } from "@common/components/ui";
@@ -714,8 +714,8 @@ export default function PbnstPrfmncSttn() {
{headMakeprfmnc(monthHeadList, [])}
</TableHeader>
<TableBody>
{/* {InputTableBody(prfData ?? [], monthHeadList, setPrfData)} */}
{InputTableBody(prfData ?? [], monthHeadList, setPrfData, false)}
{/* {pbnstTableBody(prfData ?? [], monthHeadList, setPrfData)} */}
{pbnstTableBody(prfData ?? [], monthHeadList, setPrfData, false)}
</TableBody>
</Table>
</ScrollArea>
@@ -137,68 +137,25 @@ export const rainSplHeadNmExport: chHeadType[] = [
];
export const rainChkgdnHeadListExport: TableUpperProps[] = [
{ id: "order", title: "순번", mergeCell: true },
{ id: "checkBox", title: "선택", mergeCell: true },
{ id: "cityText", title: "시도", mergeCell: true },
{ id: "sggText", title: "시군구", mergeCell: true },
{ id: "bdstNm", title: "시설명", mergeCell: true },
{ id: "operMngCdText", title: "운영관리형태", mergeCell: true },
{ id: "oilqMeasMthdText", title: "유량측정방법", mergeCell: true },
{ id: "anlOperCst", title: "연간운영비(원/년)", mergeCell: true },
{ id: "dtfcltCpct", title: "빗물저류조용량(m³)", mergeCell: true },
{ id: "chckYmd", title: "점검일", mergeCell: true },
{ id: "utztnUsgCdText", title: "이용용도" },
{ id: "totalSplyUswtrUtztnQnt", title: "연간대체수이용량(m³)" },
{ id: "totalRuseFcltUtztnQnt", title: "연간빗물이용량(m³)" },
{
id: "ph",
title: "수소이온농도(pH)",
upName: "useQuilty",
upSequnce: 1,
type: "number",
edit: true,
},
{
id: "trbt",
title: "탁도(NTU)",
upName: "useQuilty",
upSequnce: 2,
type: "number",
edit: true,
},
{
id: "grssClfg",
title: "총대장균군수(개/mg)",
upName: "useQuilty",
upSequnce: 3,
type: "number",
edit: true,
},
{
id: "vltnDt",
title: "부과일(위반일)",
upName: "feeHist",
upSequnce: 1,
type: "text",
edit: true,
},
{
id: "vltnMttr",
title: "위반사항",
upName: "feeHist",
upSequnce: 2,
type: "text",
edit: true,
},
{
id: "levyAmt",
title: "부과금액(천원)",
upName: "feeHist",
upSequnce: 3,
type: "text",
edit: true,
},
{ id: "order", title: "순번", mergeCell: true, widthPercent: 4 },
{ id: "checkBox", title: "선택", mergeCell: true, widthPercent: 4 },
{ id: "cityText", title: "시도", mergeCell: true, widthPercent: 5 },
{ id: "sggText", title: "시군구", mergeCell: true, widthPercent: 5 },
{ id: "bdstNm", title: "시설명", mergeCell: true, widthPercent: 7 },
{ id: "operMngCdText", title: "운영관리형태", mergeCell: true, widthPercent: 6 },
{ id: "oilqMeasMthdText", title: "유량측정방법", mergeCell: true, widthPercent: 6 },
{ id: "anlOperCst", title: "연간운영비(원/년)", mergeCell: true, widthPercent: 7 },
{ id: "dtfcltCpct", title: "빗물저류조용량(m³)", mergeCell: true, widthPercent: 6 },
{ id: "chckYmd", title: "점검일", mergeCell: true, widthPercent: 5 },
{ id: "utztnUsgCdText", title: "이용용도", widthPercent: 5 },
{ id: "totalSplyUswtrUtztnQnt", title: "연간대체수이용량(m³)", widthPercent: 7 },
{ id: "totalRuseFcltUtztnQnt", title: "연간빗물이용량(m³)", widthPercent: 7 },
{ id: "ph", title: "수소이온농도(pH)", upName: "useQuilty", upSequnce: 1, type: "number", edit: true, widthPercent: 5 },
{ id: "trbt", title: "탁도(NTU)", upName: "useQuilty", upSequnce: 2, type: "number", edit: true, widthPercent: 4 },
{ id: "grssClfg", title: "총대장균군수(개/mg)", upName: "useQuilty", upSequnce: 3, type: "number", edit: true, widthPercent: 5 },
{ id: "vltnDt", title: "부과일(위반일)", upName: "feeHist", upSequnce: 1, type: "text", edit: true, widthPercent: 5 },
{ id: "vltnMttr", title: "위반사항", upName: "feeHist", upSequnce: 2, type: "text", edit: true, widthPercent: 6 },
{ id: "levyAmt", title: "부과금액(천원)", upName: "feeHist", upSequnce: 3, type: "text", edit: true, widthPercent: 5 },
];
export const ruseHeadListExport: TableUpperProps[] = [
@@ -13,6 +13,11 @@ import {
} from "../rainChkgdn/excel";
import { codeToText, textToCode } from "@/components/lib/func/common";
const normalizeExcelDateInput = (value: unknown) => {
if (value === undefined || value === null) return "";
return String(value).replace(/[.\-\/\s]/g, "");
};
export const rswtMakeExcelSample = (
buildings: Buildingtype[],
sidoCode: SelectDataType[],
@@ -142,7 +147,9 @@ export const excelToRswtGuideData = (
const rswtChkgdData: rswtChkZodType[] = excelData
.map((item: any) => {
if (item["점검일"] !== undefined) {
const checkYmd = item["점검일"].replace(/\./g, "");
const checkYmd = normalizeExcelDateInput(item["점검일"]);
const operMngCd = textToCode(item["운영관리형태"], "rainRswtMng") ?? item["운영관리형태"];
const utztnUsgCd = textToCode(item["이용용도"], "dmdUszCd") ?? item["이용용도"];
const makeGuidDetail = Array.from({ length: 12 }, (_, i) => {
let month = `${i + 1}`;
@@ -150,26 +157,27 @@ export const excelToRswtGuideData = (
const rswtData = parseRawValue(item[`중수_${month}월`]);
if (month.length < 2) month = "0" + month;
return {
utztnUsgCd: utztnUsgCd,
ym: year + month,
chckYmd: checkYmd,
splyUswtrUtztnQnt: splyData !== "" ? splyData : 0,
ruseFcltUtztnQnt: rswtData !== "" ? rswtData : 0,
flg: "RST",
};
});
if (checkYmd.length > 7) {
return {
bdstRuseFcltSn: Number(item["NO"]),
operMngCd: textToCode(item["운영관리형태"], "rainRswtMng") ??item["운영관리형태"],
anlOperCst: parseCommaRawValue(item["연간운영비(원/년)"]),
prcsCpct: parseRawValue(item[`처리용량(m³)`]),
spwtPositn: item["채수지점"],
chckYmd: checkYmd,
utztnUsgCd: textToCode(item["이용용도"], "dmdUszCd")??item["이용용도"],
guideDetails: makeGuidDetail,
rgtr: TEST_UUID,
mdfr: TEST_UUID,
};
}
return {
bdstRuseFcltSn: Number(item["NO"]),
flg: "RST",
operMngCd: operMngCd,
anlOperCst: parseCommaRawValue(item["연간운영비(원/년)"]),
prcsCpct: parseRawValue(item[`처리용량(m³)`]),
spwtPositn: item["채수지점"],
chckYmd: checkYmd,
utztnUsgCd: utztnUsgCd,
guideDetails: makeGuidDetail,
rgtr: TEST_UUID,
mdfr: TEST_UUID,
};
}
})
.filter((el: any) => el !== undefined);
@@ -186,11 +194,13 @@ export const excelToRswtPolution = (excelData: any) => {
const rswtChkgdData: rswtChkZodType[] = excelData
.map((item: any) => {
if (item["점검일"] !== undefined) {
const checkYmd = item["점검일"].replace(/\./g, "");
let vltnDt = item["과태료 부과일(위반일)"];
if (vltnDt !== undefined) {
vltnDt = vltnDt.replace(/\./g, "");
}
const checkYmd = normalizeExcelDateInput(item["점검일"]);
const vltnDt = normalizeExcelDateInput(item["과태료 부과일(위반일)"]);
const operMngCd =
textToCode(item["운영관리형태"], "rainRswtMng") ??
item["운영관리형태"];
const utztnUsgCd =
textToCode(item["이용용도"], "dmdUszCd") ?? item["이용용도"];
const makePoluttion = Array.from({ length: 4 }, (_, i) => {
const quarter = `${i + 1}`;
@@ -207,6 +217,7 @@ export const excelToRswtPolution = (excelData: any) => {
const ph = String(item[`${quarter}분기_PH`]) ;
const clIonQnt = parseRawValue(item[`${quarter}분기_염화물`]);
return {
utztnUsgCd: utztnUsgCd,
node: quarter,
chckYmd: checkYmd,
grssClfgUswtr: grssClfgUswtr,
@@ -226,9 +237,10 @@ export const excelToRswtPolution = (excelData: any) => {
return {
bdstRuseFcltSn: Number(item["NO"]),
flg: "RST",
chckYmd: checkYmd,
utztnUsgCd: textToCode(item["이용용도"], "dmdUszCd")??item["이용용도"],
operMngCd: item["운영관리형태"],
utztnUsgCd: utztnUsgCd,
operMngCd: operMngCd,
anlOperCst: parseCommaRawValue(item["연간운영비(원/년)"]),
prcsCpct: parseRawValue(item["처리용량(m³)"]),
spwtPositn: item["채수지점"],
@@ -237,7 +249,7 @@ export const excelToRswtPolution = (excelData: any) => {
mdfr: TEST_UUID,
rswtrDetails: [
{
utztnUsgCd: textToCode(item["이용용도"], "dmdUszCd")??item["이용용도"],
utztnUsgCd: utztnUsgCd,
vltnDt: vltnDt,
vltnMttr: item["위반사항"],
levyAmt: Number(item["과태료 부과금액(천원)"]) ?? 0,
@@ -257,6 +269,7 @@ export const rswtExcelDataCheck = (
rswtChkgdData: rswtChkZodType[],
issues: string[]
) => {
// const chkMngType = { "01": "위탁", "02": "자체" };
rswtChkgdData.forEach((rswt: any) => {
const defaultMessage = `*중수도 수질 [NO: ${rswt.bdstRuseFcltSn}, 점검일: ${rswt.chckYmd}, 이용용도: ${codeToText(rswt.utztnUsgCd ,"dmdUszCd")??rswt.utztnUsgCd}] 항목에서 `;
@@ -264,6 +277,17 @@ export const rswtExcelDataCheck = (
if (rswt.chckYmd && isInvalidDate(rswt.chckYmd)) {
issues.push(defaultMessage + `점검일: 날짜 형식 아님 (${rswt.chckYmd})`);
}
// if (rswt.operMngCd?.length > 1 && !chkMngType[rswt.operMngCd]) {
// issues.push(
// defaultMessage +
// `운영관리형태: 위탁 혹은 자체만 허용 (${rswt.operMngCd})`
// );
// }
// if (rswt.utztnUsgCd?.length > 2 && !codeToText(rswt.utztnUsgCd, "dmdUszCd")) {
// issues.push(
// defaultMessage + `이용용도: 허용되지 않은 값 (${rswt.utztnUsgCd})`
// );
// }
if (rswt.anlOperCst.length > 2 && isInvalidNumber(rswt.anlOperCst)) {
issues.push(
@@ -327,6 +351,11 @@ export const rswtExcelDataCheck = (
defaultMessage + `${pol.node}분기_T-N: 숫자 형식 아님 (${pol.tn})`
);
}
if (pol.ph?.length > 1 && isInvalidNumber(pol.ph)) {
issues.push(
defaultMessage + `${pol.node}분기_PH: 숫자 형식 아님 (${pol.ph})`
);
}
if (isInvalidNumber(pol.clIonQnt)) {
issues.push(
@@ -53,61 +53,63 @@ const buildingZod = z.object({
});
export const rswtrPolutionsZode = z.object({
utztnUsgCdText:z.string().nullish(),
chkgdnDtlFk: z.string().nullish(),
utztnUsgCd: z.string().nullish(),
node: z.string().nullish(),
grssClfgUswtr: z.number(),
combResdch: z.number(),
trbt: z.number(),
bod: z.number(),
smll: z.number(),
crmty: z.number(),
tph: z.number(),
tn: z.number(),
ph: z.number(),
clIonQnt: z.number(),
regDt: z.string().nullish(),
rgtr: z.string().nullish(),
mdfcnDt: z.string().nullish(),
mdfr: z.string().nullish(),
edit:z.boolean().nullish()
utztnUsgCdText:z.string().nullish(), // 이용용도 코드의 화면 표시용 한글명
chkgdnDtlFk: z.string().nullish(), // 지도점검 상세 PK와 연결되는 FK
utztnUsgCd: z.string().nullish(), // 이용용도 코드
node: z.string().nullish(), // 분기 구분값(1~4분기)
grssClfgUswtr: z.number(), // 총대장균군수
combResdch: z.number(), // 결합잔류염소
trbt: z.number(), // 탁도
bod: z.number(), // BOD
smll: z.string(), // 냄새
crmty: z.number(), // 색도
tph: z.number(), // 총인(T-P)
tn: z.number(), // 총질소(T-N)
ph: z.number(), // 수소이온농도(pH)
clIonQnt: z.number(), // 염화물
regDt: z.string().nullish(), // 등록일시
rgtr: z.string().nullish(), // 등록자
mdfcnDt: z.string().nullish(), // 수정일시
mdfr: z.string().nullish(), // 수정자
edit:z.boolean().nullish() // 화면에서 수정 가능 여부
});
export const rswtrDetailsZod = z.object({
chkgdnDtlPk: z.string().nullish(),
chkgdnSn: z.string().nullish(),
utztnUsgCd: z.string(),
vltnDt: z.string(),
vltnMttr: z.string(),
levyAmt: z.number(),
regDt: z.string().nullish(),
rgtr: z.string().nullish(),
mdfcnDt: z.string().nullish(),
mdfr: z.string().nullish(),
chkgdnDtlPk: z.string().nullish(), // 지도점검 상세 PK
chkgdnSn: z.string().nullish(), // 지도점검 일련번호
utztnUsgCd: z.string(), // 이용용도 코드
vltnDt: z.string(), // 위반일
vltnMttr: z.string(), // 위반사항
levyAmt: z.number(), // 부과금액
regDt: z.string().nullish(), // 등록일시
rgtr: z.string().nullish(), // 등록자
mdfcnDt: z.string().nullish(), // 수정일시
mdfr: z.string().nullish(), // 수정자
})
// 중수도 점검 데이터를 검증하는 Zod 스키마
export const rswtChkgdZod = z.object({
chkgdnSn: z.number().nullish(),
bdstRuseFcltSn: z.number().nullish(),
chckYmd: z.string(),
prcsCpct: z.number(),
dtfcltCpct: z.number(),
spwtPositn: z.string(),
regDt: z.string().nullish(),
rgtr: z.string().nullish(),
mdfcnDt: z.string().nullish(),
mdfr: z.string().nullish(),
anlOperCst:z.number(),
oilqMeasMthd: z.string().nullish(),
utztnUsgCd: z.string(),
building: buildingZod,
guideDetails: z.array(guideDetail),
reuseBuilding: reuseBuildingZod,
chkgdnSn: z.number().nullish(), // 점검 일련번호
bdstRuseFcltSn: z.number().nullish(), // 재이용시설 일련번호
chckYmd: z.string(), // 점검일자
prcsCpct: z.number(), // 처리용량
dtfcltCpct: z.number(), // 빗물저류조용량
spwtPositn: z.string(), // 채수 지점
regDt: z.string().nullish(), // 등록일시
rgtr: z.string().nullish(), // 등록자
mdfcnDt: z.string().nullish(), // 수정일시
mdfr: z.string().nullish(), // 수정자
flg: z.string().nullable(), // 플래그
anlOperCst: z.number(), // 연간 운영비
oilqMeasMthd: z.string().nullish(), // 유량측정방법
utztnUsgCd: z.string(), // 이용용도 코드
building: buildingZod, // 건물 정보
guideDetails: z.array(guideDetail), // 가이드 점검 상세 목록
reuseBuilding: reuseBuildingZod, // 재이용 건물 정보
rswtrDetails: z.array(
rswtrDetailsZod
),
rswtrPollutions: z.array(rswtrPolutionsZode),
), // 점검 위반/내역 상세 배열
rswtrPollutions: z.array(rswtrPolutionsZode), // 수질 오염 항목 배열
});
@@ -134,117 +136,45 @@ export const rswtHeadNmExport: chHeadType[] = [
];
export const rswtChkHeadListExport: TableUpperProps[] = [
{ id: "order", title: "순번", mergeCell: true },
{ id: "checkBox", title: "선택", mergeCell: true },
{ id: "cityText", title: "시도" ,mergeCell: true },
{ id: "sggText", title: "시군구",mergeCell: true },
{ id: "bdstNm", title: "시설명" ,mergeCell: true},
{ id: "operMngCd", title: "운영관리형태" ,mergeCell: true},
{ id: "anlOperCst", title: "연간운영비(원/년)" ,mergeCell: true},
{ id: "prcsCpct", title: "처리용량(m³)" ,mergeCell: true},
{ id: "spwtPositn", title: "채수지점" ,mergeCell: true},
{ id: "chckYmd", title: "점검일" ,mergeCell: true},
{ id: "utztnUsgCdText", title: "이용용도" },
{ id: "splyUswtrUtztnQnt", title: "연간대체수이용량(m³)" ,type: "number"},
{ id: "ruseFcltUtztnQnt", title: "연간중수도이용량(m³)",type: "number"},
{
id: "grssClfgUswtr",
title: "총대장균군수(개/mg)",
upName: "useQuilty",
upSequnce: 1,
type: "number"
},
{
id: "combResdch",
title: "결합잔류염소(mh/L)",
upName: "useQuilty",
upSequnce: 2,
type: "number"
},
{ id: "trbt", title: "탁도(NTU)", upName: "useQuilty", upSequnce: 3 ,type: "number"},
{ id: "bod", title: "BOD(mg/L)", upName: "useQuilty", upSequnce: 4 ,type: "number"},
{ id: "smll", title: "냄새", upName: "useQuilty", upSequnce: 5 ,type: "text"},
{ id: "crmty", title: "색도(도)", upName: "useQuilty", upSequnce: 6 ,type: "number"},
{ id: "tn", title: "총질소(T-N)(mg/L)", upName: "useQuilty", upSequnce: 7 ,type: "number"},
{ id: "tph", title: "총인(T-P)(mg/L)", upName: "useQuilty", upSequnce: 8 ,type: "number"},
{ id: "ph", title: "수소이온농도(pH)", upName: "useQuilty", upSequnce: 9,type: "text" },
{ id: "clIonQnt", title: "염화물(mg/L)", upName: "useQuilty", upSequnce: 10 ,type: "number"},
{
id: "vltnDt",
title: "부과일(위반일)",
upName: "feeHist",
upSequnce: 1,
type: "text",
edit:true
},
{
id: "vltnMttr",
title: "위반사항",
upName: "feeHist",
upSequnce: 2,
type: "text",
edit:true
},
{
id: "levyAmt",
title: "부과금액(천원)",
upName: "feeHist",
upSequnce: 3,
type: "number",
edit:true
}];
{ id: "order", title: "순번", mergeCell: true, widthPercent: 4 },
{ id: "checkBox", title: "선택", mergeCell: true, widthPercent: 4 },
{ id: "cityText", title: "시도" ,mergeCell: true, widthPercent: 7 },
{ id: "sggText", title: "시군구",mergeCell: true, widthPercent: 5 },
{ id: "bdstNm", title: "시설명" ,mergeCell: true, widthPercent: 10 },
{ id: "operMngCd", title: "운영관리형태" ,mergeCell: true, widthPercent: 6 },
{ id: "anlOperCst", title: "연간운영비(원/년)" ,mergeCell: true, widthPercent: 7 },
{ id: "prcsCpct", title: "처리용량(m³)" ,mergeCell: true, widthPercent: 6 },
{ id: "spwtPositn", title: "채수지점" ,mergeCell: true, widthPercent: 6 },
{ id: "chckYmd", title: "점검일" ,mergeCell: true, widthPercent: 7 },
{ id: "utztnUsgCdText", title: "이용용도", widthPercent: 7 },
{ id: "splyUswtrUtztnQnt", title: "연간대체수이용량(m³)" ,type: "number", widthPercent: 5 },
{ id: "ruseFcltUtztnQnt", title: "연간중수도이용량(m³)",type: "number", widthPercent: 5 },
{ id: "grssClfgUswtr", title: "총대장균군수(개/mg)", upName: "useQuilty", upSequnce: 1 ,type: "number", widthPercent: 6 },
{ id: "combResdch", title: "결합잔류염소(mh/L)", upName: "useQuilty", upSequnce: 2 ,type: "number", widthPercent: 6 },
{ id: "trbt", title: "탁도(NTU)", upName: "useQuilty", upSequnce: 3 ,type: "number", widthPercent: 4 },
{ id: "bod", title: "BOD\n(mg/L)", upName: "useQuilty", upSequnce: 4 ,type: "number", widthPercent: 4 },
{ id: "smll", title: "냄새", upName: "useQuilty", upSequnce: 5 ,type: "text", widthPercent: 4 },
{ id: "crmty", title: "색도(도)", upName: "useQuilty", upSequnce: 6 ,type: "number", widthPercent: 4 },
{ id: "tn", title: "총질소(T-N)(mg/L)", upName: "useQuilty", upSequnce: 7 ,type: "number", widthPercent: 5 },
{ id: "tph", title: "총인(T-P)(mg/L)", upName: "useQuilty", upSequnce: 8 ,type: "number", widthPercent: 5 },
{ id: "ph", title: "수소이온농도(pH)", upName: "useQuilty", upSequnce: 9,type: "text", widthPercent: 5 },
{ id: "clIonQnt", title: "염화물(mg/L)", upName: "useQuilty", upSequnce: 10 ,type: "number", widthPercent: 5 },
{ id: "vltnDt", title: "부과일(위반일)", upName: "feeHist", upSequnce: 1, type: "text", edit:true, widthPercent: 5 },
{ id: "vltnMttr", title: "위반사항", upName: "feeHist", upSequnce: 2, type: "text", edit:true, widthPercent: 6 },
{ id: "levyAmt", title: "부과금액(천원)", upName: "feeHist", upSequnce: 3, type: "number", edit:true, widthPercent: 5 }];
export const rswtDetailHeadListExport: TableUpperProps[] = [
{ id: "utztnUsgCdText", title: "이용용도" },
{
id: "grssClfgUswtr",
title: "총대장균군수",
upName: "detailQulity",
upSequnce: 2,
edit: true,
},
{
id: "combResdch",
title: "결합잔류염소",
upName: "detailQulity",
upSequnce: 3,
edit: true,
},
{
id: "trbt",
title: "탁도",
upName: "detailQulity",
upSequnce: 4,
edit: true,
},
{ id: "bod", title: "BOD", upName: "detailQulity", upSequnce: 5, edit: true },
{
id: "smll",
title: "냄새",
upName: "detailQulity",
upSequnce: 6,
edit: true,
},
{
id: "crmty",
title: "색도",
upName: "detailQulity",
upSequnce: 7,
edit: true,
},
{ id: "tn", title: "T-N", upName: "detailQulity", upSequnce: 8, edit: true },
{ id: "tph", title: "T-P", upName: "detailQulity", upSequnce: 9, edit: true },
{ id: "ph", title: "pH", upName: "detailQulity", upSequnce: 10, edit: true },
{
id: "clIonQnt",
title: "염화물",
upName: "detailQulity",
upSequnce: 11,
edit: true,
},
{ id: "utztnUsgCdText", title: "이용용도", widthPercent: 20 },
{ id: "grssClfgUswtr", title: "총대장균군수", upName: "detailQulity", upSequnce: 2, edit: true, widthPercent: 8 },
{ id: "combResdch", title: "결합잔류염소", upName: "detailQulity", upSequnce: 3, edit: true, widthPercent: 8 },
{ id: "trbt", title: "탁도", upName: "detailQulity", upSequnce: 4, edit: true, widthPercent: 8 },
{ id: "bod", title: "BOD", upName: "detailQulity", upSequnce: 5, edit: true, widthPercent: 8 },
{ id: "smll", title: "냄새", upName: "detailQulity", upSequnce: 6, edit: true, widthPercent: 8 },
{ id: "crmty", title: "색도", upName: "detailQulity", upSequnce: 7, edit: true, widthPercent: 8 },
{ id: "tn", title: "T-N", upName: "detailQulity", upSequnce: 8, edit: true, widthPercent: 8 },
{ id: "tph", title: "T-P", upName: "detailQulity", upSequnce: 9, edit: true, widthPercent: 8 },
{ id: "ph", title: "pH", upName: "detailQulity", upSequnce: 10, edit: true, widthPercent: 8 },
{ id: "clIonQnt", title: "염화물", upName: "detailQulity", upSequnce: 11, edit: true, widthPercent: 8 },
];
@@ -41,19 +41,19 @@ export type rswtChkZodType = z.infer<typeof rswtChkgdZod>;
export type rswtrChkDetailsZodType = z.infer<typeof rswtrDetailsZod>;
export type rswtChkgdnState = {
rswtChkgdn: {
rswtChkHeadList: TableUpperProps[];
rswtDetailHeadList: TableUpperProps[];
rswtHeadNm: chHeadType[];
excelList: SheetParameter[];
rswtChkRes: rswtChkZodType[];
rswtChkPolGuide: rswtChkZodType[];
rswtSplHeadNm:chHeadType[]
rswtRuseHeadNm:chHeadType[]
isInit: boolean;
isSttn: boolean;
isLoading:boolean;
rswtChkCount: number;
rswtChkHeadList: TableUpperProps[]; // 중수도 지도점검 메인 목록 헤더
rswtDetailHeadList: TableUpperProps[]; // 중수도 지도점검 상세 목록 헤더
rswtHeadNm: chHeadType[]; // 메인 목록 다단 헤더명
excelList: SheetParameter[]; // 엑셀 다운로드용 시트 데이터
rswtChkRes: rswtChkZodType[]; // 중수도 지도점검 메인 목록 데이터
rswtChkPolGuide: rswtChkZodType[]; // 선택한 지도점검 1건의 수질/대체수 상세 데이터
rswtSplHeadNm:chHeadType[] // 공급량 관련 다단 헤더명
rswtRuseHeadNm:chHeadType[] // 재이용량 관련 다단 헤더명
isInit: boolean; // 초기 목록 조회 완료 여부
isSttn: boolean; // 테이블 선택/상세 상태 제어 플래그
isLoading:boolean; // 등록/업로드 등 비동기 처리 진행 여부
rswtChkCount: number; // 메인 목록 전체 건수
};
};
@@ -242,27 +242,29 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = (
console.log(error);
}
},
// 중수도 지도점검결과 upload
rswtChkExceDataUpload: async (excelData: rswtChkZodType[],laeSeCd:string) => {
const successMessage = "지도점검 등록이 완료되었습니다.";
const failMessage = "지도점검 등록이 실패되었습니다.";
console.log(excelData);
try {
set((state: rswtChkgdnType) => (
{ rswtChkgdn: { ...state.rswtChkgdn, isLoading: true } }));
set((state: rswtChkgdnType) => (
{ rswtChkgdn: { ...state.rswtChkgdn, isLoading: true } }));
const res = await postFectch("/reuseChk/reuseWater", excelData);
if (!res.ok) {
return failMessage;
if (res.status === 200) {
const currentYear = new Date().getFullYear().toString();
const rswtChkRes = await initFetch(1, rswtPageSize, currentYear, laeSeCd);
set((state: rswtChkgdnState) => ({
...state,
rswtChkgdn: {
...state.rswtChkgdn,
rswtChkRes: rswtChkRes.data,
rswtChkCount: rswtChkRes.count,
},
}));
return successMessage;
}
const currentYear = new Date().getFullYear().toString();
const rswtChkRes = await initFetch(1, rswtPageSize, currentYear, laeSeCd);
set((state: rswtChkgdnState) => ({
...state,
rswtChkgdn: {
...state.rswtChkgdn,
rswtChkRes: rswtChkRes.data,
rswtChkCount: rswtChkRes.count,
},
}));
return successMessage;
return failMessage;
} catch (error) {
console.log(error);
return failMessage;
@@ -56,57 +56,57 @@ export const splSttnHeadListExport: TableUpperProps[] = [
{ id: "spl12", title: "12월", upName: "splWater", upSequnce: 13 },
];
export const rswtDetailHeadListExport: TableUpperProps[] = [
{ id: "utztnUsgCdText", title: "이용용도" },
// export const rswtDetailHeadListExport: TableUpperProps[] = [
// { id: "utztnUsgCdText", title: "이용용도" },
{
id: "grssClfgUswtr",
title: "총대장균군수",
upName: "detailQulity",
upSequnce: 2,
},
{
id: "combResdch",
title: "결합잔류염소",
upName: "detailQulity",
upSequnce: 3,
},
{
id: "trbt",
title: "탁도",
upName: "detailQulity",
upSequnce: 4,
},
{
id: "bod",
title: "BOD",
upName: "detailQulity",
upSequnce: 5,
},
{
id: "smll",
title: "냄새",
upName: "detailQulity",
upSequnce: 6,
},
{
id: "crmty",
title: "색도",
upName: "detailQulity",
upSequnce: 7,
},
{ id: "tn", title: "T-N", upName: "detailQulity", upSequnce: 8},
{
id: "tph",
title: "T-P",
upName: "detailQulity",
upSequnce: 9,
},
{ id: "ph", title: "pH", upName: "detailQulity", upSequnce: 10 },
{
id: "clIonQnt",
title: "염화물",
upName: "detailQulity",
upSequnce: 11,
},
];
// {
// id: "grssClfgUswtr",
// title: "총대장균군수",
// upName: "detailQulity",
// upSequnce: 2,
// },
// {
// id: "combResdch",
// title: "결합잔류염소",
// upName: "detailQulity",
// upSequnce: 3,
// },
// {
// id: "trbt",
// title: "탁도",
// upName: "detailQulity",
// upSequnce: 4,
// },
// {
// id: "bod",
// title: "BOD",
// upName: "detailQulity",
// upSequnce: 5,
// },
// {
// id: "smll",
// title: "냄새",
// upName: "detailQulity",
// upSequnce: 6,
// },
// {
// id: "crmty",
// title: "색도",
// upName: "detailQulity",
// upSequnce: 7,
// },
// { id: "tn", title: "T-N", upName: "detailQulity", upSequnce: 8},
// {
// id: "tph",
// title: "T-P",
// upName: "detailQulity",
// upSequnce: 9,
// },
// { id: "ph", title: "pH", upName: "detailQulity", upSequnce: 10 },
// {
// id: "clIonQnt",
// title: "염화물",
// upName: "detailQulity",
// upSequnce: 11,
// },
// ];
@@ -43,16 +43,16 @@ export type rainChkSttnType = rainChkSttnState & rainChkSttnActions;
export const rainChkSttnStateExport: rainChkSttnState = {
rainChkSttn: {
rainChkRes: [],
fcltDiv: fcltDiv,
rainChkgdnHeadList: rainChkgdnHeadList,
ruseHeadList: ruseHeadList,
splHeadList: splHeadList,
rainHeadNm: rainHeadNm,
isInit: false,
sttnExcelList: [],
isSttn: false,
rainChkCount: 1,
rainChkRes: [], // 빗물이용시설 지도점검 현황 메인 목록 데이터
fcltDiv: fcltDiv, // 시설 구분 RST 중수도시설, RNB 빗물이용시설
rainChkgdnHeadList: rainChkgdnHeadList, // 메인 목록 테이블 헤더 정의
ruseHeadList: ruseHeadList, // 연간 빗물이용량 상세 테이블 헤더 정의
splHeadList: splHeadList, // 연간 대체수이용량 상세 테이블 헤더 정의
rainHeadNm: rainHeadNm, // 메인 목록 다단 헤더명 정의
isInit: false, // 현황 화면 초기 조회 완료 여부
sttnExcelList: [], // 엑셀 다운로드용 가공 데이터
isSttn: false, // 등록/현황 화면 구분 및 읽기 전용 제어 플래그
rainChkCount: 1, // 메인 목록 전체 건수
},
};
@@ -31,16 +31,15 @@ export const rswtChkSttnHeadListExport: TableUpperProps[] = [
];
export const rswtDetailSttnHeadListExport: TableUpperProps[] = [
{ id: "utztnUsgCd", title: "이용용도" },
{ id: "grssClfgUswtr", title: "총대장균군수", upName: "detailQulity", upSequnce: 2 },
{ id: "combResdch", title: "결합잔류염소", upName: "detailQulity", upSequnce: 3 },
{ id: "trbt", title: "탁도", upName: "detailQulity", upSequnce: 4 },
{ id: "bod", title: "BOD", upName: "detailQulity", upSequnce: 5 },
{ id: "smll", title: "냄새", upName: "detailQulity", upSequnce: 6 },
{ id: "crmty", title: "색도", upName: "detailQulity", upSequnce: 7 },
{ id: "tn", title: "T-N", upName: "detailQulity", upSequnce: 8 },
{ id: "tph", title: "T-P", upName: "detailQulity", upSequnce: 9 },
{ id: "ph", title: "pH", upName: "detailQulity", upSequnce: 10 },
{ id: "clIonQnt", title: "염화물", upName: "detailQulity", upSequnce: 11 },
{ id: "utztnUsgCd", title: "이용용도", widthPercent: 20 },
{ id: "grssClfgUswtr", title: "총대장균군수", upName: "detailQulity", upSequnce: 2, widthPercent: 8 },
{ id: "combResdch", title: "결합잔류염소", upName: "detailQulity", upSequnce: 3, widthPercent: 8 },
{ id: "trbt", title: "탁도", upName: "detailQulity", upSequnce: 4, widthPercent: 8 },
{ id: "bod", title: "BOD", upName: "detailQulity", upSequnce: 5, widthPercent: 8 },
{ id: "smll", title: "냄새", upName: "detailQulity", upSequnce: 6, widthPercent: 8 },
{ id: "crmty", title: "색도", upName: "detailQulity", upSequnce: 7, widthPercent: 8 },
{ id: "tn", title: "T-N", upName: "detailQulity", upSequnce: 8, widthPercent: 8 },
{ id: "tph", title: "T-P", upName: "detailQulity", upSequnce: 9, widthPercent: 8 },
{ id: "ph", title: "pH", upName: "detailQulity", upSequnce: 10, widthPercent: 8 },
{ id: "clIonQnt", title: "염화물", upName: "detailQulity", upSequnce: 11, widthPercent: 8 },
];
@@ -69,6 +69,7 @@ type pubFacActions = {
pbnstInsert(pbnstRes: customPbnst): Promise<string>;
fileDelete: (atflCd: string) => void;
fileDownLoad: (atflCd: string) => void;
fileDownLoadName: (atflCd: string, fileName: string) => void;
pbnstRuseFcltUpdate: (ruseRes: customPbnst[]) => void;
pbnstRuseBizUpdate: (ruseRes: fcBizType[]) => void;
pubHasuSearch: (page: string, sggOrCity: string, searchTxt: string) => void;
@@ -297,6 +298,34 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
throw new Error(`Error downloading file: ${error}`);
}
},
// 파일다운로드(원본 파일명 지정) : 화면에 보여지는 파일명으로 저장
fileDownLoadName: async (atflCd: string, fileName: string) => {
try {
const response = await fetch(
`/attach-service/api/v1/atflcd/download/${atflCd}`,
{
method: "GET",
headers: {
accept: "application/octet-stream",
},
}
);
if (!response.ok) {
throw new Error(`오류 발생: ${response.status}`);
}
console.log(response);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobUrl;
a.download = fileName || "downloaded_file";
a.click();
window.URL.revokeObjectURL(blobUrl);
} catch (error) {
console.error("Error downloading file with name:", error);
throw new Error(`Error downloading file with name: ${error}`);
}
},
//원수취수 팝업의 하수처리 시설 리스트 조회
getFcOrgwts: async (fcltPkList: string[], size: number) => {
const fcOrgwtsNmList = await postFectch(
@@ -10,6 +10,7 @@ export const sttnHeadListType = z.array(
})
);
// 빗물이용시설
export const rainSttHeadListExport: z.infer<typeof sttnHeadListType> = [
{ id: "cityText", title: "시도", visible: true, widthPercent: 4 },
{ id: "sggText", title: "시군구", visible: true, widthPercent: 4 },
@@ -41,7 +42,7 @@ export const rainSttHeadListExport: z.infer<typeof sttnHeadListType> = [
{ id: "grntchBdstCertGrdText", title: "녹색건축물인증등급", visible: true, widthPercent: 4 },
];
// id는 다확인해야함
//
export const rainSttNodeListExport: CommonTableProps[] = [
{ id: "bdnstInfo", title: "건축물정보", visible: true, row: 1, rowSpan: 1, colSpan: 10, },
{ id: "rainFcltInfo", title: "빗물이용시설정보", visible: true, row: 1, rowSpan: 1, colSpan: 7, },
+158 -4
View File
@@ -94,6 +94,7 @@ export const rswtChkTableData = (chkData: rswtChkZodType[]) => {
return makeRswtData;
};
// 재이용시설등록 > 지도점검결과 등록 - table body
export const chRowspanTable = (
makeTableData: any[],
headList: TableUpperProps[],
@@ -152,7 +153,7 @@ export const chRowspanTable = (
) : key === "order" ? (
++order
) : // value
["anlOperCst", "dtfcltCpct", "prcsCpct"].includes(
["anlOperCst", "dtfcltCpct", "prcsCpct"].includes( //연간운영비, 저류조용량, 처리용량
head.id
) ? (
formatCommaNumber(value)
@@ -233,7 +234,7 @@ export const chRowspanTable = (
}}
/>
)
) : ["smll", "ph"].includes(head.id) && head.type === "text" ? (
) : ["smll"].includes(head.id) && head.type === "text" ? (
<>-</>
) : [
"totalSplyUswtrUtztnQnt",
@@ -311,6 +312,9 @@ const handleChange = (
})
);
};
// 재이용실적등록 > 지도점검결과 등록 - 상세 modal의 body
export const InputTableBody = (
chgRes: any[] | [],
headList: TableUpperProps[],
@@ -336,6 +340,157 @@ export const InputTableBody = (
const isSubTotal = data["용수명"] === "소계";
const isTotal = data["구분"] === 0;
const bgClass = isTotal
? "bg-[#d5e1eb] font-bold"
: isSubTotal
? "bg-[#e6eef5] font-semibold"
: "";
return (
<TableRow key={dataIndex + "row"}>
{headList.map((head, headIndex) => {
//냄새면 계산 못하게 - 로 수정
// if (["smll"].includes(head.id)) {
// return (
// <TableCell
// key={head.id + headIndex}
// className={`w-36 ${bgClass} text-center !text-xl`}
// >
// <span>-</span>
// </TableCell>
// );
// }
const isTextColumn = head.id === "구분" || head.id === "utztnUsgCdText";
const textAlignClass = isTextColumn
? "text-center"
: "text-right";
// 총합계 행이면 "구분"에서 colSpan=2로 병합
if ( (data["구분"] === 0 || data["용수명"] === 0) && head.id === "구분" ) {
return (
<TableCell
key={head.id + headIndex}
colSpan={2}
className="font-bold text-center bg-[#d5e1eb]"
>
총합계
</TableCell>
);
}
// 총합계(합계) 행이면 "용수명" 컬럼은 렌더링하지 않음
if ( (data["구분"] === 0 || data["용수명"] === 0) && head.id === "용수명" ) {
return null;
}
// 총합계 행의 나머지 셀에도 색상 적용
if (data["구분"] === 0 || data["용수명"] === 0) {
return (
<TableCell
key={head.id + headIndex}
className="bg-[#d5e1eb] font-bold text-right"
>
{Number(data[head.id])
? formatCommaNumber(data[head.id as keyof any])
: (data[head.id as keyof any] ?? "")}
</TableCell>
);
}
// mergeCell 처리
if (head.mergeCell) {
const rowSpan = getRowSpan(chgRes, head.id, dataIndex);
if (rowSpan === 0) return null;
return (
<TableCell
key={head.id + headIndex}
rowSpan={rowSpan}
className={`w-36 align-middle text-center whitespace-nowrap ${bgClass}`}
>
{Number(data[head.id])
? formatCommaNumber(data[head.id as keyof any])
: (data[head.id as keyof any] ?? "")}
</TableCell>
);
}
// 기본 셀
// return head.edit === true && data.edit !== false ? (
return isEditable && head.edit === true && data.edit !== false ? (
<TableCell
key={head.id + headIndex}
className={`w-36 ${bgClass}`}
>
<Input
// className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`}
className={`${textAlignClass} !text-xl h-10 w-full`}
onChange={(e) =>
handleCommaNumberChange(e, (val) =>
handleChange(
dataIndex,
head.id,
String(val),
setInputData
)
)
}
value={formatCommaNumber(data[head.id as keyof any]) ?? ""}
maxLength={numSize}
/>
</TableCell>
) : (
<TableCell
key={head.id + headIndex}
className={`w-36 ${bgClass}`}
>
<div className={`${textAlignClass} !text-xl w-full h-10`}>
{Number(data[head.id])
? formatCommaNumber(data[head.id as keyof any])
: (data[head.id as keyof any] ?? "")}
</div>
</TableCell>
);
})}
</TableRow>
);
})}
{chgRes.length === 0 && (
<TableRow>
<TableCell
colSpan={headList.length}
className="h-24 text-center text-gray-500"
>
조회된 결과가 없습니다.
</TableCell>
</TableRow>
)}
</>
);
};
// 재이용실적등록 > 하수재이용실적등록 목록 body - 하수처리수재이용 실적(m³) 등록결과
export const pbnstTableBody = (
chgRes: any[] | [],
headList: TableUpperProps[],
setInputData: (data: any[]) => void,
isEditable: boolean = true
) => {
// mergeCell 처리용
const getRowSpan = (data: any[], key: string, rowIndex: number) => {
if (rowIndex > 0 && data[rowIndex][key] === data[rowIndex - 1][key]) {
return 0; // 이전과 같으면 셀을 그리지 않음
}
let span = 1;
for (let i = rowIndex + 1; i < data.length; i++) {
if (data[i][key] === data[rowIndex][key]) span++;
else break;
}
return span;
};
return (
<>
{chgRes.map((data, dataIndex) => {
// 소계/총합계 행 판별
const isSubTotal = data["용수명"] === "소계";
const isTotal = data["구분"] === 0;
const bgClass = isTotal
? "bg-[#d5e1eb] font-bold"
: isSubTotal
@@ -471,5 +626,4 @@ export const InputTableBody = (
)}
</>
);
};
};
@@ -44,7 +44,7 @@ const diagramFileTypes = ".pdf,.jpg,.jpeg,.png,.bmp";
* - with-layout 적용
* - 하수처리시설에 하수재이용 시설 시설 정보를 등록한다.
* - form으로 시설정보 및 차수별 정보의 컴포넌트 데이터를 관리한다
* indexMng: 시설정보
* indexMng: 재이용시설정보
* indexBiz: 차수별 정보
* 로 하수재이용 시설에 등록된 시설 및 차수별 정보를 관리한다.
* */
@@ -61,7 +61,7 @@ export function PbnstMngForm({
setIndexMng: (index: number) => void;
readonly: boolean;
}) {
const { code, pbnst, fileDelete, fileDownLoad } = useWrStore(
const { code, pbnst, fileDelete, fileDownLoadName } = useWrStore(
(state) => state
);
const [planFiles, setPlanFiles] = useState<FilesType[] | null>(null);
@@ -150,10 +150,10 @@ export function PbnstMngForm({
} else {
fileList?.forEach((item, index) => {
const idx = files?.indexOf(item.file);
if (idx < 0) {
setReFiles.splice(index);
fileDelete(item.atflCd as string);
}
if (idx < 0) {
setReFiles.splice(index, 1);
fileDelete(item.atflCd as string);
}
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
@@ -183,7 +183,7 @@ export function PbnstMngForm({
useEffect(() => {
setRelationFiles([]);
setPlanFiles([]);
console.log("indexMng ", indexMng);
// console.log("indexMng ", indexMng);
const currentItem = form.getValues(`items.${indexMng}`);
const defaultFiles = currentItem?.fcFiles || [];
@@ -443,7 +443,7 @@ export function PbnstMngForm({
<span
key={index + "plan"}
className="text-2xl block cursor-pointer text-blue-600 hover:underline"
onClick={() => fileDownLoad(item.atflCd)}
onClick={() => fileDownLoadName(item.atflCd, item.file.name)}
>
{item.file.name}
</span>
@@ -462,11 +462,12 @@ export function PbnstMngForm({
}}
fileTypes={diagramFileTypes}
onFileClick={(index) => {
fileDownLoad(
relationfiles?.find(
(item, fileIndex) => fileIndex === index
)?.atflCd as string
const target = relationfiles?.find(
(item, fileIndex) => fileIndex === index
);
if (target?.atflCd) {
fileDownLoadName(target.atflCd, target.file.name);
}
}}
></FileUpload>
</FormUi>
@@ -481,7 +482,7 @@ export function PbnstMngForm({
<span
key={index + "plan"}
className="text-2xl block cursor-pointer text-blue-600 hover:underline"
onClick={() => fileDownLoad(item.atflCd)}
onClick={() => fileDownLoadName(item.atflCd, item.file.name)}
>
{item.file.name}
</span>
@@ -500,11 +501,12 @@ export function PbnstMngForm({
}}
fileTypes={diagramFileTypes}
onFileClick={(index) => {
fileDownLoad(
planFiles?.find(
(item, fileIndex) => fileIndex === index
)?.atflCd as string
const target = planFiles?.find(
(item, fileIndex) => fileIndex === index
);
if (target?.atflCd) {
fileDownLoadName(target.atflCd, target.file.name);
}
}}
></FileUpload>
</FormUi>
@@ -1,3 +1,6 @@
/**
* map 시설물 layer > 하수처리시설 modal
*/
import { useWrStore } from "@/app/store";
import { btnClass } from "@/app/store/common/code";
import { pbnMapTableSize } from "@/app/store/map/pbnstMap";
@@ -1,3 +1,6 @@
/**
* map 시설물 layer > 온배수시설 modal
*/
import { useWrStore } from "@/app/store";
import { htdfMapSize } from "@/app/store/map/htdflMap";
import { rainMapSize } from "@/app/store/map/rainMap";
@@ -1,3 +1,6 @@
/**
* map 시설물 layer > 빗물이용시설 modal
*/
import { useWrStore } from "@/app/store";
import { rainMapSize } from "@/app/store/map/rainMap";
import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
@@ -1,3 +1,6 @@
/**
* map 시설물 layer > 중수도시설 modal
*/
import { useWrStore } from "@/app/store";
import { rainMapSize } from "@/app/store/map/rainMap";
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
@@ -1,3 +1,6 @@
/**
* map 시설물 layer > 하수재이용시설 modal
*/
import { useWrStore } from "@/app/store";
import { btnClass } from "@/app/store/common/code";
import { ruseMapSize } from "@/app/store/map/ruseMap";
@@ -1,3 +1,6 @@
/**
* 지도점검 결과등록 엑셀 업로드 모달
*/
import { useWrStore } from "@/app/store";
import { type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn";
import { excelToRainChkData } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel";
@@ -8,7 +11,6 @@ import {
excelToRswtPolution,
rswtExcelDataCheck,
} from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel";
import { showConfirmToast } from "@/components/lib/func/common";
import { useAlertStore } from "@/src/shared/lib";
import {
filePicked,
@@ -61,6 +63,8 @@ export default function ChkgdExcel({
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
setExcelData(undefined);
setExcelRswtData(undefined);
if (selectedFile) {
setFile(selectedFile);
}
@@ -105,12 +109,15 @@ export default function ChkgdExcel({
);
if (excelData.message) {
alert(excelData.message);
setExcelData(undefined);
}
if (excelData.issues && excelData.issues.length > 0) {
excelData.issues.some((err, index) => {
showConfirmToast(err);
// showConfirmToast(err);
alert(err);
return index === 4; // true 반환 시 반복 종료
});
setExcelData(undefined);
clearFile();
} else {
setExcelData(excelData.rainChkgdData);
@@ -123,6 +130,7 @@ export default function ChkgdExcel({
setRswtChkLoading(true);
filePickedArray(file, 0)
.then((res) => {
setExcelRswtData(undefined);
if (res && res?.length > 1) {
const guideData = excelToRswtGuideData(
res[0],
@@ -167,16 +175,24 @@ export default function ChkgdExcel({
if (issues && issues.length > 0) {
issues.some((err, index) => {
showConfirmToast(err);
return index === 6; // true 반환 시 반복 종료
// showConfirmToast(err);
alert(err);
return index === 4; // true 반환 시 반복 종료
});
console.log(issues.join("\n"));
setExcelRswtData(undefined);
clearFile();
} else {
setExcelRswtData(listTotalData);
}
} else {
alert("중수도 엑셀은 2개 시트가 필요합니다. 양식을 확인해주세요.");
setExcelRswtData(undefined);
clearFile();
}
})
.finally(() => setRswtChkLoading(false));
console.log("업로드파일 점검 완료");
}
}
}, [file]);
@@ -1,3 +1,6 @@
/**
* 지도점검 상세조회/상세수정용 모달
*/
import { useWrStore } from "@/app/store";
import type { TableUpperProps } from "@/app/store/common/type/header";
import type { splRuseWaterType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList";
@@ -11,10 +14,10 @@ import React, { useEffect, useState } from "react";
import type { z } from "zod";
export default function ChkgdModal({
type,
obj,
setIsModal,
readonly,
type, // 시설 구분값. "RNB"이면 빗물, 그 외는 중수도 상세 모달로 처리
obj, // 목록에서 선택한 1건의 상세 데이터. 가이드/수질/과태료 정보 렌더링과 저장에 사용
setIsModal, // 모달 열림 상태를 제어하는 함수. 저장 완료 시 false로 닫는다
readonly, // 편집 가능 여부. true면 입력 가능, false면 조회 전용으로 렌더링
}: {
type: string;
obj: any;
@@ -57,7 +60,7 @@ export default function ChkgdModal({
combResdch: 0,
trbt: 0,
bod: 0,
smll: 0,
smll: '-',
crmty: 0,
tph: 0,
tn: 0,
@@ -69,7 +72,7 @@ export default function ChkgdModal({
useEffect(() => {
if (rainChkSttn.isSttn) {
setHeadList({
splHeadList: rainChkSttn.splHeadList,
splHeadList: rainChkSttn.splHeadList, // 연간 대체수이용량 헤더 정의
ruseHeadList: rainChkSttn.ruseHeadList,
rswtDetailHeadList:
type === "RNB" ? [] : rswtChkSttn.rswtDetailSttnHeadList,
@@ -82,9 +85,12 @@ export default function ChkgdModal({
});
}
}, [
type,
rainChkSttn.isSttn,
rainChkgdn.splHeadList,
rainChkgdn.ruseHeadList,
rainChkSttn.splHeadList,
rainChkSttn.ruseHeadList,
rswtChkgdn.rswtDetailHeadList,
rswtChkSttn.rswtDetailSttnHeadList,
]);
@@ -156,12 +162,13 @@ export default function ChkgdModal({
if (onlyData.length === 0) return;
const total = initStats();
// 분기별 원본 수질 데이터를 모두 더해 평균 계산의 분모/분자를 만든다.
onlyData.forEach((item) => {
total.grssClfgUswtr += item.grssClfgUswtr;
total.combResdch += item.combResdch;
total.trbt += item.trbt;
total.bod += item.bod;
total.smll += item.smll;
// 냄새(smll)는 수치 평균 대상이 아니라 누적하지 않는다.
total.crmty += item.crmty;
total.tph += item.tph;
total.tn += item.tn;
@@ -169,6 +176,7 @@ export default function ChkgdModal({
total.clIonQnt += item.clIonQnt;
});
// 누적 합계를 항목 개수로 나눠 화면에 표시할 평균 행을 만든다.
const average = {
utztnUsgCdText: "평균",
edit: false,
@@ -176,7 +184,7 @@ export default function ChkgdModal({
combResdch: (total.combResdch / onlyData.length).toFixed(1),
trbt: (total.trbt / onlyData.length).toFixed(1),
bod: (total.bod / onlyData.length).toFixed(1),
smll: (total.smll / onlyData.length).toFixed(1),
smll: '-',
crmty: (total.crmty / onlyData.length).toFixed(1),
tph: (total.tph / onlyData.length).toFixed(1),
tn: (total.tn / onlyData.length).toFixed(1),
@@ -1,3 +1,6 @@
/**
* 재이용실적등록> 하수재이용 실적등록 파일등록 modal
*/
import { excelToPbnPrfData } from "@/app/store/prfmncReg/pbnstPrfmncReg/excel";
import { pbnPrfRegExcelZod } from "@/app/store/prfmncReg/pbnstPrfmncReg/headList";
import type {
@@ -17,8 +20,9 @@ import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
// import toast from "react-hot-toast";
import type { z } from "zod";
import { useAlertStore } from "@/src/shared/lib";
export default function PbnPrfExcelModal({
data,
@@ -29,6 +33,7 @@ export default function PbnPrfExcelModal({
setIsExcelOPen: (open: boolean) => void;
setModalExcelData: (excelData: PrfType) => void;
}) {
const alert = useAlertStore((state) => state.setMessage);
const [file, setFile] = useState<File | null>(null);
const [excelData, setExcelData] = useState<PrfType[]>();
const form = useForm<z.infer<typeof pbnPrfRegExcelZod>>({
@@ -65,7 +70,8 @@ export default function PbnPrfExcelModal({
if (file) {
const isFile = file;
if (form.getValues("ym").length < 1) {
toast.error("파일업로드 이전에 등록년도를 작성해주세요 ");
// toast.error("파일업로드 이전에 등록년도를 작성해주세요 ");
alert("파일업로드 이전에 등록년도를 작성해주세요 ");
setFile(null);
return;
}
@@ -8,9 +8,10 @@ import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
// import toast from "react-hot-toast";
import type { z } from "zod";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore } from "@/src/shared/lib";
export default function CoRegExcel({
isModal,
@@ -18,6 +19,7 @@ export default function CoRegExcel({
isModal: (open: boolean) => void;
}) {
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const { ldrmnt, loadingOpen } = useWrStore((state) => state);
const { isLoading } = ldrmnt;
const form = useForm<z.infer<typeof coRegExcel>>({
@@ -76,11 +78,12 @@ export default function CoRegExcel({
await ldrmntRegData(array);
isModal(false);
loadingOpen(false);
toast.success(`잠재수요처 등록이 요청되었습니다.`);
} catch (e) {
// toast.success(`잠재수요처 등록이 요청되었습니다.`);
alert("잠재수요처 등록이 요청되었습니다.");
} catch (e: any) {
console.log(e);
loadingOpen(false);
alert(e);
// alert(e.message);
return true;
}
})
@@ -90,7 +93,8 @@ export default function CoRegExcel({
});
} else {
loadingOpen(false);
toast.error(`엑셀 파일을 선택해 주세요.`);
// toast.error(`엑셀 파일을 선택해 주세요.`);
alert("엑셀 파일을 선택해 주세요.");
}
};
@@ -18,7 +18,8 @@ import {
} from "@common/business_components";
import { Button, Input, Label } from "@common/components";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
// import toast from "react-hot-toast";
import { useAlertStore } from "@/src/shared/lib";
export type objSearchType = {
sgg: string | number;
@@ -47,7 +48,7 @@ export function BdstReg<T extends MoType>({
const { code, bdst, auth, findBdst } = useWrStore((state) => {
return state;
});
const alert = useAlertStore((state) => state.setMessage);
const { laeSeCd } = auth;
const [bdstTable, setBdstTable] = useState<BdstTypeMap[T][]>();
@@ -73,7 +74,8 @@ export function BdstReg<T extends MoType>({
findBdst(String(data), searchTxt || "", moType);
}else{
toast.error("시도 또는 시군구를 선택하여 주십시오.");
// toast.error("시도 또는 시군구를 선택하여 주십시오.");
alert("시도 또는 시군구를 선택하여 주십시오.");
}
};
@@ -1,3 +1,6 @@
/**
* 재이용시설등록 > 하수재이용시설등록 - 취수하수처리장 추가 modal
*/
"use client";
import { useWrStore } from "@/app/store";
import type {
@@ -12,7 +15,8 @@ import { Button } from "@common/business_components/ui/table/lib/button";
import { Input } from "@common/components/ui";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
// import toast from "react-hot-toast";
import { useAlertStore } from "@/src/shared/lib";
export default function FcOrgwtsRegModal({
onRightSet,
@@ -23,6 +27,7 @@ export default function FcOrgwtsRegModal({
closeModal?: (val: boolean) => void;
initTableData?: any[];
}) {
const alert = useAlertStore((state) => state.setMessage);
const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore(
(state) => state
);
@@ -53,7 +58,8 @@ export default function FcOrgwtsRegModal({
closeModal?.(false)
}
else {
toast.error("취수하수처리장 목록을 선택해주세요.")
// toast.error("취수하수처리장 목록을 선택해주세요.");
alert("취수하수처리장 목록을 선택해주세요.");
}
}
@@ -15,7 +15,7 @@ import {
type fcBizType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { formH1Class } from "@/components/lib/class/comClass";
import { InputTableBody } from "@/components/lib/func/TableData";
import { pbnstTableBody } from "@/components/lib/func/TableData";
import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm";
import { ScrollArea, Title, UiTable } from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
@@ -341,7 +341,7 @@ export function PbnstSttnModal({ data, titleVisible,isMap}: { data: customPbnst;
{headMakeprfmnc(pbnstPrfmncSttn.monthSttnHeadList, [])}
</TableHeader>
<TableBody key={prfmnc.pageKey + 'pageKey'}>
{InputTableBody(prfData[ruseTab as keyof typeof prfData] || [], pbnstPrfmncSttn.monthSttnHeadList, setPrfData as any)}
{pbnstTableBody(prfData[ruseTab as keyof typeof prfData] || [], pbnstPrfmncSttn.monthSttnHeadList, setPrfData as any)}
</TableBody>
</Table>
{/* </ScrollArea> */}
@@ -1,3 +1,6 @@
/**
* 재이용실적등록 > 지도점검결과등록 - 연간대체수이용량, 연간중수도이용량 click시 상세 model의 table body
*/
"use client";
import { headMakeprfmnc } from "@/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/TableHead";
import type { TableUpperProps } from "@/app/store/common/type/header";
@@ -41,7 +44,17 @@ export function GuideDetailsTable({
return (
<>
<div className={`card !m-[1.2rem] !p-0r `}>
<Table variant="secondary">
<Table variant="secondary" className="table-fixed">
<colgroup>
{headList
.filter((head) => head.visible !== false)
.map((head) => (
<col
key={head.id}
style={{ width: head.widthPercent ? `${head.widthPercent}%` : undefined }}
/>
))}
</colgroup>
<TableHeader className="font-bold">{headMakeprfmnc(headList, upheadList)}</TableHeader>
<TableBody >
{InputTableBody(inputData ?? [], headList, setInputData, readonly)}
@@ -1,3 +1,6 @@
/**
* 재이용실적등록 > 지도점검결과 등록 - 중수도 table 목록
*/
"use client";
import { headMakeprfmnc } from "@/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/TableHead";
import { useWrStore } from "@/app/store";
@@ -152,11 +155,7 @@ export default function RswtChkgdTable({
.map((head) => (
<col
key={head.id}
style={{
width: head.widthPercent
? `${head.widthPercent}%`
: undefined,
}}
style={{ width: head.widthPercent ? `${head.widthPercent}%` : undefined, }}
/>
))}
</colgroup>
@@ -1,6 +1,7 @@
"use client";
import { CloudUpload, Paperclip } from "lucide-react";
import { FileInput, FileUploader, FileUploaderContent, FileUploaderItem } from "./lib/file_upload";
const dropZoneConfig = {
maxFiles: 5,
maxSize: 1024 * 1024 * 100,
@@ -9,14 +10,19 @@ const dropZoneConfig = {
interface FileUploadProps {
files: File[]; // 파일 배열
setFiles: (files: File[] | null) => void; // 파일 배열 세터
fileTypes?: string; // 옵션 (파일 확장자 문자열)
onFileClick?:(index:number)=> void;
maxFiles?: number; // 파일 클릭 핸들러
files: File[]; // 화면에 표시할 현재 파일 목록
setFiles: (files: File[] | null) => void; // 추가/삭제 후 변경된 파일 목록을 부모에 전달
fileTypes?: string; // 허용할 확장자 문자열(ex: ".pdf,.jpg")
onFileClick?:(index:number)=> void; // 파일명 영역 클릭 시 현재 목록 기준 index 전달
maxFiles?: number; // 업로드 가능한 최대 파일 수
}
/**
* 공통 파일 업로드 UI
* - 실제 파일 상태는 부모가 관리하고, 이 컴포넌트는 목록 표시/추가/삭제 이벤트만 위임한다.
* - 삭제 아이콘 클릭 시 FileUploader 내부에서 index 기준으로 파일을 제외한 배열을 만든 뒤 setFiles로 전달한다.
*/
export const FileUpload =
({ files, setFiles, fileTypes ,onFileClick, maxFiles}: FileUploadProps) => {
if(maxFiles){
@@ -51,6 +57,7 @@ export const FileUpload =
{files&&
files.length > 0 &&
files.map((file:File, i:number) => (
// i는 현재 files 배열 순번이며, 삭제/클릭 이벤트에서 같은 순번값을 사용한다.
<FileUploaderItem key={i} index={i} onFileClick={onFileClick }>
<Paperclip className="h-4 w-4 stroke-current" />
<span className="w-72 truncate">{file.name}</span>
@@ -59,4 +66,4 @@ export const FileUpload =
</FileUploaderContent>
</FileUploader>
)
}
}
@@ -37,6 +37,7 @@ type FileUploaderContextType = {
const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
// FileUploader 하위 컴포넌트들이 공통 상태와 핸들러를 사용할 수 있도록 context를 노출한다.
export const useFileUpload = () => {
const context = useContext(FileUploaderContext);
if (!context) {
@@ -54,7 +55,10 @@ type FileUploaderProps = {
};
/**
* File upload Docs: {@link: https://localhost:3000/docs/file-upload}
* 파일 업로드의 공통 동작을 담당하는 컨테이너 컴포넌트
* - 현재 파일 목록(value)을 기준으로 drag & drop / 선택 / 삭제를 처리한다.
* - 실제 파일 상태는 부모가 관리하며, 변경 결과는 onValueChange로만 전달한다.
* - FileInput, FileUploaderContent, FileUploaderItem은 이 context를 통해 같은 상태를 공유한다.
*/
export const FileUploader = forwardRef<
@@ -80,7 +84,7 @@ export const FileUploader = forwardRef<
const [isLOF, setIsLOF] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
// MIME 타입 매핑 함수 추가
// ".pdf,.jpg" 형태의 확장자 문자열을 react-dropzone accept 객체로 변환한다.
const getAcceptObject = (fileTypes?: string): Record<string, string[]> => {
if (!fileTypes) {
return { "image/*": [".jpg", ".jpeg", ".png", ".gif"] };
@@ -131,6 +135,7 @@ export const FileUploader = forwardRef<
const reSelectAll = maxFiles === 1 ? true : reSelect;
const direction: DirectionOptions = dir === "rtl" ? "rtl" : "ltr";
// 현재 value 배열에서 index 위치 파일만 제외한 새 배열을 부모로 전달한다.
const removeFileFromSet = useCallback(
(i: number) => {
if (!value) return;
@@ -140,6 +145,7 @@ export const FileUploader = forwardRef<
[value, onValueChange],
);
// 키보드로 파일 목록 탐색 및 삭제를 지원한다.
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
@@ -196,6 +202,7 @@ export const FileUploader = forwardRef<
[value, activeIndex, removeFileFromSet],
);
// 드롭/선택된 파일을 현재 목록과 합쳐서 부모 상태로 반영한다.
const onDrop = useCallback(
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
const files = acceptedFiles;
@@ -238,6 +245,7 @@ export const FileUploader = forwardRef<
[reSelectAll, value],
);
// 최대 파일 수에 도달하면 입력 영역을 비활성화한다.
useEffect(() => {
if (!value) return;
if (value.length === maxFiles) {
@@ -247,6 +255,7 @@ export const FileUploader = forwardRef<
setIsLOF(false);
}, [value, maxFiles]);
// react-dropzone에 넘길 최종 옵션 구성
const opts = dropzoneOptions
? { ...dropzoneOptions, accept: getAcceptObject(acceptName) }
: { accept, maxFiles, maxSize, multiple };
@@ -311,7 +320,7 @@ export const FileUploaderContent = forwardRef<
{...props}
ref={ref}
className={cn(
"w-full flex rounded-xl gap-1",
"w-full flex rounded-xl gap-0",
orientation === "horizontal" ? "flex-raw flex-wrap" : "flex-col",
className,
)}
@@ -335,26 +344,31 @@ export const FileUploaderItem = forwardRef<
ref={ref}
className={cn(
buttonVariants({ variant: "ghost" }),
"h-6 p-1 justify-between cursor-pointer relative w-full",
"h-9 justify-between cursor-pointer relative w-full",
className,
isSelected ? "bg-muted" : "",
)}
{...props}
>
<div className="font-medium leading-none tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
<div className="font-medium leading-normal tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
onClick={() =>{onFileClick?.(index)
}}>
{children}
</div>
<button
type="button"
className={cn(
"absolute",
"!w-1/5 z-50",
direction === "rtl" ? "top-1 left-1" : "top-1 right-1",
direction === "rtl"
? "left-1 top-1/2 -translate-y-1/2"
: "right-1 top-1/2 -translate-y-1/2",
)}
onClick={() =>{
onClick={(e) =>{
e.preventDefault();
e.stopPropagation();
removeFileFromSet(index)}}
>
<span className="sr-only">remove item {index}</span>