Merge branch 'iipbs/as/dev'

This commit is contained in:
2026-08-10 18:01:33 +09:00
75 changed files with 620 additions and 309 deletions
@@ -6,11 +6,26 @@ export interface ApproverStatusConfig {
}
export const APPROVER_STATUS_MAP: Record<string, ApproverStatusConfig> = {
ATRZ_PRGRS_001: { label: "대기", badgeClass: "bg-gray-100 text-gray-700" },
ATRZ_PRGRS_002: { label: "요청", badgeClass: "bg-yellow-100 text-yellow-700" },
ATRZ_PRGRS_003: { label: "승인", badgeClass: "bg-green-100 text-green-700" },
ATRZ_PRGRS_004: { label: "반려", badgeClass: "bg-red-100 text-red-700" },
ATRZ_PRGRS_999: { label: "결재안함", badgeClass: "bg-blue-100 text-blue-700" },
ATRZ_PRGRS_001: {
label: "대기",
badgeClass: "bg-gray-100 text-gray-700",
},
ATRZ_PRGRS_002: {
label: "결재요청",
badgeClass: "bg-yellow-100 text-yellow-700",
},
ATRZ_PRGRS_003: {
label: "결재승인",
badgeClass: "bg-green-100 text-green-700",
},
ATRZ_PRGRS_004: {
label: "반려",
badgeClass: "bg-red-100 text-red-700",
},
ATRZ_PRGRS_999: {
label: "결재안함",
badgeClass: "bg-blue-100 text-blue-700",
},
};
export function sortByAtrzSeqNo(
@@ -19,10 +34,19 @@ export function sortByAtrzSeqNo(
return list.slice().sort((a, b) => Number(a.atrzSeqNo ?? 0) - Number(b.atrzSeqNo ?? 0));
}
export function getApproverStatus(status: string | undefined): ApproverStatusConfig {
export function getApproverStatus(
atrzPrgrsSttsComCd?: string,
atrzSeComCd?: string,
): ApproverStatusConfig {
if (atrzPrgrsSttsComCd === "ATRZ_PRGRS_003" && atrzSeComCd === "ATRZ01")
return {
label: "기술검토 접수",
badgeClass: "bg-green-100 text-green-700",
};
return (
APPROVER_STATUS_MAP[status ?? ""] ?? {
label: status || "-",
APPROVER_STATUS_MAP[atrzPrgrsSttsComCd ?? ""] ?? {
label: "-",
badgeClass: "bg-gray-100 text-gray-700",
}
);
@@ -314,8 +314,8 @@ function ElaprApprovalDialog({
<col width="10%" />
<col width="30%" />
<col width="10%" />
<col width="10%" />
<col width="24%" />
<col width="14%" />
<col width="20%" />
</colgroup>
<TableHeader>
<TableRow>
@@ -4,7 +4,9 @@ import { Fragment } from "react";
import { ChevronRight } from "lucide-react";
import { dayjs, useUserStore } from "@/shared/lib";
import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model";
import { SectionCollapsible } from "@/shared/ui";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
import { cn, Skeleton } from "@common/components";
@@ -18,7 +20,7 @@ interface ElaprDocPrgrsStatusProps {
}
function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
const status = getApproverStatus(item.atrzPrgrsSttsComCd);
const status = getApproverStatus(item.atrzPrgrsSttsComCd, item.atrzSeComCd);
const isRejected = item.atrzPrgrsSttsComCd === REJECT_STATUS_CD;
const rjctRsn = item.rjctRsn?.trim();
const metaParts = [item.ognzNm, item.jbpsSeNm].filter(Boolean);
@@ -26,7 +28,7 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
const statusBadge = (
<span
className={cn(
"inline-flex items-center rounded-sm px-2 py-1 text-lg font-medium",
"inline-flex items-center rounded-sm px-4 py-1.5 text-2xl font-semibold",
status.badgeClass,
isRejected && rjctRsn && "cursor-help underline decoration-dotted underline-offset-4",
)}
@@ -36,7 +38,7 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
);
return (
<div className="flex min-w-[12rem] flex-1 flex-col items-center gap-3 p-4 text-center">
<div className="card card-border flex min-w-[12rem] flex-1 flex-col items-center gap-4 border p-6 text-center">
{isRejected && rjctRsn ? (
<Tooltip>
<TooltipTrigger asChild>{statusBadge}</TooltipTrigger>
@@ -49,11 +51,16 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
)}
<div className="flex flex-col items-center gap-1">
<div className="flex items-center gap-2">
<span className="text-xl text-muted-foreground">[{item.atrzSeComNm || "-"}]</span>
<span className="text-xl font-semibold">{item.aprvrNm || item.aprvrUuid || "-"}</span>
<p className="text-2xl font-semibold text-foreground">
<span className="text-muted-foreground">[{item.atrzSeComNm || "-"}]</span>{" "}
{item.aprvrNm || item.aprvrUuid || "-"}
</p>
</div>
{metaParts.length > 0 && (
<span className="text-base text-gray-500">{metaParts.join(" · ")}</span>
<span className="text-lg text-gray-500">{metaParts.join(" · ")}</span>
)}
{item.atrzDt && (
<span className="text-lg text-gray-500">{dayjs(item.atrzDt).format("YYYY-MM-DD")}</span>
)}
</div>
</div>
@@ -61,34 +68,45 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
}
function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) {
const userInfo = useUserStore((state) => state.userInfo);
const { data, isLoading } = useElaprDocPrgrsByDocNo(elaprDocNo);
const steps = sortByAtrzSeqNo(data ?? []);
if (userInfo?.dtlInstCd !== "ROLE_USER01") {
return null;
}
if (isLoading) {
return (
<div className="flex items-center gap-3 px-4 py-6">
<Skeleton className="h-24 flex-1 rounded-sm" />
<Skeleton className="h-24 flex-1 rounded-sm" />
<Skeleton className="h-24 flex-1 rounded-sm" />
</div>
<SectionCollapsible title="결재현황">
<div className="flex items-center gap-3 px-4 py-6">
<Skeleton className="h-24 flex-1 rounded-sm" />
<Skeleton className="h-24 flex-1 rounded-sm" />
<Skeleton className="h-24 flex-1 rounded-sm" />
</div>
</SectionCollapsible>
);
}
if (steps.length === 0) {
return <div className="py-8 text-center text-xl text-gray-500">결재 정보가 없습니다.</div>;
return null;
}
return (
<div className="flex flex-wrap items-stretch justify-start gap-4 overflow-x-auto">
{steps.map((item, index) => (
<Fragment key={item.elaprPrgrsId ?? `${item.atrzSeqNo}-${item.aprvrUuid ?? index}`}>
<div className="flex items-center">
{index > 0 && <ChevronRight className="size-8 text-muted-foreground" />}
</div>
<ElaprDocPrgrsStep item={item} />
</Fragment>
))}
</div>
<SectionCollapsible title="결재현황">
<div className="flex flex-wrap items-stretch justify-start gap-4 overflow-x-auto p-4">
{steps.map((item, index) => (
<Fragment key={item.elaprPrgrsId ?? `${item.atrzSeqNo}-${item.aprvrUuid ?? index}`}>
{index > 0 && (
<div className="flex items-center">
{<ChevronRight className="size-8 text-muted-foreground" />}
</div>
)}
<ElaprDocPrgrsStep item={item} />
</Fragment>
))}
</div>
</SectionCollapsible>
);
}
@@ -73,14 +73,14 @@ function SortablePrgrsSttsRow({
const isDcarbable = isPrgrsSttsRowDcarbable(row);
const rowDisabled = isLocked && !isEditing;
const atrzSeLabel = row.atrzSeComNm || findCodeLabel(atrzSeCodes, row.atrzSeComCd);
const status = getApproverStatus(row.atrzPrgrsSttsComCd);
const status = getApproverStatus(row.atrzPrgrsSttsComCd, row.atrzSeComCd);
const isRejectedRow = row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_004";
const rjctRsn = row.rjctRsn?.trim();
const statusBadge = (
<span
className={cn(
"inline-flex items-center rounded-sm px-3 py-1.5 text-lg font-semibold",
"inline-flex items-center whitespace-nowrap rounded-sm px-3 py-1.5 text-lg font-semibold",
status.badgeClass,
isRejectedRow && rjctRsn && "cursor-help underline decoration-dotted underline-offset-4",
)}
@@ -92,6 +92,7 @@ interface FnrsCnsltnMngResponseExtension {
mdfcnDt?: string;
sido?: string;
gugun?: string;
techRplyHstryYn?: "Y" | "N";
}
/** 백엔드 FnrsCnsltnMngResponseDto */
@@ -148,6 +148,8 @@ export interface InstlSncnMngResponseDto extends InstlSncnMngSaveRequestDto {
taskSeCdNm: string;
/** 진행상태공통코드명 */
prgrsSttsCdNm: string;
/** 기술검토회신이력여부 */
techRplyHstryYn: "Y" | "N";
/** 재이용시설구분공통코드명 */
rtlztnFcltSeComNm: string;
/** 재이용용도구분공통코드명 */
@@ -1,6 +1,7 @@
import type {
AtflMngClientResponseDto,
CklstRvwRsltTreeDto,
DocSttsChgHstryResponseDto,
EsntlAtchFileMngTreeResponseDto,
PageableParams,
PageResponse,
@@ -61,6 +62,7 @@ export interface SewerBscPlanResponseDto {
rgtr?: string;
rgtrNm?: string;
mdfcnDt?: string;
techRplyHstryYn?: "Y" | "N";
}
/** 백엔드 SewerBscPlanDtlResponseDto */
@@ -199,6 +201,7 @@ export interface SewerBscPlanDetResponseDto {
bizCstList?: SewerBscPlanBizCstResponseDto[];
tmplntList?: SewerBscPlanTmplntResponseDto[];
tmplntTmprStrgList?: SewerBscPlanTmplntTmprStrgResponseDto[];
sttsChgHstryList?: DocSttsChgHstryResponseDto[];
}
/** Controller public 메서드명·시그니처와 동일 */
@@ -11,62 +11,54 @@ import { Badge } from "@common/components";
const columnHelper = createColumnHelper<DesignCnstnMngResponseDto>();
const MAIN_STEP_LABELS = {
save: "저장",
apply: "신청완료",
review: "설계자문 검토",
reply: "설계검토 회신",
apply: "설계자문 신청",
techReview: "기술검토",
reply: "검토결과 회신",
} as const;
const REVIEW_STATUS_CODES = new Set(["CS", "CR"]);
export const PROGRESS_STATUS_COLUMNS = [
{ label: "설계자문 신청 (시군)", sttsComCd: "ER" },
{ label: "기술검토", sttsComCd: "TE" },
{ label: "기술검토 회신", sttsComCd: "CP" },
];
type BadgeState = "completed" | "current" | "pending" | "action";
type BadgeState = "completed" | "current" | "pending";
interface ProgressBadge {
label: string;
state: BadgeState;
}
function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] {
const reviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.review;
const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply;
function buildProgressBadges(prgrsSttsComCd: string): ProgressBadge[] {
if (prgrsSttsComCd === "CV") {
return [
{ label: MAIN_STEP_LABELS.save, state: "current" },
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.review, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "CT") {
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "CS" || prgrsSttsComCd === "SL") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "current" },
{ label: MAIN_STEP_LABELS.review, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (REVIEW_STATUS_CODES.has(prgrsSttsComCd)) {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: reviewLabel, state: "action" },
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "CP" || prgrsSttsComCd === "EP") {
if (prgrsSttsComCd === "CP") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.review, state: "completed" },
{ label: replyLabel, state: "action" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
];
}
return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }];
return [
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
function getBadgeColor(state: BadgeState) {
@@ -77,8 +69,6 @@ function getBadgeColor(state: BadgeState) {
return "blue";
case "pending":
return "lightGray";
case "action":
return "orange";
}
}
@@ -88,11 +78,10 @@ function formatOptional(value: string | undefined) {
interface ProgressStepBadgeProps {
prgrsSttsComCd: string;
prgrsSttsCdNm: string;
}
function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm);
function ProgressStepBadge({ prgrsSttsComCd }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd);
return (
<div className="flex flex-wrap items-center justify-start">
@@ -142,12 +131,7 @@ export function createDesignCnstnColumns() {
id: "progress",
header: "진행상황",
meta: { align: "center" },
cell: ({ row }) => (
<ProgressStepBadge
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
prgrsSttsCdNm={row.original.prgrsSttsCdNm || ""}
/>
),
cell: ({ row }) => <ProgressStepBadge prgrsSttsComCd={row.original.prgrsSttsComCd || ""} />,
}),
columnHelper.accessor("regDt", {
header: "신청일",
@@ -22,6 +22,7 @@ import {
ContactInfoFormSection,
isFormEsntlAtchComplete,
PrivacyConsentSection,
ProgressStatusSection,
} from "@/widgets/pbadms-sprt";
import { Button } from "@common/components";
@@ -31,6 +32,7 @@ import {
formatTaskSeComCdForApi,
} from "../lib/design-cnstn-regist-schema";
import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
@@ -174,6 +176,13 @@ function DesignFormPage({ id, mode, initialData, createDefaults }: DesignFormPag
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="mt-6 space-y-4">
{initialData?.detDto?.designCnstnDocNo && (
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={initialData?.sttsChgHstryList ?? []}
elaprDocNo={initialData.detDto.designCnstnDocNo}
/>
)}
<BasicInfoFormSection />
<ContactInfoFormSection fieldPrefix="designCnstnMng" />
<PrivacyConsentSection />
@@ -6,11 +6,12 @@ import { useUserStore } from "@/shared/lib";
import { useDesignCnstnMng } from "@/entities/design-cnstn";
import { ElaprDocPrgrsStatus } from "@/entities/elapr";
import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { ProgressStatusSection } from "@/widgets/pbadms-sprt";
import { Skeleton } from "@common/components";
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
import { getEditableReviewStep } from "../lib/review-step-utils";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { FloatingActionButtons } from "./components/floating-action-buttons";
import { AttachmentsSection } from "./sections/attachments-section";
@@ -65,11 +66,14 @@ function DesignViewPage({ id }: DesignViewPageProps) {
return <div className="mt-6 text-[1.4rem]">상세 정보를 찾을 수 없습니다.</div>;
}
const detDto = data.detDto;
const bizSclList = data.bizSclList ?? [];
const esntlFileList = data.esntlFileList ?? [];
const etcFileList = data.etcFileList ?? [];
const rvwList = data.rvwList ?? [];
const {
detDto,
bizSclList = [],
esntlFileList = [],
etcFileList = [],
rvwList = [],
sttsChgHstryList = [],
} = data;
const editableReviewStep = getEditableReviewStep(
userInfo?.dtlInstCd,
@@ -78,11 +82,14 @@ function DesignViewPage({ id }: DesignViewPageProps) {
return (
<div className="mt-6 space-y-4">
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={sttsChgHstryList}
elaprDocNo={detDto.designCnstnDocNo}
/>
<BasicInfoSection det={detDto} />
<ContactInfoSection det={detDto} />
<SectionCollapsible title="결재현황">
<ElaprDocPrgrsStatus elaprDocNo={detDto.designCnstnDocNo} />
</SectionCollapsible>
<ElaprDocPrgrsStatus elaprDocNo={detDto.designCnstnDocNo} />
<BusinessScaleSection bizSclList={bizSclList} />
<AttachmentsSection esntlFileList={esntlFileList} etcFileList={etcFileList} />
<TechReviewOpinionSection
@@ -1,12 +1,12 @@
"use client";
import { formatFileSize } from "@/shared/lib/file-utils";
import { SectionCollapsible } from "@/shared/ui";
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
import type {
AtflMngClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
} from "@/entities/design-cnstn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface AttachmentsSectionProps {
@@ -3,10 +3,10 @@
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError, FieldGroup } from "@/shared/ui/field";
import { findCodeLabel, useCodeList } from "@/entities/code";
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Input,
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BasicInfoSectionProps {
@@ -1,7 +1,8 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { BizSclTreeDto } from "@/entities/design-cnstn";
import { fmtNumber, indentPx, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { fmtNumber, indentPx } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BusinessScaleSectionProps {
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { DesignCnstnMngResponseDto } from "@/entities/design-cnstn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface ContactInfoSectionProps {
@@ -7,10 +7,10 @@ import { Paperclip } from "lucide-react";
import { PROXY_HOST } from "@/shared/config";
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
import { attachService } from "@/entities/attach";
import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
RadioGroup,
+53 -31
View File
@@ -11,29 +11,33 @@ import { Badge } from "@common/components";
const columnHelper = createColumnHelper<FnrsCnsltnMngResponseDto>();
const MAIN_STEP_LABELS = {
save: "저장",
apply: "신청완료",
apply: "재원협의 신청",
envoReview: "환경청 검토",
techReview: "기술검토",
reply: "검토결과 회신",
} as const;
const TECH_REVIEW_STATUS_CODES = new Set(["LE", "ER", "EE", "TE", "TS", "TR", "TP"]);
export const PROGRESS_STATUS_COLUMNS = [
{ label: "재원협의 신청 (시군)", sttsComCd: "ST" },
{ label: "재원협의 신청 (시도)", sttsComCd: "LE" },
{ label: "환경청 검토", sttsComCd: "ER" },
{ label: "기술검토", sttsComCd: "TE" },
{ label: "기술검토 회신", sttsComCd: "TP" },
{ label: "환경청 검토 결과 회신", sttsComCd: "EP" },
];
type BadgeState = "completed" | "current" | "pending" | "action";
type BadgeState = "completed" | "current" | "pending";
interface ProgressBadge {
label: string;
state: BadgeState;
}
function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] {
const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview;
const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply;
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
if (prgrsSttsComCd === "SV") {
return [
{ label: MAIN_STEP_LABELS.save, state: "current" },
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
@@ -41,41 +45,61 @@ function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): Pro
if (prgrsSttsComCd === "ST") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "current" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) {
if (prgrsSttsComCd === "LE") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: techReviewLabel, state: "action" },
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
return [
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "TP") {
return [
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{
label: MAIN_STEP_LABELS.techReview,
state: techRplyHstryYn === "Y" ? "completed" : "pending",
},
{ label: MAIN_STEP_LABELS.reply, state: "current" },
];
}
if (prgrsSttsComCd === "EP") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: replyLabel, state: "action" },
];
}
if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{
label: MAIN_STEP_LABELS.techReview,
state: techRplyHstryYn === "Y" ? "completed" : "pending",
},
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
];
}
return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }];
return [
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
function getBadgeColor(state: BadgeState) {
@@ -86,8 +110,6 @@ function getBadgeColor(state: BadgeState) {
return "blue";
case "pending":
return "lightGray";
case "action":
return "orange";
}
}
@@ -97,11 +119,11 @@ function formatOptional(value: string | undefined) {
interface ProgressStepBadgeProps {
prgrsSttsComCd: string;
prgrsSttsCdNm: string;
techRplyHstryYn: "Y" | "N";
}
function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm);
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
return (
<div className="flex flex-wrap items-center justify-start">
@@ -154,7 +176,7 @@ export function createFnrsCnsltnColumns() {
cell: ({ row }) => (
<ProgressStepBadge
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
prgrsSttsCdNm={row.original.prgrsSttsCdNm || ""}
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
/>
),
}),
@@ -30,6 +30,7 @@ import {
isFormChecklistComplete,
isFormEsntlAtchComplete,
PrivacyConsentSection,
ProgressStatusSection,
recalculateFnrsSharTotals,
SplmntDmndFormSection,
toFnrsSharSaveList,
@@ -43,6 +44,7 @@ import {
formatTaskSeComCdForApi,
} from "../lib/fnrs-cnsltn-regist-schema";
import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
interface FnrsFormPageProps {
@@ -270,6 +272,13 @@ function FnrsFormPage({ id, mode, initialData, createDefaults }: FnrsFormPagePro
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="mt-6 space-y-4">
{initialData?.detDto?.fnrsCnsltnDocNo && (
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={initialData?.sttsChgHstryList ?? []}
elaprDocNo={initialData.detDto.fnrsCnsltnDocNo}
/>
)}
<BasicInfoFormSection />
<ContactInfoFormSection fieldPrefix="fnrsCnsltnMng" />
<PrivacyConsentSection />
@@ -6,12 +6,13 @@ import { useUserStore } from "@/shared/lib";
import { ElaprDocPrgrsStatus } from "@/entities/elapr";
import { useFnrsCnsltnMng } from "@/entities/fnrs-cnsltn";
import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw";
import { SectionCollapsible, SupplementDemandSection } from "@/widgets/pbadms-sprt";
import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt";
import { Skeleton } from "@common/components";
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
import { getEditableReviewStep } from "../lib/review-step-utils";
import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { FloatingActionButtons } from "./components/floating-action-buttons";
import { AttachmentsSection } from "./sections/attachments-section";
@@ -71,15 +72,18 @@ function FnrsViewPage({ id }: FnrsViewPageProps) {
return <div className="mt-6 text-[1.4rem]">상세 정보를 찾을 수 없습니다.</div>;
}
const detDto = data.detDto;
const bizSclList = data.bizSclList ?? [];
const bizCstList = data.bizCstList ?? [];
const cklstResultList = data.cklstResultList ?? [];
const esntlFileList = data.esntlFileList ?? [];
const etcFileList = data.etcFileList ?? [];
const rvwList = data.rvwList ?? [];
const splmntDmndList = data.splmntDmndList ?? [];
const fnrsSharList = data.fnrsSharList ?? [];
const {
detDto,
bizSclList = [],
bizCstList = [],
cklstResultList = [],
esntlFileList = [],
etcFileList = [],
rvwList = [],
splmntDmndList = [],
fnrsSharList = [],
sttsChgHstryList = [],
} = data;
const editableReviewStep = getEditableReviewStep(
userInfo?.dtlInstCd,
@@ -88,11 +92,14 @@ function FnrsViewPage({ id }: FnrsViewPageProps) {
return (
<div className="mt-6 space-y-4">
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={sttsChgHstryList}
elaprDocNo={detDto.fnrsCnsltnDocNo}
/>
<BasicInfoSection det={detDto} />
<ContactInfoSection det={detDto} />
<SectionCollapsible title="결재현황">
<ElaprDocPrgrsStatus elaprDocNo={detDto.fnrsCnsltnDocNo} />
</SectionCollapsible>
<ElaprDocPrgrsStatus elaprDocNo={detDto.fnrsCnsltnDocNo} />
<BusinessScaleSection bizSclList={bizSclList} />
<BusinessCostSection bizCstList={bizCstList} />
<FnrsSharSection fnrsSharList={fnrsSharList} />
@@ -1,12 +1,12 @@
"use client";
import { formatFileSize } from "@/shared/lib/file-utils";
import { SectionCollapsible } from "@/shared/ui";
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
import type {
AtflMngClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
} from "@/entities/fnrs-cnsltn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface AttachmentsSectionProps {
@@ -3,10 +3,10 @@
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
import { findCodeLabel, useCodeList } from "@/entities/code";
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Checkbox,
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BasicInfoSectionProps {
@@ -2,9 +2,9 @@
import { useMemo } from "react";
import { SectionCollapsible } from "@/shared/ui";
import type { BizCstTreeDto } from "@/entities/fnrs-cnsltn";
import { calcDiffPercent, fmtNumber, indentPx } from "@/widgets/pbadms-sprt";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
import { sortBizCstListByTreePath } from "../../lib/business-cost-utils";
@@ -1,8 +1,8 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { BizSclTreeDto } from "@/entities/fnrs-cnsltn";
import { fmtNumber, indentPx } from "@/widgets/pbadms-sprt";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BusinessScaleSectionProps {
@@ -2,8 +2,8 @@
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from "react";
import { SectionCollapsible } from "@/shared/ui";
import type { CklstRvwRsltTreeDto } from "@/entities/fnrs-cnsltn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import {
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface ContactInfoSectionProps {
@@ -2,8 +2,9 @@
import { useMemo } from "react";
import { SectionCollapsible } from "@/shared/ui";
import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se";
import { fmtNumber, indentPx, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { fmtNumber, indentPx } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface FnrsSharSectionProps {
@@ -7,10 +7,10 @@ import { Paperclip } from "lucide-react";
import { PROXY_HOST } from "@/shared/config";
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
import { attachService } from "@/entities/attach";
import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
RadioGroup,
+49 -26
View File
@@ -11,29 +11,34 @@ import { Badge } from "@common/components";
const columnHelper = createColumnHelper<InstlSncnMngResponseDto>();
const MAIN_STEP_LABELS = {
save: "저장",
apply: "신청완료",
apply: "설치인가 신청",
envoReview: "환경청 검토",
techReview: "기술검토",
reply: "검토결과 회신",
} as const;
const TECH_REVIEW_STATUS_CODES = new Set(["LE", "ER", "EE", "TE", "TS", "TR", "TP"]);
export const PROGRESS_STATUS_COLUMNS = [
{ label: "설치인가 신청 (시군)", sttsComCd: "ST" },
{ label: "설치인가 신청 (시도)", sttsComCd: "LE" },
{ label: "환경청 검토", sttsComCd: "ER" },
{ label: "기술검토", sttsComCd: "TE" },
{ label: "기술검토 회신", sttsComCd: "TP" },
{ label: "검토결과 회신", sttsComCd: "EP" },
{ label: "설치인가 승인", sttsComCd: "TA" },
];
type BadgeState = "completed" | "current" | "pending" | "action";
type BadgeState = "completed" | "current" | "pending";
interface ProgressBadge {
label: string;
state: BadgeState;
}
function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] {
const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview;
const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply;
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
if (prgrsSttsComCd === "SV") {
return [
{ label: MAIN_STEP_LABELS.save, state: "current" },
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
@@ -41,41 +46,61 @@ function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): Pro
if (prgrsSttsComCd === "ST") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "current" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) {
if (prgrsSttsComCd === "LE") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: techReviewLabel, state: "action" },
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "EP") {
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: replyLabel, state: "action" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "TP" || prgrsSttsComCd === "EP") {
return [
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{
label: MAIN_STEP_LABELS.techReview,
state: techRplyHstryYn === "Y" ? "completed" : "pending",
},
{ label: MAIN_STEP_LABELS.reply, state: "current" },
];
}
if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{
label: MAIN_STEP_LABELS.techReview,
state: techRplyHstryYn === "Y" ? "completed" : "pending",
},
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
];
}
return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }];
return [
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
function getBadgeColor(state: BadgeState) {
@@ -86,8 +111,6 @@ function getBadgeColor(state: BadgeState) {
return "blue";
case "pending":
return "lightGray";
case "action":
return "orange";
}
}
@@ -97,11 +120,11 @@ function formatOptional(value: string | undefined) {
interface ProgressStepBadgeProps {
prgrsSttsComCd: string;
prgrsSttsCdNm: string;
techRplyHstryYn: "Y" | "N";
}
function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm);
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
return (
<div className="flex flex-wrap items-center justify-start">
@@ -154,7 +177,7 @@ export function createInstlSncnColumns() {
cell: ({ row }) => (
<ProgressStepBadge
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
prgrsSttsCdNm={row.original.prgrsSttsCdNm || ""}
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
/>
),
}),
@@ -25,6 +25,7 @@ import {
isFormChecklistComplete,
isFormEsntlAtchComplete,
PrivacyConsentSection,
ProgressStatusSection,
SplmntDmndFormSection,
} from "@/widgets/pbadms-sprt";
import { Button } from "@common/components";
@@ -35,6 +36,7 @@ import {
type InstlSncnRegistFormData,
instlSncnRegistSchema,
} from "../lib/instl-sncn-regist-schema";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
interface InstlFormPageProps {
@@ -228,6 +230,13 @@ function InstlFormPage({ id, mode, initialData, createDefaults }: InstlFormPageP
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="mt-6 space-y-4">
{initialData?.detDto.instlSncnDocNo && (
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={initialData?.sttsChgHstryList ?? []}
elaprDocNo={initialData.detDto.instlSncnDocNo}
/>
)}
<BasicInfoFormSection />
<ContactInfoFormSection fieldPrefix="instlSncnMng" />
<PrivacyConsentSection />
@@ -6,12 +6,13 @@ import { useUserStore } from "@/shared/lib";
import { ElaprDocPrgrsStatus } from "@/entities/elapr";
import { useInstlSncnDetail } from "@/entities/instl-sncn";
import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw";
import { SectionCollapsible, SupplementDemandSection } from "@/widgets/pbadms-sprt";
import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt";
import { Skeleton } from "@common/components";
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
import { getEditableReviewStep } from "../lib/review-step-utils";
import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { FloatingActionButtons } from "./components/floating-action-buttons";
import { AttachmentsSection } from "./sections/attachments-section";
@@ -79,6 +80,7 @@ function InstlViewPage({ id }: InstlViewPageProps) {
etcFileList,
rvwList = [],
splmntDmndList = [],
sttsChgHstryList = [],
} = data;
const editableReviewStep = getEditableReviewStep(
@@ -88,11 +90,14 @@ function InstlViewPage({ id }: InstlViewPageProps) {
return (
<div className="mt-6 space-y-4">
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={sttsChgHstryList}
elaprDocNo={detDto.instlSncnDocNo}
/>
<BasicInfoSection det={detDto} />
<ContactInfoSection det={detDto} />
<SectionCollapsible title="결재현황">
<ElaprDocPrgrsStatus elaprDocNo={detDto.instlSncnDocNo} />
</SectionCollapsible>
<ElaprDocPrgrsStatus elaprDocNo={detDto.instlSncnDocNo} />
<BusinessScaleSection bizSclList={bizSclList} />
<BusinessCostSection bizCstList={bizCstList} />
<ChecklistSection
@@ -1,12 +1,12 @@
"use client";
import { formatFileSize } from "@/shared/lib/file-utils";
import { SectionCollapsible } from "@/shared/ui";
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
import type {
AtflMngClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
} from "@/entities/instl-sncn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface AttachmentsSectionProps {
@@ -3,10 +3,10 @@
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
import { findCodeLabel, useCodeList } from "@/entities/code";
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Checkbox,
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { InstlSncnMngResponseDto } from "@/entities/instl-sncn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BasicInfoSectionProps {
@@ -2,9 +2,9 @@
import { useMemo } from "react";
import { SectionCollapsible } from "@/shared/ui";
import type { BizCstTreeDto } from "@/entities/instl-sncn";
import { calcDiffPercent, fmtNumber, indentPx } from "@/widgets/pbadms-sprt";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
import { sortBizCstListByTreePath } from "../../lib/business-cost-utils";
@@ -1,8 +1,8 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { BizSclTreeDto } from "@/entities/instl-sncn";
import { fmtNumber, indentPx } from "@/widgets/pbadms-sprt";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BusinessScaleSectionProps {
@@ -2,8 +2,8 @@
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from "react";
import { SectionCollapsible } from "@/shared/ui";
import type { CklstRvwRsltTreeDto, ReviewStepCode } from "@/entities/instl-sncn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import {
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { InstlSncnMngResponseDto } from "@/entities/instl-sncn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface ContactInfoSectionProps {
@@ -7,10 +7,10 @@ import { Paperclip } from "lucide-react";
import { PROXY_HOST } from "@/shared/config";
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
import { attachService } from "@/entities/attach";
import type { InstlSncnRvwOpnnResponseDto, ReviewStepCode } from "@/entities/instl-sncn";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
RadioGroup,
@@ -11,30 +11,30 @@ import { Badge } from "@common/components";
const columnHelper = createColumnHelper<SewerBscPlanResponseDto>();
const MAIN_STEP_LABELS = {
save: "저장",
apply: "신청완료",
apply: "기본계획 신청",
envoReview: "환경청 검토",
techReview: "기술검토",
reply: "검토결과 회신",
supplement: "보완요청",
} as const;
const TECH_REVIEW_STATUS_CODES = new Set(["ER", "TE", "TP"]);
export const PROGRESS_STATUS_COLUMNS = [
{ label: "기본계획 신청 (시군)", sttsComCd: "LE" },
{ label: "환경청 검토", sttsComCd: "ER" },
{ label: "기술검토", sttsComCd: "TE" },
{ label: "기술검토 회신", sttsComCd: "TP" },
{ label: "횐경청 검토 결과 회신", sttsComCd: "EP" },
];
type BadgeState = "completed" | "current" | "pending" | "action";
type BadgeState = "completed" | "current" | "pending";
interface ProgressBadge {
label: string;
state: BadgeState;
}
function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] {
const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview;
const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply;
function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] {
if (prgrsSttsComCd === "SV") {
return [
{ label: MAIN_STEP_LABELS.save, state: "current" },
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
@@ -42,57 +42,63 @@ function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): Pro
];
}
if (prgrsSttsComCd === "ST") {
return [
{ label: MAIN_STEP_LABELS.apply, state: "current" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "LE") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "action" },
{ label: MAIN_STEP_LABELS.envoReview, state: "current" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "SL") {
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{ label: MAIN_STEP_LABELS.supplement, state: "action" },
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) {
if (prgrsSttsComCd === "TP") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{ label: techReviewLabel, state: "action" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "TA") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
{
label: MAIN_STEP_LABELS.techReview,
state: techRplyHstryYn === "Y" ? "completed" : "pending",
},
{ label: MAIN_STEP_LABELS.reply, state: "current" },
];
}
if (prgrsSttsComCd === "EP") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.envoReview, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: replyLabel, state: "completed" },
{
label: MAIN_STEP_LABELS.techReview,
state: techRplyHstryYn === "Y" ? "completed" : "pending",
},
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
];
}
return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }];
return [
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.envoReview, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
function getBadgeColor(state: BadgeState) {
@@ -103,8 +109,6 @@ function getBadgeColor(state: BadgeState) {
return "blue";
case "pending":
return "lightGray";
case "action":
return "orange";
}
}
@@ -114,11 +118,11 @@ function formatOptional(value: string | undefined) {
interface ProgressStepBadgeProps {
prgrsSttsComCd: string;
prgrsSttsCdNm: string;
techRplyHstryYn: "Y" | "N";
}
function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm);
function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) {
const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn);
return (
<div className="flex flex-wrap items-center justify-start">
@@ -177,7 +181,7 @@ export function createSewerBscPlanColumns() {
cell: ({ row }) => (
<ProgressStepBadge
prgrsSttsComCd={row.original.prgrsSttsComCd || ""}
prgrsSttsCdNm={row.original.prgrsSttsCdNm || ""}
techRplyHstryYn={row.original.techRplyHstryYn || "N"}
/>
),
}),
@@ -3,13 +3,13 @@
import { useEffect, useMemo, useState } from "react";
import { useAlertStore } from "@/shared/lib";
import { SectionCollapsible } from "@/shared/ui";
import {
type CmCdAdstDstrComboResponseDto,
useFindGugunCombo,
useFindSidoCombo,
} from "@/entities/adstdstr";
import { useLvNowDmgMtncGnsvy, useNowDmgMtncGnsvy } from "@/entities/dc";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Dialog,
@@ -3,12 +3,12 @@
import { Paperclip } from "lucide-react";
import { formatFileSize } from "@/shared/lib/file-utils";
import { SectionCollapsible } from "@/shared/ui";
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
import type {
AtflMngClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
} from "@/entities/sewer-bsc-plan";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface AttachmentsSectionProps {
@@ -3,8 +3,8 @@
import { Controller, useFormContext } from "react-hook-form";
import { useUserStore } from "@/shared/lib";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError } from "@/shared/ui/field";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Input,
Select,
@@ -1,10 +1,10 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type {
SewerBscPlanDtlResponseDto,
SewerBscPlanResponseDto,
} from "@/entities/sewer-bsc-plan";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface BasicInfoSectionProps {
@@ -3,9 +3,10 @@
import { useEffect, useMemo } from "react";
import { useFormContext, useWatch } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { useAcntgSeMngTree } from "@/entities/acntg";
import { buildAcntgCostItems, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { buildAcntgCostItems } from "@/widgets/pbadms-sprt";
import {
Input,
Skeleton,
@@ -2,12 +2,13 @@
import { useMemo } from "react";
import { SectionCollapsible } from "@/shared/ui";
import { useAcntgSeMngTree } from "@/entities/acntg";
import type {
SewerBscPlanBizCstResponseDto,
SewerBscPlanDtlResponseDto,
} from "@/entities/sewer-bsc-plan";
import { buildAcntgCostItems, fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { buildAcntgCostItems, fmtNumber } from "@/widgets/pbadms-sprt";
import {
Skeleton,
Table,
@@ -2,8 +2,8 @@
import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from "react";
import { SectionCollapsible } from "@/shared/ui";
import type { CklstRvwRsltTreeDto } from "@/entities/sewer-bsc-plan";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
import {
@@ -1,7 +1,7 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type { SewerBscPlanDtlResponseDto } from "@/entities/sewer-bsc-plan";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface ContactInfoSectionProps {
@@ -3,9 +3,10 @@
import { useMemo } from "react";
import { useFormContext, useWatch } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { useAcntgSeMngTree } from "@/entities/acntg";
import { buildAcntgCostItems, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { buildAcntgCostItems } from "@/widgets/pbadms-sprt";
import {
cn,
Input,
@@ -2,12 +2,13 @@
import { useMemo } from "react";
import { SectionCollapsible } from "@/shared/ui";
import { useAcntgSeMngTree } from "@/entities/acntg";
import type {
SewerBscPlanDtlResponseDto,
SewerBscPlanFnrsCstResponseDto,
} from "@/entities/sewer-bsc-plan";
import { buildAcntgCostItems, fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { buildAcntgCostItems, fmtNumber } from "@/widgets/pbadms-sprt";
import {
Skeleton,
Table,
@@ -4,8 +4,8 @@ import { useState } from "react";
import { useFormContext, useWatch } from "react-hook-form";
import dynamic from "next/dynamic";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Input,
@@ -1,10 +1,11 @@
"use client";
import { SectionCollapsible } from "@/shared/ui";
import type {
SewerBscPlanDtlResponseDto,
SewerBscPlanOtlnResponseDto,
} from "@/entities/sewer-bsc-plan";
import { fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { fmtNumber } from "@/widgets/pbadms-sprt";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
interface OutlineSectionProps {
@@ -7,10 +7,10 @@ import { Paperclip } from "lucide-react";
import { PROXY_HOST } from "@/shared/config";
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
import { attachService } from "@/entities/attach";
import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
RadioGroup,
@@ -7,11 +7,11 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { attachService } from "@/entities/attach";
import { useFacilityList } from "@/entities/bns-if";
import { useNowDmgMtncGnsvy } from "@/entities/dc";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Checkbox,
@@ -4,6 +4,7 @@ import { useMemo, useState } from "react";
import { Paperclip } from "lucide-react";
import { SectionCollapsible } from "@/shared/ui";
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
import { useFacilityList } from "@/entities/bns-if";
import { useNowDmgMtncGnsvy } from "@/entities/dc";
@@ -12,7 +13,7 @@ import type {
SewerBscPlanResponseDto,
SewerBscPlanTmplntResponseDto,
} from "@/entities/sewer-bsc-plan";
import { fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt";
import { fmtNumber } from "@/widgets/pbadms-sprt";
import {
Table,
TableBody,
@@ -25,6 +25,7 @@ import {
isFormChecklistComplete,
isFormEsntlAtchComplete,
PrivacyConsentSection,
ProgressStatusSection,
SplmntDmndFormSection,
} from "@/widgets/pbadms-sprt";
import { Button } from "@common/components";
@@ -39,6 +40,7 @@ import {
type SewerBscPlanRegistFormData,
sewerBscPlanRegistSchema,
} from "../lib/sewer-bsc-plan-regist-schema";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { BasicInfoFormSection } from "./sections/basic-info-form-section";
import { BizCstMatrixFormSection } from "./sections/biz-cst-matrix-form-section";
@@ -242,6 +244,13 @@ function SewerBscPlanFormPage({
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSave)} className="mt-6 space-y-4">
{initialData?.detDto?.sewerBscPlanDocNo && (
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={initialData?.sttsChgHstryList ?? []}
elaprDocNo={initialData.detDto.sewerBscPlanDocNo}
/>
)}
<BasicInfoFormSection />
<ContactInfoFormSection fieldPrefix="sewerBscPlanDtl" />
<PrivacyConsentSection />
@@ -6,12 +6,13 @@ import { useUserStore } from "@/shared/lib";
import { ElaprDocPrgrsStatus } from "@/entities/elapr";
import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw";
import { useSewerBscPlanDetails } from "@/entities/sewer-bsc-plan";
import { SectionCollapsible, SupplementDemandSection } from "@/widgets/pbadms-sprt";
import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt";
import { Skeleton } from "@common/components";
import { needsRoleUser01AtrzResolve } from "../lib/button-config";
import { getEditableReviewStep } from "../lib/review-step-utils";
import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state";
import { PROGRESS_STATUS_COLUMNS } from "../model/columns";
import { FloatingActionButtons } from "./components/floating-action-buttons";
import { AttachmentsSection } from "./sections/attachments-section";
@@ -74,17 +75,20 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) {
return <div className="mt-6 text-[1.4rem]">상세 정보를 찾을 수 없습니다.</div>;
}
const detDto = data.detDto;
const activeDtlDto = data.activeDtlDto;
const otlnList = data.otlnList ?? [];
const bizCstList = data.bizCstList ?? [];
const fnrsCstList = data.fnrsCstList ?? [];
const tmplntList = data.tmplntList ?? [];
const cklstResultRows = data.cklstResultRows ?? [];
const esntlFileList = data.esntlFileList ?? [];
const etcFileList = data.etcFileList ?? [];
const rvwOpnnList = data.rvwOpnnList ?? [];
const splmntDmndList = data.splmntDmndList ?? [];
const {
detDto,
activeDtlDto,
otlnList = [],
bizCstList = [],
fnrsCstList = [],
tmplntList = [],
cklstResultRows = [],
esntlFileList = [],
etcFileList = [],
rvwOpnnList = [],
splmntDmndList = [],
sttsChgHstryList = [],
} = data;
const editableReviewStep = getEditableReviewStep(
userInfo?.dtlInstCd,
@@ -93,11 +97,14 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) {
return (
<div className="mt-6 space-y-4">
<ProgressStatusSection
columns={PROGRESS_STATUS_COLUMNS}
sttsChgHstryList={sttsChgHstryList}
elaprDocNo={detDto.sewerBscPlanDocNo}
/>
<BasicInfoSection det={detDto} dtl={activeDtlDto} />
<ContactInfoSection dtl={activeDtlDto} />
<SectionCollapsible title="결재현황">
<ElaprDocPrgrsStatus elaprDocNo={detDto.sewerBscPlanDocNo} />
</SectionCollapsible>
<ElaprDocPrgrsStatus elaprDocNo={detDto.sewerBscPlanDocNo} />
<OutlineSection otlnList={otlnList} dtl={activeDtlDto} />
<BizCstMatrixSection bizCstList={bizCstList} dtl={activeDtlDto} />
<FnrsCstSection fnrsCstList={fnrsCstList} dtl={activeDtlDto} />
+1
View File
@@ -38,6 +38,7 @@ export type {
CklstResultSaveBase,
CklstRvwRsltTreeDto,
DocPrgrsUpdateRequestDto,
DocSttsChgHstryResponseDto,
ElaprDocPrgrsClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
EsntlAtchFileSaveBase,
+17
View File
@@ -155,6 +155,22 @@ export interface ElaprDocPrgrsClientResponseDto {
dcarbYn?: string;
}
/** 문서 상태 변경 이력 응답 DTO */
export interface DocSttsChgHstryResponseDto {
chgHstrySn: number;
docSeComCd: string;
docSeComNm: string;
docNo: string;
chgBfrSttsComCd: string;
chgBfrSttsComNm: string;
chgAftrSttsComCd: string;
chgAftrSttsComNm: string;
rmrk: string;
rgtrUuid: string;
rgtrNm: string;
regDt: string;
}
/** 사업규모 저장 공통 필드 */
export interface BzScaleSaveBase {
cnstpSeCd?: string;
@@ -244,4 +260,5 @@ export interface PbadmsMngDetResponseBase<TDet, TRvwOpnn = never, TSplmntDmnd =
rvwList?: TRvwOpnn[];
splmntDmndList?: TSplmntDmnd[];
elaprDocPrgrsList?: ElaprDocPrgrsClientResponseDto[] | null;
sttsChgHstryList?: DocSttsChgHstryResponseDto[];
}
+1
View File
@@ -1,4 +1,5 @@
export { NoData } from "./no-data";
export { PageLayout } from "./page-layout";
export { SectionCollapsible } from "./section-collapsible";
export type { TextShimmerProps } from "./text-shimmer";
export { TEXT_SHIMMER_CLASS, TextShimmer } from "./text-shimmer";
@@ -1,37 +1,37 @@
"use client";
import type { ReactNode } from "react";
import { ChevronDown } from "lucide-react";
import { cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from "@common/components";
interface SectionCollapsibleProps {
title: ReactNode;
defaultOpen?: boolean;
children: ReactNode;
}
function SectionCollapsible({ title, defaultOpen = true, children }: SectionCollapsibleProps) {
return (
<Collapsible defaultOpen={defaultOpen} className="mx-4 rounded-sm border bg-background">
<CollapsibleTrigger asChild>
<button
type="button"
className="group flex w-full cursor-pointer items-center justify-between border-b p-4 text-left data-[state=closed]:border-b-0"
>
<h2 className="text-2xl font-bold">{title}</h2>
<ChevronDown
className={cn(
"size-6 shrink-0 text-muted-foreground transition-transform",
"group-data-[state=open]:rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent className="p-2">{children}</CollapsibleContent>
</Collapsible>
);
}
export { SectionCollapsible };
"use client";
import type { ReactNode } from "react";
import { ChevronDown } from "lucide-react";
import { cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from "@common/components";
interface SectionCollapsibleProps {
title: ReactNode;
defaultOpen?: boolean;
children: ReactNode;
}
function SectionCollapsible({ title, defaultOpen = true, children }: SectionCollapsibleProps) {
return (
<Collapsible defaultOpen={defaultOpen} className="mx-4 rounded-sm border bg-background">
<CollapsibleTrigger asChild>
<button
type="button"
className="group flex w-full cursor-pointer items-center justify-between border-b p-4 text-left data-[state=closed]:border-b-0"
>
<h2 className="text-2xl font-bold">{title}</h2>
<ChevronDown
className={cn(
"size-6 shrink-0 text-muted-foreground transition-transform",
"group-data-[state=open]:rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent className="p-2">{children}</CollapsibleContent>
</Collapsible>
);
}
export { SectionCollapsible };
@@ -12,12 +12,12 @@ import {
import { useDataTable } from "@/shared/lib";
import type { PageResponse } from "@/shared/model";
import { SectionCollapsible } from "@/shared/ui";
import { NoData } from "@/shared/ui/no-data";
import type { CmCdAdstDstrComboResponseDto } from "@/entities/adstdstr";
import { useFindGugunCombo, useFindSidoCombo } from "@/entities/adstdstr";
import type { BnsDetailResponseDto, BnsListResponseDto, BnsSearchDto } from "@/entities/bns-if";
import { buildBnsSearchDto, useBnsDetail, useBnsList } from "@/entities/bns-if";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import {
Button,
Dialog,
@@ -1,4 +1,5 @@
import React from "react";
import { formatFileSize } from "@/shared/lib/file-utils";
type EtcFile = {
+1 -1
View File
@@ -39,7 +39,7 @@ export {
type PbadmsTaskApplySelection,
} from "./ui/pbadms-task-apply-dialog";
export { PrivacyConsentSection } from "./ui/privacy-consent-section";
export { SectionCollapsible } from "./ui/section-collapsible";
export { ProgressStatusSection } from "./ui/progress-status-section";
export {
SplmntDmndDialog,
type SplmntDmndDialogConfirmItem,
@@ -0,0 +1,59 @@
import type {
DocSttsChgHstryResponseDto,
ElaprDocPrgrsClientResponseDto,
} from "@/shared/model/pbadms-sprt";
interface ProgressStatusItem {
rgtrNm: string;
regDt: string;
}
const TECH_REVIEW_STTS_COM_CD = "TE";
const TECH_REVIEW_ATRZ_SE_COM_CD = "ATRZ02";
const TECH_REVIEW_ATRZ_PRGRS_STTS_COM_CD = "ATRZ_PRGRS_003";
function findProgressStatusItem(
sttsChgHstryList: DocSttsChgHstryResponseDto[],
sttsComCd: string,
): ProgressStatusItem[] {
return sttsChgHstryList
.filter((item) => item.chgAftrSttsComCd === sttsComCd)
.map((item) => ({
rgtrNm: item.rgtrNm,
regDt: item.regDt,
}));
}
function findTechReviewProgressItem(
elaprDocPrgrsList: ElaprDocPrgrsClientResponseDto[],
): ProgressStatusItem[] {
return elaprDocPrgrsList
.filter(
(item) =>
item.atrzSeComCd === TECH_REVIEW_ATRZ_SE_COM_CD &&
item.atrzPrgrsSttsComCd === TECH_REVIEW_ATRZ_PRGRS_STTS_COM_CD,
)
.map((item) => ({
rgtrNm: item.aprvrNm ?? "",
regDt: item.atrzDt ?? "",
}));
}
function resolveProgressStatusItem(
sttsComCd: string,
sttsChgHstryList: DocSttsChgHstryResponseDto[],
elaprDocPrgrsList: ElaprDocPrgrsClientResponseDto[],
): ProgressStatusItem[] {
if (sttsComCd === TECH_REVIEW_STTS_COM_CD) {
return findTechReviewProgressItem(elaprDocPrgrsList);
}
return findProgressStatusItem(sttsChgHstryList, sttsComCd);
}
export {
findProgressStatusItem,
findTechReviewProgressItem,
resolveProgressStatusItem,
TECH_REVIEW_STTS_COM_CD,
};
@@ -9,6 +9,7 @@ import { ENDPOINT } from "@/shared/config";
import { useTus } from "@/shared/lib";
import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto } from "@/shared/model";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { attachService } from "@/entities/attach";
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
import {
@@ -29,9 +30,7 @@ import {
type FieldPrefix,
isEtcFileKey,
toFileAccept,
} from "../lib/attachments-form-utils";
import { SectionCollapsible } from "./section-collapsible";
} from "../lib/attachments-form-utils";
interface AttachmentsFormData {
esntlAtchFileList: Array<{
@@ -3,6 +3,7 @@
import { useEffect, useMemo } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { useAcntgSeMngTree } from "@/entities/acntg";
import {
@@ -17,9 +18,7 @@ import {
} from "@common/components";
import { buildAcntgCostItems, recalculateBizCstTotals } from "../lib/business-cost-form-utils";
import { calcDiffPercent } from "../lib/format-utils";
import { SectionCollapsible } from "./section-collapsible";
import { calcDiffPercent } from "../lib/format-utils";
interface BusinessCostFormData {
bizCstList: Array<{
@@ -3,6 +3,7 @@
import { useEffect, useMemo } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { useCnstpSeMngTree } from "@/entities/cnstp";
import {
@@ -16,9 +17,7 @@ import {
TableRow,
} from "@common/components";
import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils";
import { SectionCollapsible } from "./section-collapsible";
import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils";
interface BusinessScaleFormData {
bzScaleList: Array<{
@@ -5,6 +5,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form";
import { useQuery } from "@tanstack/react-query";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldLabel } from "@/shared/ui/field";
import { cklstMngService, type CklstTreeNode } from "@/entities/cklst";
import { useCodeList } from "@/entities/code";
@@ -29,9 +30,7 @@ import {
buildChecklistTabs,
normalizeTaskSeComCdArray,
syncCklstResultListFromTree,
} from "../lib/checklist-form-utils";
import { SectionCollapsible } from "./section-collapsible";
} from "../lib/checklist-form-utils";
type FieldPrefix = "fnrsCnsltnMng" | "instlSncnMng" | "designCnstnMng" | "sewerBscPlan";
@@ -3,8 +3,8 @@
import { Controller, useFormContext } from "react-hook-form";
import { useUserStore } from "@/shared/lib";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError } from "@/shared/ui/field";
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
import { Input, Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
type FieldPrefix =
@@ -5,6 +5,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form";
import { getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { Field } from "@/shared/ui/field";
import { useAcntgSeMngTree } from "@/entities/acntg";
import type { AtflMngClientResponseDto } from "@/entities/fnrs-cnsltn";
@@ -34,7 +35,6 @@ import {
import { calcDiffPercent } from "../lib/format-utils";
import { BizCstTotalDisplayTable } from "./biz-cst-total-display-table";
import { SectionCollapsible } from "./section-collapsible";
const SHAR_FILE_KEY_PREFIX = "fnrs-shar::";
@@ -2,6 +2,7 @@
import { Controller, useFormContext } from "react-hook-form";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError, FieldLabel, FieldSet } from "@/shared/ui/field";
import {
RadioGroup,
@@ -11,9 +12,7 @@ import {
TableHead,
TableHeader,
TableRow,
} from "@common/components";
import { SectionCollapsible } from "./section-collapsible";
} from "@common/components";
interface PrivacyConsentFormData {
privacyConsent: "Y" | "N";
@@ -0,0 +1,88 @@
"use client";
import dayjs from "dayjs";
import { DocSttsChgHstryResponseDto } from "@/shared/model/pbadms-sprt";
import { SectionCollapsible } from "@/shared/ui";
import { useElaprDocPrgrsByDocNo } from "@/entities/elapr";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
import { resolveProgressStatusItem } from "../lib/progress-status-utils";
interface ProgressStatusColumn {
label: string;
sttsComCd: string;
}
interface ProgressStatusItem {
rgtrNm: string;
regDt: string;
}
interface ProgressStatusSectionProps {
columns: ProgressStatusColumn[];
sttsChgHstryList: DocSttsChgHstryResponseDto[];
elaprDocNo: string;
}
interface ProgressStatusCellProps {
items: ProgressStatusItem[];
}
function ProgressStatusCell({ items }: ProgressStatusCellProps) {
if (items.length === 0) return "-";
return (
<div className="flex flex-col gap-2">
{items.map((item, index) => (
<div key={`${item.rgtrNm}-${item.regDt}-${index}`}>
{item.rgtrNm || "-"}
{item.regDt && (
<p className="mt-1 text-lg text-muted-foreground">
{dayjs(item.regDt).format("YYYY-MM-DD")}
</p>
)}
</div>
))}
</div>
);
}
function ProgressStatusSection({
columns,
sttsChgHstryList,
elaprDocNo,
}: ProgressStatusSectionProps) {
const { data: elaprDocPrgrsList = [] } = useElaprDocPrgrsByDocNo(elaprDocNo);
return (
<SectionCollapsible title="진행 상태">
<Table variant="secondary">
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead key={column.sttsComCd}>{column.label}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
{columns.map((column) => (
<TableCell key={column.sttsComCd} align="center">
<ProgressStatusCell
items={resolveProgressStatusItem(
column.sttsComCd,
sttsChgHstryList,
elaprDocPrgrsList,
)}
/>
</TableCell>
))}
</TableRow>
</TableBody>
</Table>
</SectionCollapsible>
);
}
export { ProgressStatusSection };
@@ -7,6 +7,7 @@ import { Paperclip } from "lucide-react";
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
import { TextShimmer } from "@/shared/ui";
import { SectionCollapsible } from "@/shared/ui";
import { attachService, useDownloadAttachmentByAtflCd } from "@/entities/attach";
import {
Badge,
@@ -20,8 +21,6 @@ import {
Textarea,
} from "@common/components";
import { SectionCollapsible } from "./section-collapsible";
const SPLMNT_CMPTN_FILE_KEY_PREFIX = "splmnt-cmptn::";
interface UploadedSplmntFile {
@@ -4,6 +4,7 @@ import { Paperclip } from "lucide-react";
import { dayjs } from "@/shared/lib";
import type { SplmntDmndResponseExtension, SplmntDmndSaveBase } from "@/shared/model";
import { SectionCollapsible } from "@/shared/ui";
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
import {
Badge,
@@ -15,8 +16,6 @@ import {
TableRow,
} from "@common/components";
import { SectionCollapsible } from "./section-collapsible";
type SplmntDmndItem = SplmntDmndSaveBase & SplmntDmndResponseExtension;
interface SupplementDemandSectionProps {