Merge branch 'iipbs/as/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/as/dev

This commit is contained in:
2026-05-28 13:38:55 +09:00
3 changed files with 136 additions and 48 deletions
@@ -8,7 +8,8 @@ import axios from "axios";
import { type HttpResponse, Upload } from "tus-js-client";
import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT, PROXY_HOST } from "@/shared/config";
import { getCookie, setCookie } from "@/shared/lib";
import { getCookie, setCookie, useAlertStore, useUserStore } from "@/shared/lib";
import { deleteAttachment } from "@/shared/lib/attach";
import type { AcntgSeMngTreeDto } from "@/entities/acntg";
import { acntgService } from "@/entities/acntg";
import type { CklstTreeNode } from "@/entities/cklst";
@@ -85,6 +86,9 @@ const ReadCell = ({ value }: { value: string | number }) => (
/* ------------------------------------------------------------------ */
function CnfmRegistPage() {
const router = useRouter();
const userInfo = useUserStore((state) => state.userInfo);
const alert = useAlertStore((state) => state.setMessage);
const [docSeComCd] = useState("INSTL");
const [taskSeComCdNm, setTaskSeComCdNm] = useState("재이용수");
const [taskSeComCd, setTaskSeComCd] = useState("RE");
@@ -227,6 +231,28 @@ function CnfmRegistPage() {
upload.start();
};
/** 기타첨부파일 삭제 핸들러: 백엔드에 DELETE 요청을 보내 소프트삭제 처리 후 상태 갱신 */
const handleDeleteEtcFile = async (fileId: string, atflId: string) => {
try {
if (atflId) {
await deleteAttachment(atflId);
}
} catch (error) {
console.error("기타 첨부파일 삭제 실패", error);
alert("파일 삭제 중 오류가 발생했습니다.");
return;
}
setEtcFiles((prev) => {
const next = prev.filter((x) => x.id !== fileId);
if (next.length === 0) {
setEtcFileGroupId("");
etcFileGroupIdRef.current = "";
}
return next;
});
};
const handleRequiredFileChange = (
key: RequiredAttachKey,
e: React.ChangeEvent<HTMLInputElement>,
@@ -280,7 +306,7 @@ function CnfmRegistPage() {
name: file.name,
ext,
size: toFileSizeText(file.size),
uploader: "(로그인 사용자)",
uploader: userInfo?.userName ?? "알 수 없음",
atflCd: uploadedAtflCd,
atflId: uploadedAtflId,
});
@@ -737,10 +763,10 @@ function CnfmRegistPage() {
<table className="cnfm-form-table">
<tbody>
<tr>
<th>
<th style={{ textAlign: "center" }}>
<span className="text-red-500">*</span> 사업명
</th>
<td colSpan={3}>
<td>
<div className="flex gap-2">
<Input
placeholder="사업명을 입력하거나 사업찾기를 이용하세요"
@@ -751,12 +777,10 @@ function CnfmRegistPage() {
<button className="cnfm-btn-search whitespace-nowrap">🔍 사업찾기</button>
</div>
</td>
</tr>
<tr>
<th>
<th style={{ textAlign: "center" }}>
<span className="text-red-500">*</span> 사업기간
</th>
<td colSpan={3}>
<td>
<div className="flex items-center gap-2">
<Input
placeholder="시작연도(YYYY)"
@@ -775,7 +799,7 @@ function CnfmRegistPage() {
</td>
</tr>
<tr>
<th>
<th style={{ textAlign: "center" }}>
<span className="text-red-500">*</span> 사업내용
</th>
<td colSpan={3}>
@@ -788,19 +812,23 @@ function CnfmRegistPage() {
</td>
</tr>
<tr>
<th>구분</th>
<th style={{ textAlign: "center" }}>구분</th>
<td>
<div className="flex gap-4 text-[1.3rem]">{taskSeComCdNm}</div>
<input type="hidden" name="taskSeComCd" value={taskSeComCd} />
</td>
<th>신청 지자체</th>
<th style={{ textAlign: "center" }}>신청 지자체</th>
<td>
<span className="text-[1.3rem] text-gray-500">(로그인 정보 자동 세팅)</span>
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">
{userInfo?.enoNm ?? "\u00A0"}
</span>
</td>
</tr>
<tr>
<th>사업구분</th>
<td>
<th style={{ textAlign: "center" }}>
<span className="text-red-500">*</span>사업구분
</th>
<td colSpan={3}>
<div className="flex gap-4 text-[1.3rem]">
{[
["CNFM", "설치인가"],
@@ -831,27 +859,45 @@ function CnfmRegistPage() {
<table className="cnfm-form-table">
<tbody>
<tr>
<th>신청자 이름</th>
<th
rowSpan={2}
style={{ textAlign: "center" }}
className="w-[6.5rem] text-center align-middle"
>
신청자
</th>
<th style={{ textAlign: "center" }}>이름</th>
<td>
<span className="text-[1.3rem] text-gray-500">(로그인 사용자 자동 세팅)</span>
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">
{userInfo?.userName ?? "\u00A0"}
</span>
</td>
<th>전화번호</th>
<th style={{ textAlign: "center" }}>전화번호</th>
<td>
<span className="text-[1.3rem] text-gray-500">(로그인 사용자 자동 세팅)</span>
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">
{userInfo?.mphn ?? userInfo?.telno ?? "\u00A0"}
</span>
</td>
</tr>
<tr>
<th>부서</th>
<th style={{ textAlign: "center" }}>부서</th>
<td>
<span className="text-[1.3rem] text-gray-500">(로그인 사용자 자동 세팅)</span>
<span className="text-[1.3rem] text-gray-500">
{userInfo?.instSeNm ?? "\u00A0"}
</span>
</td>
<th>이메일</th>
<th style={{ textAlign: "center" }}>이메일</th>
<td>
<span className="text-[1.3rem] text-gray-500">(로그인 사용자 자동 세팅)</span>
<span className="text-[1.3rem] text-gray-500">{userInfo?.email ?? "\u00A0"}</span>
</td>
</tr>
<tr>
<th>설계자 이름</th>
<th rowSpan={2} style={{ textAlign: "center" }}>
설계자
</th>
<th style={{ textAlign: "center" }}>이름</th>
<td>
<Input
placeholder="설계자 성명"
@@ -860,7 +906,7 @@ function CnfmRegistPage() {
onChange={(e) => setDsgnrNm(e.target.value)}
/>
</td>
<th>전화번호</th>
<th style={{ textAlign: "center" }}>전화번호</th>
<td>
<Input
placeholder="설계자 전화번호"
@@ -871,16 +917,17 @@ function CnfmRegistPage() {
</td>
</tr>
<tr>
<th>소속(용역사)</th>
<th style={{ textAlign: "center" }}>소속</th>
<td>
<Input
placeholder="용역사명"
placeholder="소속명"
className="!text-[1.3rem]"
value={dsgnrPsitn}
onChange={(e) => setDsgnrPsitn(e.target.value)}
/>
</td>
<th>이메일</th>
<th style={{ textAlign: "center" }}>이메일</th>
<td>
<Input
placeholder="설계자 이메일"
@@ -897,19 +944,42 @@ function CnfmRegistPage() {
{/* ── 개인정보 동의 ── */}
<div className="cnfm-card p-4">
<h3 className="mb-3 border-b pb-2 text-[1.5rem] font-bold text-gray-700">개인정보 동의</h3>
<div className="mb-3 rounded border bg-gray-50 p-3 text-[1.25rem] leading-relaxed text-gray-600">
<p className="mb-1 font-semibold">[개인정보 수집·이용 목적]</p>
<ul className="ml-4 list-disc space-y-1">
<li>수집항목: 소속기관, 성명, 전화번호, 이메일, IP Address, 접속 기기 정보</li>
<li>수집목적: 하수도 시설물 설치인가 신청 및 처리 목적</li>
<li>보유기간: 관련 법령에 따른 의무 보유 기간 준수</li>
</ul>
<p className="mt-2 text-[1.2rem] text-gray-500">
※ 개인정보 제공에 동의하지 않을 수 있으나, 미동의 시 서비스 이용이 제한될 수 있습니다.
</p>
<div className="mb-3 max-h-80 overflow-auto whitespace-pre-wrap rounded border bg-gray-50 p-3 pr-2 text-[1.25rem] leading-relaxed text-gray-600">
<p className="mb-1 font-semibold">[ 개인정보 수집항목 ]</p>
<div>
* 필수항목 : 소속기관, 소속부서, 담당자, 직급, 사무실 번호, FAX 번호
<br />
․ 선택항목 : 휴대폰 번호, 이메일 주소
<br />
․ 기타항목 : IP Address, 쿠키, 시스템 접속 및 이용 기록
<br />※ 시스템 이용과정에서 기타 항목의 정보들이 자동으로 생성되어 수집될 수 있습니다.
</div>
<p className="mt-4 font-semibold">[ 개인정보 이용목적 ]</p>
<div>
․ 국가하수도정보시스템 웹서비스 이용에 따른 본인 확인 절차에 이용
<br />
․ 국가하수도정보시스템 사용에 필요한 권한 부여
<br />․ 사용자별 맞춤 컨텐츠 (처리시설의 운영 데이터 조회, 수질현황 데이터 조회, 예산
편성 / 예산편성 등) 제공에 이용
</div>
<div className="tit mt-4">● 개인정보의 보유 및 이용기간</div>
<div>
․ 수집된 고객의 개인정보는 2년 동안 보유하며 그 이후에는 문서를 파쇄합니다.
<br />․ 정보제공자가 개인정보 파쇄를 요청할 경우 즉시 파쇄합니다.
</div>
<br />
<div className="tit">● 개인정보 제공 거부에 따른 제한사항</div>
<div>
․ 귀하는 개인정보 제공 동의를 거부할 권리가 있으나, 동의 거부 시 국가하수도정보시스템
이용 서비스에 제한이 있을 수 있습니다.
</div>
</div>
<div className="flex gap-6 text-[1.3rem]">
<label className="flex cursor-pointer items-center gap-2 font-semibold text-blue-600">
<label className="flex cursor-pointer items-center gap-2 font-semibold">
<input
type="radio"
name="privacyAgree"
@@ -1356,16 +1426,7 @@ function CnfmRegistPage() {
<button
type="button"
className="text-[1.2rem] text-red-500 hover:text-red-700"
onClick={() =>
setEtcFiles((prev) => {
const next = prev.filter((x) => x.id !== f.id);
if (next.length === 0) {
setEtcFileGroupId("");
etcFileGroupIdRef.current = "";
}
return next;
})
}
onClick={() => void handleDeleteEtcFile(f.id, f.atflId)}
>
✕
</button>
+17
View File
@@ -0,0 +1,17 @@
import { getCookie } from "@/shared/lib";
import { CLAIM_NAME, AUTH_USER_ID } from "@/shared/config/env";
import axios from "axios";
import { PROXY_HOST, ENDPOINT } from "@/shared/config";
/**
* Common helper to delete attachment using cookies for auth headers.
*/
export async function deleteAttachment(atflId: string): Promise<void> {
if (!atflId) return;
const headers: Record<string, string> = {
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
[AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "",
};
const url = `${PROXY_HOST}${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`;
await axios.delete(url, { headers });
}
+10
View File
@@ -33,6 +33,16 @@ export interface IUserInfo {
naverId: string;
isSocialUser: boolean;
hasPassword: boolean;
aprvYmd: string;
enoCd: string;
instSeCd: string;
dtlInstCd: string;
laeSeNm: string;
ogdpSeNm: string;
instSeNm: string;
userStateNm: string;
enoNm: string;
dtlInstNm: string;
}
export type UserState = {