From a4e73284ed7f3e94d2155f56256c78638d8a0b2e Mon Sep 17 00:00:00 2001 From: jangwoong Date: Tue, 19 May 2026 11:03:14 +0900 Subject: [PATCH] =?UTF-8?q?[as=20instl=20service]=20vscode=20=EB=94=94?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EB=AA=A8=EB=93=88=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EC=B2=98=EB=A6=AC=EB=B0=8F=20=EC=84=A4=EC=B9=98?= =?UTF-8?q?=EC=9D=B8=EA=B0=80=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 88 ++++ .vscode/settings.json | 5 + .../instl-sncn/[id]/modify/page.tsx | 10 + .../instl-sncn/[id]/page.tsx | 10 + .../(with-base-layout)/instl-sncn/page.tsx | 3 + .../instl-sncn/register/page.tsx | 3 + .../instl-sncn/api/instl-sncn-service.ts | 24 + apps/as/src/entities/instl-sncn/index.ts | 6 + .../instl-sncn/model/instl-sncn-interface.ts | 58 +++ apps/as/src/pages/instl-sncn/index.ts | 4 + .../pages/instl-sncn/ui/cnfm-list-page.tsx | 318 ++++++++++++ .../pages/instl-sncn/ui/cnfm-modify-page.tsx | 79 +++ apps/as/src/pages/instl-sncn/ui/cnfm-page.css | 490 ++++++++++++++++++ .../pages/instl-sncn/ui/cnfm-regist-page.tsx | 89 ++++ .../pages/instl-sncn/ui/cnfm-view-page.tsx | 131 +++++ 15 files changed, 1318 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 apps/as/app/(with-base-layout)/instl-sncn/[id]/modify/page.tsx create mode 100644 apps/as/app/(with-base-layout)/instl-sncn/[id]/page.tsx create mode 100644 apps/as/app/(with-base-layout)/instl-sncn/page.tsx create mode 100644 apps/as/app/(with-base-layout)/instl-sncn/register/page.tsx create mode 100644 apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts create mode 100644 apps/as/src/entities/instl-sncn/index.ts create mode 100644 apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts create mode 100644 apps/as/src/pages/instl-sncn/index.ts create mode 100644 apps/as/src/pages/instl-sncn/ui/cnfm-list-page.tsx create mode 100644 apps/as/src/pages/instl-sncn/ui/cnfm-modify-page.tsx create mode 100644 apps/as/src/pages/instl-sncn/ui/cnfm-page.css create mode 100644 apps/as/src/pages/instl-sncn/ui/cnfm-regist-page.tsx create mode 100644 apps/as/src/pages/instl-sncn/ui/cnfm-view-page.tsx diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..ea1cd8dc1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,88 @@ +{ + // IntelliSense를 사용하여 가능한 특성에 대해 알아보세요. + // 기존 특성에 대한 설명을 보려면 가리킵니다. + // 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요. + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "dev:as 디버그", + "runtimeExecutable": "pnpm", + "runtimeArgs": [ + "run", + "dev:as" + ], + "skipFiles": [ + "/**" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "sourceMaps": true, + "env": { + "NODE_OPTIONS": "--inspect" + } + }, + { + "type": "node", + "request": "launch", + "name": "dev:cs 디버그", + "runtimeExecutable": "pnpm", + "runtimeArgs": [ + "run", + "dev:cs" + ], + "skipFiles": [ + "/**" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "sourceMaps": true, + "env": { + "NODE_OPTIONS": "--inspect" + } + }, + { + "type": "node", + "request": "attach", + "name": "dev:cs 서버 연결 (attach)", + "port": 9229, + "restart": true, + "skipFiles": [ + "/**" + ], + "sourceMaps": true, + "resolveSourceMapLocations": [ + "${workspaceFolder}/apps/cs/**", + "!**/node_modules/**" + ] + }, + { + "type": "chrome", + "request": "launch", + "name": "dev:cs 클라이언트 디버그 (Chrome)", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}/apps/cs", + "sourceMaps": true + }, + { + "type": "node", + "request": "launch", + "name": "dev:st 디버그", + "runtimeExecutable": "pnpm", + "runtimeArgs": [ + "run", + "dev:st" + ], + "skipFiles": [ + "/**" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "sourceMaps": true, + "env": { + "NODE_OPTIONS": "--inspect" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..f60bf453a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "chat.tools.terminal.autoApprove": { + "pnpm": true + } +} \ No newline at end of file diff --git a/apps/as/app/(with-base-layout)/instl-sncn/[id]/modify/page.tsx b/apps/as/app/(with-base-layout)/instl-sncn/[id]/modify/page.tsx new file mode 100644 index 000000000..e4718eceb --- /dev/null +++ b/apps/as/app/(with-base-layout)/instl-sncn/[id]/modify/page.tsx @@ -0,0 +1,10 @@ +import { CnfmModifyPage } from "@/pages/instl-sncn"; + +export default async function InstlSncnModifyPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + return ; +} diff --git a/apps/as/app/(with-base-layout)/instl-sncn/[id]/page.tsx b/apps/as/app/(with-base-layout)/instl-sncn/[id]/page.tsx new file mode 100644 index 000000000..3f25c4a3e --- /dev/null +++ b/apps/as/app/(with-base-layout)/instl-sncn/[id]/page.tsx @@ -0,0 +1,10 @@ +import { CnfmViewPage } from "@/pages/instl-sncn"; + +export default async function InstlSncnViewPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + return ; +} diff --git a/apps/as/app/(with-base-layout)/instl-sncn/page.tsx b/apps/as/app/(with-base-layout)/instl-sncn/page.tsx new file mode 100644 index 000000000..7cd7bf82e --- /dev/null +++ b/apps/as/app/(with-base-layout)/instl-sncn/page.tsx @@ -0,0 +1,3 @@ +import { CnfmListPage } from "@/pages/instl-sncn"; + +export default CnfmListPage; diff --git a/apps/as/app/(with-base-layout)/instl-sncn/register/page.tsx b/apps/as/app/(with-base-layout)/instl-sncn/register/page.tsx new file mode 100644 index 000000000..f2e0cd185 --- /dev/null +++ b/apps/as/app/(with-base-layout)/instl-sncn/register/page.tsx @@ -0,0 +1,3 @@ +import { CnfmRegistPage } from "@/pages/instl-sncn"; + +export default CnfmRegistPage; diff --git a/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts b/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts new file mode 100644 index 000000000..29d15fcf0 --- /dev/null +++ b/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts @@ -0,0 +1,24 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + InstlSncnMng, + InstlSncnMngSearchParams, + InstlSncnMngService, + SpringPage, +} from "../model/instl-sncn-interface"; + +export class InstlSncnApiService implements InstlSncnMngService { + public async getPage(params: InstlSncnMngSearchParams): Promise> { + const response = await client.get>(ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN, { + params, + }); + return response.data; + } + + public async getById(id: string): Promise { + const response = await client.get(`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/${id}`); + return response.data; + } +} + +export const InstlSncnApi = new InstlSncnApiService(); diff --git a/apps/as/src/entities/instl-sncn/index.ts b/apps/as/src/entities/instl-sncn/index.ts new file mode 100644 index 000000000..8fdbb442f --- /dev/null +++ b/apps/as/src/entities/instl-sncn/index.ts @@ -0,0 +1,6 @@ +export { InstlSncnApi } from "./api/instl-sncn-service"; +export type { + InstlSncnMng, + InstlSncnMngSearchParams, + SpringPage, +} from "./model/instl-sncn-interface"; diff --git a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts new file mode 100644 index 000000000..00a51b76b --- /dev/null +++ b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts @@ -0,0 +1,58 @@ +export interface SpringPage { + content: T[]; + totalElements: number; + totalPages: number; + number: number; + size: number; + first: boolean; + last: boolean; + numberOfElements: number; +} + +export interface InstlSncnMngSearchParams { + bizNm?: string; + taskSeComCd?: string; + sidoComCd?: string; + sggComCd?: string; + page?: number; + size?: number; + sort?: string; +} + +export interface InstlSncnMng { + instlSncnDocNo: string; + dongId: string; + sidoNm?: string; + sggNm?: string; + chrgrNm?: string; + instNm?: string; + envoSeComCd: string; + envoSeCdNm: string; + taskSeComCd: string; + taskSeCdNm: string; + bgtBizCd: string; + bgtAplyYr: string; + bizNm: string; + bizBgngYr: string; + bizEndYr: string; + bizCn: string; + aplcntNm: string; + aplcntMblTelno: string; + aplcntOgdpNm: string; + aplcntEmlAddr: string; + dsgnrNm: string; + dsgnrTelno: string; + dsgnrOgdpNm: string; + dsgnrEmlAddr: string; + atchFileGroupId: string; + prgrsSttsComCd: string; + prgrsSttsCdNm: string; + regDt: string; + mdfcnDt: string; + rgtrNm: string; +} + +export interface InstlSncnMngService { + getPage: (params: InstlSncnMngSearchParams) => Promise>; + getById: (id: string) => Promise; +} diff --git a/apps/as/src/pages/instl-sncn/index.ts b/apps/as/src/pages/instl-sncn/index.ts new file mode 100644 index 000000000..570133ba4 --- /dev/null +++ b/apps/as/src/pages/instl-sncn/index.ts @@ -0,0 +1,4 @@ +export { CnfmListPage } from "./ui/cnfm-list-page"; +export { CnfmViewPage } from "./ui/cnfm-view-page"; +export { CnfmRegistPage } from "./ui/cnfm-regist-page"; +export { CnfmModifyPage } from "./ui/cnfm-modify-page"; diff --git a/apps/as/src/pages/instl-sncn/ui/cnfm-list-page.tsx b/apps/as/src/pages/instl-sncn/ui/cnfm-list-page.tsx new file mode 100644 index 000000000..8b0bb9f38 --- /dev/null +++ b/apps/as/src/pages/instl-sncn/ui/cnfm-list-page.tsx @@ -0,0 +1,318 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { useRouter } from "next/navigation"; + +import { useQuery } from "@tanstack/react-query"; + +import { dayjs } from "@/shared/lib"; +import { InstlSncnApi, type InstlSncnMngSearchParams } from "@/entities/instl-sncn"; + +import "./cnfm-page.css"; + +const TASK_OPTIONS = [ + { value: "", label: "전체" }, + { value: "SF", label: "하수처리시설" }, + { value: "SP", label: "하수관로" }, + { value: "RE", label: "재이용수" }, +]; + +const SIDO_OPTIONS = [ + { value: "", label: "전체" }, + { value: "11", label: "서울특별시" }, + { value: "26", label: "부산광역시" }, + { value: "27", label: "대구광역시" }, + { value: "28", label: "인천광역시" }, + { value: "29", label: "광주광역시" }, + { value: "30", label: "대전광역시" }, + { value: "31", label: "울산광역시" }, + { value: "36", label: "세종특별자치시" }, + { value: "41", label: "경기도" }, + { value: "43", label: "충청북도" }, + { value: "44", label: "충청남도" }, + { value: "45", label: "전라북도" }, + { value: "46", label: "전라남도" }, + { value: "47", label: "경상북도" }, + { value: "48", label: "경상남도" }, + { value: "50", label: "제주특별자치도" }, +]; + +type StepCode = "SV" | "ST" | "ER" | "TE" | "TR" | "TP" | "TA"; + +const STEPS: { code: StepCode; label: string }[] = [ + { code: "SV", label: "저장" }, + { code: "ST", label: "신청완료" }, + { code: "ER", label: "환경청 검토" }, + { code: "TE", label: "기술검토" }, + { code: "TR", label: "검토결과 회신" }, + { code: "TP", label: "기술평가" }, + { code: "TA", label: "최종승인" }, +]; + +const PAGE_GROUP_SIZE = 5; + +function CnfmListPage() { + const router = useRouter(); + const [bizNm, setBizNm] = useState(""); + const [taskSeComCd, setTaskSeComCd] = useState(""); + const [sidoComCd, setSidoComCd] = useState(""); + const [sggComCd, setSggComCd] = useState(""); + const [search, setSearch] = useState({ + bizNm: "", + taskSeComCd: "", + sidoComCd: "", + sggComCd: "", + }); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + + const query = useQuery({ + queryKey: ["instl-sncn-list", search, pageIndex, pageSize], + queryFn: () => + InstlSncnApi.getPage({ + ...search, + page: pageIndex, + size: pageSize, + sort: "regDt,desc", + }), + }); + + const totalPages = query.data?.totalPages ?? 0; + + const pageGroupStart = Math.floor(pageIndex / PAGE_GROUP_SIZE) * PAGE_GROUP_SIZE; + const pageNumbers = useMemo(() => { + const end = Math.min(pageGroupStart + PAGE_GROUP_SIZE, totalPages); + return Array.from({ length: end - pageGroupStart }, (_, i) => pageGroupStart + i); + }, [pageGroupStart, totalPages]); + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setPageIndex(0); + setSearch({ bizNm, taskSeComCd, sidoComCd, sggComCd }); + }; + + return ( +
+ {/* 검색 영역 */} +
+
+
+ + +
+ +
+ + +
+ +
+ + setBizNm(e.target.value)} + placeholder="사업명을 입력하세요" + /> +
+ +
+ + +
+ + +
+
+ + {/* 목록 영역 */} +
+
+

