[as] feat: 행정지원 문서 상세페이지 게이트 구현

This commit is contained in:
2026-08-13 17:11:13 +09:00
parent 9bcc8b95e7
commit 335872eaec
22 changed files with 257 additions and 87 deletions
@@ -1,4 +1,4 @@
import { DesignViewPage } from "@/pages/design-cnstn";
import { DesignDetailGate } from "@/pages/design-cnstn";
interface PageProps {
params: Promise<{ id: string }>;
@@ -6,5 +6,5 @@ interface PageProps {
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <DesignViewPage id={id} />;
return <DesignDetailGate id={id} />;
}
@@ -1,4 +1,4 @@
import { FnrsViewPage } from "@/pages/fnrs-cnsltn";
import { FnrsDetailGate } from "@/pages/fnrs-cnsltn";
interface PageProps {
params: Promise<{ id: string }>;
@@ -6,5 +6,5 @@ interface PageProps {
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <FnrsViewPage id={id} />;
return <FnrsDetailGate id={id} />;
}
@@ -1,6 +1,6 @@
import { InstlViewPage } from "@/pages/instl-sncn";
import { InstlSncnDetailGate } from "@/pages/instl-sncn";
export default async function InstlSncnDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <InstlViewPage id={id} />;
return <InstlSncnDetailGate id={id} />;
}
@@ -1,4 +1,4 @@
import { SewerBscPlanViewPage } from "@/pages/sewer-bsc-plan";
import { SewerBscPlanDetailGate } from "@/pages/sewer-bsc-plan";
interface PageProps {
params: Promise<{ id: string }>;
@@ -9,5 +9,5 @@ export default async function Page({ params, searchParams }: PageProps) {
const { id } = await params;
const { chgCycl } = await searchParams;
const propChgCycl = chgCycl ? Number(chgCycl) : undefined;
return <SewerBscPlanViewPage id={id} propChgCycl={propChgCycl} />;
return <SewerBscPlanDetailGate id={id} propChgCycl={propChgCycl} />;
}
+1
View File
@@ -1,3 +1,4 @@
export { DesignDetailGate } from "./ui/design-detail-gate";
export { DesignEditPage } from "./ui/design-edit-page";
export { DesignFormPage } from "./ui/design-form-page";
export { DesignListPage } from "./ui/design-list-page";
@@ -0,0 +1,35 @@
"use client";
import { useUserStore } from "@/shared/lib";
import { useDesignCnstnMng } from "@/entities/design-cnstn";
import { canEditPbadmsDoc, PbadmsDetailGate } from "@/widgets/pbadms-sprt";
import { DesignViewPage } from "./design-view-page";
interface DesignDetailGateProps {
id: string;
}
function DesignDetailGate({ id }: DesignDetailGateProps) {
const userInfo = useUserStore((state) => state.userInfo);
const { data, isLoading, isError } = useDesignCnstnMng(id);
const shouldRedirectToEdit = canEditPbadmsDoc({
docSeComCd: "CNFM",
prgrsSttsComCd: data?.detDto?.prgrsSttsComCd,
rgtr: data?.detDto?.rgtr,
loggedInUserUnqId: userInfo?.userUnqId,
});
return (
<PbadmsDetailGate
isPending={!userInfo || isLoading}
isError={isError}
shouldRedirectToEdit={Boolean(userInfo) && shouldRedirectToEdit}
editHref={`/design-cnstn/${id}/edit`}
>
<DesignViewPage id={id} />
</PbadmsDetailGate>
);
}
export { DesignDetailGate };
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useDataTable, useUserStore } from "@/shared/lib";
import { useDataTable } from "@/shared/lib";
import {
type DesignCnstnMngResponseDto,
type DesignCnstnMngSearchDto,
@@ -24,7 +24,6 @@ interface DesignListProps {
}
function DesignList({ searchParams, pagination, onPaginationChange }: DesignListProps) {
const userInfo = useUserStore((state) => state.userInfo);
const router = useRouter();
const [isTaskSeDialogOpen, setIsTaskSeDialogOpen] = useState(false);
@@ -44,13 +43,7 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
const columns = useMemo(() => createDesignCnstnColumns(), []);
const handleRowClick = (row: DesignCnstnMngResponseDto) => {
const canEdit = row.prgrsSttsComCd === "CV" && row.rgtr === userInfo?.userUnqId;
router.push(
canEdit
? `/design-cnstn/${row.designCnstnDocNo}/edit`
: `/design-cnstn/${row.designCnstnDocNo}`,
);
router.push(`/design-cnstn/${row.designCnstnDocNo}`);
};
const { tableComponent } = useDataTable({
+1
View File
@@ -1,3 +1,4 @@
export { FnrsDetailGate } from "./ui/fnrs-detail-gate";
export { FnrsEditPage } from "./ui/fnrs-edit-page";
export { FnrsFormPage } from "./ui/fnrs-form-page";
export { FnrsListPage } from "./ui/fnrs-list-page";
@@ -0,0 +1,35 @@
"use client";
import { useUserStore } from "@/shared/lib";
import { useFnrsCnsltnMng } from "@/entities/fnrs-cnsltn";
import { canEditPbadmsDoc, PbadmsDetailGate } from "@/widgets/pbadms-sprt";
import { FnrsViewPage } from "./fnrs-view-page";
interface FnrsDetailGateProps {
id: string;
}
function FnrsDetailGate({ id }: FnrsDetailGateProps) {
const userInfo = useUserStore((state) => state.userInfo);
const { data, isLoading, isError } = useFnrsCnsltnMng(id);
const shouldRedirectToEdit = canEditPbadmsDoc({
docSeComCd: "FNRS",
prgrsSttsComCd: data?.detDto?.prgrsSttsComCd,
rgtr: data?.detDto?.rgtr,
loggedInUserUnqId: userInfo?.userUnqId,
});
return (
<PbadmsDetailGate
isPending={!userInfo || isLoading}
isError={isError}
shouldRedirectToEdit={Boolean(userInfo) && shouldRedirectToEdit}
editHref={`/fnrs-cnsltn/${id}/edit`}
>
<FnrsViewPage id={id} />
</PbadmsDetailGate>
);
}
export { FnrsDetailGate };
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useDataTable, useUserStore } from "@/shared/lib";
import { useDataTable } from "@/shared/lib";
import {
FnrsCnsltnMngResponseDto,
type FnrsCnsltnMngSearchDto,
@@ -24,7 +24,6 @@ interface FnrsListProps {
}
function FnrsList({ searchParams, pagination, onPaginationChange }: FnrsListProps) {
const userInfo = useUserStore((state) => state.userInfo);
const router = useRouter();
const [isTaskSeDialogOpen, setIsTaskSeDialogOpen] = useState(false);
@@ -44,13 +43,7 @@ function FnrsList({ searchParams, pagination, onPaginationChange }: FnrsListProp
const columns = useMemo(() => createFnrsCnsltnColumns(), []);
const handleRowClick = (row: FnrsCnsltnMngResponseDto) => {
const canEdit =
(row.prgrsSttsComCd === "SV" || row.prgrsSttsComCd === "SL") &&
row.rgtr === userInfo?.userUnqId;
router.push(
canEdit ? `/fnrs-cnsltn/${row.fnrsCnsltnDocNo}/edit` : `/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`,
);
router.push(`/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`);
};
const { tableComponent } = useDataTable({
+1
View File
@@ -1,3 +1,4 @@
export { InstlSncnDetailGate } from "./ui/instl-detail-gate";
export { InstlEditPage } from "./ui/instl-edit-page";
export { InstlFormPage } from "./ui/instl-form-page";
export { InstlListPage } from "./ui/instl-list-page";
@@ -0,0 +1,35 @@
"use client";
import { useUserStore } from "@/shared/lib";
import { useInstlSncnDetail } from "@/entities/instl-sncn";
import { canEditPbadmsDoc, PbadmsDetailGate } from "@/widgets/pbadms-sprt";
import { InstlViewPage } from "./instl-view-page";
interface InstlSncnDetailGateProps {
id: string;
}
function InstlSncnDetailGate({ id }: InstlSncnDetailGateProps) {
const userInfo = useUserStore((state) => state.userInfo);
const { data, isLoading, isError } = useInstlSncnDetail(id);
const shouldRedirectToEdit = canEditPbadmsDoc({
docSeComCd: "INSTL",
prgrsSttsComCd: data?.detDto.prgrsSttsComCd,
rgtr: data?.detDto.rgtr,
loggedInUserUnqId: userInfo?.userUnqId,
});
return (
<PbadmsDetailGate
isPending={!userInfo || isLoading}
isError={isError}
shouldRedirectToEdit={Boolean(userInfo) && shouldRedirectToEdit}
editHref={`/instl-sncn/${id}/edit`}
>
<InstlViewPage id={id} />
</PbadmsDetailGate>
);
}
export { InstlSncnDetailGate };
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useDataTable, useUserStore } from "@/shared/lib";
import { useDataTable } from "@/shared/lib";
import {
type InstlSncnMngResponseDto,
type InstlSncnMngSearchParams,
@@ -24,7 +24,6 @@ interface InstlListProps {
}
function InstlList({ searchParams, pagination, onPaginationChange }: InstlListProps) {
const userInfo = useUserStore((state) => state.userInfo);
const router = useRouter();
const [isTaskSeDialogOpen, setIsTaskSeDialogOpen] = useState(false);
@@ -44,13 +43,7 @@ function InstlList({ searchParams, pagination, onPaginationChange }: InstlListPr
const columns = useMemo(() => createInstlSncnColumns(), []);
const handleRowClick = (row: InstlSncnMngResponseDto) => {
const canEdit =
(row.prgrsSttsComCd === "SV" || row.prgrsSttsComCd === "SL") &&
row.rgtr === userInfo?.userUnqId;
router.push(
canEdit ? `/instl-sncn/${row.instlSncnDocNo}/edit` : `/instl-sncn/${row.instlSncnDocNo}`,
);
router.push(`/instl-sncn/${row.instlSncnDocNo}`);
};
const { tableComponent } = useDataTable({
@@ -1,42 +1,19 @@
import type { MyAdssDocResponseDto } from "@/entities/elapr";
function canEditByDocSe(
docSeComCd: string,
prgrsSttsComCd: string | undefined,
rgtr: string | undefined,
loggedInUserUnqId: string | undefined,
): boolean {
if (!loggedInUserUnqId || !rgtr || rgtr !== loggedInUserUnqId) return false;
if (docSeComCd === "CNFM") return prgrsSttsComCd === "CV";
if (docSeComCd === "INSTL" || docSeComCd === "FNRS" || docSeComCd === "SEWER")
return prgrsSttsComCd === "SV" || prgrsSttsComCd === "SL";
return false;
}
function resolveMyPbadmsSprtDetailPath(
row: MyAdssDocResponseDto,
loggedInUserUnqId: string | undefined,
): string | null {
const { docNo, docSeComCd, prgrsSttsComCd, rgtr, chgCycl } = row;
function resolveMyPbadmsSprtDetailPath(row: MyAdssDocResponseDto): string | null {
const { docNo, docSeComCd, chgCycl } = row;
if (!docNo || !docSeComCd) return null;
const canEdit = canEditByDocSe(docSeComCd, prgrsSttsComCd, rgtr, loggedInUserUnqId);
switch (docSeComCd) {
case "FNRS":
return canEdit ? `/fnrs-cnsltn/${docNo}/edit` : `/fnrs-cnsltn/${docNo}`;
return `/fnrs-cnsltn/${docNo}`;
case "INSTL":
return canEdit ? `/instl-sncn/${docNo}/edit` : `/instl-sncn/${docNo}`;
return `/instl-sncn/${docNo}`;
case "CNFM":
return canEdit ? `/design-cnstn/${docNo}/edit` : `/design-cnstn/${docNo}`;
return `/design-cnstn/${docNo}`;
case "SEWER": {
const cycl = chgCycl ?? 1;
return canEdit
? `/sewer-bsc-plan/${docNo}/edit?chgCycl=${cycl}`
: `/sewer-bsc-plan/${docNo}?chgCycl=${cycl}`;
return `/sewer-bsc-plan/${docNo}?chgCycl=${cycl}`;
}
default:
return null;
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useDataTable, useUserStore } from "@/shared/lib";
import { useDataTable } from "@/shared/lib";
import {
type ElaprMyDocSearchDto,
type MyAdssDocResponseDto,
@@ -28,7 +28,6 @@ function toOptionalParam(value: string | undefined) {
function MyPbadmsSprtList({ searchParams, pagination, onPaginationChange }: MyPbadmsSprtListProps) {
const router = useRouter();
const userInfo = useUserStore((state) => state.userInfo);
const queryParams = useMemo(
(): ElaprMyDocSearchDto => ({
@@ -54,7 +53,7 @@ function MyPbadmsSprtList({ searchParams, pagination, onPaginationChange }: MyPb
);
const handleRowClick = (row: MyAdssDocResponseDto) => {
const path = resolveMyPbadmsSprtDetailPath(row, userInfo?.userUnqId);
const path = resolveMyPbadmsSprtDetailPath(row);
if (path) router.push(path);
};
@@ -26,14 +26,6 @@ function toOptionalParam(value: string | undefined) {
return trimmed ? trimmed : undefined;
}
function navigateNotiUrl(url: string, push: (href: string) => void) {
if (/^https?:\/\//i.test(url)) {
window.location.href = url;
return;
}
push(url.startsWith("/") ? url : `/${url}`);
}
function NtiHstryList({ searchParams, pagination, onPaginationChange }: NtiHstryListProps) {
const router = useRouter();
const { mutateAsync: readNoti, isPending: isReadPending } = useReadNoti();
@@ -57,7 +49,7 @@ function NtiHstryList({ searchParams, pagination, onPaginationChange }: NtiHstry
await readNoti(row.notiId);
const notiUrl = row.notiUrl?.trim();
if (!notiUrl) return;
navigateNotiUrl(notiUrl, (href) => router.push(href));
router.push(notiUrl);
},
[readNoti, router],
);
@@ -1,3 +1,4 @@
export { SewerBscPlanDetailGate } from "./ui/sewer-bsc-plan-detail-gate";
export { SewerBscPlanEditPage } from "./ui/sewer-bsc-plan-edit-page";
export { SewerBscPlanFormPage } from "./ui/sewer-bsc-plan-form-page";
export { SewerBscPlanListPage } from "./ui/sewer-bsc-plan-list-page";
@@ -0,0 +1,37 @@
"use client";
import { useUserStore } from "@/shared/lib";
import { useSewerBscPlan } from "@/entities/sewer-bsc-plan";
import { canEditPbadmsDoc, PbadmsDetailGate } from "@/widgets/pbadms-sprt";
import { SewerBscPlanViewPage } from "./sewer-bsc-plan-view-page";
interface SewerBscPlanDetailGateProps {
id: string;
propChgCycl?: number;
}
function SewerBscPlanDetailGate({ id, propChgCycl }: SewerBscPlanDetailGateProps) {
const userInfo = useUserStore((state) => state.userInfo);
const chgCycl = propChgCycl ?? 1;
const { data, isLoading, isError } = useSewerBscPlan(id);
const shouldRedirectToEdit = canEditPbadmsDoc({
docSeComCd: "SEWER",
prgrsSttsComCd: data?.prgrsSttsComCd,
rgtr: data?.rgtr,
loggedInUserUnqId: userInfo?.userUnqId,
});
return (
<PbadmsDetailGate
isPending={!userInfo || isLoading}
isError={isError}
shouldRedirectToEdit={Boolean(userInfo) && shouldRedirectToEdit}
editHref={`/sewer-bsc-plan/${id}/edit?chgCycl=${chgCycl}`}
>
<SewerBscPlanViewPage id={id} propChgCycl={propChgCycl} />
</PbadmsDetailGate>
);
}
export { SewerBscPlanDetailGate };
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { useDataTable, useUserStore } from "@/shared/lib";
import { useDataTable } from "@/shared/lib";
import {
type SewerBscPlanResponseDto,
type SewerBscPlanSearchRequestDto,
@@ -27,7 +27,6 @@ interface SewerBscPlanListProps {
}
function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: SewerBscPlanListProps) {
const userInfo = useUserStore((state) => state.userInfo);
const router = useRouter();
const [isApplyDialogOpen, setIsApplyDialogOpen] = useState(false);
@@ -48,15 +47,7 @@ function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: Sewe
const columns = useMemo(() => createSewerBscPlanColumns(), []);
const handleRowClick = (row: SewerBscPlanResponseDto) => {
const canEdit =
(row.prgrsSttsComCd === "SV" || row.prgrsSttsComCd === "SL") &&
row.rgtr === userInfo?.userUnqId;
router.push(
canEdit
? `/sewer-bsc-plan/${row.sewerBscPlanDocNo}/edit?chgCycl=${row.chgCycl}`
: `/sewer-bsc-plan/${row.sewerBscPlanDocNo}?chgCycl=${row.chgCycl}`,
);
router.push(`/sewer-bsc-plan/${row.sewerBscPlanDocNo}?chgCycl=${row.chgCycl}`);
};
const { tableComponent } = useDataTable({
+2
View File
@@ -11,6 +11,7 @@ export {
recalculateBizCstTotals,
} from "./lib/business-cost-form-utils";
export { buildCnstpScaleItems, recalculateBzScaleTotals } from "./lib/business-scale-form-utils";
export { canEditPbadmsDoc, type CanEditPbadmsDocParams } from "./lib/can-edit-pbadms-doc";
export {
buildChecklistTabs,
isFormChecklistComplete,
@@ -34,6 +35,7 @@ export { BusinessScaleFormSection } from "./ui/business-scale-form-section";
export { ChecklistFormSection } from "./ui/checklist-form-section";
export { ContactInfoFormSection } from "./ui/contact-info-form-section";
export { FnrsSharFormSection } from "./ui/fnrs-shar-form-section";
export { PbadmsDetailGate, type PbadmsDetailGateProps } from "./ui/pbadms-detail-gate";
export {
PbadmsTaskApplyDialog,
type PbadmsTaskApplySelection,
@@ -0,0 +1,25 @@
interface CanEditPbadmsDocParams {
docSeComCd: string;
prgrsSttsComCd: string | undefined;
rgtr: string | undefined;
loggedInUserUnqId: string | undefined;
}
function canEditPbadmsDoc({
docSeComCd,
prgrsSttsComCd,
rgtr,
loggedInUserUnqId,
}: CanEditPbadmsDocParams): boolean {
if (!loggedInUserUnqId || !rgtr || rgtr !== loggedInUserUnqId) return false;
if (docSeComCd === "CNFM") return prgrsSttsComCd === "CV";
if (docSeComCd === "INSTL" || docSeComCd === "FNRS" || docSeComCd === "SEWER")
return prgrsSttsComCd === "SV" || prgrsSttsComCd === "SL";
return false;
}
export { canEditPbadmsDoc };
export type { CanEditPbadmsDocParams };
@@ -0,0 +1,59 @@
"use client";
import { type ReactNode, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Skeleton } from "@common/components";
interface PbadmsDetailGateProps {
isPending: boolean;
isError: boolean;
shouldRedirectToEdit: boolean;
editHref: string;
children: ReactNode;
errorMessage?: string;
}
function DetailSkeleton() {
return (
<div className="mt-6 flex flex-col gap-4 px-6">
<div className="flex flex-col gap-2">
<Skeleton className="h-[4rem] rounded-xl" />
<Skeleton className="h-[16rem] rounded-xl" />
</div>
<div className="flex flex-col gap-2">
<Skeleton className="h-[4rem] rounded-xl" />
<Skeleton className="h-[16rem] rounded-xl" />
</div>
<div className="flex flex-col gap-2">
<Skeleton className="h-[4rem] rounded-xl" />
<Skeleton className="h-[32rem] rounded-xl" />
</div>
</div>
);
}
function PbadmsDetailGate({
isPending,
isError,
shouldRedirectToEdit,
editHref,
children,
errorMessage = "상세 정보를 찾을 수 없습니다.",
}: PbadmsDetailGateProps) {
const router = useRouter();
useEffect(() => {
if (!shouldRedirectToEdit) return;
router.replace(editHref);
}, [editHref, router, shouldRedirectToEdit]);
if (isPending || shouldRedirectToEdit) return <DetailSkeleton />;
if (isError) return <div className="mt-6 text-[1.4rem]">{errorMessage}</div>;
return children;
}
export { PbadmsDetailGate };
export type { PbadmsDetailGateProps };