[as] WIP: 하수도기본계획 신청 페이지(이전기본계획조회)
This commit is contained in:
@@ -2,9 +2,12 @@ import { SewerBscPlanEditPage } from "@/pages/sewer-bsc-plan";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
searchParams: Promise<{ chgCycl?: string }>;
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps) {
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
const { id } = await params;
|
||||
return <SewerBscPlanEditPage id={id} />;
|
||||
const { chgCycl } = await searchParams;
|
||||
const propChgCycl = chgCycl ? Number(chgCycl) : undefined;
|
||||
return <SewerBscPlanEditPage id={id} propChgCycl={propChgCycl} />;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
ClsfSystmRequestDto,
|
||||
ClsfSystmResponseDto,
|
||||
DcService,
|
||||
LvNowDmgMtncGnsvyResponseDto,
|
||||
LvNowDmgMtncGnsvySearchDto,
|
||||
NowDmgMtncGnsvyResponseDto,
|
||||
NowDmgMtncGnsvySearchDto,
|
||||
} from "../model/dc-interface";
|
||||
@@ -10,10 +15,31 @@ async function getNowDmgMtncGnsvy(
|
||||
params: NowDmgMtncGnsvySearchDto,
|
||||
): Promise<NowDmgMtncGnsvyResponseDto[]> {
|
||||
const response = await client.get<NowDmgMtncGnsvyResponseDto[]>(
|
||||
ENDPOINT.DC_SERVICE.NOW_DMG_MTNC_GNSVY,
|
||||
`${ENDPOINT.DC_SERVICE}/nowDmgMtncGnsvy`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export { getNowDmgMtncGnsvy };
|
||||
async function getLvNowDmgMtncGnsvy(
|
||||
params: LvNowDmgMtncGnsvySearchDto,
|
||||
): Promise<LvNowDmgMtncGnsvyResponseDto[]> {
|
||||
const response = await client.get<LvNowDmgMtncGnsvyResponseDto[]>(
|
||||
`${ENDPOINT.DC_SERVICE}/lvNowDmgMtncGnsvy`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function getClsfSystm(params: ClsfSystmRequestDto): Promise<ClsfSystmResponseDto[]> {
|
||||
const response = await client.get<ClsfSystmResponseDto[]>(`${ENDPOINT.DC_SERVICE}/clsfSystm`, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const dcService = {
|
||||
getNowDmgMtncGnsvy,
|
||||
getLvNowDmgMtncGnsvy,
|
||||
getClsfSystm,
|
||||
} satisfies DcService;
|
||||
|
||||
@@ -2,16 +2,35 @@ import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type { NowDmgMtncGnsvySearchDto } from "../model/dc-interface";
|
||||
import type {
|
||||
ClsfSystmRequestDto,
|
||||
LvNowDmgMtncGnsvySearchDto,
|
||||
NowDmgMtncGnsvySearchDto,
|
||||
} from "../model/dc-interface";
|
||||
|
||||
import { getNowDmgMtncGnsvy } from "./dc-service";
|
||||
import { dcService } from "./dc-service";
|
||||
|
||||
export const nowDmgMtncGnsvyBaseKey = [ENDPOINT.DC_SERVICE.NOW_DMG_MTNC_GNSVY] as const;
|
||||
export const baseKey = [ENDPOINT.DC_SERVICE] as const;
|
||||
|
||||
export function useNowDmgMtncGnsvy(params: NowDmgMtncGnsvySearchDto) {
|
||||
return useQuery({
|
||||
queryKey: [...nowDmgMtncGnsvyBaseKey, params],
|
||||
queryFn: () => getNowDmgMtncGnsvy(params),
|
||||
queryKey: [...baseKey, "nowDmgMtncGnsvy", params],
|
||||
queryFn: () => dcService.getNowDmgMtncGnsvy(params),
|
||||
enabled: Boolean(params.swtrmtFcltCd && params.ctpvNm),
|
||||
});
|
||||
}
|
||||
|
||||
export function useLvNowDmgMtncGnsvy(params: LvNowDmgMtncGnsvySearchDto) {
|
||||
return useQuery({
|
||||
queryKey: [...baseKey, "lvNowDmgMtncGnsvy", params],
|
||||
queryFn: () => dcService.getLvNowDmgMtncGnsvy(params),
|
||||
enabled: Boolean(params.level && params.ctpvNm && params.sggNm && params.gnsvyYr),
|
||||
});
|
||||
}
|
||||
|
||||
export function useClsfSystm(params: ClsfSystmRequestDto) {
|
||||
return useQuery({
|
||||
queryKey: [...baseKey, "clsfSystm", params],
|
||||
queryFn: () => dcService.getClsfSystm(params),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
export { getNowDmgMtncGnsvy } from "./api/dc-service";
|
||||
export { nowDmgMtncGnsvyBaseKey, useNowDmgMtncGnsvy } from "./api/queries";
|
||||
export type {
|
||||
NowDmgMtncGnsvyResponseDto,
|
||||
NowDmgMtncGnsvySearchDto,
|
||||
} from "./model/dc-interface";
|
||||
export { dcService } from "./api/dc-service";
|
||||
export { baseKey as nowDmgMtncGnsvyBaseKey, useNowDmgMtncGnsvy } from "./api/queries";
|
||||
export type { NowDmgMtncGnsvyResponseDto, NowDmgMtncGnsvySearchDto } from "./model/dc-interface";
|
||||
|
||||
@@ -5,6 +5,18 @@ export interface NowDmgMtncGnsvySearchDto {
|
||||
targetYear?: string;
|
||||
}
|
||||
|
||||
export interface LvNowDmgMtncGnsvySearchDto {
|
||||
level?: string;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
gnsvyYr?: string;
|
||||
}
|
||||
|
||||
export interface ClsfSystmRequestDto {
|
||||
clsfSystmNm?: string;
|
||||
upClsfSystmSn?: string;
|
||||
}
|
||||
|
||||
type NullableNumber = number | null;
|
||||
type NullableString = string | null;
|
||||
|
||||
@@ -46,7 +58,6 @@ type StagedMetricFields = {
|
||||
[K in keyof NowDmgMtncGnsvyMetricFields as `${K & string}${StageSuffix}`]?: NowDmgMtncGnsvyMetricFields[K];
|
||||
};
|
||||
|
||||
/** DC nowDmgMtncGnsvy 응답 DTO */
|
||||
export interface NowDmgMtncGnsvyResponseDto
|
||||
extends NowDmgMtncGnsvyMetricFields, StagedMetricFields {
|
||||
swtrmtFcltCd?: NullableString;
|
||||
@@ -55,3 +66,28 @@ export interface NowDmgMtncGnsvyResponseDto
|
||||
ctpvNm?: NullableString;
|
||||
sggNm?: NullableString;
|
||||
}
|
||||
|
||||
export interface LvNowDmgMtncGnsvyResponseDto {
|
||||
level: number;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
gnsvyYr: string;
|
||||
swtrmtFcltCd: string;
|
||||
swtrmtFcltNm: string;
|
||||
children: LvNowDmgMtncGnsvyResponseDto[];
|
||||
}
|
||||
|
||||
export interface ClsfSystmResponseDto {
|
||||
clsfSystmSn: string;
|
||||
upClsfSystmSn: string;
|
||||
clsfSystmNm: string;
|
||||
sortSeq: string;
|
||||
droppAble: boolean;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface DcService {
|
||||
getNowDmgMtncGnsvy(params: NowDmgMtncGnsvySearchDto): Promise<NowDmgMtncGnsvyResponseDto[]>;
|
||||
getLvNowDmgMtncGnsvy(params: LvNowDmgMtncGnsvySearchDto): Promise<LvNowDmgMtncGnsvyResponseDto[]>;
|
||||
getClsfSystm(params: ClsfSystmRequestDto): Promise<ClsfSystmResponseDto[]>;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
SewerBscPlanApplySaveRequestDto,
|
||||
SewerBscPlanApplySaveResponseDto,
|
||||
SewerBscPlanDetResponseDto,
|
||||
SewerBscPlanResponseDto,
|
||||
SewerBscPlanSaveRequestDto,
|
||||
@@ -45,8 +46,10 @@ async function deleteFn(sewerBscPlanDocNo: string): Promise<void> {
|
||||
await client.delete<void>(`${BASE}/${sewerBscPlanDocNo}`);
|
||||
}
|
||||
|
||||
async function saveApply(body: SewerBscPlanApplySaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/save-apply`, body);
|
||||
async function saveApply(
|
||||
body: SewerBscPlanApplySaveRequestDto,
|
||||
): Promise<SewerBscPlanApplySaveResponseDto> {
|
||||
const response = await client.post<SewerBscPlanApplySaveResponseDto>(`${BASE}/save-apply`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export type {
|
||||
EsntlAtchFileItem,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
SewerBscPlanApplySaveRequestDto,
|
||||
SewerBscPlanApplySaveResponseDto,
|
||||
SewerBscPlanBizCstItem,
|
||||
SewerBscPlanBizCstResponseDto,
|
||||
SewerBscPlanDetResponseDto,
|
||||
|
||||
@@ -307,6 +307,12 @@ export interface SewerBscPlanApplySaveRequestDto extends PbadmsApplySaveHeader {
|
||||
tmplntTmprStrgList?: SewerBscPlanTmplntTmprStrgItem[];
|
||||
}
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveResponseDto */
|
||||
export interface SewerBscPlanApplySaveResponseDto {
|
||||
sewerBscPlanDocNo: string;
|
||||
chgCycl: number;
|
||||
}
|
||||
|
||||
/** 백엔드 SewerBscPlanDetResponseDto */
|
||||
export interface SewerBscPlanDetResponseDto {
|
||||
detDto?: SewerBscPlanResponseDto;
|
||||
@@ -333,7 +339,7 @@ export interface SewerBscPlanService {
|
||||
body: SewerBscPlanUpdateRequestDto,
|
||||
): Promise<SewerBscPlanResponseDto>;
|
||||
delete(sewerBscPlanDocNo: string): Promise<void>;
|
||||
saveApply(body: SewerBscPlanApplySaveRequestDto): Promise<string>;
|
||||
saveApply(body: SewerBscPlanApplySaveRequestDto): Promise<SewerBscPlanApplySaveResponseDto>;
|
||||
findDetails(sewerBscPlanDocNo: string, chgCycl: number): Promise<SewerBscPlanDetResponseDto>;
|
||||
submitSupplement(id: string, body: SewerBscPlanApplySaveRequestDto): Promise<string>;
|
||||
submitReqSupplement(
|
||||
|
||||
@@ -9,6 +9,8 @@ import { recalcFnrsCstSumAmt } from "./fnrs-cst-form-utils";
|
||||
import { type SewerBscPlanRegistFormData } from "./sewer-bsc-plan-regist-schema";
|
||||
|
||||
const PLAN_STEPS = [1, 2, 3, 4] as const;
|
||||
/** 처리시설 상세: 0=현재, 1~4=변경 단계 */
|
||||
const TMPLNT_DTL_STEPS = [0, 1, 2, 3, 4] as const;
|
||||
|
||||
function createEmptyOtlnList() {
|
||||
return PLAN_STEPS.map((planStpNo) => ({ planStpNo }));
|
||||
@@ -38,7 +40,7 @@ function mapEsntlAtchFileList(esntlFileList: SewerBscPlanDetResponseDto["esntlFi
|
||||
}
|
||||
|
||||
function createEmptyTmplntDtlList(swtrmtFcltCd: string) {
|
||||
return PLAN_STEPS.map((planStpNo) => ({
|
||||
return TMPLNT_DTL_STEPS.map((planStpNo) => ({
|
||||
planStpNo,
|
||||
swtrmtFcltCd,
|
||||
prcsZoneArea: 0,
|
||||
@@ -183,6 +185,7 @@ export function mapDetailToFormData(data: SewerBscPlanDetResponseDto): SewerBscP
|
||||
swtrmtFcltCd: code,
|
||||
swtrmtFcltNm: item.swtrmtFcltNm,
|
||||
atchFileGroupId: item.atchFileGroupId,
|
||||
atchFileNm: item.atchFileInfo?.orgnlFileNm,
|
||||
sewerBscPlanTmplntDtlList: createEmptyTmplntDtlList(code).map((empty) => {
|
||||
const dtlItem = dtlMap.get(empty.planStpNo);
|
||||
if (!dtlItem) return empty;
|
||||
@@ -257,11 +260,14 @@ export function buildApplyPayload(
|
||||
sewerBscPlanOtlnList: data.sewerBscPlanOtlnList,
|
||||
bizCstList: data.bizCstList,
|
||||
fnrsCstList: data.fnrsCstList.map((item) => recalcFnrsCstSumAmt(item)),
|
||||
sewerBscPlanTmplntList: data.sewerBscPlanTmplntList.map((item) => ({
|
||||
swtrmtFcltCd: item.swtrmtFcltCd,
|
||||
atchFileGroupId: item.atchFileGroupId,
|
||||
sewerBscPlanTmplntDtlList: item.sewerBscPlanTmplntDtlList,
|
||||
})),
|
||||
sewerBscPlanTmplntList: data.sewerBscPlanTmplntList.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
({ atchFileNm, swtrmtFcltNm, ...item }) => ({
|
||||
swtrmtFcltCd: item.swtrmtFcltCd,
|
||||
atchFileGroupId: item.atchFileGroupId,
|
||||
sewerBscPlanTmplntDtlList: item.sewerBscPlanTmplntDtlList,
|
||||
}),
|
||||
),
|
||||
cklstResultList: data.cklstResultList.map((item) => ({
|
||||
...item,
|
||||
selected: item.selected ?? undefined,
|
||||
|
||||
@@ -86,6 +86,7 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
swtrmtFcltCd: z.string(),
|
||||
swtrmtFcltNm: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
atchFileNm: z.string().optional(),
|
||||
sewerBscPlanTmplntDtlList: z.array(tmplntDtlSchema).optional(),
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { NowDmgMtncGnsvyResponseDto } from "@/entities/dc";
|
||||
|
||||
/**
|
||||
* 폼 매트릭스 행 키 ↔ DC 당초(Dc) 필드 prefix.
|
||||
* API에 대응 필드가 없으면 null (당초 셀 `-`).
|
||||
*/
|
||||
const TMPLNT_DC_FIELD_PREFIX: Record<string, string | null> = {
|
||||
prcsZoneArea: "swtrmtZoneArea",
|
||||
swtrmtPpltnCnt: "swtrmtPpltnCnt",
|
||||
planSwqty: "planSwageQntSum",
|
||||
fcltCpct: "fcltCpctSum",
|
||||
sprsysDrtwPplLen: "brcPplSum",
|
||||
sprsysExlnPplLen: "exclnPplSum",
|
||||
cbsysPplLen: null,
|
||||
icptgPplLen: "icptgPplSum",
|
||||
drgCnt: "mltvFclSum",
|
||||
drtwRlyPumpCnt: null,
|
||||
drtwPumpCnt: "pmstnSum",
|
||||
exlnPumpCnt: null,
|
||||
};
|
||||
|
||||
export type TmplntFieldKey =
|
||||
| "prcsZoneArea"
|
||||
| "swtrmtPpltnCnt"
|
||||
| "planSwqty"
|
||||
| "fcltCpct"
|
||||
| "sprsysDrtwPplLen"
|
||||
| "sprsysExlnPplLen"
|
||||
| "cbsysPplLen"
|
||||
| "icptgPplLen"
|
||||
| "drgCnt"
|
||||
| "drtwRlyPumpCnt"
|
||||
| "drtwPumpCnt"
|
||||
| "exlnPumpCnt";
|
||||
|
||||
export const PLAN_STP_NOS = [1, 2, 3, 4] as const;
|
||||
|
||||
export type PlanStpNo = (typeof PLAN_STP_NOS)[number];
|
||||
|
||||
function formatOriginalValue(value: string | number | null | undefined): string {
|
||||
if (value === undefined || value === null || value === "") return "-";
|
||||
const num = Number(value);
|
||||
if (!Number.isNaN(num)) return num.toLocaleString();
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function getOriginalYear(
|
||||
data: NowDmgMtncGnsvyResponseDto | null | undefined,
|
||||
planStpNo: PlanStpNo,
|
||||
): string {
|
||||
if (!data) return "-";
|
||||
const key = `planYrDc${planStpNo}` as keyof NowDmgMtncGnsvyResponseDto;
|
||||
const value = data[key];
|
||||
if (value === undefined || value === null || value === "") return "-";
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function getOriginalValue(
|
||||
data: NowDmgMtncGnsvyResponseDto | null | undefined,
|
||||
fieldKey: TmplntFieldKey,
|
||||
planStpNo: PlanStpNo,
|
||||
): string {
|
||||
if (!data) return "-";
|
||||
const prefix = TMPLNT_DC_FIELD_PREFIX[fieldKey];
|
||||
if (!prefix) return "-";
|
||||
const key = `${prefix}Dc${planStpNo}` as keyof NowDmgMtncGnsvyResponseDto;
|
||||
return formatOriginalValue(data[key] as string | number | null | undefined);
|
||||
}
|
||||
|
||||
export { getOriginalValue, getOriginalYear };
|
||||
@@ -1,14 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
|
||||
import { useId, useMemo, useState } from "react";
|
||||
import { Controller, useFieldArray, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
import { attachService, useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import { useFacilityList } from "@/entities/bns-if";
|
||||
import { useNowDmgMtncGnsvy } from "@/entities/dc";
|
||||
import { SectionCollapsible } from "@/widgets/pbadms-sprt";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Table,
|
||||
@@ -25,66 +30,386 @@ import {
|
||||
|
||||
import { createEmptyTmplntDtlList } from "../../lib/form-data-mapper";
|
||||
import type { SewerBscPlanRegistFormData } from "../../lib/sewer-bsc-plan-regist-schema";
|
||||
import {
|
||||
getOriginalValue,
|
||||
getOriginalYear,
|
||||
PLAN_STP_NOS,
|
||||
type TmplntFieldKey,
|
||||
} from "../../lib/tmplnt-original-mapper";
|
||||
|
||||
interface FacilityOption {
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const TMPLNT_FIELD_ROWS = [
|
||||
{ key: "prcsZoneArea" as const, label: "처리구역면적(km²)" },
|
||||
{ key: "swtrmtPpltnCnt" as const, label: "하수처리인구(명)" },
|
||||
{ key: "planSwqty" as const, label: "계획하수량(㎥/일)" },
|
||||
{ key: "fcltCpct" as const, label: "시설용량(㎥/일)" },
|
||||
{ key: "sprsysDrtwPplLen" as const, label: "분류식 오수(km)" },
|
||||
{ key: "sprsysExlnPplLen" as const, label: "분류식 우수(km)" },
|
||||
{ key: "cbsysPplLen" as const, label: "합류식(km)" },
|
||||
{ key: "icptgPplLen" as const, label: "차집관로(km)" },
|
||||
{ key: "drgCnt" as const, label: "배수설비(개소)" },
|
||||
{ key: "drtwRlyPumpCnt" as const, label: "오수 중계 펌프(개소)" },
|
||||
{ key: "drtwPumpCnt" as const, label: "오수 일반 펌프(개소)" },
|
||||
{ key: "exlnPumpCnt" as const, label: "우수 펌프(개소)" },
|
||||
interface ChangeStepHeader {
|
||||
planStpNo: (typeof PLAN_STP_NOS)[number];
|
||||
year: string | undefined;
|
||||
}
|
||||
|
||||
interface UploadedTmplntFile {
|
||||
fileName: string;
|
||||
fileKey: string;
|
||||
atflCd?: string;
|
||||
atflId?: string;
|
||||
}
|
||||
|
||||
const TMPLNT_ATCH_ACCEPT = ".pdf,.png,.jpg,.jpeg";
|
||||
const TMPLNT_ATCH_FORMAT_LABEL = ".pdf, .png, .jpg, .jpeg";
|
||||
|
||||
const TMPLNT_FIELD_ROWS: { key: TmplntFieldKey; label: string }[] = [
|
||||
{ key: "prcsZoneArea", label: "처리구역면적(km²)" },
|
||||
{ key: "swtrmtPpltnCnt", label: "하수처리인구(명)" },
|
||||
{ key: "planSwqty", label: "계획하수량(㎥/일)" },
|
||||
{ key: "fcltCpct", label: "시설용량(㎥/일)" },
|
||||
{ key: "sprsysDrtwPplLen", label: "분류식 오수(km)" },
|
||||
{ key: "sprsysExlnPplLen", label: "분류식 우수(km)" },
|
||||
{ key: "cbsysPplLen", label: "합류식(km)" },
|
||||
{ key: "icptgPplLen", label: "차집관로(km)" },
|
||||
{ key: "drgCnt", label: "배수설비(개소)" },
|
||||
{ key: "drtwRlyPumpCnt", label: "오수 중계 펌프(개소)" },
|
||||
{ key: "drtwPumpCnt", label: "오수 일반 펌프(개소)" },
|
||||
{ key: "exlnPumpCnt", label: "우수 펌프(개소)" },
|
||||
];
|
||||
|
||||
interface TmplntDtlInputProps {
|
||||
facilityIndex: number;
|
||||
planStpNo: number;
|
||||
fieldKey: TmplntFieldKey;
|
||||
}
|
||||
|
||||
function TmplntDtlInput({ facilityIndex, planStpNo, fieldKey }: TmplntDtlInputProps) {
|
||||
const { control } = useFormContext<SewerBscPlanRegistFormData>();
|
||||
const fieldName =
|
||||
`sewerBscPlanTmplntList.${facilityIndex}.sewerBscPlanTmplntDtlList.${planStpNo}.${fieldKey}` as const;
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={fieldName}
|
||||
render={({ field }) => (
|
||||
<Field>
|
||||
<Input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
className="text-right"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => {
|
||||
const numValue = Number(e.target.value.replace(/,/g, "")) || 0;
|
||||
field.onChange(numValue);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface TmplntFacilityMatrixProps {
|
||||
swtrmtFcltCd: string;
|
||||
sido: string;
|
||||
gugun: string;
|
||||
facilityIndex: number;
|
||||
crtrYr: string | undefined;
|
||||
changeStepHeaders: ChangeStepHeader[];
|
||||
}
|
||||
|
||||
function TmplntFacilityMatrix({
|
||||
swtrmtFcltCd,
|
||||
sido,
|
||||
gugun,
|
||||
facilityIndex,
|
||||
crtrYr,
|
||||
changeStepHeaders,
|
||||
}: TmplntFacilityMatrixProps) {
|
||||
const searchParams = useMemo(
|
||||
() => ({
|
||||
swtrmtFcltCd,
|
||||
ctpvNm: sido,
|
||||
sggNm: gugun,
|
||||
}),
|
||||
[swtrmtFcltCd, sido, gugun],
|
||||
);
|
||||
const { data } = useNowDmgMtncGnsvy(searchParams);
|
||||
const originalData = Array.isArray(data) ? (data[0] ?? null) : null;
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="9.75%" />
|
||||
<col width="9.75%" />
|
||||
<col width="9.75%" />
|
||||
<col width="9.75%" />
|
||||
<col width="10%" />
|
||||
<col width="9.75%" />
|
||||
<col width="9.75%" />
|
||||
<col width="9.75%" />
|
||||
<col width="9.75%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[22rem]" rowSpan={2}>
|
||||
구분
|
||||
</TableHead>
|
||||
<TableHead className="text-center" colSpan={4}>
|
||||
당초
|
||||
</TableHead>
|
||||
<TableHead className="text-center" rowSpan={2}>
|
||||
현재
|
||||
<br />
|
||||
<span className="font-normal text-muted-foreground">({crtrYr || "-"}년)</span>
|
||||
</TableHead>
|
||||
<TableHead className="text-center" colSpan={4}>
|
||||
변경
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
{PLAN_STP_NOS.map((planStpNo) => (
|
||||
<TableHead key={`original-${planStpNo}`} className="text-center">
|
||||
{planStpNo}단계
|
||||
<br />
|
||||
<span className="font-normal text-muted-foreground">
|
||||
({getOriginalYear(originalData, planStpNo)})
|
||||
</span>
|
||||
</TableHead>
|
||||
))}
|
||||
{changeStepHeaders.map((step) => (
|
||||
<TableHead key={`change-${step.planStpNo}`} className="text-center">
|
||||
{step.planStpNo}단계
|
||||
<br />
|
||||
<span className="font-normal text-muted-foreground">({step.year || "-"})</span>
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{TMPLNT_FIELD_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
{PLAN_STP_NOS.map((planStpNo) => (
|
||||
<TableCell key={`original-${row.key}-${planStpNo}`} className="text-right">
|
||||
{getOriginalValue(originalData, row.key, planStpNo)}
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell key={`current-${row.key}`}>
|
||||
<TmplntDtlInput facilityIndex={facilityIndex} planStpNo={0} fieldKey={row.key} />
|
||||
</TableCell>
|
||||
{changeStepHeaders.map((step) => (
|
||||
<TableCell key={`change-${row.key}-${step.planStpNo}`}>
|
||||
<TmplntDtlInput
|
||||
facilityIndex={facilityIndex}
|
||||
planStpNo={step.planStpNo}
|
||||
fieldKey={row.key}
|
||||
/>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
interface TmplntFacilityAttachmentsProps {
|
||||
facilityIndex: number;
|
||||
facilityName?: string;
|
||||
atchFileGroupId?: string;
|
||||
atchFileNm?: string;
|
||||
}
|
||||
|
||||
function TmplntFacilityAttachments({
|
||||
facilityIndex,
|
||||
facilityName,
|
||||
atchFileGroupId,
|
||||
atchFileNm,
|
||||
}: TmplntFacilityAttachmentsProps) {
|
||||
const formId = useId();
|
||||
const { setValue } = useFormContext<SewerBscPlanRegistFormData>();
|
||||
const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd();
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedTmplntFile | null>(() =>
|
||||
atchFileGroupId && atchFileNm
|
||||
? {
|
||||
fileName: atchFileNm,
|
||||
fileKey: `server::${atchFileGroupId}`,
|
||||
atflCd: atchFileGroupId,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
accept: TMPLNT_ATCH_ACCEPT,
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd || !atflId) return;
|
||||
setValue(`sewerBscPlanTmplntList.${facilityIndex}.atchFileGroupId`, atflCd, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflCd, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
|
||||
const fileInputId = `${formId}-tmplnt-atch-${facilityIndex}`;
|
||||
const uploading = uploadedFile ? (isUploading[uploadedFile.fileKey] ?? false) : false;
|
||||
const displayFileName = uploadedFile?.fileName ?? atchFileNm ?? "";
|
||||
const downloadAtflCd = uploadedFile?.atflCd || atchFileGroupId;
|
||||
const hasFile = Boolean(uploadedFile || atchFileNm);
|
||||
|
||||
async function handleFileUpload(files: FileList | null) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const file = files[0];
|
||||
const fileKey = getDefaultFileKey(file);
|
||||
const existingAtflCd = atchFileGroupId || uploadedFile?.atflCd;
|
||||
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
setValue(`sewerBscPlanTmplntList.${facilityIndex}.atchFileNm`, file.name, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, {
|
||||
fileKey,
|
||||
metadata: existingAtflCd ? { atflCd: existingAtflCd } : undefined,
|
||||
});
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileRemove() {
|
||||
if (!uploadedFile) return;
|
||||
|
||||
uploads[uploadedFile.fileKey]?.abort();
|
||||
|
||||
if (uploadedFile.atflId) {
|
||||
try {
|
||||
await attachService.deleteAttachmentByAtflId(uploadedFile.atflId);
|
||||
} catch (error) {
|
||||
console.error("처리시설 첨부파일 삭제 실패:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setUploadedFile(null);
|
||||
setValue(`sewerBscPlanTmplntList.${facilityIndex}.atchFileGroupId`, "", { shouldDirty: true });
|
||||
setValue(`sewerBscPlanTmplntList.${facilityIndex}.atchFileNm`, "", { shouldDirty: true });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6 space-y-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">처리구역 필수 첨부파일</h3>
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="40%" />
|
||||
<col width="15%" />
|
||||
<col width="45%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead align="left">파일명</TableHead>
|
||||
<TableHead>파일 확장자</TableHead>
|
||||
<TableHead>파일첨부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>{facilityName} 계획평면도</TableCell>
|
||||
<TableCell className="text-center">{TMPLNT_ATCH_FORMAT_LABEL}</TableCell>
|
||||
<TableCell>
|
||||
{hasFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{downloadAtflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(downloadAtflCd)}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0" />
|
||||
{displayFileName}
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-xl">{displayFileName}</span>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
className="size-8 shrink-0 !bg-destructive"
|
||||
disabled={uploading}
|
||||
onClick={() => void handleFileRemove()}
|
||||
>
|
||||
<i className="diveicon di-x text-xs" aria-label="삭제" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label htmlFor={fileInputId}>
|
||||
<input
|
||||
id={fileInputId}
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept={TMPLNT_ATCH_ACCEPT}
|
||||
onChange={(e) => {
|
||||
void handleFileUpload(e.target.files);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button type="button" size="sm" variant="outline" asChild>
|
||||
<span>파일선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TmplntFormSection() {
|
||||
const { confirm } = useConfirm();
|
||||
const { control, setValue, getValues } = useFormContext<SewerBscPlanRegistFormData>();
|
||||
const { control, getValues } = useFormContext<SewerBscPlanRegistFormData>();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: "sewerBscPlanTmplntList",
|
||||
});
|
||||
const tmplntList = useWatch({ control, name: "sewerBscPlanTmplntList" }) ?? [];
|
||||
const sido = useWatch({ control, name: "sewerBscPlan.sido" }) ?? "";
|
||||
const gugun = useWatch({ control, name: "sewerBscPlan.gugun" }) ?? "";
|
||||
const frlvlGoalYr = useWatch({ control, name: "sewerBscPlanDtl.frlvlGoalYr" });
|
||||
const sclvlGoalYr = useWatch({ control, name: "sewerBscPlanDtl.sclvlGoalYr" });
|
||||
const thlvlGoalYr = useWatch({ control, name: "sewerBscPlanDtl.thlvlGoalYr" });
|
||||
const folvlGoalYr = useWatch({ control, name: "sewerBscPlanDtl.folvlGoalYr" });
|
||||
|
||||
const { data: facilityListData } = useFacilityList({ sido, gugun }, { enabled: Boolean(sido) });
|
||||
|
||||
const facilityOptions = useMemo(
|
||||
() =>
|
||||
(facilityListData?.fctList ?? [])
|
||||
.filter((facility) => facility.bscFctCd)
|
||||
.map((facility) => ({
|
||||
code: facility.bscFctCd!,
|
||||
name: facility.bscFctNm ?? facility.bscFctCd!,
|
||||
})),
|
||||
[facilityListData?.fctList],
|
||||
const [sido, gugun] = useWatch({
|
||||
control,
|
||||
name: ["sewerBscPlan.sido", "sewerBscPlan.gugun"],
|
||||
});
|
||||
const [frlvlGoalYr, sclvlGoalYr, thlvlGoalYr, folvlGoalYr, crtrYr] = useWatch({
|
||||
control,
|
||||
name: [
|
||||
"sewerBscPlanDtl.frlvlGoalYr",
|
||||
"sewerBscPlanDtl.sclvlGoalYr",
|
||||
"sewerBscPlanDtl.thlvlGoalYr",
|
||||
"sewerBscPlanDtl.folvlGoalYr",
|
||||
"sewerBscPlanDtl.crtrYr",
|
||||
],
|
||||
});
|
||||
const { data: facilityListData } = useFacilityList(
|
||||
{ sido: sido ?? "", gugun: gugun ?? "" },
|
||||
{ enabled: Boolean(sido) },
|
||||
);
|
||||
|
||||
const facilityOptions =
|
||||
facilityListData?.fctList
|
||||
?.filter((facility) => facility.bscFctCd)
|
||||
.map((facility) => ({
|
||||
code: facility.bscFctCd!,
|
||||
name: facility.bscFctNm ?? facility.bscFctCd!,
|
||||
})) ?? [];
|
||||
|
||||
const [activeTab, setActiveTab] = useState("");
|
||||
|
||||
const selectedCodes = useMemo(
|
||||
() => new Set(tmplntList.map((item) => item.swtrmtFcltCd)),
|
||||
[tmplntList],
|
||||
);
|
||||
const selectedCodes = new Set(tmplntList.map((item) => item.swtrmtFcltCd));
|
||||
|
||||
const stepHeaders = [
|
||||
{ planStpNo: 1, label: "변경 1단계", year: frlvlGoalYr },
|
||||
{ planStpNo: 2, label: "변경 2단계", year: sclvlGoalYr },
|
||||
{ planStpNo: 3, label: "변경 3단계", year: thlvlGoalYr },
|
||||
{ planStpNo: 4, label: "변경 4단계", year: folvlGoalYr },
|
||||
const changeStepHeaders: ChangeStepHeader[] = [
|
||||
{ planStpNo: 1, year: frlvlGoalYr },
|
||||
{ planStpNo: 2, year: sclvlGoalYr },
|
||||
{ planStpNo: 3, year: thlvlGoalYr },
|
||||
{ planStpNo: 4, year: folvlGoalYr },
|
||||
];
|
||||
|
||||
async function handleFacilityToggle(option: FacilityOption, checked: boolean) {
|
||||
@@ -93,13 +418,14 @@ function TmplntFormSection() {
|
||||
swtrmtFcltCd: option.code,
|
||||
swtrmtFcltNm: option.name,
|
||||
atchFileGroupId: "",
|
||||
atchFileNm: "",
|
||||
sewerBscPlanTmplntDtlList: createEmptyTmplntDtlList(option.code),
|
||||
});
|
||||
setActiveTab(option.code);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirm(`「${option.name}」을(를) 제거하시겠습니까?`))) return;
|
||||
if (!(await confirm(`${option.name}을(를) 제거하시겠습니까?`))) return;
|
||||
|
||||
const index = getValues("sewerBscPlanTmplntList").findIndex(
|
||||
(item) => item.swtrmtFcltCd === option.code,
|
||||
@@ -111,26 +437,6 @@ function TmplntFormSection() {
|
||||
setActiveTab(remaining[0]?.swtrmtFcltCd ?? "");
|
||||
}
|
||||
|
||||
function handleDtlChange(
|
||||
facilityIndex: number,
|
||||
planStpNo: number,
|
||||
fieldKey: (typeof TMPLNT_FIELD_ROWS)[number]["key"],
|
||||
value: string,
|
||||
) {
|
||||
const numValue = Number(value.replace(/,/g, "")) || 0;
|
||||
const list = [...getValues("sewerBscPlanTmplntList")];
|
||||
const facility = list[facilityIndex];
|
||||
if (!facility) return;
|
||||
|
||||
const dtlList = [...(facility.sewerBscPlanTmplntDtlList ?? [])];
|
||||
const dtlIndex = dtlList.findIndex((item) => item.planStpNo === planStpNo);
|
||||
if (dtlIndex < 0) return;
|
||||
|
||||
dtlList[dtlIndex] = { ...dtlList[dtlIndex], [fieldKey]: numValue };
|
||||
list[facilityIndex] = { ...facility, sewerBscPlanTmplntDtlList: dtlList };
|
||||
setValue("sewerBscPlanTmplntList", list, { shouldDirty: true });
|
||||
}
|
||||
|
||||
const resolvedActiveTab =
|
||||
activeTab && selectedCodes.has(activeTab) ? activeTab : (tmplntList[0]?.swtrmtFcltCd ?? "");
|
||||
|
||||
@@ -142,7 +448,7 @@ function TmplntFormSection() {
|
||||
<p className="text-xl text-muted-foreground">조회된 처리시설이 없습니다.</p>
|
||||
) : (
|
||||
facilityOptions.map((option) => (
|
||||
<label key={option.code} className="flex items-center gap-2 text-xl">
|
||||
<label key={option.code} className="flex cursor-pointer items-center gap-2 text-xl">
|
||||
<Checkbox
|
||||
checked={selectedCodes.has(option.code)}
|
||||
onCheckedChange={(checked) => handleFacilityToggle(option, checked === true)}
|
||||
@@ -156,71 +462,46 @@ function TmplntFormSection() {
|
||||
{fields.length > 0 ? (
|
||||
<Tabs value={resolvedActiveTab} onValueChange={setActiveTab} variant="underline">
|
||||
<TabsList className="p-0">
|
||||
{fields.map((field) => (
|
||||
<TabsTrigger
|
||||
key={field.id}
|
||||
value={field.swtrmtFcltCd}
|
||||
className="px-6 py-3 text-2xl"
|
||||
>
|
||||
{field.swtrmtFcltNm || field.swtrmtFcltCd}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
{fields.map((field) => {
|
||||
const facilityName =
|
||||
field.swtrmtFcltNm ||
|
||||
facilityOptions.find((option) => option.code === field.swtrmtFcltCd)?.name;
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={field.id}
|
||||
value={field.swtrmtFcltCd}
|
||||
className="px-6 py-3 text-2xl"
|
||||
>
|
||||
{facilityName}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
{fields.map((field, facilityIndex) => {
|
||||
const dtlList = tmplntList[facilityIndex]?.sewerBscPlanTmplntDtlList ?? [];
|
||||
const facilityName =
|
||||
field.swtrmtFcltNm ||
|
||||
facilityOptions.find((option) => option.code === field.swtrmtFcltCd)?.name;
|
||||
return (
|
||||
<TabsContent
|
||||
key={field.id}
|
||||
value={field.swtrmtFcltCd}
|
||||
className="mt-0 border-0 p-0 shadow-none"
|
||||
>
|
||||
<Table variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[22rem]">구분</TableHead>
|
||||
{stepHeaders.map((step) => (
|
||||
<TableHead key={step.planStpNo} className="text-center">
|
||||
{step.label}
|
||||
<br />
|
||||
<span className="font-normal text-muted-foreground">
|
||||
({step.year || "-"})
|
||||
</span>
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{TMPLNT_FIELD_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
{stepHeaders.map((step) => {
|
||||
const dtl = dtlList.find((item) => item.planStpNo === step.planStpNo);
|
||||
return (
|
||||
<TableCell key={step.planStpNo}>
|
||||
<Field>
|
||||
<Input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
className="text-right"
|
||||
value={dtl?.[row.key] ?? 0}
|
||||
onChange={(e) =>
|
||||
handleDtlChange(
|
||||
facilityIndex,
|
||||
step.planStpNo,
|
||||
row.key,
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TmplntFacilityMatrix
|
||||
swtrmtFcltCd={field.swtrmtFcltCd}
|
||||
sido={sido ?? ""}
|
||||
gugun={gugun ?? ""}
|
||||
facilityIndex={facilityIndex}
|
||||
crtrYr={crtrYr}
|
||||
changeStepHeaders={changeStepHeaders}
|
||||
/>
|
||||
<TmplntFacilityAttachments
|
||||
facilityIndex={facilityIndex}
|
||||
facilityName={facilityName}
|
||||
atchFileGroupId={tmplntList[facilityIndex]?.atchFileGroupId}
|
||||
atchFileNm={tmplntList[facilityIndex]?.atchFileNm}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useSewerBscPlan, useSewerBscPlanDetails } from "@/entities/sewer-bsc-plan";
|
||||
import { useSewerBscPlanDetails } from "@/entities/sewer-bsc-plan";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import { SewerBscPlanFormPage } from "./sewer-bsc-plan-form-page";
|
||||
|
||||
interface SewerBscPlanEditPageProps {
|
||||
id: string;
|
||||
propChgCycl?: number;
|
||||
}
|
||||
|
||||
function SewerBscPlanEditPage({ id }: SewerBscPlanEditPageProps) {
|
||||
const { data: master, isLoading: isMasterLoading } = useSewerBscPlan(id);
|
||||
const chgCycl = master?.chgCycl ?? 0;
|
||||
function SewerBscPlanEditPage({ id, propChgCycl }: SewerBscPlanEditPageProps) {
|
||||
const chgCycl = propChgCycl ?? 1;
|
||||
const { data, isLoading: isDetailsLoading } = useSewerBscPlanDetails(id, chgCycl);
|
||||
|
||||
if (isMasterLoading || isDetailsLoading) {
|
||||
if (isDetailsLoading) {
|
||||
return (
|
||||
<div className="mt-6 space-y-6 px-4">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import {
|
||||
SewerBscPlanApplySaveResponseDto,
|
||||
sewerBscPlanBaseKey,
|
||||
type SewerBscPlanDetResponseDto,
|
||||
sewerBscPlanService,
|
||||
@@ -98,7 +99,7 @@ function SewerBscPlanFormPage({
|
||||
async function submitForm(
|
||||
data: SewerBscPlanRegistFormData,
|
||||
prgrsSttsComCd: "SV" | "LE",
|
||||
): Promise<string | undefined> {
|
||||
): Promise<SewerBscPlanApplySaveResponseDto | undefined> {
|
||||
if (!assertFnrsCstBalance(data)) return undefined;
|
||||
|
||||
const payload = buildApplyPayload(data, prgrsSttsComCd, {
|
||||
@@ -113,11 +114,11 @@ function SewerBscPlanFormPage({
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const docNo = await submitForm(data, "SV");
|
||||
if (!docNo) return;
|
||||
const result = await submitForm(data, "SV");
|
||||
if (!result?.sewerBscPlanDocNo) return;
|
||||
|
||||
alert("저장되었습니다.");
|
||||
router.push(`/sewer-bsc-plan/${docNo}/edit`);
|
||||
router.push(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}/edit?chgCycl=${result.chgCycl}`);
|
||||
if (isEditMode && id) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...sewerBscPlanBaseKey, "findDetails", id],
|
||||
|
||||
@@ -54,7 +54,7 @@ function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: Sewe
|
||||
|
||||
router.push(
|
||||
canEdit
|
||||
? `/sewer-bsc-plan/${row.sewerBscPlanDocNo}/edit`
|
||||
? `/sewer-bsc-plan/${row.sewerBscPlanDocNo}/edit?chgCycl=${row.chgCycl}`
|
||||
: `/sewer-bsc-plan/${row.sewerBscPlanDocNo}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -44,9 +44,7 @@ export const ENDPOINT = {
|
||||
STATVERIFY_SERVICE: {
|
||||
ST_ANLS_MST: "/statverify-service/api/v1/st-anls-mst",
|
||||
},
|
||||
DC_SERVICE: {
|
||||
NOW_DMG_MTNC_GNSVY: "/dc-service/api/v1/nowDmgMtncGnsvy",
|
||||
},
|
||||
DC_SERVICE: "/dc-service/api/v1",
|
||||
};
|
||||
|
||||
export const JWT_EXPIRATION_TIME = 1800000;
|
||||
|
||||
@@ -374,9 +374,9 @@ function AttachmentsFormSection({
|
||||
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="50%" />
|
||||
<col width="10%" />
|
||||
<col width="40%" />
|
||||
<col width="15%" />
|
||||
<col width="45%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -384,13 +384,13 @@ function AttachmentsFormSection({
|
||||
파일명
|
||||
</TableHead>
|
||||
<TableHead>파일 확장자</TableHead>
|
||||
<TableHead>업로드된 파일</TableHead>
|
||||
<TableHead>파일첨부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{essentialFiles.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} className="py-12 text-center text-muted-foreground">
|
||||
<TableCell colSpan={3} className="py-12 text-center text-muted-foreground">
|
||||
조회된 필수 첨부파일 항목이 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -446,7 +446,7 @@ function AttachmentsFormSection({
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="outline" asChild>
|
||||
<span>파일 선택</span>
|
||||
<span>파일선택</span>
|
||||
</Button>
|
||||
</label>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user