설치인가 목록

+
+ + +
+
+ +
+ + + + + + + + + + + + + + + {query.isLoading && ( + + + + )} + {!query.isLoading && (query.data?.content?.length ?? 0) === 0 && ( + + + + )} + {(query.data?.content ?? []).map((row) => { + const currentStepIndex = STEPS.findIndex((s) => s.code === row.prgrsSttsComCd); + return ( + router.push(`/instl-sncn/${row.instlSncnDocNo}`)} + > + + + + + + + + + + ); + })} + +
시도시군구사업명구분담당자해당기관진행상황시행일
조회 중입니다...
조회 결과가 없습니다.
{row.sidoNm || "-"}{row.sggNm || "-"}{row.bizNm || "-"}{row.taskSeCdNm || row.taskSeComCd || "-"}{row.chrgrNm || row.aplcntNm || "-"}{row.instNm || row.aplcntOgdpNm || "-"} +
+
+ {STEPS.slice(0, 4).map((step, index) => { + const state = + index < currentStepIndex + ? "completed" + : index === currentStepIndex + ? "current" + : "pending"; + return ( + + + {step.label} + + ); + })} +
+
+ {STEPS.slice(4).map((step, i) => { + const index = i + 4; + const state = + index < currentStepIndex + ? "completed" + : index === currentStepIndex + ? "current" + : "pending"; + return ( + + + {step.label} + + ); + })} +
+
+
{row.regDt ? dayjs(row.regDt).format("YYYY-MM-DD") : "-"}
+
+ + {/* 페이지네이션 */} +
+ + + + {pageNumbers.map((pageNo) => ( + + ))} + + + +
+
+
+ ); +} + +export { CnfmListPage }; diff --git a/apps/as/src/pages/instl-sncn/ui/cnfm-modify-page.tsx b/apps/as/src/pages/instl-sncn/ui/cnfm-modify-page.tsx new file mode 100644 index 000000000..854d2515d --- /dev/null +++ b/apps/as/src/pages/instl-sncn/ui/cnfm-modify-page.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { Button, Input } from "@common/components"; + +import { InstlSncnApi } from "@/entities/instl-sncn"; + +import "./cnfm-page.css"; + +function CnfmModifyPage({ id }: { id: string }) { + const router = useRouter(); + const query = useQuery({ + queryKey: ["instl-sncn-modify", id], + queryFn: () => InstlSncnApi.getById(id), + }); + + if (query.isLoading) { + return
수정 정보를 불러오는 중입니다...
; + } + + if (!query.data) { + return
수정 대상을 찾을 수 없습니다.
; + } + + const data = query.data; + + return ( +
+
+

설치인가 수정

+

+ 상세 조회 API 데이터를 기반으로 수정 화면을 구성했습니다. +

+
+ +
+

기본 정보

+ + + + + + + + + + + + + + + + + + + + + +
문서번호{data.instlSncnDocNo}진행상태{data.prgrsSttsCdNm || data.prgrsSttsComCd || "-"}
사업명 + + 사업내용 + +
설계자명 + + 설계자 연락처 + +
+
+ +
+ + +
+
+ ); +} + +export { CnfmModifyPage }; diff --git a/apps/as/src/pages/instl-sncn/ui/cnfm-page.css b/apps/as/src/pages/instl-sncn/ui/cnfm-page.css new file mode 100644 index 000000000..49d7a6bce --- /dev/null +++ b/apps/as/src/pages/instl-sncn/ui/cnfm-page.css @@ -0,0 +1,490 @@ +/* ============================ + 페이지 레이아웃 +============================= */ +.cnfm-page-wrap { + padding: 2rem 2.4rem; +} + +/* ============================ + 카드 +============================= */ +.cnfm-card { + border: 1px solid #e2e8f0; + border-radius: 10px; + background: #ffffff; + padding: 1.6rem 2rem; +} + +.cnfm-card.mt-4 { + margin-top: 1.6rem; +} + +/* ============================ + 검색 바 +============================= */ +.cnfm-search-bar { + display: flex; + align-items: center; + gap: 1.2rem; + flex-wrap: wrap; +} + +.cnfm-search-field { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.cnfm-search-field--grow { + flex: 1; + min-width: 18rem; +} + +.cnfm-search-label { + font-size: 1.3rem; + font-weight: 600; + color: #374151; + white-space: nowrap; +} + +.cnfm-input { + height: 3.6rem; + border: 1px solid #d1d5db; + border-radius: 6px; + padding: 0 1rem; + font-size: 1.3rem; + color: #111827; + width: 100%; + outline: none; + background: #fff; +} + +.cnfm-input:focus { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15); +} + +.cnfm-select { + height: 3.6rem; + border: 1px solid #d1d5db; + border-radius: 6px; + padding: 0 0.8rem; + font-size: 1.3rem; + color: #111827; + background: #fff; + min-width: 11rem; + cursor: pointer; + outline: none; +} + +.cnfm-select--wide { + min-width: 16rem; +} + +.cnfm-select:focus { + border-color: #3b82f6; +} + +.cnfm-btn-search { + height: 3.6rem; + padding: 0 2rem; + background: #3b82f6; + color: #fff; + font-size: 1.3rem; + font-weight: 600; + border: none; + border-radius: 6px; + cursor: pointer; + white-space: nowrap; +} + +.cnfm-btn-search:hover { + background: #2563eb; +} + +/* ============================ + 목록 헤더 +============================= */ +.cnfm-list-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.2rem; +} + +.cnfm-list-title { + font-size: 1.5rem; + font-weight: 700; + color: #1f2937; +} + +.cnfm-list-actions { + display: flex; + align-items: center; + gap: 0.8rem; +} + +.cnfm-btn-register { + height: 3.4rem; + padding: 0 1.6rem; + background: #22c55e; + color: #fff; + font-size: 1.3rem; + font-weight: 600; + border: none; + border-radius: 6px; + cursor: pointer; + white-space: nowrap; +} + +.cnfm-btn-register:hover { + background: #16a34a; +} + +/* ============================ + 테이블 +============================= */ +.cnfm-table-wrap { + overflow-x: auto; +} + +.cnfm-table { + width: 100%; + border-collapse: collapse; + font-size: 1.3rem; +} + +.cnfm-table th, +.cnfm-table td { + border-bottom: 1px solid #e5e7eb; + padding: 1.2rem 0.8rem; + text-align: center; + vertical-align: middle; +} + +.cnfm-table thead th { + background: #f8fafc; + color: #374151; + font-weight: 600; + font-size: 1.25rem; + border-bottom: 2px solid #e2e8f0; +} + +.cnfm-table tbody td { + color: #111827; +} + +.cnfm-td-empty { + padding: 3rem 0; + color: #9ca3af; + font-size: 1.3rem; +} + +.cnfm-row-clickable { + cursor: pointer; +} + +.cnfm-row-clickable:hover { + background: #f8fafc; +} + +/* ============================ + 진행상황 스텝 배지 +============================= */ +.cnfm-step-wrap { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: flex-start; +} + +.cnfm-step-row { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: nowrap; +} + +.cnfm-step-badge { + display: inline-flex; + align-items: center; + gap: 0.4rem; + border-radius: 9999px; + padding: 0.3rem 0.9rem; + font-size: 1.15rem; + font-weight: 500; + white-space: nowrap; + border: 1px solid transparent; +} + +.cnfm-step-badge--completed { + background: #dbeafe; + border-color: #93c5fd; + color: #1d4ed8; +} + +.cnfm-step-badge--current { + background: #fed7aa; + border-color: #fb923c; + color: #c2410c; + font-weight: 700; +} + +.cnfm-step-badge--pending { + background: #f3f4f6; + border-color: #d1d5db; + color: #9ca3af; +} + +.cnfm-step-dot { + display: inline-block; + width: 0.7rem; + height: 0.7rem; + border-radius: 50%; + flex-shrink: 0; +} + +.cnfm-step-dot--completed { + background: #3b82f6; +} + +.cnfm-step-dot--current { + background: #f97316; +} + +.cnfm-step-dot--pending { + background: #d1d5db; +} + +/* ============================ + 페이지네이션 +============================= */ +.cnfm-pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 0.4rem; + margin-top: 1.6rem; + padding-bottom: 0.4rem; +} + +.cnfm-page-btn { + min-width: 3.2rem; + height: 3.2rem; + border: 1px solid #e2e8f0; + border-radius: 6px; + background: #fff; + font-size: 1.3rem; + color: #374151; + cursor: pointer; + transition: all 0.15s; + padding: 0 0.6rem; +} + +.cnfm-page-btn:hover:not(:disabled) { + border-color: #3b82f6; + color: #3b82f6; +} + +.cnfm-page-btn.active { + border-color: #3b82f6; + background: #3b82f6; + color: #fff; + font-weight: 700; +} + +.cnfm-page-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +/* ============================ + 폼 테이블 (상세/등록 페이지용) +============================= */ +.cnfm-form-table { + width: 100%; + border-collapse: collapse; + font-size: 1.3rem; +} + +.cnfm-form-table th, +.cnfm-form-table td { + border: 1px solid #e5e7eb; + padding: 0.9rem 1rem; +} + +.cnfm-form-table th { + width: 16rem; + background: #f9fafb; + text-align: left; + font-weight: 700; +} + +.cnfm-form-table td { + background: #fff; +} + +.cnfm-action-row { + display: flex; + justify-content: center; + gap: 1rem; + margin-top: 1.8rem; +} + +/* ============================ + 기존 호환 클래스 유지 +============================= */ +.cnfm-section-title { + font-size: 1.8rem; + font-weight: 700; + color: #1f2937; +} + +@media (max-width: 1100px) { + .cnfm-search-bar { + flex-direction: column; + align-items: stretch; + } + .cnfm-search-field { + flex-direction: column; + align-items: flex-start; + } + .cnfm-search-field--grow { + width: 100%; + } +} + + +.cnfm-section-title { + font-size: 1.8rem; + font-weight: 700; + color: #1f2937; +} + +.cnfm-search-grid { + display: grid; + grid-template-columns: 8rem 1fr 6rem 16rem auto auto; + gap: 1.2rem; + align-items: center; +} + +.cnfm-table-wrap { + overflow-x: auto; +} + +.cnfm-table { + width: 100%; + border-collapse: collapse; + font-size: 1.3rem; +} + +.cnfm-table th, +.cnfm-table td { + border: 1px solid #e5e7eb; + padding: 1rem 0.8rem; + text-align: center; + vertical-align: middle; +} + +.cnfm-table thead th { + background: #f3f4f6; + color: #111827; + font-weight: 700; +} + +.cnfm-row-clickable { + cursor: pointer; +} + +.cnfm-row-clickable:hover { + background: #f9fafb; +} + +.cnfm-step { + display: inline-flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; + justify-content: center; +} + +.cnfm-step-badge { + border-radius: 9999px; + border: 1px solid #d1d5db; + background: #f9fafb; + color: #6b7280; + font-size: 1.1rem; + padding: 0.25rem 0.65rem; +} + +.cnfm-step-badge.completed { + border-color: #93c5fd; + background: #dbeafe; + color: #1d4ed8; +} + +.cnfm-step-badge.current { + border-color: #16a34a; + background: #dcfce7; + color: #166534; + font-weight: 700; +} + +.cnfm-pagination { + display: flex; + justify-content: center; + gap: 0.6rem; + margin-top: 1.2rem; +} + +.cnfm-pagination button { + min-width: 3.2rem; + height: 3.2rem; + border: 1px solid #d1d5db; + border-radius: 6px; + background: #fff; + font-size: 1.3rem; +} + +.cnfm-pagination button.active { + border-color: #2563eb; + background: #2563eb; + color: #fff; + font-weight: 700; +} + +.cnfm-form-table { + width: 100%; + border-collapse: collapse; + font-size: 1.3rem; +} + +.cnfm-form-table th, +.cnfm-form-table td { + border: 1px solid #e5e7eb; + padding: 0.9rem 1rem; +} + +.cnfm-form-table th { + width: 16rem; + background: #f9fafb; + text-align: left; + font-weight: 700; +} + +.cnfm-form-table td { + background: #fff; +} + +.cnfm-action-row { + display: flex; + justify-content: center; + gap: 1rem; + margin-top: 1.8rem; +} + +@media (max-width: 1100px) { + .cnfm-search-grid { + grid-template-columns: 1fr; + } +} diff --git a/apps/as/src/pages/instl-sncn/ui/cnfm-regist-page.tsx b/apps/as/src/pages/instl-sncn/ui/cnfm-regist-page.tsx new file mode 100644 index 000000000..0b2af414a --- /dev/null +++ b/apps/as/src/pages/instl-sncn/ui/cnfm-regist-page.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Button, Input } from "@common/components"; + +import "./cnfm-page.css"; + +function CnfmRegistPage() { + const router = useRouter(); + + return ( +
+
+

설치인가 신청

+

+ AS-IS 화면 설계(신청 화면)를 반영한 입력 레이아웃입니다. +

+
+ +
+

기본 정보

+ + + + + + + + + + + + + + + +
사업명 + + 사업구분 + +
사업기간(시작) + + 사업기간(종료) + +
+
+ +
+

신청자 / 설계자 정보

+ + + + + + + + + + + + + + + +
신청자명 + + 신청자 연락처 + +
설계자명 + + 설계자 소속 + +
+
+ +
+ + + +
+
+ ); +} + +export { CnfmRegistPage }; diff --git a/apps/as/src/pages/instl-sncn/ui/cnfm-view-page.tsx b/apps/as/src/pages/instl-sncn/ui/cnfm-view-page.tsx new file mode 100644 index 000000000..6593ee011 --- /dev/null +++ b/apps/as/src/pages/instl-sncn/ui/cnfm-view-page.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { Button } from "@common/components"; + +import { InstlSncnApi } from "@/entities/instl-sncn"; +import { dayjs } from "@/shared/lib"; + +import "./cnfm-page.css"; + +function CnfmViewPage({ id }: { id: string }) { + const router = useRouter(); + + const query = useQuery({ + queryKey: ["instl-sncn-detail", id], + queryFn: () => InstlSncnApi.getById(id), + }); + + if (query.isLoading) { + return
상세 정보를 불러오는 중입니다...
; + } + + if (!query.data) { + return
상세 정보를 찾을 수 없습니다.
; + } + + const data = query.data; + + return ( +
+
+

설치인가 상세보기

+

+ 문서번호: {data.instlSncnDocNo} +

+
+ +
+

기본 정보

+ + + + + + + + + + + + + + + + + + + + + + + + + +
사업명{data.bizNm || "-"}업무구분{data.taskSeCdNm || data.taskSeComCd || "-"}
사업기간 + {(data.bizBgngYr || "-") + " ~ " + (data.bizEndYr || "-")} + 진행상태{data.prgrsSttsCdNm || data.prgrsSttsComCd || "-"}
행정동ID{data.dongId || "-"}예산사업코드{data.bgtBizCd || "-"}
사업내용{data.bizCn || "-"}
+
+ +
+

담당자 정보

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
신청자명{data.aplcntNm || "-"}신청자 연락처{data.aplcntMblTelno || "-"}
신청자 소속{data.aplcntOgdpNm || "-"}신청자 이메일{data.aplcntEmlAddr || "-"}
설계자명{data.dsgnrNm || "-"}설계자 연락처{data.dsgnrTelno || "-"}
설계자 소속{data.dsgnrOgdpNm || "-"}설계자 이메일{data.dsgnrEmlAddr || "-"}
+
+ +
+

이력 정보

+ + + + + + + + + + + + + + + +
등록일시{data.regDt ? dayjs(data.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}수정일시{data.mdfcnDt ? dayjs(data.mdfcnDt).format("YYYY-MM-DD HH:mm:ss") : "-"}
등록자명{data.rgtrNm || "-"}첨부파일그룹ID{data.atchFileGroupId || "-"}
+
+ +
+ + +
+
+ ); +} + +export { CnfmViewPage };