Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a323c42acf | ||
|
|
095101ad2f | ||
|
|
7ee5ebd506 | ||
|
|
f90ef6297d | ||
|
|
90c1cb1f52 | ||
|
|
8c614b84a7 | ||
|
|
7d1a501ad8 | ||
|
|
48589274c8 | ||
|
|
83f7676db4 | ||
|
|
c8b30810f6 | ||
|
|
f71fa7d97a | ||
|
|
4c7c4c00ab | ||
|
|
ae341d66ba | ||
|
|
e346417c29 | ||
|
|
5fcc184a88 | ||
|
|
f6d3f89b5b | ||
|
|
106e09a7a2 | ||
|
|
d1b4600392 | ||
|
|
4204dfa624 | ||
|
|
d7fd5be068 | ||
|
|
735bb397ec | ||
|
|
8b5a2b713c | ||
|
|
c0c2d3d1c9 | ||
|
|
858844e81a | ||
|
|
1ff2731121 | ||
|
|
70db7662bb | ||
|
|
bb57ff72e7 | ||
|
|
7946c08c72 | ||
|
|
6f20ef7273 | ||
|
|
657ae77dde | ||
|
|
4ff80dd257 | ||
|
|
9bb1069d26 | ||
|
|
78d8fea7f5 | ||
|
|
22ed78c87b | ||
|
|
f5331d4c50 | ||
|
|
f32fe0ec5b | ||
|
|
3b914fbc2e | ||
|
|
533df0eb16 | ||
|
|
7b506320b4 | ||
|
|
2b7c497c3e | ||
|
|
4dee1309eb | ||
|
|
f3f8909e45 | ||
|
|
d45cd79422 | ||
|
|
7cbb58ff32 | ||
|
|
f5ce74d0ee | ||
|
|
3114280e83 | ||
|
|
c7b8b36f4a | ||
|
|
4f50593eac | ||
|
|
8818dd21c6 | ||
|
|
db8ee298d9 | ||
|
|
9eef99a435 | ||
|
|
b73b83a49f | ||
|
|
549debf8bf |
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { DesignListPage } from "@/pages/design-cnstn";
|
||||
|
||||
export default DesignListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<DesignListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { FnrsListPage } from "@/pages/fnrs-cnsltn";
|
||||
|
||||
export default FnrsListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<FnrsListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { InstlListPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default InstlListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<InstlListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { SewerBscPlanListPage } from "@/pages/sewer-bsc-plan";
|
||||
|
||||
export default SewerBscPlanListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<SewerBscPlanListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export {
|
||||
useFindUserGugunCombo,
|
||||
useFindUserSidoCombo,
|
||||
} from "./api/queries";
|
||||
export { useFindUserSidoGugunCombo } from "./lib/use-find-user-sido-gugun-combo";
|
||||
export type {
|
||||
CmCdAdstDstrComboResponseDto,
|
||||
CmCdAdstDstrService,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
|
||||
import { useFindUserGugunCombo, useFindUserSidoCombo } from "../api/queries";
|
||||
import type { CmCdAdstDstrComboResponseDto } from "../model/adstdstr-interface";
|
||||
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
export function useFindUserSidoGugunCombo(sidoComCd: string) {
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const sidoFilterUserInfo =
|
||||
userInfo?.dtlInstCd === "ROLE_USER04" || userInfo?.dtlInstCd === "ROLE_USER05"
|
||||
? userInfo
|
||||
: undefined;
|
||||
|
||||
const { data: userSidoData, isPending: isUserSidoPending } =
|
||||
useFindUserSidoCombo(sidoFilterUserInfo);
|
||||
const { data: userGugunData, isPending: isUserGugunPending } =
|
||||
useFindUserGugunCombo(sidoComCd);
|
||||
|
||||
return {
|
||||
userSidoData: userSidoData ?? EMPTY_ADST_LIST,
|
||||
userGugunData: userGugunData ?? EMPTY_ADST_LIST,
|
||||
isUserSidoPending,
|
||||
isUserGugunPending,
|
||||
};
|
||||
}
|
||||
@@ -103,6 +103,8 @@ function ElaprApprovalDialog({
|
||||
|
||||
const currentUserPrgrs = findCurrentUserPendingApprovalRequest(cyclDocPrgrsList, currentUserUuid);
|
||||
const canApprove = Boolean(currentUserPrgrs?.elaprPrgrsId);
|
||||
const canReject =
|
||||
currentUserPrgrs?.atrzSeComCd === "ATRZ03" || currentUserPrgrs?.atrzSeComCd === "ATRZ99";
|
||||
|
||||
const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined;
|
||||
const isBusy = isProcessing || savePrgrsSttsMutation.isPending;
|
||||
@@ -392,9 +394,11 @@ function ElaprApprovalDialog({
|
||||
<Button onClick={handleApprove} disabled={isBusy}>
|
||||
결재
|
||||
</Button>
|
||||
<Button color="red" onClick={handleReject} disabled={isBusy}>
|
||||
반려
|
||||
</Button>
|
||||
{canReject && (
|
||||
<Button color="red" onClick={handleReject} disabled={isBusy}>
|
||||
반려
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -17,8 +17,8 @@ const MAIN_STEP_LABELS = {
|
||||
} as const;
|
||||
|
||||
export const PROGRESS_STATUS_COLUMNS = [
|
||||
{ label: "설계자문 신청 (시군)", sttsComCd: "ER" },
|
||||
{ label: "기술검토", sttsComCd: "TE" },
|
||||
{ label: "설계자문 신청 (시군)", sttsComCd: "CT" },
|
||||
{ label: "기술검토", sttsComCd: "CS" },
|
||||
{ label: "기술검토 회신", sttsComCd: "CP" },
|
||||
];
|
||||
|
||||
@@ -38,7 +38,7 @@ function buildProgressBadges(prgrsSttsComCd: string): ProgressBadge[] {
|
||||
];
|
||||
}
|
||||
|
||||
if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "CS" || prgrsSttsComCd === "SL") {
|
||||
if (prgrsSttsComCd === "CT" || prgrsSttsComCd === "CS" || prgrsSttsComCd === "SL") {
|
||||
return [
|
||||
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
|
||||
{ label: MAIN_STEP_LABELS.techReview, state: "current" },
|
||||
@@ -61,6 +61,13 @@ function buildProgressBadges(prgrsSttsComCd: string): ProgressBadge[] {
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
@@ -110,7 +117,11 @@ export function createDesignCnstnColumns() {
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type DesignCnstnApplySaveRequestDto,
|
||||
designCnstnKeys,
|
||||
@@ -103,11 +103,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === DESIGN_CNSTN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/design-cnstn");
|
||||
router.push(getListHref("/design-cnstn"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/design-cnstn/${designCnstnDocNo}/edit`);
|
||||
router.push(withListQs(`/design-cnstn/${designCnstnDocNo}/edit`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: DesignCnstnViewButtonAction) {
|
||||
@@ -177,7 +177,7 @@ function FloatingActionButtons({
|
||||
action: DESIGN_CNSTN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/design-cnstn");
|
||||
router.push(getListHref("/design-cnstn"));
|
||||
}
|
||||
|
||||
function getReviewOpinionFormData() {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import {
|
||||
designCnstnKeys,
|
||||
type DesignCnstnMngDetResponseDto,
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { type DesignCnstnRegistFormData, designCnstnRegistSchema } from "../lib/design-cnstn-regist-schema";
|
||||
import {
|
||||
type DesignCnstnRegistFormData,
|
||||
designCnstnRegistSchema,
|
||||
} from "../lib/design-cnstn-regist-schema";
|
||||
import {
|
||||
buildFormPayload,
|
||||
getCreateDefaultValues,
|
||||
@@ -91,7 +94,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
|
||||
alert("저장되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: designCnstnKeys.all });
|
||||
router.push(`/design-cnstn/${designCnstnDocNo}/edit`);
|
||||
router.push(withListQs(`/design-cnstn/${designCnstnDocNo}/edit`));
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
@@ -115,7 +118,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: designCnstnKeys.all });
|
||||
router.push(`/design-cnstn/${designCnstnDocNo}`);
|
||||
router.push(withListQs(`/design-cnstn/${designCnstnDocNo}`));
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
alert("신청 중 오류가 발생했습니다.");
|
||||
@@ -144,7 +147,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
await designCnstnMngService.delete(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: designCnstnKeys.list() });
|
||||
router.push("/design-cnstn");
|
||||
router.push(getListHref("/design-cnstn"));
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
alert("삭제 중 오류가 발생했습니다.");
|
||||
@@ -184,7 +187,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/design-cnstn")}
|
||||
onClick={() => router.push(getListHref("/design-cnstn"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
@@ -217,7 +220,6 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindGugunCombo,
|
||||
useFindSidoCombo,
|
||||
} from "@/entities/adstdstr";
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import { useFindUserSidoGugunCombo } from "@/entities/adstdstr";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import type { DesignCnstnMngSearchDto } from "@/entities/design-cnstn";
|
||||
import {
|
||||
@@ -27,48 +24,58 @@ import { DesignList } from "./design-list";
|
||||
const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "designCnstnDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: DesignCnstnMngSearchDto = {
|
||||
bizNm: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
function DesignListPage() {
|
||||
const [bizNm, setBizNm] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<DesignCnstnMngSearchDto>(DEFAULT_SEARCH_PARAMS);
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
taskSeComCd: "",
|
||||
bizNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
};
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
function DesignListPage() {
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [bizNm, setBizNm] = useState(params.bizNm);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { userSidoData, userGugunData } = useFindUserSidoGugunCombo(sidoComCd);
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: DesignCnstnMngSearchDto = useMemo(
|
||||
() => ({
|
||||
bizNm: params.bizNm,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
bizNm,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -89,7 +96,7 @@ function DesignListPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{sidoOptions.map((option) => (
|
||||
{userSidoData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -111,7 +118,7 @@ function DesignListPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{gugunOptions.map((option) => (
|
||||
{userGugunData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -143,7 +150,7 @@ function DesignListPage() {
|
||||
<DesignList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type DesignCnstnMngResponseDto,
|
||||
type DesignCnstnMngSearchDto,
|
||||
@@ -43,7 +43,7 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
|
||||
const columns = useMemo(() => createDesignCnstnColumns(), []);
|
||||
|
||||
const handleRowClick = (row: DesignCnstnMngResponseDto) => {
|
||||
router.push(`/design-cnstn/${row.designCnstnDocNo}`);
|
||||
router.push(withListQs(`/design-cnstn/${row.designCnstnDocNo}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -83,7 +83,9 @@ function DesignViewPage({ id }: DesignViewPageProps) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<SectionCollapsibleToggle />
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
|
||||
@@ -7,15 +7,7 @@ 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 {
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { Input, Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
import type { DesignCnstnRegistFormData } from "../../lib/design-cnstn-regist-schema";
|
||||
|
||||
@@ -69,9 +61,9 @@ function BasicInfoFormSection() {
|
||||
aria-invalid={fieldState.invalid}
|
||||
className="w-[40rem]"
|
||||
/>
|
||||
<Button type="button" onClick={() => setIsPickerOpen(true)}>
|
||||
{/* <Button type="button" onClick={() => setIsPickerOpen(true)}>
|
||||
사업찾기
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
<FieldError errors={[fieldState.error]} />
|
||||
</Field>
|
||||
|
||||
@@ -102,6 +102,13 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N")
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
@@ -152,7 +159,11 @@ export function createFnrsCnsltnColumns() {
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
|
||||
@@ -48,8 +48,8 @@ export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "기술검토",
|
||||
KECO: "환경청검토",
|
||||
ENV: "환경청검토",
|
||||
KECO: "기술검토",
|
||||
};
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
AI_AUTO_SE,
|
||||
type AiAutoPrcsLatestResponseDto,
|
||||
@@ -219,11 +219,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === FNRS_CNSLTN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/fnrs-cnsltn");
|
||||
router.push(getListHref("/fnrs-cnsltn"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: FnrsCnsltnViewButtonAction) {
|
||||
@@ -314,7 +314,7 @@ function FloatingActionButtons({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/fnrs-cnsltn");
|
||||
router.push(getListHref("/fnrs-cnsltn"));
|
||||
}
|
||||
|
||||
async function handleEnvoSbmsnClick() {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
@@ -153,7 +153,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
|
||||
alert("보완 완료 처리되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${docNo}`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${docNo}`));
|
||||
} catch (error) {
|
||||
console.error("보완 완료 실패:", error);
|
||||
alert("보완 완료 처리 중 오류가 발생했습니다.");
|
||||
@@ -172,7 +172,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
|
||||
alert("저장되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`));
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
@@ -206,7 +206,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${fnrsCnsltnDocNo}`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${fnrsCnsltnDocNo}`));
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
alert("신청 중 오류가 발생했습니다.");
|
||||
@@ -235,7 +235,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
await fnrsCnsltnMngService.remove(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push("/fnrs-cnsltn");
|
||||
router.push(getListHref("/fnrs-cnsltn"));
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
alert("삭제 중 오류가 발생했습니다.");
|
||||
@@ -285,7 +285,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/fnrs-cnsltn")}
|
||||
onClick={() => router.push(getListHref("/fnrs-cnsltn"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
@@ -303,7 +303,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
onClick={handleApplyClick}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
신청완료
|
||||
신청 완료
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
@@ -350,7 +350,6 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
onClose={() => setIsPwrkCnsltnDocMngOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindGugunCombo,
|
||||
useFindSidoCombo,
|
||||
} from "@/entities/adstdstr";
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import { useFindUserSidoGugunCombo } from "@/entities/adstdstr";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import type { FnrsCnsltnMngSearchDto } from "@/entities/fnrs-cnsltn";
|
||||
import {
|
||||
@@ -27,48 +24,58 @@ import { FnrsList } from "./fnrs-list";
|
||||
const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "fnrsCnsltnDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: FnrsCnsltnMngSearchDto = {
|
||||
bizNm: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
function FnrsListPage() {
|
||||
const [bizNm, setBizNm] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<FnrsCnsltnMngSearchDto>(DEFAULT_SEARCH_PARAMS);
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
taskSeComCd: "",
|
||||
bizNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
};
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
function FnrsListPage() {
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [bizNm, setBizNm] = useState(params.bizNm);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { userSidoData, userGugunData } = useFindUserSidoGugunCombo(sidoComCd);
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: FnrsCnsltnMngSearchDto = useMemo(
|
||||
() => ({
|
||||
bizNm: params.bizNm,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
bizNm,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -89,7 +96,7 @@ function FnrsListPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{sidoOptions.map((option) => (
|
||||
{userSidoData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -111,7 +118,7 @@ function FnrsListPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{gugunOptions.map((option) => (
|
||||
{userGugunData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -143,7 +150,7 @@ function FnrsListPage() {
|
||||
<FnrsList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
FnrsCnsltnMngResponseDto,
|
||||
type FnrsCnsltnMngSearchDto,
|
||||
@@ -43,7 +43,7 @@ function FnrsList({ searchParams, pagination, onPaginationChange }: FnrsListProp
|
||||
const columns = useMemo(() => createFnrsCnsltnColumns(), []);
|
||||
|
||||
const handleRowClick = (row: FnrsCnsltnMngResponseDto) => {
|
||||
router.push(`/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -103,6 +103,13 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N")
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
@@ -153,7 +160,11 @@ export function createInstlSncnColumns() {
|
||||
columnHelper.accessor("bizNm", {
|
||||
header: "사업명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("taskSeCdNm", {
|
||||
header: "업무구분",
|
||||
|
||||
@@ -50,8 +50,8 @@ export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "기술검토",
|
||||
KECO: "환경청검토",
|
||||
ENV: "환경청검토",
|
||||
KECO: "기술검토",
|
||||
};
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
ElaprApprovalDialog,
|
||||
@@ -114,11 +114,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === INSTL_SNCN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/instl-sncn");
|
||||
router.push(getListHref("/instl-sncn"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/instl-sncn/${instlSncnDocNo}/edit`);
|
||||
router.push(withListQs(`/instl-sncn/${instlSncnDocNo}/edit`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: InstlSncnViewButtonAction) {
|
||||
@@ -212,7 +212,7 @@ function FloatingActionButtons({
|
||||
action: INSTL_SNCN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/instl-sncn");
|
||||
router.push(getListHref("/instl-sncn"));
|
||||
}
|
||||
|
||||
async function handleInstlSncnAncmntClick() {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
instlSncnKeys,
|
||||
@@ -30,8 +30,15 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { buildFormPayload, getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper";
|
||||
import { type InstlSncnRegistFormData, instlSncnRegistSchema } from "../lib/instl-sncn-regist-schema";
|
||||
import {
|
||||
buildFormPayload,
|
||||
getCreateDefaultValues,
|
||||
mapDetailToFormData,
|
||||
} from "../lib/form-data-mapper";
|
||||
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";
|
||||
@@ -94,7 +101,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
(item) => !item.splmntCn || !item.splmntCn.trim(),
|
||||
);
|
||||
if (hasEmptySplmntCn) {
|
||||
alert("보완요청내용이 있습니다. 보완완료내용을 입력해 주세요.");
|
||||
alert("보완 요청 내용이 있습니다.\n보완 완료 내용을 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,12 +116,12 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
const result = await submitSupplementForm(data);
|
||||
if (!result) return;
|
||||
|
||||
alert("보완완료 처리되었습니다.");
|
||||
router.push(`/instl-sncn/${docNo}`);
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
alert("보완 완료 처리되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
router.push(withListQs(`/instl-sncn/${docNo}`));
|
||||
} catch (error) {
|
||||
console.error("보완완료 실패:", error);
|
||||
alert("보완완료 처리 중 오류가 발생했습니다.");
|
||||
console.error("보완 완료 실패:", error);
|
||||
alert("보완 완료 처리 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -129,7 +136,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
if (!instlSncnDocNo) return;
|
||||
|
||||
alert("저장되었습니다.");
|
||||
router.push(`/instl-sncn/${instlSncnDocNo}/edit`);
|
||||
router.push(withListQs(`/instl-sncn/${instlSncnDocNo}/edit`));
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
@@ -158,7 +165,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
if (!instlSncnDocNo) return;
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
router.push(`/instl-sncn/${instlSncnDocNo}`);
|
||||
router.push(withListQs(`/instl-sncn/${instlSncnDocNo}`));
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
@@ -184,7 +191,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
await instlSncnService.remove(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.list() });
|
||||
router.push("/instl-sncn");
|
||||
router.push(getListHref("/instl-sncn"));
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
alert("삭제 중 오류가 발생했습니다.");
|
||||
@@ -227,7 +234,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/instl-sncn")}
|
||||
onClick={() => router.push(getListHref("/instl-sncn"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
@@ -245,7 +252,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
onClick={form.handleSubmit(onApply)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
신청완료
|
||||
신청 완료
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
@@ -264,7 +271,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
onClick={form.handleSubmit(onSplmntCmptn)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
보완완료
|
||||
보완 완료
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -272,7 +279,6 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import type { CmCdAdstDstrComboResponseDto } from "@/entities/adstdstr";
|
||||
import { useFindGugunCombo, useFindSidoCombo } from "@/entities/adstdstr";
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import { useFindUserSidoGugunCombo } from "@/entities/adstdstr";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import type { InstlSncnMngSearchParams } from "@/entities/instl-sncn";
|
||||
import {
|
||||
@@ -24,46 +24,58 @@ import { InstlList } from "./instl-list";
|
||||
const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "instlSncnDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: InstlSncnMngSearchParams = {
|
||||
bizNm: "",
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
bizNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
function InstlListPage() {
|
||||
const [bizNm, setBizNm] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<InstlSncnMngSearchParams>(DEFAULT_SEARCH_PARAMS);
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [bizNm, setBizNm] = useState(params.bizNm);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
const { userSidoData, userGugunData } = useFindUserSidoGugunCombo(sidoComCd);
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: InstlSncnMngSearchParams = useMemo(
|
||||
() => ({
|
||||
bizNm: params.bizNm,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
bizNm,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -75,7 +87,7 @@ function InstlListPage() {
|
||||
<Select
|
||||
value={sidoComCd}
|
||||
onValueChange={(value) => {
|
||||
setSidoComCd(value);
|
||||
setSidoComCd(value === ALL_SELECT_VALUE ? "" : value);
|
||||
setSggComCd("");
|
||||
}}
|
||||
>
|
||||
@@ -83,7 +95,8 @@ function InstlListPage() {
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sidoOptions.map((option) => (
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{userSidoData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -95,12 +108,17 @@ function InstlListPage() {
|
||||
<Label htmlFor="sggComCd" className="whitespace-nowrap">
|
||||
시군구
|
||||
</Label>
|
||||
<Select value={sggComCd} onValueChange={setSggComCd} disabled={!sidoComCd}>
|
||||
<Select
|
||||
value={sggComCd}
|
||||
onValueChange={(value) => setSggComCd(value === ALL_SELECT_VALUE ? "" : value)}
|
||||
disabled={!sidoComCd}
|
||||
>
|
||||
<SelectTrigger id="sggComCd" className="w-[12rem]">
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{gugunOptions.map((option) => (
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{userGugunData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -132,7 +150,7 @@ function InstlListPage() {
|
||||
<InstlList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type InstlSncnMngResponseDto,
|
||||
type InstlSncnMngSearchParams,
|
||||
@@ -43,7 +43,7 @@ function InstlList({ searchParams, pagination, onPaginationChange }: InstlListPr
|
||||
const columns = useMemo(() => createInstlSncnColumns(), []);
|
||||
|
||||
const handleRowClick = (row: InstlSncnMngResponseDto) => {
|
||||
router.push(`/instl-sncn/${row.instlSncnDocNo}`);
|
||||
router.push(withListQs(`/instl-sncn/${row.instlSncnDocNo}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -95,7 +95,9 @@ function InstlViewPage({ id }: InstlViewPageProps) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<SectionCollapsibleToggle />
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
|
||||
@@ -55,7 +55,7 @@ export function resolveNotiStatus(
|
||||
|
||||
return {
|
||||
label: "미조회",
|
||||
badgeProps: { color: "yellow" },
|
||||
badgeProps: { color: "yellow", size: "lg" },
|
||||
actionLabel: "처리하기",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,6 +101,13 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N")
|
||||
];
|
||||
}
|
||||
|
||||
function getProgressStatusBadge(prgrsSttsComCd?: string | null) {
|
||||
if (prgrsSttsComCd === "SL") {
|
||||
return <Badge color="orange">보완요청</Badge>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBadgeColor(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
@@ -151,7 +158,11 @@ export function createSewerBscPlanColumns() {
|
||||
columnHelper.accessor("sewerBscPlanNm", {
|
||||
header: "기본계획명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
cell: ({ row, getValue }) => (
|
||||
<>
|
||||
{getProgressStatusBadge(row.original.prgrsSttsComCd)} {formatOptional(getValue())}
|
||||
</>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("sewerBscPlanSeCdNm", {
|
||||
header: "계획구분",
|
||||
|
||||
@@ -44,8 +44,8 @@ export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "기술검토",
|
||||
KECO: "환경청검토",
|
||||
ENV: "환경청검토",
|
||||
KECO: "기술검토",
|
||||
};
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { type QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
ElaprApprovalDialog,
|
||||
@@ -116,11 +116,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/sewer-bsc-plan");
|
||||
router.push(getListHref("/sewer-bsc-plan"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/sewer-bsc-plan/${sewerBscPlanDocNo}/edit?chgCycl=${chgCycl}`);
|
||||
router.push(withListQs(`/sewer-bsc-plan/${sewerBscPlanDocNo}/edit?chgCycl=${chgCycl}`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: SewerBscPlanViewButtonAction) {
|
||||
@@ -201,7 +201,7 @@ function FloatingActionButtons({
|
||||
action: SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/sewer-bsc-plan");
|
||||
router.push(getListHref("/sewer-bsc-plan"));
|
||||
}
|
||||
|
||||
function getReviewOpinionFormData() {
|
||||
@@ -502,7 +502,9 @@ async function handleDmndSplmnt(
|
||||
context.request,
|
||||
);
|
||||
await context.queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
context.router.replace(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl}`);
|
||||
context.router.replace(
|
||||
withListQs(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl}`),
|
||||
);
|
||||
context.onSuccess?.();
|
||||
} catch (error) {
|
||||
context.onError?.(error as Error);
|
||||
|
||||
@@ -4,11 +4,7 @@ import { useEffect, useState } from "react";
|
||||
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindUserGugunCombo,
|
||||
useFindUserSidoCombo,
|
||||
} from "@/entities/adstdstr";
|
||||
import { useFindUserSidoGugunCombo } from "@/entities/adstdstr";
|
||||
import { useCodeList } from "@/entities/code";
|
||||
import {
|
||||
Button,
|
||||
@@ -48,8 +44,6 @@ interface SewerBscPlanApplyDialogProps {
|
||||
onConfirm: (selection: SewerBscPlanApplySelection) => void;
|
||||
}
|
||||
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlanApplyDialogProps) {
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
@@ -57,37 +51,48 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
const [sewerBscPlanSeComCd, setSewerBscPlanSeComCd] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
|
||||
const sidoFilterUserInfo =
|
||||
userInfo?.dtlInstCd === "ROLE_USER04" || userInfo?.dtlInstCd === "ROLE_USER05"
|
||||
? userInfo
|
||||
: undefined;
|
||||
|
||||
const { data: sidoData, isPending: isSidoPending } = useFindUserSidoCombo(sidoFilterUserInfo);
|
||||
const { data: gugunData, isPending: isGugunPending } = useFindUserGugunCombo(sidoComCd);
|
||||
const canSidoGugunSelectable = userInfo?.dtlInstCd !== "ROLE_USER05";
|
||||
const {
|
||||
userSidoData,
|
||||
userGugunData,
|
||||
isUserSidoPending,
|
||||
isUserGugunPending,
|
||||
} = useFindUserSidoGugunCombo(sidoComCd);
|
||||
const { data: planSeOptions, isPending: isPlanSePending } = useCodeList("SEWERBSCPLANSE");
|
||||
const { data: taskSeOptions, isPending: isTaskSePending } = useCodeList("SEWER");
|
||||
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
const isPending = isSidoPending || isPlanSePending || isTaskSePending;
|
||||
const isPending = isUserSidoPending || isPlanSePending || isTaskSePending;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setSidoComCd("");
|
||||
setSggComCd("");
|
||||
setSewerBscPlanSeComCd("");
|
||||
setTaskSeComCd("");
|
||||
}, [open]);
|
||||
|
||||
const canSidoGugunSelectable = userInfo?.dtlInstCd !== "ROLE_USER05";
|
||||
if (!canSidoGugunSelectable) return;
|
||||
setSidoComCd("");
|
||||
setSggComCd("");
|
||||
}, [open, canSidoGugunSelectable]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canSidoGugunSelectable || userSidoData.length === 0) return;
|
||||
const nextSidoComCd = userSidoData[0].adstDstrCd;
|
||||
setSidoComCd((current) => (current === nextSidoComCd ? current : nextSidoComCd));
|
||||
}, [canSidoGugunSelectable, userSidoData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canSidoGugunSelectable || !sidoComCd || userGugunData.length === 0) return;
|
||||
const nextSggComCd = userGugunData[0].adstDstrCd;
|
||||
setSggComCd((current) => current || nextSggComCd);
|
||||
}, [canSidoGugunSelectable, sidoComCd, userGugunData]);
|
||||
|
||||
const canConfirm =
|
||||
!!sewerBscPlanSeComCd && !!taskSeComCd && (!canSidoGugunSelectable || !!sidoComCd);
|
||||
|
||||
function handleConfirm() {
|
||||
if (!canConfirm) return;
|
||||
|
||||
const sidoOption = sidoOptions.find((option) => option.adstDstrCd === sidoComCd);
|
||||
const gugunOption = gugunOptions.find((option) => option.adstDstrCd === sggComCd);
|
||||
const sidoOption = userSidoData.find((option) => option.adstDstrCd === sidoComCd);
|
||||
const gugunOption = userGugunData.find((option) => option.adstDstrCd === sggComCd);
|
||||
const planSeOption = planSeOptions?.find((option) => option.lwpoCd === sewerBscPlanSeComCd);
|
||||
const taskSeOption = taskSeOptions?.find((option) => option.lwpoCd === taskSeComCd);
|
||||
|
||||
@@ -135,7 +140,7 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sidoOptions.map((option) => (
|
||||
{userSidoData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -148,13 +153,13 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
<Select
|
||||
value={sggComCd || undefined}
|
||||
onValueChange={setSggComCd}
|
||||
disabled={!sidoComCd || isGugunPending}
|
||||
disabled={!sidoComCd || isUserGugunPending}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{gugunOptions.map((option) => (
|
||||
{userGugunData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
@@ -123,7 +123,9 @@ function SewerBscPlanFormPage({
|
||||
if (!result?.sewerBscPlanDocNo) return;
|
||||
|
||||
alert("저장되었습니다.");
|
||||
router.push(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}/edit?chgCycl=${result.chgCycl}`);
|
||||
router.push(
|
||||
withListQs(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}/edit?chgCycl=${result.chgCycl}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
@@ -162,7 +164,9 @@ function SewerBscPlanFormPage({
|
||||
if (!result?.sewerBscPlanDocNo) return;
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
router.push(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl ?? 1}`);
|
||||
router.push(
|
||||
withListQs(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl ?? 1}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
@@ -179,7 +183,7 @@ function SewerBscPlanFormPage({
|
||||
(item) => item.cmptnYn === "N" && (!item.splmntCn || !item.splmntCn.trim()),
|
||||
);
|
||||
if (hasEmptySplmntCn) {
|
||||
alert("보완요청 항목이 있습니다.\n보완완료 내용을 입력해 주세요.");
|
||||
alert("보완 요청 항목이 있습니다.\n보완 완료 내용을 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,13 +201,13 @@ function SewerBscPlanFormPage({
|
||||
const result = await sewerBscPlanService.submitSupplement(docNo, payload);
|
||||
if (!result) return;
|
||||
|
||||
alert("보완완료 처리되었습니다.");
|
||||
alert("보완 완료 처리되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
const chgCycl = data.sewerBscPlanDtl.chgCycl ?? initialData?.detDto?.chgCycl ?? 1;
|
||||
router.push(`/sewer-bsc-plan/${docNo}?chgCycl=${chgCycl}`);
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
router.push(withListQs(`/sewer-bsc-plan/${docNo}?chgCycl=${chgCycl}`));
|
||||
} catch (error) {
|
||||
console.error("보완완료 실패:", error);
|
||||
alert("보완완료 처리 중 오류가 발생했습니다.");
|
||||
console.error("보완 완료 실패:", error);
|
||||
alert("보완 완료 처리 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -228,7 +232,7 @@ function SewerBscPlanFormPage({
|
||||
setIsSubmitting(true);
|
||||
await sewerBscPlanService.delete(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
router.push("/sewer-bsc-plan");
|
||||
router.push(getListHref("/sewer-bsc-plan"));
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.list() });
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
@@ -240,7 +244,7 @@ function SewerBscPlanFormPage({
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="mt-6 space-y-4">
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
@@ -274,7 +278,7 @@ function SewerBscPlanFormPage({
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/sewer-bsc-plan")}
|
||||
onClick={() => router.push(getListHref("/sewer-bsc-plan"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
@@ -292,7 +296,7 @@ function SewerBscPlanFormPage({
|
||||
onClick={handleApplyClick}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
신청완료
|
||||
신청 완료
|
||||
</Button>
|
||||
<Button
|
||||
color="red"
|
||||
@@ -311,7 +315,7 @@ function SewerBscPlanFormPage({
|
||||
onClick={form.handleSubmit(onSplmntCmptn)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
보완완료
|
||||
보완 완료
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -319,7 +323,6 @@ function SewerBscPlanFormPage({
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
<RhfDevTool control={form.control} />
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindGugunCombo,
|
||||
useFindSidoCombo,
|
||||
} from "@/entities/adstdstr";
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import { useFindUserSidoGugunCombo } from "@/entities/adstdstr";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import type { SewerBscPlanSearchRequestDto } from "@/entities/sewer-bsc-plan";
|
||||
import {
|
||||
@@ -27,52 +24,62 @@ import { SewerBscPlanList } from "./sewer-bsc-plan-list";
|
||||
const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "sewerBscPlanDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: SewerBscPlanSearchRequestDto = {
|
||||
sewerBscPlanNm: "",
|
||||
sewerBscPlanSeComCd: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
function SewerBscPlanListPage() {
|
||||
const [sewerBscPlanNm, setSewerBscPlanNm] = useState("");
|
||||
const [sewerBscPlanSeComCd, setSewerBscPlanSeComCd] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] =
|
||||
useState<SewerBscPlanSearchRequestDto>(DEFAULT_SEARCH_PARAMS);
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
sewerBscPlanSeComCd: "",
|
||||
taskSeComCd: "",
|
||||
sewerBscPlanNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
};
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
function SewerBscPlanListPage() {
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [sewerBscPlanNm, setSewerBscPlanNm] = useState(params.sewerBscPlanNm);
|
||||
const [sewerBscPlanSeComCd, setSewerBscPlanSeComCd] = useState(params.sewerBscPlanSeComCd);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { userSidoData, userGugunData } = useFindUserSidoGugunCombo(sidoComCd);
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: SewerBscPlanSearchRequestDto = useMemo(
|
||||
() => ({
|
||||
sewerBscPlanNm: params.sewerBscPlanNm,
|
||||
sewerBscPlanSeComCd: params.sewerBscPlanSeComCd,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
sewerBscPlanNm,
|
||||
sewerBscPlanSeComCd,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -93,7 +100,7 @@ function SewerBscPlanListPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{sidoOptions.map((option) => (
|
||||
{userSidoData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -115,7 +122,7 @@ function SewerBscPlanListPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_SELECT_VALUE}>전체</SelectItem>
|
||||
{gugunOptions.map((option) => (
|
||||
{userGugunData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -163,7 +170,7 @@ function SewerBscPlanListPage() {
|
||||
<SewerBscPlanList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type SewerBscPlanResponseDto,
|
||||
type SewerBscPlanSearchRequestDto,
|
||||
@@ -47,7 +47,7 @@ function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: Sewe
|
||||
const columns = useMemo(() => createSewerBscPlanColumns(), []);
|
||||
|
||||
const handleRowClick = (row: SewerBscPlanResponseDto) => {
|
||||
router.push(`/sewer-bsc-plan/${row.sewerBscPlanDocNo}?chgCycl=${row.chgCycl}`);
|
||||
router.push(withListQs(`/sewer-bsc-plan/${row.sewerBscPlanDocNo}?chgCycl=${row.chgCycl}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -102,7 +102,9 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<SectionCollapsibleToggle />
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
export { getCookie, removeCookie, setCookie } from "./cookie";
|
||||
export { dayjs } from "./dayjs";
|
||||
export { getListHref, withListQs } from "./list-return-href";
|
||||
export { matchQueryStatus } from "./match-query-status";
|
||||
export { queryClient } from "./query-client";
|
||||
export { useAlertStore } from "./use-alert-store";
|
||||
export { useDataTable } from "./use-data-table";
|
||||
export { useMenuStore } from "./use-menu-store";
|
||||
export { useMutationWrapper } from "./use-mutation-wrapper";
|
||||
export { useSearchParamsState } from "./use-search-params-state";
|
||||
export { getDefaultFileKey, useTus } from "./use-tus";
|
||||
export { useUserStore } from "./use-user-store";
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 목록 조회 조건을 sessionStorage에 저장하고, 목록 복귀 시 복원하기 위한 헬퍼.
|
||||
*
|
||||
* - 목록 → 상세: 현재 목록 쿼리를 `list-qs-{listPath}` 키로 저장
|
||||
* - 상세 → 수정·저장 후 이동: storage를 건드리지 않음 (같은 탭에서 유지)
|
||||
* - 목록/삭제 후 이동: storage에서 읽어 `/listPath?bizNm=...` 로 복원
|
||||
*/
|
||||
|
||||
function toListSlug(listPath: string) {
|
||||
return listPath.replace(/^\/+|\/+$/g, "").replaceAll("/", "-");
|
||||
}
|
||||
|
||||
function storageKey(listPath: string) {
|
||||
return `list-qs-${toListSlug(listPath)}`;
|
||||
}
|
||||
|
||||
/** 상세/수정 href에서 목록 slug를 추출한다. */
|
||||
function inferListPath(href: string) {
|
||||
const path = href.split("?")[0] ?? "";
|
||||
const segments = path.split("/").filter(Boolean);
|
||||
return segments[0] ?? "";
|
||||
}
|
||||
|
||||
function isOnListPage(slug: string) {
|
||||
if (typeof window === "undefined" || !slug) return false;
|
||||
const { pathname } = window.location;
|
||||
return pathname === `/${slug}` || pathname === `/${slug}/`;
|
||||
}
|
||||
|
||||
/** 목록에 있을 때 현재 쿼리를 저장하고, href는 그대로 반환한다. */
|
||||
export function withListQs(href: string) {
|
||||
if (typeof window === "undefined") return href;
|
||||
|
||||
const slug = inferListPath(href);
|
||||
if (!isOnListPage(slug)) return href;
|
||||
|
||||
const search = window.location.search.replace(/^\?/, "");
|
||||
if (search) sessionStorage.setItem(storageKey(slug), search);
|
||||
else sessionStorage.removeItem(storageKey(slug));
|
||||
|
||||
return href;
|
||||
}
|
||||
|
||||
/** sessionStorage에 저장된 목록 쿼리로 href를 만든다. */
|
||||
export function getListHref(listPath: string) {
|
||||
if (typeof window === "undefined") return listPath;
|
||||
|
||||
const listQs = sessionStorage.getItem(storageKey(listPath));
|
||||
return listQs ? `${listPath}?${listQs}` : listPath;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
/**
|
||||
* 조회 조건을 URL searchParams와 동기화하는 훅.
|
||||
*
|
||||
* - URL이 단일 소스 오브 트루스(SSOT)이며, 새로고침·뒤로가기 시에도 조건이 유지된다.
|
||||
* - 기본값과 동일한 값은 URL에서 제거해 쿼리스트링을 깨끗하게 유지한다.
|
||||
* - `window.history.replaceState` 기반 shallow 갱신으로 서버 라운드트립 없이 동작한다.
|
||||
* - 숫자 기본값이 주어진 키는 자동으로 `Number` 파싱한다.
|
||||
*
|
||||
* @example
|
||||
* const { params, setParams, resetParams } = useSearchParamsState({
|
||||
* keyword: "",
|
||||
* page: 0,
|
||||
* size: 10,
|
||||
* });
|
||||
*/
|
||||
export function useSearchParamsState<T extends Record<string, ParamValue>>(
|
||||
defaults: T,
|
||||
): UseSearchParamsStateReturn<T> {
|
||||
const searchParams = useSearchParams();
|
||||
const defaultsRef = useRef(defaults);
|
||||
|
||||
const params = useMemo(
|
||||
() => parseParams(searchParams, defaultsRef.current),
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
const setParams = useCallback((partial: Partial<T>) => {
|
||||
const next = new URLSearchParams(window.location.search);
|
||||
for (const [key, value] of Object.entries(partial)) {
|
||||
if (value === undefined || value === defaultsRef.current[key]) {
|
||||
next.delete(key);
|
||||
} else {
|
||||
next.set(key, String(value));
|
||||
}
|
||||
}
|
||||
replaceUrl(next);
|
||||
}, []);
|
||||
|
||||
const resetParams = useCallback(() => {
|
||||
const next = new URLSearchParams(window.location.search);
|
||||
for (const key of Object.keys(defaultsRef.current)) {
|
||||
next.delete(key);
|
||||
}
|
||||
replaceUrl(next);
|
||||
}, []);
|
||||
|
||||
return { params, setParams, resetParams };
|
||||
}
|
||||
|
||||
function parseParams<T extends Record<string, ParamValue>>(
|
||||
searchParams: ReturnType<typeof useSearchParams>,
|
||||
defaults: T,
|
||||
): T {
|
||||
const result = { ...defaults };
|
||||
|
||||
for (const key of Object.keys(defaults) as (keyof T)[]) {
|
||||
const raw = searchParams?.get(key as string) ?? null;
|
||||
if (raw === null) continue;
|
||||
|
||||
if (typeof defaults[key] === "number") {
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isNaN(parsed)) result[key] = parsed as T[keyof T];
|
||||
} else {
|
||||
result[key] = raw as T[keyof T];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function replaceUrl(searchParams: URLSearchParams) {
|
||||
const qs = searchParams.toString();
|
||||
const url = qs
|
||||
? `${window.location.pathname}?${qs}`
|
||||
: window.location.pathname;
|
||||
window.history.replaceState(null, "", url);
|
||||
}
|
||||
|
||||
type ParamValue = string | number | null;
|
||||
|
||||
interface UseSearchParamsStateReturn<T> {
|
||||
params: T;
|
||||
setParams: (partial: Partial<T>) => void;
|
||||
resetParams: () => void;
|
||||
}
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
SectionCollapsible,
|
||||
UploadedFileChip,
|
||||
} from "@/shared/ui";
|
||||
import { attachService, useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import {
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflCd,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
Button,
|
||||
@@ -373,32 +377,26 @@ function AttachmentsFormSection({
|
||||
return (
|
||||
<SectionCollapsible title="첨부파일">
|
||||
<div className="space-y-6">
|
||||
<div className="px-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="44%" />
|
||||
<col width="12%" />
|
||||
<col width="44%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]" align="left">
|
||||
파일명
|
||||
</TableHead>
|
||||
<TableHead>파일 확장자</TableHead>
|
||||
<TableHead>파일첨부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{essentialFiles.length === 0 ? (
|
||||
{essentialFiles.length > 0 && (
|
||||
<div className="px-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="44%" />
|
||||
<col width="12%" />
|
||||
<col width="44%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="py-12 text-center text-muted-foreground">
|
||||
조회된 필수 첨부파일 항목이 없습니다.
|
||||
</TableCell>
|
||||
<TableHead className="w-[30rem]" align="left">
|
||||
파일명
|
||||
</TableHead>
|
||||
<TableHead>파일 확장자</TableHead>
|
||||
<TableHead>파일첨부</TableHead>
|
||||
</TableRow>
|
||||
) : (
|
||||
essentialFiles.map((file) => {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{essentialFiles.map((file) => {
|
||||
const atflCd = file.atchFileGroupId ?? "";
|
||||
|
||||
return (
|
||||
@@ -445,11 +443,11 @@ function AttachmentsFormSection({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="px-2">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
@@ -468,9 +466,7 @@ function AttachmentsFormSection({
|
||||
fileName={item.file?.name ?? item.fileName}
|
||||
isUploading={isUploading[item.fileKey] ?? false}
|
||||
onRemove={() => void removeEtcFile(idx)}
|
||||
onDownload={
|
||||
item.atflId ? () => downloadByAtflId(item.atflId!) : undefined
|
||||
}
|
||||
onDownload={item.atflId ? () => downloadByAtflId(item.atflId!) : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,7 @@ import { useEffect, useState } from "react";
|
||||
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
import { Field, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindUserGugunCombo,
|
||||
useFindUserSidoCombo,
|
||||
} from "@/entities/adstdstr";
|
||||
import { useFindUserSidoGugunCombo } from "@/entities/adstdstr";
|
||||
import { useCodeList } from "@/entities/code";
|
||||
import {
|
||||
Button,
|
||||
@@ -46,8 +42,6 @@ interface PbadmsTaskApplyDialogProps {
|
||||
codeUpCd?: string;
|
||||
}
|
||||
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
function PbadmsTaskApplyDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -60,18 +54,15 @@ function PbadmsTaskApplyDialog({
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
|
||||
|
||||
const sidoFilterUserInfo =
|
||||
userInfo?.dtlInstCd === "ROLE_USER04" || userInfo?.dtlInstCd === "ROLE_USER05"
|
||||
? userInfo
|
||||
: undefined;
|
||||
|
||||
const { data: sidoData, isPending: isSidoPending } = useFindUserSidoCombo(sidoFilterUserInfo);
|
||||
const { data: gugunData, isPending: isGugunPending } = useFindUserGugunCombo(sidoComCd);
|
||||
const {
|
||||
userSidoData,
|
||||
userGugunData,
|
||||
isUserSidoPending,
|
||||
isUserGugunPending,
|
||||
} = useFindUserSidoGugunCombo(sidoComCd);
|
||||
const { data: taskOptions, isPending: isTaskSePending } = useCodeList(codeUpCd);
|
||||
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
const isPending = isSidoPending || isTaskSePending;
|
||||
const isPending = isUserSidoPending || isTaskSePending;
|
||||
const canSidoGugunSelectable = userInfo?.dtlInstCd !== "ROLE_USER05";
|
||||
const canConfirm = selectedCodes.length > 0 && (!canSidoGugunSelectable || !!sidoComCd);
|
||||
|
||||
@@ -93,8 +84,8 @@ function PbadmsTaskApplyDialog({
|
||||
function handleConfirm() {
|
||||
if (!canConfirm) return;
|
||||
|
||||
const sidoOption = sidoOptions.find((option) => option.adstDstrCd === sidoComCd);
|
||||
const gugunOption = gugunOptions.find((option) => option.adstDstrCd === sggComCd);
|
||||
const sidoOption = userSidoData.find((option) => option.adstDstrCd === sidoComCd);
|
||||
const gugunOption = userGugunData.find((option) => option.adstDstrCd === sggComCd);
|
||||
|
||||
onConfirm({
|
||||
admdstCd: sggComCd || sidoComCd,
|
||||
@@ -137,7 +128,7 @@ function PbadmsTaskApplyDialog({
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sidoOptions.map((option) => (
|
||||
{userSidoData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
@@ -150,13 +141,13 @@ function PbadmsTaskApplyDialog({
|
||||
<Select
|
||||
value={sggComCd || undefined}
|
||||
onValueChange={setSggComCd}
|
||||
disabled={!sidoComCd || isGugunPending}
|
||||
disabled={!sidoComCd || isUserGugunPending}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{gugunOptions.map((option) => (
|
||||
{userGugunData.map((option) => (
|
||||
<SelectItem key={option.adstDstrCd} value={option.adstDstrCd}>
|
||||
{option.adstDstrNm}
|
||||
</SelectItem>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { create } from "zustand";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PplLengthContent, SewerageService } from "../model/sn-interface";
|
||||
import type { BasinStofcbrCountContent, PplLengthContent, SewerageService } from "../model/sn-interface";
|
||||
import type { OldPplChartData, mapPplOldType, mapPplOldItem } from "../model/sn-interface";
|
||||
|
||||
export type SnState = {
|
||||
@@ -35,26 +35,7 @@ const initialUsePplChartData: OldPplChartData = {
|
||||
};
|
||||
const initialOldPplChartData: OldPplChartData = {
|
||||
labels: ["금강", "한강", "낙동강", "영산강"],
|
||||
datasets: [
|
||||
{
|
||||
label: "20-25년",
|
||||
data: [1140, 3181, 3100, 1662],
|
||||
backgroundColor: "#3B82F6",
|
||||
borderRadius: 4
|
||||
},
|
||||
{
|
||||
label: "25-30년",
|
||||
data: [1391, 3615, 1814, 1411],
|
||||
backgroundColor: "#EF4444",
|
||||
borderRadius: 4
|
||||
},
|
||||
{
|
||||
label: "30년이상",
|
||||
data: [2014, 10303, 9831, 2000],
|
||||
backgroundColor: "#F87171",
|
||||
borderRadius: 4
|
||||
}
|
||||
]
|
||||
datasets: []
|
||||
};
|
||||
type OldPplStoreState = {
|
||||
chartData: OldPplChartData;
|
||||
@@ -70,23 +51,122 @@ export const useUsePplStore = create<OldPplStoreState>((set) => ({
|
||||
}));
|
||||
|
||||
export const initailMapOldPplData: mapPplOldType = {
|
||||
colTitle: { totalLen: "총길이", pplLen2025: "20~25년", pplLen2530: "25~30년", pplLen30: "30년 이상" }, //헤더 영역
|
||||
colTitle: { stofcbrCount: "우수토실개소수", totalLen: "하수관로총연장" },
|
||||
data: [
|
||||
{ name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30: 2014 },
|
||||
{ name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30: 10303 },
|
||||
{ name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30: 9831 },
|
||||
{ name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30: 2000 },
|
||||
{ name: "금강", totalLen: 0, stofcbrCount: 0 },
|
||||
{ name: "한강", totalLen: 0, stofcbrCount: 0 },
|
||||
{ name: "낙동강", totalLen: 0, stofcbrCount: 0 },
|
||||
{ name: "영산강", totalLen: 0, stofcbrCount: 0 },
|
||||
]
|
||||
};
|
||||
type mapPplOldState = {
|
||||
mapData: mapPplOldItem[];
|
||||
setMapData: (mapData: mapPplOldItem[]) => void;
|
||||
setStofcbrCounts: (counts: BasinStofcbrCountContent[]) => void;
|
||||
};
|
||||
export const useMapOldPplStore = create<mapPplOldState>((set) => ({
|
||||
mapData: initailMapOldPplData.data,
|
||||
setMapData: (newMapData) => set({ mapData: newMapData }),
|
||||
setMapData: (newMapData) => set((state) => ({
|
||||
mapData: newMapData.map((item) => ({
|
||||
...item,
|
||||
stofcbrCount: state.mapData.find((current) => current.name === item.name)?.stofcbrCount ?? item.stofcbrCount,
|
||||
})),
|
||||
})),
|
||||
setStofcbrCounts: (counts) => set((state) => ({
|
||||
mapData: state.mapData.map((item) => ({
|
||||
...item,
|
||||
stofcbrCount: counts.find((count) => count.zoneNm === item.name)?.stofcbrCount ?? item.stofcbrCount,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
const DEFAULT_BASIN_ORDER = ["금강", "한강", "낙동강", "영산강"];
|
||||
const YEAR_GROUP_START_COLOR = "#FACC15";
|
||||
const YEAR_GROUP_END_COLOR = "#F87171";
|
||||
|
||||
/**
|
||||
* 시작색과 종료색 사이를 RGB 기준으로 보간해 구간 수만큼 색상을 생성합니다.
|
||||
* 구간이 하나면 시작색을, 둘 이상이면 첫 구간은 파란색·마지막 구간은 빨간색으로 고정합니다.
|
||||
*/
|
||||
function buildYearGroupColors(count: number): string[] {
|
||||
if (count <= 0) return [];
|
||||
if (count === 1) return [YEAR_GROUP_START_COLOR];
|
||||
|
||||
const parseHex = (color: string) => [
|
||||
Number.parseInt(color.slice(1, 3), 16),
|
||||
Number.parseInt(color.slice(3, 5), 16),
|
||||
Number.parseInt(color.slice(5, 7), 16),
|
||||
];
|
||||
const toHex = (value: number) => Math.round(value).toString(16).padStart(2, "0");
|
||||
const start = parseHex(YEAR_GROUP_START_COLOR);
|
||||
const end = parseHex(YEAR_GROUP_END_COLOR);
|
||||
|
||||
return Array.from({ length: count }, (_, index) => {
|
||||
const ratio = index / (count - 1);
|
||||
return `#${start.map((channel, channelIndex) => {
|
||||
const endChannel = end[channelIndex] ?? channel;
|
||||
return toHex(channel + (endChannel - channel) * ratio);
|
||||
}).join("")}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 별도 정렬순서가 없는 MV 계약에서 구간명의 첫 숫자를 기준으로 표시순서를 결정합니다.
|
||||
* 숫자가 없는 비정형 구간은 숫자 구간 뒤에 한글 오름차순으로 배치합니다.
|
||||
*/
|
||||
function compareYearGroups(left: string, right: string): number {
|
||||
const leftNumber = left.match(/\d+/)?.[0];
|
||||
const rightNumber = right.match(/\d+/)?.[0];
|
||||
|
||||
if (leftNumber !== undefined && rightNumber !== undefined) {
|
||||
const numberDifference = Number(leftNumber) - Number(rightNumber);
|
||||
if (numberDifference !== 0) return numberDifference;
|
||||
} else if (leftNumber !== undefined) {
|
||||
return -1;
|
||||
} else if (rightNumber !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return left.localeCompare(right, "ko");
|
||||
}
|
||||
|
||||
/**
|
||||
* MV 응답의 yearGroup 목록으로 노후관로 차트 데이터를 동적으로 생성합니다.
|
||||
* 구간명이나 구간 개수가 바뀌어도 고정 매핑 없이 동일한 로직을 사용합니다.
|
||||
*/
|
||||
export function buildOldPplChartData(items: PplLengthContent[]): OldPplChartData {
|
||||
const responseZones = [...new Set(items.map((item) => item.zoneNm).filter(Boolean))];
|
||||
const labels = [
|
||||
...DEFAULT_BASIN_ORDER,
|
||||
...responseZones.filter((zone) => !DEFAULT_BASIN_ORDER.includes(zone)).sort((left, right) => left.localeCompare(right, "ko")),
|
||||
];
|
||||
const yearGroups = [
|
||||
...new Set(
|
||||
items
|
||||
.map((item) => item.yearGroup?.trim())
|
||||
.filter((yearGroup): yearGroup is string => Boolean(yearGroup)),
|
||||
),
|
||||
].sort(compareYearGroups);
|
||||
const colors = buildYearGroupColors(yearGroups.length);
|
||||
|
||||
const lengthByZoneAndGroup = new Map<string, number>();
|
||||
items.forEach((item) => {
|
||||
const yearGroup = item.yearGroup?.trim();
|
||||
if (!item.zoneNm || !yearGroup) return;
|
||||
lengthByZoneAndGroup.set(`${item.zoneNm}\u0000${yearGroup}`, Number(item.sumPrlgVl) || 0);
|
||||
});
|
||||
|
||||
return {
|
||||
labels,
|
||||
datasets: yearGroups.map((yearGroup, index) => ({
|
||||
label: yearGroup,
|
||||
data: labels.map((zone) => lengthByZoneAndGroup.get(`${zone}\u0000${yearGroup}`) ?? 0),
|
||||
backgroundColor: colors[index] ?? YEAR_GROUP_START_COLOR,
|
||||
borderRadius: 4,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* OldPplChartData를 지도 팝업 데이터 형태인 mapPplOldItem[]으로 변환합니다.
|
||||
* @param chartData 차트 데이터
|
||||
@@ -96,22 +176,16 @@ export function transformChartDataToMapData(chartData: OldPplChartData): mapPplO
|
||||
const mapData: mapPplOldItem[] = [];
|
||||
const { labels, datasets } = chartData;
|
||||
|
||||
const pplLen2025Data = datasets.find(d => d.label === '20-25년')?.data || [];
|
||||
const pplLen2530Data = datasets.find(d => d.label === '25-30년')?.data || [];
|
||||
const pplLen30Data = datasets.find(d => d.label === '30년이상')?.data || [];
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const pplLen2025 = pplLen2025Data[index] || 0;
|
||||
const pplLen2530 = pplLen2530Data[index] || 0;
|
||||
const pplLen30 = pplLen30Data[index] || 0;
|
||||
const totalLen = pplLen2025 + pplLen2530 + pplLen30;
|
||||
const totalLen = datasets.reduce((sum, dataset) => {
|
||||
const value = dataset.data[index];
|
||||
return sum + (typeof value === "number" ? value : 0);
|
||||
}, 0);
|
||||
|
||||
mapData.push({
|
||||
name: label,
|
||||
totalLen,
|
||||
pplLen2025,
|
||||
pplLen2530,
|
||||
pplLen30,
|
||||
stofcbrCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,7 +245,7 @@ export class SewerageApiService implements SewerageService {
|
||||
}));
|
||||
return list;
|
||||
}
|
||||
// 유역별 노후도별 관로 길이(km) MV조회 : yearGroup - 20년~25년미만,25년~30년미만,30년이상
|
||||
// 유역별 노후도별 관로 길이(km) MV조회: yearGroup은 MV가 제공하는 동적 구간명
|
||||
public async getMvOldPplLength(): Promise<PplLengthContent[]> {
|
||||
const response = await client.get<{
|
||||
PplLengthList: PplLengthContent[];
|
||||
@@ -184,6 +258,18 @@ export class SewerageApiService implements SewerageService {
|
||||
return list;
|
||||
}
|
||||
|
||||
// CM 대시보드 유역별 우수토실 개소수 조회
|
||||
public async getBasinStofcbrCount(): Promise<BasinStofcbrCountContent[]> {
|
||||
const response = await client.get<{
|
||||
basinStofcbrCountList: BasinStofcbrCountContent[];
|
||||
}>(ENDPOINT.SEWERAGE_SERVICE.BASIN_STOFCBR_COUNT);
|
||||
|
||||
return (response.data.basinStofcbrCountList ?? []).map((item) => ({
|
||||
...item,
|
||||
stofcbrCount: Number(item.stofcbrCount),
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const SewerageApi = new SewerageApiService();
|
||||
|
||||
@@ -12,7 +12,7 @@ import { svcInfo } from "../model/map-data";
|
||||
import type { mapOperFcltType, mapReuseMapType} from "../model/fo-interface";
|
||||
import type { mapPplOldType } from "../model/sn-interface";
|
||||
import { useMapOperFcltStore, OperationApi, useMapReuseMapStore } from "../api/fo-service";
|
||||
import { useMapOldPplStore } from "../api/sn-service";
|
||||
import { SewerageApi, useMapOldPplStore } from "../api/sn-service";
|
||||
import {Button} from "@common/components";
|
||||
|
||||
const SVG_URL = "/images/region_map.svg"; // public 폴더에 region_map.svg 위치 필요
|
||||
@@ -31,12 +31,12 @@ const mapOperFcltData: mapOperFcltType = {
|
||||
]
|
||||
};
|
||||
const mapPplOldData: mapPplOldType = {
|
||||
colTitle: {totalLen: "총길이",pplLen2025: "20~25년",pplLen2530: "25~30년",pplLen30 : "30년 이상" }, //헤더 영역
|
||||
colTitle: {stofcbrCount: "우수토실개소수", totalLen: "하수관로총연장" },
|
||||
data: [
|
||||
{name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30 : 2014},
|
||||
{name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30 : 10303},
|
||||
{name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30 : 9831},
|
||||
{name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30 : 2000},
|
||||
{name: "금강", totalLen: 0, stofcbrCount: 0},
|
||||
{name: "한강", totalLen: 0, stofcbrCount: 0},
|
||||
{name: "낙동강", totalLen: 0, stofcbrCount: 0},
|
||||
{name: "영산강", totalLen: 0, stofcbrCount: 0},
|
||||
]
|
||||
};
|
||||
const mapReuseMapData: mapReuseMapType = {
|
||||
@@ -62,17 +62,18 @@ export function KoreaBasinMap() {
|
||||
const { selectedBasin, setSelectedBasin } = useStore(basinStore);
|
||||
const selectedBasinKey = basinKeyMap[selectedBasin];
|
||||
const {mapOperFclt} = useMapOperFcltStore();
|
||||
const {mapData: mapPplOld} = useMapOldPplStore();
|
||||
const {mapData: mapPplOld, setStofcbrCounts} = useMapOldPplStore();
|
||||
const {setMapData: setMapReuseMapData} = useMapReuseMapStore();
|
||||
mapOperFcltData.data = mapOperFclt;
|
||||
mapPplOldData.data = mapPplOld;
|
||||
// mapReuseMapData.data = mapReuseMap;
|
||||
let standardYear = '2024';
|
||||
const [standardYear, setStandardYear] = useState('2024');
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
standardYear = await OperationApi.getStandardYear();
|
||||
const mapReuseMapRslt = await OperationApi.getReuseMap(standardYear);
|
||||
const fetchedStandardYear = await OperationApi.getStandardYear();
|
||||
setStandardYear(fetchedStandardYear);
|
||||
const mapReuseMapRslt = await OperationApi.getReuseMap(fetchedStandardYear);
|
||||
setMapReuseMapData(mapReuseMapRslt);
|
||||
mapReuseMapData.data = mapReuseMapRslt;
|
||||
} catch (e) {
|
||||
@@ -80,7 +81,13 @@ export function KoreaBasinMap() {
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
}, [setMapReuseMapData]);
|
||||
|
||||
useEffect(() => {
|
||||
SewerageApi.getBasinStofcbrCount()
|
||||
.then(setStofcbrCounts)
|
||||
.catch((e) => console.error("유역별 우수토실 개소수 조회 실패", e));
|
||||
}, [setStofcbrCounts]);
|
||||
// 유역정보
|
||||
const showFor = useCallback((g: SVGGElement) => {
|
||||
const activeService = services.find(s => s.active);
|
||||
@@ -104,24 +111,16 @@ export function KoreaBasinMap() {
|
||||
});
|
||||
} else if (activeService.svcId === 'sn') { // 하수관로
|
||||
const item = mapPplOldData.data.find(d => d.name === basinName);
|
||||
newPopupContent.title = `${standardYear}년도 ${basinName}유역 노후하수관로 현황`;
|
||||
newPopupContent.title = `${standardYear}년도 ${basinName}유역 하수관로 현황`;
|
||||
const colTitles = mapPplOldData.colTitle;
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.stofcbrCount,
|
||||
data: item ? `${fmt(item.stofcbrCount)}개소` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.totalLen,
|
||||
data: item ? `${fmt(item.totalLen)}km` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.pplLen2025,
|
||||
data: item ? `${fmt(item.pplLen2025)}km` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.pplLen2530,
|
||||
data: item ? `${fmt(item.pplLen2530)}km` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.pplLen30,
|
||||
data: item ? `${fmt(item.pplLen30)}km` : '-',
|
||||
});
|
||||
} else if (activeService.svcId === 'wr') { // 물재이용
|
||||
const item = mapReuseMapData.data.find(d => d.name === basinName);
|
||||
newPopupContent.title = `${standardYear}년도 ${basinName}유역 물재이용 현황`;
|
||||
@@ -160,7 +159,7 @@ export function KoreaBasinMap() {
|
||||
);
|
||||
popupRef.current.style.left = x + "px";
|
||||
}
|
||||
}, [services, mapOperFclt]);
|
||||
}, [services, mapOperFclt, mapPplOld, standardYear]);
|
||||
|
||||
// selectedBasinKey prop이 변경될 때 지도를 업데이트하는 useEffect
|
||||
useEffect(() => {
|
||||
@@ -410,8 +409,14 @@ export function KoreaBasinMap() {
|
||||
};
|
||||
// 스타일은 필요에 따라 tailwind, css 등으로 커스터마이즈하세요.
|
||||
return (
|
||||
<div className="col-span-3 !p-0 relative z-10" style={{ height: "765px", background: "rgba(255, 255, 255, 0.6)" }}>
|
||||
<div className="card card-border" style={{ height: "100%" }}>
|
||||
<div
|
||||
className="absolute inset-0 z-10 col-span-3 h-full !p-0"
|
||||
style={{ background: "rgba(255, 255, 255, 0.6)" }}
|
||||
>
|
||||
<div
|
||||
className="card card-border"
|
||||
style={{ height: "calc(100% - 1.2rem)" }}
|
||||
>
|
||||
<div className="card-header text-foreground">
|
||||
<h3 className="text-2xl font-medium">유역별 정보</h3>
|
||||
|
||||
|
||||
@@ -6,11 +6,17 @@ export interface PplLengthContent {
|
||||
yearGroup?: string;
|
||||
}
|
||||
|
||||
export interface BasinStofcbrCountContent {
|
||||
zoneNm: string;
|
||||
stofcbrCount: number;
|
||||
}
|
||||
|
||||
export interface SewerageService {
|
||||
getPplLength(ymdFrom: string, ymdTo: string, useCd: string): Promise<PplLengthContent[]>;
|
||||
getPplUseLength(ymdFrom: string, ymdTo: string, useCd: string): Promise<PplLengthContent[]>;
|
||||
getMvUsgPplLength(): Promise<PplLengthContent[]>;
|
||||
getMvOldPplLength(): Promise<PplLengthContent[]>;
|
||||
getBasinStofcbrCount(): Promise<BasinStofcbrCountContent[]>;
|
||||
}
|
||||
|
||||
export interface ChartDataSet {
|
||||
@@ -37,17 +43,13 @@ type SnOldPplLength = {
|
||||
export type mapPplOldItem = {
|
||||
name: string;
|
||||
totalLen: number;
|
||||
pplLen2025: number;
|
||||
pplLen2530: number;
|
||||
pplLen30: number;
|
||||
stofcbrCount: number;
|
||||
};
|
||||
|
||||
export type mapPplOldType = {
|
||||
colTitle: {
|
||||
totalLen: string; // 총길이
|
||||
pplLen2025: string; // 20~25년
|
||||
pplLen2530: string; // 25~30년
|
||||
pplLen30: string; // 30년 이상
|
||||
stofcbrCount: string; // 우수토실개소수
|
||||
totalLen: string; // 총연장
|
||||
};
|
||||
data: mapPplOldItem[];
|
||||
};
|
||||
@@ -82,4 +84,4 @@ export function buildSnOldPplLength(
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ function DashboardPage() {
|
||||
<div className="p-2 sm:p-4 md:p-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* 첫번째 라인 - 좌측 6개 패널(2x3) + 우측 지도/사용자 정보 */}
|
||||
<div className="flex flex-col gap-1 lg:flex-row">
|
||||
<div className="flex flex-col gap-1 lg:flex-row lg:items-stretch">
|
||||
{/* 좌측: 드래그 가능한 6개 패널 (2x3 그리드, 화면의 50%) */}
|
||||
<div className="w-full lg:w-1/2">
|
||||
{isMounted ? (
|
||||
@@ -173,9 +173,9 @@ function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* 우측: 고정된 지도와 사용자 정보 (화면의 50%) */}
|
||||
<div className="flex w-full gap-1 lg:w-1/2">
|
||||
<div className="flex w-full flex-col items-stretch gap-1 lg:w-1/2 lg:flex-row">
|
||||
{/* 지도영역 - 고정 (드래그 불가) */}
|
||||
<div className="flex-1">
|
||||
<div className="relative min-h-[765px] w-full self-stretch lg:min-h-0 lg:flex-1">
|
||||
<KoreaBasinMap />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from "chart.js";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { Button } from "@common/components";
|
||||
import type { PplLengthContent, ChartDataSet, OldPplChartData } from "../model/sn-interface";
|
||||
import { SewerageApi, useOldPplStore,useMapOldPplStore, transformChartDataToMapData } from "../api/sn-service";
|
||||
import type { PplLengthContent } from "../model/sn-interface";
|
||||
import { buildOldPplChartData, SewerageApi, useOldPplStore,useMapOldPplStore, transformChartDataToMapData } from "../api/sn-service";
|
||||
import { useDragHandle } from "./sortable-panel";
|
||||
|
||||
|
||||
@@ -41,45 +41,7 @@ export default function SnOldPpl() {
|
||||
useEffect(() => {
|
||||
if (oldPplLenData.length === 0) return;
|
||||
|
||||
// 유역 목록 추출 (중복 제거)
|
||||
const allZones = [...new Set(oldPplLenData.map(d => d.zoneNm))];
|
||||
|
||||
// yearGroup 매핑: API 응답값 -> 차트 label
|
||||
const yearGroupMap: { [key: string]: { label: string; color: string } } = {
|
||||
"20년~25년미만": { label: "20-25년", color: "#3B82F6" },
|
||||
"25년~30년미만": { label: "25-30년", color: "#EF4444" },
|
||||
"30년이상": { label: "30년이상", color: "#F87171" },
|
||||
};
|
||||
|
||||
// 유역별, 노후년도별 데이터 구조화
|
||||
const dataByZone: { [zone: string]: { [yearGroup: string]: number } } = {};
|
||||
allZones.forEach(zone => {
|
||||
dataByZone[zone] = {};
|
||||
});
|
||||
|
||||
oldPplLenData.forEach(item => {
|
||||
let zoneData = dataByZone[item.zoneNm];
|
||||
if (!zoneData) {
|
||||
zoneData = {};
|
||||
dataByZone[item.zoneNm] = zoneData;
|
||||
}
|
||||
if (item.yearGroup) {
|
||||
zoneData[item.yearGroup] = item.sumPrlgVl;
|
||||
}
|
||||
});
|
||||
|
||||
// 데이터셋 생성
|
||||
const newDatasets: ChartDataSet[] = Object.entries(yearGroupMap).map(([yearGroup, { label, color }]) => ({
|
||||
label,
|
||||
data: allZones.map(zone => dataByZone[zone]?.[yearGroup] ?? 0),
|
||||
backgroundColor: color,
|
||||
borderRadius: 4,
|
||||
}));
|
||||
|
||||
const newChartData: OldPplChartData = {
|
||||
labels: allZones,
|
||||
datasets: newDatasets,
|
||||
};
|
||||
const newChartData = buildOldPplChartData(oldPplLenData);
|
||||
|
||||
setChartData(newChartData);
|
||||
|
||||
@@ -110,9 +72,7 @@ export default function SnOldPpl() {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
min: 0,
|
||||
max: 10000,
|
||||
ticks: {
|
||||
stepSize: 2000,
|
||||
callback: function (value) {
|
||||
return Number(value).toLocaleString() + 'km';
|
||||
}
|
||||
@@ -192,4 +152,4 @@ export default function SnOldPpl() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ export const ENDPOINT = {
|
||||
OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts",
|
||||
MV_USG_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvUsgPplLength",
|
||||
MV_OLD_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvOldPplLength",
|
||||
BASIN_STOFCBR_COUNT: "/sewerage-service/api/v1/ms/basinStofcbrCount",
|
||||
},
|
||||
OPERATION_SERVICE: {
|
||||
MAIN_YEAR: "/operation-service/api/v1/dashBoard/main/year",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
"use client";
|
||||
|
||||
export { PostPage as NoticePage } from "@/widgets/post";
|
||||
import { PostPage } from "@/widgets/post";
|
||||
|
||||
export function NoticePage({ bbsId }: { bbsId: string }) {
|
||||
return <PostPage bbsId={bbsId} tableCaption="공지사항 목록" />;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,13 @@ import { columns as tableColumns } from "../model/post-table-columns";
|
||||
import { createColumns as createGridViewColumns } from "../model/grid-view-table-columns";
|
||||
import { SortSelect } from "./sort-select";
|
||||
|
||||
function PostPage({ bbsId }: { bbsId: string }) {
|
||||
function PostPage({
|
||||
bbsId,
|
||||
tableCaption,
|
||||
}: {
|
||||
bbsId: string;
|
||||
tableCaption?: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { confirm } = useConfirm();
|
||||
@@ -116,8 +122,6 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
meta: { bbsTypeCd: bbsInfo?.bbsTypeCd },
|
||||
});
|
||||
|
||||
const DataTableComponent = isGridViewPage ? GridViewDataTable : DataTable;
|
||||
|
||||
const onPostDelete = async () => {
|
||||
const selectedPosts = table
|
||||
.getFilteredSelectedRowModel()
|
||||
@@ -245,11 +249,20 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
{isLoading ? (
|
||||
<Skeleton className="h-[36rem] w-full" />
|
||||
) : (
|
||||
<DataTableComponent
|
||||
table={table}
|
||||
onRowClick={handleRowClick}
|
||||
viewPagination
|
||||
/>
|
||||
isGridViewPage ? (
|
||||
<GridViewDataTable
|
||||
table={table}
|
||||
onRowClick={handleRowClick}
|
||||
viewPagination
|
||||
/>
|
||||
) : (
|
||||
<DataTable
|
||||
table={table}
|
||||
onRowClick={handleRowClick}
|
||||
viewPagination
|
||||
caption={tableCaption}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { PageableList, PageableParams } from "@/shared/model";
|
||||
|
||||
export interface AuthorizationsParams extends PageableParams {
|
||||
keywordType?: string;
|
||||
keyword?: string;
|
||||
authorizationName?: string;
|
||||
urlPatternValue?: string;
|
||||
httpMethodCode?: string;
|
||||
useYn?: string;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { columns } from "../model/authorizations-table-columns";
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
authorizationName: "",
|
||||
urlPatternValue: "",
|
||||
httpMethodCode: "",
|
||||
sort: "regDt,desc",
|
||||
page: 0,
|
||||
@@ -36,6 +37,9 @@ function ModulePage() {
|
||||
const [authorizationName, setAuthorizationName] = useState(
|
||||
params.authorizationName,
|
||||
);
|
||||
const [urlPatternValue, setUrlPatternValue] = useState(
|
||||
params.urlPatternValue,
|
||||
);
|
||||
const [httpMethodCode, setHttpMethodCode] = useState(params.httpMethodCode);
|
||||
|
||||
const query = useQuery({
|
||||
@@ -43,6 +47,7 @@ function ModulePage() {
|
||||
queryFn: () =>
|
||||
AuthorizationsApi.getAuthorizationsList({
|
||||
authorizationName: params.authorizationName,
|
||||
urlPatternValue: params.urlPatternValue,
|
||||
httpMethodCode: params.httpMethodCode,
|
||||
sort: params.sort,
|
||||
page: params.page,
|
||||
@@ -63,11 +68,12 @@ function ModulePage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setParams({ authorizationName, httpMethodCode, page: 0 });
|
||||
setParams({ authorizationName, urlPatternValue, httpMethodCode, page: 0 });
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setAuthorizationName("");
|
||||
setUrlPatternValue("");
|
||||
setHttpMethodCode("");
|
||||
resetParams();
|
||||
};
|
||||
@@ -113,12 +119,23 @@ function ModulePage() {
|
||||
API명
|
||||
</Label>
|
||||
<Input
|
||||
id="keyword"
|
||||
id="authorizationName"
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={authorizationName}
|
||||
onChange={(e) => setAuthorizationName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="urlPatternValue" className="text-label">
|
||||
API URL
|
||||
</Label>
|
||||
<Input
|
||||
id="urlPatternValue"
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={urlPatternValue}
|
||||
onChange={(e) => setUrlPatternValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMenuStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import { BoardApi } from "@/entities/board";
|
||||
import { PostApi } from "@/entities/post";
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
@@ -12,14 +19,11 @@ import {
|
||||
RadioGroupItem,
|
||||
Skeleton,
|
||||
} from "@common/components";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import { PostApi } from "@/entities/post";
|
||||
import { BoardApi } from "@/entities/board";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper, useMenuStore } from "@/shared/lib";
|
||||
|
||||
import { PostEvaluationApi } from "../api/post-evaluation-service";
|
||||
|
||||
import { PostAction } from "./post-action";
|
||||
import { PostComment } from "./post-comment";
|
||||
import { PostEvaluationApi } from "../api/post-evaluation-service";
|
||||
|
||||
function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const router = useRouter();
|
||||
@@ -255,7 +259,9 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
)}
|
||||
</div>
|
||||
</PostAction>
|
||||
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
|
||||
{bbsInfo?.cmntUseYn === "Y" && (
|
||||
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+3
-1
@@ -134,6 +134,8 @@ Codex 작업 루트는 가능하면 `../../../../../`로 잡고, 공통 시작
|
||||
|
||||
## 작업 원칙
|
||||
|
||||
- 일반 목록·모든 팝업은 공통 AGENTS.md의 서버 페이징·지도·Excel 계약을 따른다. 지도 전체 표출에 현재 그리드 페이지 배열을 재사용하지 않는다. Excel은 적용조건 전체를 사용하고 대량 원시 자료는 서버 생성으로 분리한다. 화면 페이징/페이징 없음 예외는 사유와 검증 범위를 메뉴 > 화면명별로 기록한다.
|
||||
|
||||
1. 모든 변경은 원본 요구사항정의서와 화면설계서, 2차 요구사항, 기능 명세 또는 승인된 작업 문서를 근거로 한다.
|
||||
2. `apps/sn`과 `packages/common/business_components` 범위를 넘는 수정은 하지 않는다.
|
||||
3. 공통 패키지 수정은 `sn` 요구사항을 충족하기 위한 최소 범위로 제한한다.
|
||||
@@ -228,7 +230,7 @@ VSCode나 별도 터미널에서 `sn` Next 개발 서버가 실행 중이면 `ne
|
||||
|
||||
### 프론트엔드 실행 프로세스 관리
|
||||
|
||||
- 모든 세션·역할·문서/검증 섹션에 공통 D:/workspace/AGENTS.md의 테스트 임시 파일·결과 파일 정리 기준을 적용한다. 일회성 검증 스크립트·캡처·Network/서버 로그·원시 결과 파일은 최종 보고서에 요약 후 삭제한다. 정식 회귀 테스트·최종 산출물·사용자 원본/복구 백업은 유지한다. 사용자 dev server가 쓰는 .next·공유 캐시·node_modules는 임시 검증물이라는 이유로 포괄 삭제하지 않는다.
|
||||
- 모든 세션·역할·문서/검증 섹션에 공통 D:/workspace/AGENTS.md의 테스트 파일 정리 기준을 적용한다. 에이전트가 테스트를 위해 만든 파일은 정식 회귀 테스트·재사용 가능한 소스·fixture·mock·임시 라우트를 포함해 위치와 관계없이 결과를 요약한 뒤 작업 종료 전에 삭제한다(2026-09-23 사용자 지시). 전용 빈 폴더와 끊어진 실행 명령도 정리한다. 명시 보존 요청 파일·최종 산출물·사용자 원본/복구 백업은 유지한다. 기존 타 작업자 테스트 자산은 삭제 권한을 먼저 확인한다. 사용자 dev server의 .next·공유 캐시·node_modules는 포괄 삭제하지 않는다.
|
||||
|
||||
- Codex 에이전트가 작업 또는 검증을 위해 직접 기동한 `next dev`, `dev:sn`, Turborepo dev와 기타 프론트엔드 장시간 실행 프로세스는 작업 종료 전에 반드시 종료한다.
|
||||
- 셸, 백그라운드, 숨김 창 등 VSCode 밖에서 에이전트가 기동한 프로세스도 종료 대상이다.
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { NoFooterLayout } from "@common/components";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { useMenuStore } from "@/shared/lib";
|
||||
import useStore from '../store/useStore';
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { authService } from "@/entities/auth";
|
||||
|
||||
export default function MapLayout({
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@
|
||||
"use client";
|
||||
|
||||
import * as XLSX from "xlsx";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import {
|
||||
getAgedPipeStatusByCtpvValidationMessage,
|
||||
normalizeAgedPipeStatusByCtpvFilters,
|
||||
type AgedPipeStatusByCtpvFilters,
|
||||
type AgedPipeStatusByCtpvResponse,
|
||||
} from "./agedPipeStatusByCtpvUtils";
|
||||
} from "./aged-pipe-status-by-ctpv-utils";
|
||||
|
||||
/** 적용조건의 시도별 노후관로현황 전체를 조회해 연도 그룹 Excel로 생성한다. */
|
||||
export const downloadAgedPipeStatusByCtpvExcel = async (
|
||||
+8
-8
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { getSnRowNumber } from "@/shared/lib/row-number";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import {
|
||||
getStandardRowNumber,
|
||||
StandardDataTable,
|
||||
StandardPopupLayout,
|
||||
} from "@common/business_components";
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
type ColumnDef,
|
||||
type PaginationState,
|
||||
} from "@tanstack/react-table";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import {
|
||||
AGED_PIPE_STATUS_BY_CTPV_DEFAULT_ELAPSED_YEARS,
|
||||
buildAgedPipeStatusYearOptions,
|
||||
@@ -32,9 +32,9 @@ import {
|
||||
type AgedPipeStatusByCtpvFilters,
|
||||
type AgedPipeStatusByCtpvResponse,
|
||||
type AgedPipeStatusByCtpvRow,
|
||||
} from "./agedPipeStatusByCtpvUtils";
|
||||
import { downloadAgedPipeStatusByCtpvExcel } from "./agedPipeStatusByCtpvExport";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
} from "./aged-pipe-status-by-ctpv-utils";
|
||||
import { downloadAgedPipeStatusByCtpvExcel } from "./aged-pipe-status-by-ctpv-export";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
|
||||
// CS 목록과 동일하게 한 페이지에는 최대 10개 시도만 표시한다.
|
||||
const AGED_PIPE_STATUS_PAGE_SIZE = 10;
|
||||
@@ -71,7 +71,7 @@ export default function AgedPipeStatusByCtpvPopupPage() {
|
||||
id: "rowNumber",
|
||||
header: "순번",
|
||||
cell: ({ row }) =>
|
||||
getStandardRowNumber({
|
||||
getSnRowNumber({
|
||||
total: rows.length,
|
||||
rowIndex: row.index,
|
||||
}),
|
||||
+3
-7
@@ -1,13 +1,9 @@
|
||||
/** 노후관로 목록의 엑셀 다운로드 데이터와 서식을 생성한다. */
|
||||
"use client";
|
||||
|
||||
import { postBlobData } from "@/components/lib/api";
|
||||
import { fileDownload } from "@/components/lib/utils/fileDownload";
|
||||
import {
|
||||
AGED_PIPE_EXCEL_FIELDS,
|
||||
normalizeAgedPipeFilters,
|
||||
type AgedPipeFilters,
|
||||
} from "./agedPipeUtils";
|
||||
import { postBlobData } from "@/shared/api/file-api";
|
||||
import { fileDownload } from "@/components/lib/utils/file-download";
|
||||
import { AGED_PIPE_EXCEL_FIELDS, normalizeAgedPipeFilters, type AgedPipeFilters } from "@/entities/facility/lib/aged-pipe-utils";
|
||||
|
||||
type AgedPipeExportProgress = {
|
||||
fileName?: string;
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { validateElapsedYears, type AgedPipeFilters } from "./agedPipeUtils";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
import { validateElapsedYears, type AgedPipeFilters } from "@/entities/facility/lib/aged-pipe-utils";
|
||||
import LayerSettingCard from "../layer-setting/layer-setting-card";
|
||||
|
||||
type MaybePromise<T = void> = T | Promise<T>;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/** 노후관로의 지도와 목록이 사용하는 DB 레이어 설정 계약이다. */
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import {
|
||||
buildAgedPipeCqlFilters,
|
||||
isAgedPipeLayerNode,
|
||||
type AgedPipeFilters,
|
||||
} from "@/entities/facility/lib/aged-pipe-utils";
|
||||
|
||||
/** DB 응답을 그대로 보존하며 최초 표출 전에 필터만 해석한다. */
|
||||
export const prepareAgedPipeLayerRows = (
|
||||
rows: any[],
|
||||
filters: AgedPipeFilters,
|
||||
): any[] =>
|
||||
rows.map((row) => {
|
||||
const next = { ...row };
|
||||
if (isAgedPipeLayerNode(row)) {
|
||||
next.agedPipeFilterTemplate = row.flterSql;
|
||||
try {
|
||||
validateAgedPipeLayer(row);
|
||||
next.flterSql = buildAgedPipeCqlFilters(filters, row.flterSql).join(
|
||||
" AND ",
|
||||
);
|
||||
} catch (error) {
|
||||
// 노후관로 DB 미전환이 다른 시설물 레이어까지 중단시키지 않도록 이 행만 차단한다.
|
||||
next.agedPipeConfigError =
|
||||
error instanceof Error ? error.message : "노후관로 레이어 설정 오류";
|
||||
next.actlLyrYn = "N";
|
||||
next.flterSql = "EXCLUDE";
|
||||
}
|
||||
}
|
||||
if (Array.isArray(row.children))
|
||||
next.children = prepareAgedPipeLayerRows(row.children, filters);
|
||||
return next;
|
||||
});
|
||||
|
||||
/** 환경마다 달라질 수 있는 일련번호 대신 DB 업무명을 통해 설정 행을 찾는다. */
|
||||
export const findAgedPipeLayer = (rows: any[]): any | undefined => {
|
||||
for (const row of rows) {
|
||||
if (isAgedPipeLayerNode(row)) return row;
|
||||
const child = Array.isArray(row.children)
|
||||
? findAgedPipeLayer(row.children)
|
||||
: undefined;
|
||||
if (child) return child;
|
||||
}
|
||||
};
|
||||
|
||||
/** 예전 제어용 행을 실제 WFS 레이어처럼 요청하지 않도록 전환 상태를 확인한다. */
|
||||
export const validateAgedPipeLayer = (row: any) => {
|
||||
if (row?.agedPipeConfigError) throw new Error(row.agedPipeConfigError);
|
||||
if (
|
||||
!row ||
|
||||
row.actlLyrYn !== "Y" ||
|
||||
row.srvcNm !== "wfs" ||
|
||||
!row.lyrTyNm ||
|
||||
row.lyrTyNm === "tb_old_pipe"
|
||||
) {
|
||||
throw new Error(
|
||||
"노후관로 DB 레이어 설정을 실제 WFS 레이어와 sql_filt 템플릿으로 갱신해 주세요.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** 독립 목록 팝업도 부모창의 하드코딩 없이 동일 설정 API를 조회한다. */
|
||||
export const fetchAgedPipeLayer = async () => {
|
||||
const rows = await getRawJson(
|
||||
"/sewerage-service/api/v1/map/lyrStngs/SN_NETWORK",
|
||||
);
|
||||
const row = findAgedPipeLayer(Array.isArray(rows) ? rows : []);
|
||||
validateAgedPipeLayer(row);
|
||||
return row;
|
||||
};
|
||||
+79
-39
@@ -3,36 +3,40 @@
|
||||
* 노후관로를 검색하고 목록·엑셀 다운로드·지도 위치 확인 기능을 제공한다.
|
||||
*/
|
||||
"use client";
|
||||
import { useFacilityCodeCatalog } from "@/entities/facility/lib/use-facility-code-catalog";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import useStore from "@/store/useStore";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import {
|
||||
AGED_PIPE_DEFAULT_FILTERS,
|
||||
AGED_PIPE_LAYER_TYPE,
|
||||
AGED_PIPE_LIST_HEADLIST,
|
||||
AGED_PIPE_PAGE_SIZE,
|
||||
buildAgedPipeCqlFilters,
|
||||
getAgedPipeManagementName,
|
||||
normalizeAgedPipeFilters,
|
||||
toDisplayPipeRow,
|
||||
validateElapsedYears,
|
||||
type AgedPipeFilters,
|
||||
type AgedPipeRow,
|
||||
} from "./agedPipeUtils";
|
||||
import { downloadAgedPipeExcel } from "./agedPipeExport";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
type AgedPipeManagementAgency,
|
||||
} from "@/entities/facility/lib/aged-pipe-utils";
|
||||
import { fetchAgedPipeLayer } from "./aged-pipe-layer";
|
||||
import { downloadAgedPipeExcel } from "./aged-pipe-export";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
|
||||
const POPUP_ROW_SELECT_MESSAGE = "SN_AGED_PIPE_ROW_SELECT";
|
||||
const POPUP_MINIMIZE_MESSAGE = "SN_AGED_PIPE_POPUP_MINIMIZE";
|
||||
const POPUP_FILTER_SYNC_MESSAGE = "SN_AGED_PIPE_FILTER_SYNC";
|
||||
|
||||
export default function AgedPipePopupPage() {
|
||||
const facilityCodes = useFacilityCodeCatalog();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const searchParams = useSearchParams();
|
||||
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
|
||||
@@ -53,6 +57,18 @@ export default function AgedPipePopupPage() {
|
||||
const [appliedFilters, setAppliedFilters] =
|
||||
useState<AgedPipeFilters>(initialFilters);
|
||||
const [rows, setRows] = useState<AgedPipeRow[]>([]);
|
||||
// 관리기관명 조회가 늦게 완료되어도 기존 목록을 재조회하지 않고 표시만 갱신한다.
|
||||
const [managementAgencies, setManagementAgencies] = useState<
|
||||
AgedPipeManagementAgency[]
|
||||
>([]);
|
||||
const displayRows = useMemo(
|
||||
() =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
mngInstNm: getAgedPipeManagementName(row, managementAgencies),
|
||||
})),
|
||||
[rows, managementAgencies],
|
||||
);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -73,15 +89,34 @@ export default function AgedPipePopupPage() {
|
||||
if (sgg.length === 0) fetchSgg();
|
||||
}, [ctpv.length, fetchCtpv, fetchSgg, sgg.length]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
// 기존 GIS 관리 화면과 같은 기관 목록을 사용하고, 실패 시 관로 조회는 유지한다.
|
||||
getData("v1/fcltPpl/mngInsts")
|
||||
.then((response) => {
|
||||
if (active)
|
||||
setManagementAgencies(Array.isArray(response) ? response : []);
|
||||
})
|
||||
.catch(() => {
|
||||
if (active)
|
||||
alert("관리기관명을 불러오지 못해 관리기관 코드로 표시합니다.");
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [alert]);
|
||||
|
||||
const fetchRows = useCallback(
|
||||
async (page: number, nextFilters: AgedPipeFilters, size: number) => {
|
||||
const normalized = normalizeAgedPipeFilters(nextFilters);
|
||||
// 독립 팝업에서도 현재 DB의 원천 레이어와 필터 템플릿을 사용한다.
|
||||
const layer = await fetchAgedPipeLayer();
|
||||
const queryParams = new URLSearchParams({
|
||||
lyrTypeNm: AGED_PIPE_LAYER_TYPE,
|
||||
lyrTypeNm: layer.lyrTyNm,
|
||||
page: String(page - 1),
|
||||
size: String(size),
|
||||
});
|
||||
const cqlFilters = buildAgedPipeCqlFilters(normalized);
|
||||
const cqlFilters = buildAgedPipeCqlFilters(normalized, layer.flterSql);
|
||||
if (cqlFilters.length > 0) {
|
||||
queryParams.append("cqlFiter", cqlFilters.join(" and "));
|
||||
}
|
||||
@@ -92,12 +127,12 @@ export default function AgedPipePopupPage() {
|
||||
const total = Number(response?.count ?? 0);
|
||||
const rows = responseRows.map(
|
||||
(item: Record<string, unknown>, index: number) =>
|
||||
toDisplayPipeRow(item, index, page, ctpv, sgg),
|
||||
toDisplayPipeRow(item, index, page, ctpv, sgg, facilityCodes),
|
||||
);
|
||||
|
||||
return { rows, total };
|
||||
},
|
||||
[ctpv, sgg],
|
||||
[ctpv, sgg, facilityCodes],
|
||||
);
|
||||
|
||||
const loadRows = useCallback(
|
||||
@@ -115,13 +150,18 @@ export default function AgedPipePopupPage() {
|
||||
setAppliedFilters(normalizeAgedPipeFilters(nextFilters));
|
||||
} catch (error) {
|
||||
console.error("노후관로 목록 조회 중 오류:", error);
|
||||
alert(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "노후관로 목록 조회 중 오류가 발생했습니다.",
|
||||
);
|
||||
setRows([]);
|
||||
setTotal(0);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchRows, filters],
|
||||
[alert, fetchRows, filters],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -247,31 +287,6 @@ export default function AgedPipePopupPage() {
|
||||
) : null}
|
||||
|
||||
<PopupSearchPanel contentClassName="flex flex-wrap items-end gap-x-5 gap-y-3">
|
||||
<label className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap text-[1.3rem] text-label">
|
||||
경과년도
|
||||
</span>
|
||||
<Input
|
||||
className="w-[12rem]"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
autoComplete="off"
|
||||
value={String(filters.elapsedYears ?? "")}
|
||||
onChange={(event) => {
|
||||
const digits = event.target.value.replace(/[^0-9]/g, "");
|
||||
setFilters({
|
||||
...filters,
|
||||
elapsedYears:
|
||||
digits === "" ? ("" as unknown as number) : Number(digits),
|
||||
});
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void handleSearch(1);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap text-[1.3rem] text-label">
|
||||
시도
|
||||
@@ -316,6 +331,31 @@ export default function AgedPipePopupPage() {
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap text-[1.3rem] text-label">
|
||||
경과년도
|
||||
</span>
|
||||
<Input
|
||||
className="w-[12rem]"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
autoComplete="off"
|
||||
value={String(filters.elapsedYears ?? "")}
|
||||
onChange={(event) => {
|
||||
const digits = event.target.value.replace(/[^0-9]/g, "");
|
||||
setFilters({
|
||||
...filters,
|
||||
elapsedYears:
|
||||
digits === "" ? ("" as unknown as number) : Number(digits),
|
||||
});
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void handleSearch(1);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -341,7 +381,7 @@ export default function AgedPipePopupPage() {
|
||||
</PopupSearchPanel>
|
||||
|
||||
<MapPopupDataTable
|
||||
rows={rows as Array<AgedPipeRow & Record<string, unknown>>}
|
||||
rows={displayRows}
|
||||
headlist={AGED_PIPE_LIST_HEADLIST}
|
||||
title="노후관로 목록"
|
||||
total={total}
|
||||
+3
-3
@@ -17,13 +17,13 @@ import {
|
||||
Waves,
|
||||
} from "lucide-react";
|
||||
|
||||
import { createPredictionAnalysisTimeRange } from "@/features/fldngPredcPrst/model/analysisTimeRange";
|
||||
import { createPredictionAnalysisTimeRange } from "@/features/fldng-predc-prst/model/analysis-time-range";
|
||||
import {
|
||||
windowPopupDefinitions,
|
||||
type WindowPopupKey,
|
||||
} from "@/features/windowPopup/model/popupDefinitions";
|
||||
} from "@/features/window-popup/model/popup-definitions";
|
||||
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
|
||||
type Props = {
|
||||
onOpenPopup: (popupKey: WindowPopupKey) => Window | null;
|
||||
@@ -1,72 +0,0 @@
|
||||
/** 선택한 지도 레이어 종류에 맞는 시설 상세 컴포넌트를 반환한다. */
|
||||
import MsSwagePpl from "../popupComponent/MsSwagePpl"; // 하수관로
|
||||
import sewagePump from "../popupComponent/sewagePump"; // 하수펌프
|
||||
import smh from "../popupComponent/smh";
|
||||
import Ssc from "../popupComponent/Ssc";
|
||||
import StormWaterPump from "../popupComponent/stormWaterPump";
|
||||
import SwtrmtFclt from "../popupComponent/SwtrmtFclt";
|
||||
import SwtrmtPrcsZoneAnalysis from "../popupComponent/SwtrmtPrcsZoneAnalysis";
|
||||
|
||||
type LayerComponentProps = {
|
||||
featureData: any;
|
||||
isMainSsc?: boolean;
|
||||
isDrtwSewagePump?: boolean;
|
||||
rainMeasureData?: any;
|
||||
onSaveClick?: () => void;
|
||||
onFormDataChange?: (data: any) => void; // 추가
|
||||
chmbrXcnts: string;
|
||||
chmbrYdnts: string;
|
||||
gisObject?: Record<string, any>; // GIS 객체 추가
|
||||
rawFeatureData?: any;
|
||||
surveyResultContent?: React.ReactNode;
|
||||
};
|
||||
type LayerContentMap = {
|
||||
[key: string]: {
|
||||
component?: React.ComponentType<any>;
|
||||
props?: any;
|
||||
};
|
||||
};
|
||||
// 레이어 이름과 컴포넌트를 매핑
|
||||
// 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업
|
||||
const layerContentMap: LayerContentMap = {
|
||||
"하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리
|
||||
"하수처리구역": { component: SwtrmtPrcsZoneAnalysis },
|
||||
"우수토실": { component: Ssc, },
|
||||
"중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정
|
||||
"하수관로": { component: MsSwagePpl, },
|
||||
"하수맨홀": { component: smh, },
|
||||
"하수펌프장": { component: sewagePump },
|
||||
// "빗물펌프": { component: StormWaterPump },
|
||||
// "오수펌프": { component: sewagePump },
|
||||
};
|
||||
|
||||
// 동적 컴포넌트 반환 함수
|
||||
export const getLayerContentComponent = (layerNm: string): React.FC<LayerComponentProps> | null => {
|
||||
const component = layerContentMap[layerNm];
|
||||
if (!component || !component.component) {
|
||||
// component가 없으면 null 반환
|
||||
return () => null;
|
||||
}
|
||||
return component.component as React.FC<LayerComponentProps>
|
||||
};
|
||||
// 사용자 입력 필드 (보수 및 갱신, 청소 및 준설, 점검 및 진단)
|
||||
export const handleInputChange = (section: string,
|
||||
index: number,
|
||||
field: string,
|
||||
value: string,
|
||||
dataMap: {
|
||||
[key: string]: {
|
||||
data: { year: string; type: string, bizInfoNm: string, bizType: string }[];
|
||||
setData: React.Dispatch<React.SetStateAction<{ year: string; type: string, bizInfoNm: string, bizType: string }[]>>;
|
||||
};
|
||||
}
|
||||
) => {
|
||||
const target = dataMap[section];
|
||||
if (!target) return;
|
||||
|
||||
const updated = [...target.data];
|
||||
if (updated[index]) {
|
||||
updated[index][field as keyof typeof updated[number]] = value;
|
||||
}
|
||||
target.setData(updated);
|
||||
};
|
||||
@@ -1,924 +0,0 @@
|
||||
/** 지도 시설 상세 및 목록에서 사용하는 컬럼·코드 정의를 제공한다. */
|
||||
// 하수관로
|
||||
export const MsSwagePplCol = [
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
{ id: 'mngInstNm', title: '관리기관' },
|
||||
{ id: 'inst_mng_no', title: '기관관리번호' },
|
||||
{ id: 'flowDirectionBasis', title: '유하방향 근거' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'elapsedYears', title: '경과년도' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
{ id: 'pdmt_vl', title: '관경(mm)' },
|
||||
{ id: 'prlg_vl', title: '연장(m)' },
|
||||
{ id: 'usg_cd', title: '용도' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'avg_grdn', title: '평균구배(m/m)' },
|
||||
{ id: 'drtw_mltv_area', title: '오수배수면적(m²)' },
|
||||
{ id: 'excln_mltv_area', title: '우수배수면적(m²)' },
|
||||
{ id: 'tmpt_dpth', title: '시점깊이(m)' },
|
||||
{ id: 'tmpt_mhl_mng_no', title: '시점맨홀관리번호' },
|
||||
{ id: 'scend_dpth', title: '종점깊이(m)' },
|
||||
{ id: 'scend_mhl_mng_no', title: '종점맨홀관리번호' },
|
||||
{ id: 'trfln_cnt', title: '차선수' },
|
||||
{ id: 'vrtc_len', title: '세로길이(m)' },
|
||||
{ id: 'wdth_len', title: '가로길이(m)' },
|
||||
// { id: 'rnyssn_fvlc', title: '우기 시 유속(m/s)' },
|
||||
// { id: 'clar_fvlc', title: '건기 시 유속(m/s)' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
// { id: 'lyrTypeNm', title: '레이어타입명' },
|
||||
]
|
||||
// 하수관로 searchModal 기본정보tab
|
||||
export const MsSwagePplCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
{ id: 'fcltMngNo', title: '시설\n관리번호' },
|
||||
{ id: 'instMngNo', title: '기관\n관리번호' },
|
||||
{ id: 'flowDirectionBasis', title: '유하방향\n근거' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: "swtrmt_zone_nm", title: "처리구역" },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
{ id: 'usgCd', title: '용도' },
|
||||
{ id: 'pdmtVl', title: '관경\n(mm)' },
|
||||
{ id: 'prlgVl', title: '연장\n(m)' },
|
||||
// { id: 'avgGrdn', title: '평균구배\n(m/m)' },
|
||||
{ id: 'tmptDpth', title: '시점깊이\n(m)' },
|
||||
{ id: 'tmptMhlMngNo', title: '시점맨홀\n관리번호' },
|
||||
{ id: 'scendDpth', title: '종점깊이\n(m)' },
|
||||
{ id: 'scendMhlMngNo', title: '종점맨홀\n관리번호' },
|
||||
{ id: 'vrtcLen', title: '세로길이\n(m)' },
|
||||
{ id: 'wdthLen', title: '가로길이\n(m)' },
|
||||
{ id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'mqltCd', title: '재질코드' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
// { id: 'lyrTypeNm', title: '레이어타입명' },
|
||||
]
|
||||
export const MsSwagePplExcelCol = {
|
||||
'fcltMngNo': '시설 관리번호',
|
||||
'instMngNo': '기관 관리번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'swtrmt_zone_nm': '처리구역',
|
||||
'instlYmd': '설치일자',
|
||||
'usgCd': '용도',
|
||||
'pdmtVl': '관경(mm)',
|
||||
'prlgVl': '연장(m)',
|
||||
'tmptDpth': '시점 깊이(m)',
|
||||
'tmptMhlMngNo': '시점맨홀 관리번호',
|
||||
'scendDpth': '종점 깊이(m)',
|
||||
'scendMhlMngNo': '종점맨홀 관리번호',
|
||||
'vrtcLen': '세로길이(m)',
|
||||
'wdthLen': '가로길이(m)',
|
||||
'fcltyShpCd': '시설물형태코드',
|
||||
'mqltCd': '재질코드',
|
||||
};
|
||||
// 하수관로 용도 코드표
|
||||
export const MS_SWAGE_PPL_USE_CODES = [
|
||||
{ val: "SBA000", text: "미분류" },
|
||||
{ val: "SBA001", text: "합류관" },
|
||||
{ val: "SBA002", text: "차집관" },
|
||||
{ val: "SBA003", text: "우수관" },
|
||||
{ val: "SBA004", text: "오수관" },
|
||||
{ val: "SBA999", text: "기타" },
|
||||
];
|
||||
// 하수관로 재질 코드표
|
||||
export const MS_SWAGE_PPL_CODES = [
|
||||
{ val: "MOP000", text: "미분류" },
|
||||
{ val: "MOP001", text: "닥타일주철관(DCIP)" },
|
||||
{ val: "MOP002", text: "강관(SP)" },
|
||||
{ val: "MOP003", text: "아연도강관(GS)" },
|
||||
{ val: "MOP004", text: "염화비닐관(PVC)" },
|
||||
{ val: "MOP005", text: "폴리에틸렌관(PE)" },
|
||||
{ val: "MOP006", text: "스테인레스관(SSP)" },
|
||||
{ val: "MOP007", text: "동관(COP)" },
|
||||
{ val: "MOP008", text: "P.S.콘크리트관(PC)" },
|
||||
{ val: "MOP009", text: "석면시멘트관(AS)" },
|
||||
{ val: "MOP010", text: "회주철관(CIP)" },
|
||||
{ val: "MOP011", text: "연관(LP)" },
|
||||
{ val: "MOP012", text: "흉관(HP)" },
|
||||
{ val: "MOP013", text: "철근콘크리트관(RC)" },
|
||||
{ val: "MOP014", text: "강관(ST)" },
|
||||
{ val: "MOP015", text: "주철관(CI)" },
|
||||
{ val: "MOP016", text: "화학제품류관(PV)" },
|
||||
{ val: "MOP017", text: "도관(VC)" },
|
||||
{ val: "MOP018", text: "석축(SE)" },
|
||||
{ val: "MOP019", text: "도복장 강관(SP)" },
|
||||
{ val: "MOP020", text: "흉관(EP)" },
|
||||
{ val: "MOP021", text: "아연도강관(GP)" },
|
||||
{ val: "MOP022", text: "청동관(BR)" },
|
||||
{ val: "MOP023", text: "고무몰딩(RM)" },
|
||||
{ val: "MOP024", text: "주강(CS)" },
|
||||
{ val: "MOP025", text: "VR관" },
|
||||
{ val: "MOP026", text: "HDPE관" },
|
||||
{ val: "MOP027", text: "시멘트라이닝주철관(C)" },
|
||||
{ val: "MOP028", text: "수도용 PE관(PE)" },
|
||||
{ val: "MOP029", text: "석면시멘트관(AC)" },
|
||||
{ val: "MOP030", text: "폴리에틸렌분체라이닝강관(PEP)" },
|
||||
{ val: "MOP031", text: "에폭시코팅강관(EPS)" },
|
||||
{ val: "MOP032", text: "HI-3P" },
|
||||
{ val: "MOP033", text: "철근콘크리트박스(RCB)" },
|
||||
{ val: "MOP034", text: "콘크리트중선함(CD)" },
|
||||
{ val: "MOP035", text: "후랙시불전선함(FP)" },
|
||||
{ val: "MOP036", text: "전선함(FD)" },
|
||||
{ val: "MOP038", text: "이중보온관" },
|
||||
{ val: "MOP039", text: "폴리에틸렌피복강관(PLP)" },
|
||||
{ val: "MOP041", text: "FRP" },
|
||||
{ val: "MOP042", text: "강재" },
|
||||
{ val: "MOP999", text: "기타" },
|
||||
{ val: "MOP800", text: "2중벽PE관" },
|
||||
{ val: "MOP801", text: "3중벽PE관" },
|
||||
{ val: "MOP802", text: "가스배관용 강관" },
|
||||
{ val: "MOP803", text: "강관(피복 Double, 용접접합)" },
|
||||
{ val: "MOP804", text: "강관(피복 Single, 용접접합)" },
|
||||
{ val: "MOP805", text: "강관(CP)" },
|
||||
{ val: "MOP806", text: "강철" },
|
||||
{ val: "MOP807", text: "강화플라스틱복합관" },
|
||||
{ val: "MOP808", text: "경질염화비닐관(HI-VP)" },
|
||||
{ val: "MOP809", text: "고강성PVC이중벽관" },
|
||||
{ val: "MOP810", text: "고광도PVC이중벽관" },
|
||||
{ val: "MOP811", text: "고밀도폴리에틸렌관(THP)" },
|
||||
{ val: "MOP812", text: "고밀도폴리에틸렌파상형이중벽관" },
|
||||
{ val: "MOP813", text: "공장제 박스관거" },
|
||||
{ val: "MOP814", text: "관로없음(알케이블)" },
|
||||
{ val: "MOP815", text: "나선형강관" },
|
||||
{ val: "MOP816", text: "내충격수도관" },
|
||||
{ val: "MOP817", text: "내충격용하수도용경질염화비닐관(HI-VG)" },
|
||||
{ val: "MOP818", text: "내충격HI-VG2관" },
|
||||
{ val: "MOP819", text: "내충격PVC하수관" },
|
||||
{ val: "MOP820", text: "녹석(돌산호과 일종)" },
|
||||
{ val: "MOP821", text: "동관(구리관, 용접)" },
|
||||
{ val: "MOP822", text: "둥근잔돌" },
|
||||
{ val: "MOP823", text: "무근콘크리트" },
|
||||
{ val: "MOP824", text: "백관" },
|
||||
{ val: "MOP825", text: "복합철판파형관" },
|
||||
{ val: "MOP826", text: "분체라이닝관" },
|
||||
{ val: "MOP827", text: "석축박스" },
|
||||
{ val: "MOP828", text: "석축철콘" },
|
||||
{ val: "MOP829", text: "수도용경질염화비관" },
|
||||
{ val: "MOP830", text: "수도용경질염화비닐이음관" },
|
||||
{ val: "MOP831", text: "수도용엑포시수지분체내외면코팅강관" },
|
||||
{ val: "MOP832", text: "수지파형강관(PCS)" },
|
||||
{ val: "MOP833", text: "수지CSP" },
|
||||
{ val: "MOP834", text: "스테인레스 주름관" },
|
||||
{ val: "MOP835", text: "스테인레스관(STS)" },
|
||||
{ val: "MOP836", text: "스테인레스스틸파이프관" },
|
||||
{ val: "MOP837", text: "아스콘" },
|
||||
{ val: "MOP838", text: "아스팔트" },
|
||||
{ val: "MOP839", text: "아스팔트도복장강관(ASP)" },
|
||||
{ val: "MOP840", text: "아연" },
|
||||
{ val: "MOP841", text: "아연도금(백관)" },
|
||||
{ val: "MOP842", text: "알루미늄" },
|
||||
{ val: "MOP843", text: "알루미늄합금" },
|
||||
{ val: "MOP844", text: "압력배관용 탄소강관" },
|
||||
{ val: "MOP845", text: "양철관" },
|
||||
{ val: "MOP846", text: "양철주름관(CSP)" },
|
||||
{ val: "MOP847", text: "연합인슈강판" },
|
||||
{ val: "MOP848", text: "와사관(GSP)" },
|
||||
{ val: "MOP849", text: "원심력철근콘크리트" },
|
||||
{ val: "MOP850", text: "유리섬유관(GRP)" },
|
||||
{ val: "MOP851", text: "유리섬유복합관(GRP)" },
|
||||
{ val: "MOP852", text: "이음매없는동및동합금관" },
|
||||
{ val: "MOP853", text: "일반배관용스테인리스강관" },
|
||||
{ val: "MOP854", text: "전압 콘크리트관" },
|
||||
{ val: "MOP855", text: "주름관" },
|
||||
{ val: "MOP856", text: "주름주철파형강관" },
|
||||
{ val: "MOP857", text: "지중 저압케이블 입상관" },
|
||||
{ val: "MOP858", text: "진동및전압철근콘크리트관" },
|
||||
{ val: "MOP859", text: "철근콘크리트" },
|
||||
{ val: "MOP860", text: "철근콘크리트 블록" },
|
||||
{ val: "MOP861", text: "철근콘크리트조립흙막이" },
|
||||
{ val: "MOP862", text: "철판매입관" },
|
||||
{ val: "MOP863", text: "청동" },
|
||||
{ val: "MOP864", text: "코아식프리스트레스트콘크리트관" },
|
||||
{ val: "MOP865", text: "콘크리트" },
|
||||
{ val: "MOP866", text: "콘크리트 폴륨관" },
|
||||
{ val: "MOP867", text: "콘크리트블록" },
|
||||
{ val: "MOP868", text: "탄소강" },
|
||||
{ val: "MOP869", text: "탄소강(카본스틸)" },
|
||||
{ val: "MOP870", text: "탄소주강" },
|
||||
{ val: "MOP871", text: "트러후" },
|
||||
{ val: "MOP872", text: "파선형강관(RCF)" },
|
||||
{ val: "MOP873", text: "파형강관(CSP)" },
|
||||
{ val: "MOP874", text: "파형관" },
|
||||
{ val: "MOP875", text: "파형플라스틱관" },
|
||||
{ val: "MOP876", text: "폴리머관" },
|
||||
{ val: "MOP877", text: "폴리에스테르수지콘크리트관" },
|
||||
{ val: "MOP878", text: "폴리에틸렌 전선관" },
|
||||
{ val: "MOP879", text: "폴리에틸렌 피복강관" },
|
||||
{ val: "MOP880", text: "폴리에틸렌관 1호관" },
|
||||
{ val: "MOP881", text: "폴리에틸렌관 2호관" },
|
||||
{ val: "MOP882", text: "폴리에틸렌관(2중벽)" },
|
||||
{ val: "MOP883", text: "폴리에틸렌관(3중벽)" },
|
||||
{ val: "MOP884", text: "폴리에틸렌분체라이닝" },
|
||||
{ val: "MOP885", text: "폴리에틸렌이중벽하수관" },
|
||||
{ val: "MOP886", text: "폴리탄산에스테르" },
|
||||
{ val: "MOP887", text: "프리스트레스트콘크리트실린더관" },
|
||||
{ val: "MOP888", text: "플라스틱" },
|
||||
{ val: "MOP889", text: "플라스틱주름관" },
|
||||
{ val: "MOP890", text: "하수도용고강성경질염화비닐이중벽주름관" },
|
||||
{ val: "MOP891", text: "현장타설관" },
|
||||
{ val: "MOP892", text: "현장타설철근콘크리트관" },
|
||||
{ val: "MOP893", text: "흑관" },
|
||||
{ val: "MOP894", text: "흙" },
|
||||
{ val: "MOP895", text: "AP" },
|
||||
{ val: "MOP896", text: "API관" },
|
||||
{ val: "MOP897", text: "ASP" },
|
||||
{ val: "MOP898", text: "CMCIP" },
|
||||
{ val: "MOP899", text: "CSP" },
|
||||
{ val: "MOP900", text: "DRP" },
|
||||
{ val: "MOP901", text: "DSP" },
|
||||
{ val: "MOP902", text: "DTC관" },
|
||||
{ val: "MOP903", text: "ELP" },
|
||||
{ val: "MOP904", text: "EPP" },
|
||||
{ val: "MOP905", text: "FC관" },
|
||||
{ val: "MOP906", text: "H" },
|
||||
{ val: "MOP907", text: "H6" },
|
||||
{ val: "MOP908", text: "HCP" },
|
||||
{ val: "MOP909", text: "HI_2P관" },
|
||||
{ val: "MOP910", text: "HI-KP" },
|
||||
{ val: "MOP911", text: "HL" },
|
||||
{ val: "MOP912", text: "IP" },
|
||||
{ val: "MOP913", text: "KCGP" },
|
||||
{ val: "MOP914", text: "KP관" },
|
||||
{ val: "MOP915", text: "P2S관" },
|
||||
{ val: "MOP916", text: "PC박스(PCB)" },
|
||||
{ val: "MOP917", text: "PCS" },
|
||||
{ val: "MOP918", text: "PDP" },
|
||||
{ val: "MOP919", text: "PE이중벽관" },
|
||||
{ val: "MOP920", text: "PE적측벽1종관" },
|
||||
{ val: "MOP921", text: "PFP관" },
|
||||
{ val: "MOP922", text: "PG관" },
|
||||
{ val: "MOP923", text: "PIP관(이중보온관)" },
|
||||
{ val: "MOP924", text: "PLCSP" },
|
||||
{ val: "MOP925", text: "PS" },
|
||||
{ val: "MOP926", text: "PUB" },
|
||||
{ val: "MOP927", text: "PVB" },
|
||||
{ val: "MOP928", text: "PVC이중벽관(PVC2)" },
|
||||
{ val: "MOP929", text: "PVC-DC" },
|
||||
{ val: "MOP930", text: "PVC-DV" },
|
||||
{ val: "MOP931", text: "SBK" },
|
||||
{ val: "MOP932", text: "SCP" },
|
||||
{ val: "MOP933", text: "SEB" },
|
||||
{ val: "MOP934", text: "SIS관(이중보온관)" },
|
||||
{ val: "MOP935", text: "SMC" },
|
||||
{ val: "MOP936", text: "STW" },
|
||||
{ val: "MOP937", text: "SUS" },
|
||||
{ val: "MOP938", text: "THP나선관" },
|
||||
{ val: "MOP939", text: "U관" },
|
||||
{ val: "MOP940", text: "VG2" },
|
||||
{ val: "MOP941", text: "WSP(와사피)" },
|
||||
{ val: "MOP942", text: "Steel Grating" },
|
||||
{ val: "MOP943", text: "모래" },
|
||||
{ val: "MOP944", text: "인조석" },
|
||||
{ val: "MOP945", text: "SPOL" },
|
||||
{ val: "MOP946", text: "PCF관(강판보강 PE 나선형관)" },
|
||||
{ val: "MOP947", text: "PVC3(PVC3중벽관)" },
|
||||
{ val: "MOP948", text: "PE다중벽관" },
|
||||
{ val: "MOP977", text: "전주용입상관" },
|
||||
{ val: "MOP978", text: "MD관" },
|
||||
];
|
||||
// 하수관로 'fcltyShpCd' '시설물형태코드'
|
||||
export const MS_SWAGE_PPL_SHAPE = [
|
||||
{ val: "FOR000 ", text: "미분류" },
|
||||
{ val: "FOR001 ", text: "원형" },
|
||||
{ val: "FOR002 ", text: "원추형" },
|
||||
{ val: "FOR003 ", text: "원통형" },
|
||||
{ val: "FOR004 ", text: "구형" },
|
||||
{ val: "FOR005 ", text: "타원형" },
|
||||
{ val: "FOR006 ", text: "난원" },
|
||||
{ val: "FOR007 ", text: "반원형" },
|
||||
|
||||
{ val: "FOR010 ", text: "피라미드형" },
|
||||
{ val: "FOR011 ", text: "사면체형" },
|
||||
{ val: "FOR012 ", text: "기둥,축" },
|
||||
{ val: "FOR013 ", text: "돛대,활대처럼 둥글고 긴 원재" },
|
||||
{ val: "FOR014 ", text: "아치형" },
|
||||
{ val: "FOR015 ", text: "다중아치형" },
|
||||
{ val: "FOR016 ", text: "돔형" },
|
||||
{ val: "FOR017 ", text: "고형체형" },
|
||||
|
||||
{ val: "FOR020 ", text: "사각형" },
|
||||
{ val: "FOR021 ", text: "평방형" },
|
||||
{ val: "FOR022 ", text: "다이아몬드형" },
|
||||
{ val: "FOR023 ", text: "사다리꼴" },
|
||||
{ val: "FOR024 ", text: "각형" },
|
||||
{ val: "FOR025 ", text: "마제형관" },
|
||||
{ val: "FOR026 ", text: "태형" },
|
||||
|
||||
{ val: "FOR030 ", text: "A형" },
|
||||
{ val: "FOR031 ", text: "H형" },
|
||||
{ val: "FOR032 ", text: "I형" },
|
||||
{ val: "FOR033 ", text: "Y형" },
|
||||
{ val: "FOR034 ", text: "T형" },
|
||||
|
||||
{ val: "FOR050 ", text: "불규칙한 형태" },
|
||||
|
||||
{ val: "FOR900 ", text: "계란형" },
|
||||
{ val: "FOR901 ", text: "구(사각)형" },
|
||||
{ val: "FOR902 ", text: "높이조절철개형" },
|
||||
{ val: "FOR903 ", text: "박스형(RCB)" },
|
||||
{ val: "FOR904 ", text: "반월개거" },
|
||||
{ val: "FOR905 ", text: "반월개거(측구)" },
|
||||
{ val: "FOR906 ", text: "벽돌조" },
|
||||
{ val: "FOR907 ", text: "사각철판" },
|
||||
{ val: "FOR908 ", text: "석축개거" },
|
||||
{ val: "FOR909 ", text: "석축개거(측구)" },
|
||||
{ val: "FOR910 ", text: "승수로" },
|
||||
{ val: "FOR911 ", text: "옹벽개거" },
|
||||
{ val: "FOR912 ", text: "옹벽개거(측구)" },
|
||||
{ val: "FOR913 ", text: "이단" },
|
||||
{ val: "FOR914 ", text: "이중침전형" },
|
||||
{ val: "FOR915 ", text: "자연개거" },
|
||||
{ val: "FOR916 ", text: "자연개거(측구)" },
|
||||
{ val: "FOR917 ", text: "조립식" },
|
||||
{ val: "FOR918 ", text: "지상관" },
|
||||
{ val: "FOR919 ", text: "지상단구" },
|
||||
{ val: "FOR920 ", text: "지상쌍구" },
|
||||
{ val: "FOR921 ", text: "지하단구" },
|
||||
{ val: "FOR922 ", text: "지하쌍구" },
|
||||
{ val: "FOR923 ", text: "직사각형" },
|
||||
{ val: "FOR924 ", text: "타설식" },
|
||||
{ val: "FOR925 ", text: "토류형" },
|
||||
{ val: "FOR926 ", text: "흄관원형" },
|
||||
{ val: "FOR927 ", text: "L형측구" },
|
||||
{ val: "FOR928 ", text: "LO형측구" },
|
||||
{ val: "FOR929 ", text: "LU형측구" },
|
||||
{ val: "FOR930 ", text: "PVC이중벽관" },
|
||||
{ val: "FOR931 ", text: "RCB 맨홀" },
|
||||
{ val: "FOR932 ", text: "U형" },
|
||||
{ val: "FOR933 ", text: "U형측구(미복개)" },
|
||||
{ val: "FOR934 ", text: "U형측구(복개)" },
|
||||
|
||||
{ val: "FOR999 ", text: "기타" }
|
||||
];
|
||||
// 하수처리시설
|
||||
export const SwtrmtFcltCol = [
|
||||
{ id: 'swtrmt_fclt_nm', title: '하수처리시설명' },
|
||||
{ id: 'fclt_cpct', title: '시설용량(㎥/d)' },
|
||||
{ id: 'rgn_expln', title: '지역설명' },
|
||||
{ id: 'cmcn_ymd', title: '준공일자' },
|
||||
{ id: 'oprtng_strt_ymd', title: '가동개시일자' },
|
||||
{ id: 'clsg_ymd', title: '폐쇄일자' },
|
||||
// { id: 'prcs_msrc_cd', title: '처리공법코드' },
|
||||
// { id: 'lae_cd', title: '지자체코드' },
|
||||
{ id: 'lae_nm', title: '지자체명' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
// { id: 'sarea_cd', title: '권역코드' },
|
||||
{ id: 'sarea_nm', title: '권역명' },
|
||||
{ id: 'lrge_sarea_nm', title: '대권역명' },
|
||||
{ id: 'rvr_nm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'dtl_addr', title: '상세주소' },
|
||||
{ id: 'rdnm_addr', title: '도로명주소' },
|
||||
{ id: 'dtl_rdnm_addr', title: '상세도로명주소' },
|
||||
{ id: 'dschg_wtst_nm', title: '방류수계명' },
|
||||
{ id: 'plln_area', title: '부지면적(m²)' },
|
||||
{ id: 'mng_rprtv_bzenty_nm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmt_ppltn_cnt', title: '하수처리인구수(인)' },
|
||||
{ id: 'prcs_zone_area_sum', title: '처리구역면적합계(m²)' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
// { id: 'oprtngSttsCd', title: '가동상태코드' },
|
||||
{ id: 'rprs_msrc_cn', title: '대표공법내용' },
|
||||
// { id: 'stty_crtr_bod', title: '법정기준생물화학적산소요구량(mg/L)' },
|
||||
// { id: 'stty_crtr_toc', title: '법정기준TOC((mg/L))' },
|
||||
// { id: 'stty_crtr_ss', title: '법정기준SS(mg/L)' },
|
||||
// { id: 'stty_crtr_tn', title: '법정기준총질소(mg/L)' },
|
||||
// { id: 'stty_crtr_tph', title: '법정기준총인(mg/L)' },
|
||||
// { id: 'stty_crtr_clbcl_cnt', title: '법정기준대장균수(군수/100mL)' },
|
||||
{ id: 'bsn_nm', title: '유역명' },
|
||||
// { id: 'obsrvnSptCd', title: '관측지점코드' },
|
||||
];
|
||||
export const SwtrmtFcltCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리\n시설명' },
|
||||
{ id: 'fcltCpct', title: '시설\n용량\n(㎥/d)' },
|
||||
{ id: 'rgnExpln', title: '지역설명' },
|
||||
// { id: 'cmcnYmd', title: '준공일자' },
|
||||
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
|
||||
// { id: 'clsgYmd', title: '폐쇄일자' },
|
||||
// { id: 'prcsMsrcCd', title: '처리공법코드' },
|
||||
// { id: 'laeCd', title: '지자체코드' },
|
||||
{ id: 'laeNm', title: '지자체명' },
|
||||
// { id: 'ctpvNm', title: '시도명' },
|
||||
// { id: 'sggNm', title: '시군구명' },
|
||||
// { id: 'sareaCd', title: '권역코드' },
|
||||
{ id: 'sareaNm', title: '권역명' },
|
||||
// { id: 'lrgeSareaNm', title: '대권역명' },
|
||||
{ id: 'rvrNm', title: '하천명' },
|
||||
// { id: 'addr', title: '주소' },
|
||||
// { id: 'dtlAddr', title: '상세주소' },
|
||||
// { id: 'rdnmAddr', title: '도로명주소' },
|
||||
// { id: 'dtlRdnmAddr', title: '상세도로명주소' },
|
||||
{ id: 'dschgWtstNm', title: '방류수계명' },
|
||||
// { id: 'pllnArea', title: '부지면적(m²)' },
|
||||
// { id: 'mngRprtvBzentyNm', title: '관리대행업체명' },
|
||||
{ id: 'swtrmtPpltnCnt', title: '하수\n처리\n인구수\n(인)' },
|
||||
// { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' },
|
||||
// { id: 'xcrd', title: 'X좌표' },
|
||||
// { id: 'ycrd', title: 'Y좌표' },
|
||||
// { id: 'oprtngSttsCd', title: '가동상태코드' },
|
||||
{ id: 'rprsMsrcCn', title: '대표\n공법\n내용' },
|
||||
// { id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량(mg/L)' },
|
||||
// { id: 'sttyCrtrToc', title: '법정기준TOC(mg/L)' },
|
||||
// { id: 'sttyCrtrSs', title: '법정기준SS(mg/L)' },
|
||||
// { id: 'sttyCrtrTn', title: '법정기준총질소(mg/L)' },
|
||||
// { id: 'sttyCrtrTph', title: '법정기준총인(mg/L)' },
|
||||
// { id: 'sttyCrtrClbclCnt', title: '법정기준대장균수(군수/100mL)' },
|
||||
// { id: 'bsnNm', title: '유역명' },
|
||||
// { id: 'obsrvnSptCd', title: '관측지점코드' },
|
||||
];
|
||||
export const SwtrmtFcltExcelCol = {
|
||||
'swtrmtFcltNm': '하수처리시설명',
|
||||
'fcltCpct': '시설 용량(㎥/d)',
|
||||
'rgnExpln': '지역설명',
|
||||
'oprtngStrtYmd': '가동개시 일자',
|
||||
'laeNm': '지자체명',
|
||||
'sareaNm': '권역명',
|
||||
'rvrNm': '하천명',
|
||||
'dschgWtstNm': '방류수계명',
|
||||
'swtrmtPpltnCnt': '하수 처리 인구수(인)',
|
||||
'rprsMsrcCn': '대표 공법 내용'
|
||||
};
|
||||
|
||||
// 하수처리구역
|
||||
export const SwtrmtPrcsZoneCol = [
|
||||
// { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
|
||||
{ id: 'swtrmt_zone_nm', title: '하수처리구역명' },
|
||||
// { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' },
|
||||
// { id: 'ctpv_cd', title: '시도코드' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
// { id: 'sgg_cd', title: '시군구코드' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'swtrmt_fclt_nm', title: '하수처리시설명' },
|
||||
];
|
||||
export const SwtrmtPrcsZoneCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
|
||||
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
|
||||
// { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' },
|
||||
// { id: 'ctpv_cd', title: '시도코드' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
// { id: 'sgg_cd', title: '시군구코드' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
];
|
||||
export const SwtrmtPrcsZoneExcelCol = {
|
||||
'swtrmtZoneNm': '하수처리구역명',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'swtrmtFcltNm': '하수처리시설명'
|
||||
};
|
||||
|
||||
// 침수흔적도
|
||||
export const FldngWtlvMapCol = [
|
||||
{ id: 'fldng_yr', title: '침수년도' },
|
||||
{ id: 'fldng_dstr_nm', title: '침수재해명' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'fldng_bgng_dtm', title: '침수시작일시' },
|
||||
{ id: 'fldng_end_dtm', title: '침수종료일시' },
|
||||
{ id: 'fldng_hr', title: '침수시간' },
|
||||
{ id: 'fldng_dstrct_nm', title: '침수지구명' },
|
||||
{ id: 'fldng_cs_dtl_cd', title: '침수원인상세코드' },
|
||||
{ id: 'dtl_ocrn_cs_cn', title: '침수발생원인' },
|
||||
{ id: 'avg_fldng_wtlv', title: '평균침수위' },
|
||||
{ id: 'fldng_area', title: '침수면적' },
|
||||
{ id: 'exmn_yr', title: '조사년도' },
|
||||
{ id: 'dam_amt', title: '피해금액' },
|
||||
{ id: 'rain_bgng_tm', title: '강우시작일시' },
|
||||
{ id: 'rain_end_tm', title: '강우종료일시' },
|
||||
{ id: 'rain_tot', title: '총강우량(mm)' },
|
||||
{ id: 'rain_str', title: '강우강도(mm/hr)' },
|
||||
];
|
||||
export const FldngWtlvMapCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
{ id: 'fldngYr', title: '침수년도' },
|
||||
{ id: 'fldngDstrNm', title: '침수재해명' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: 'fldngBgngDtm', title: '침수시작일시' },
|
||||
{ id: 'fldngEndDtm', title: '침수종료일시' },
|
||||
{ id: 'fldngHr', title: '침수시간' },
|
||||
{ id: 'fldngDstrctNm', title: '침수지구명' },
|
||||
{ id: 'fldngCsDtlCd', title: '침수원인상세코드' },
|
||||
{ id: 'avgFldngWtlv', title: '평균침수위' },
|
||||
{ id: 'fldngArea', title: '침수면적' },
|
||||
{ id: 'exmnYr', title: '조사년도' },
|
||||
{ id: 'damAmt', title: '피해금액' },
|
||||
{ id: 'rainBgngTm', title: '강우시작일시' },
|
||||
{ id: 'rainEndTm', title: '강우종료일시' },
|
||||
{ id: 'rainTot', title: '총강우량(mm)' },
|
||||
{ id: 'rainStr', title: '강우강도(mm/hr)' },
|
||||
];
|
||||
export const FldngDpthMapCol = [
|
||||
{ id: 'fldng_yr', title: '침수년도' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'fldng_dpth', title: '침수깊이(m)' },
|
||||
{ id: 'fldng_area', title: '침수면적(㎡)' },
|
||||
// { id: 'shape_area', title: '형상면적(㎡)' },
|
||||
// { id: 'shape_len', title: '형상길이' },
|
||||
];
|
||||
export const FldngWtlvMapExcelCol = {
|
||||
'fldngYr': '침수년도',
|
||||
'fldngDstrNm': '침수재해명',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'bgngYmd': '침수시작일자',
|
||||
'fldngBgngHm': '침수시작시분',
|
||||
'endYmd': '침수종료일자',
|
||||
'fldngEndHm': '침수종료시분',
|
||||
'fldngHr': '침수시간',
|
||||
'fldngDstrctNm': '침수지구명',
|
||||
'fldngCsDtlCd': '침수원인상세코드',
|
||||
'avgFldngWtlv': '평균침수위',
|
||||
'fldngArea': '침수면적',
|
||||
'exmnYr': '조사년도',
|
||||
'damAmt': '피해금액'
|
||||
};
|
||||
|
||||
// 하수맨홀
|
||||
export const MsSwageMhlCol = [
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'last_drdgn_ymd', title: '최종준설일자' },
|
||||
// { id: 'usg_cd', title: '용도코드' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
// { id: 'mhl_knd_cd', title: '맨홀종류코드' },
|
||||
{ id: 'swage_mhL_diam_vl', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'wdth_len', title: '가로길이(m)' },
|
||||
{ id: 'vrtc_len', title: '세로길이(m)' },
|
||||
// { id: 'ang_dir', title: '표고' },
|
||||
{ id: 'altd_vl', title: '지표고(EL.m)' },
|
||||
{ id: 'dpthl_vl', title: '맨홀저고(EL.m)' },
|
||||
// { id: 'inst_mng_no', title: '기관관리번호' }
|
||||
];
|
||||
export const MsSwageMhlCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'lastDrdgnYmd', title: '최종준설일자' },
|
||||
{ id: 'usgCd', title: '용도' },
|
||||
// { id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
// { id: 'mhlKndCd', title: '맨홀종류코드' },
|
||||
{ id: 'swageMhlDiamVl', title: '하수맨홀구경(mm)' },
|
||||
// { id: 'mqltCd', title: '재질코드' },
|
||||
{ id: 'wdthLen', title: '가로길이(m)' },
|
||||
{ id: 'vrtcLen', title: '세로길이(m)' },
|
||||
// { id: 'angDir', title: '표고' },
|
||||
{ id: 'altdVl', title: '지표고(EL.m)' },
|
||||
{ id: 'dpthlVl', title: '맨홀저고(EL.m)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' }
|
||||
];
|
||||
export const MsSwageMhlExcelCol = {
|
||||
'fcltMngNo': '시설관리번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'instlYmd': '설치일자',
|
||||
'usgCd': '용도',
|
||||
'swageMhlDiamVl': '하수맨홀구경(mm)',
|
||||
'wdthLen': '가로길이(m)',
|
||||
'vrtcLen': '세로길이(m)',
|
||||
'altdVl': '지표고(EL.m)',
|
||||
'dpthlVl': '맨홀저고(EL.m)'
|
||||
};
|
||||
// 하수맨홀 뚜껑재질 코드표
|
||||
export const MS_SWAGE_MHL_CODES = [
|
||||
{ val: "SBC000", text: "미분류" },
|
||||
{ val: "SBC001", text: "주철" },
|
||||
{ val: "SBC002", text: "철판" },
|
||||
{ val: "SBC003", text: "콘크리트" },
|
||||
{ val: "SBC004", text: "그레이팅" },
|
||||
{ val: "SBC091", text: "칼라" },
|
||||
{ val: "SBC092", text: "S.G" },
|
||||
{ val: "SBC999", text: "기타" },
|
||||
{ val: "SBC900", text: "각주철" },
|
||||
{ val: "SBC901", text: "강재" },
|
||||
{ val: "SBC902", text: "나무" },
|
||||
{ val: "SBC903", text: "나무,널판지,합판" },
|
||||
{ val: "SBC904", text: "대리석" },
|
||||
{ val: "SBC905", text: "뚜껑없음" },
|
||||
{ val: "SBC906", text: "보도용콘크리트" },
|
||||
{ val: "SBC907", text: "스틸(ST)" },
|
||||
{ val: "SBC908", text: "신형 CON" },
|
||||
{ val: "SBC909", text: "알루미늄" },
|
||||
{ val: "SBC910", text: "염화비닐(PE)" },
|
||||
{ val: "SBC911", text: "인조석" },
|
||||
{ val: "SBC912", text: "인조석(주철)" },
|
||||
{ val: "SBC913", text: "인조CONC" },
|
||||
{ val: "SBC914", text: "철, 철근" },
|
||||
{ val: "SBC915", text: "칼라콘크리트" },
|
||||
{ val: "SBC916", text: "플라스틱" },
|
||||
{ val: "SBC917", text: "PVC" },
|
||||
{ val: "SBC918", text: "SP" },
|
||||
{ val: "SBC919", text: "SS" },
|
||||
{ val: "SBC920", text: "닥타일주철" },
|
||||
{ val: "SBC921", text: "철근G" },
|
||||
{ val: "SBC922", text: "폴리에틸렌(PE)" },
|
||||
{ val: "SBC923", text: "회주철" }
|
||||
];
|
||||
// 하수맨홀 용도 코드표
|
||||
export const MS_SWAGE_MHL_USE_CODES = [
|
||||
{ val: "SMU000 ", text: "미분류" },
|
||||
{ val: "SMU001 ", text: "우수" },
|
||||
{ val: "SMU002 ", text: "오수" },
|
||||
{ val: "SMU003 ", text: "집수" },
|
||||
{ val: "SMU004 ", text: "합류" },
|
||||
{ val: "SMU005 ", text: "차집" },
|
||||
{ val: "SMU999 ", text: "기타" },
|
||||
{ val: "SMU900 ", text: "1호" },
|
||||
{ val: "SMU901 ", text: "2호" },
|
||||
{ val: "SMU902 ", text: "3호" },
|
||||
{ val: "SMU903 ", text: "4호" },
|
||||
{ val: "SMU904 ", text: "5호" },
|
||||
{ val: "SMU905 ", text: "6호" },
|
||||
{ val: "SMU906 ", text: "7호" },
|
||||
{ val: "SMU907 ", text: "8호" },
|
||||
{ val: "SMU908 ", text: "구거용맨홀" },
|
||||
{ val: "SMU909 ", text: "부관부착맨홀" },
|
||||
{ val: "SMU910 ", text: "조립식맨홀" },
|
||||
{ val: "SMU911 ", text: "특1호" },
|
||||
{ val: "SMU912 ", text: "특2호" },
|
||||
{ val: "SMU913 ", text: "특3호" },
|
||||
{ val: "SMU914 ", text: "특4호" },
|
||||
{ val: "SMU915 ", text: "특5호" },
|
||||
{ val: "SMU916 ", text: "하수맨홀용도" },
|
||||
{ val: "SMU917 ", text: "현장타설관거용" }
|
||||
];
|
||||
|
||||
// 우수펌프장
|
||||
export const ExclnSewagePumpCol = [
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'swage_pmstn_nm', title: '하수펌프장명' },
|
||||
{ id: 'plln_area', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'daily_prcs_cpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'max_impnd_cpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altd_vl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlv_vl', title: '수위(m)' },
|
||||
{ id: 'excln_wpmp_ablt_vl', title: '우수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const ExclnSewagePumpCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'swagePmstnNm', title: '하수펌프장명' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'pllnArea', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'dailyPrcsCpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'maxImpndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altdVl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlvVl', title: '수위(m)' },
|
||||
{ id: 'exclnWpmpAbltVl', title: '우수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const ExclnSewagePumpExcelCol = {
|
||||
'fcltMngNo': '시설관리번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'swagePmstnNm': '하수펌프장명',
|
||||
'instlYmd': '설치일자',
|
||||
'dailyPrcsCpct': '일일처리용량(㎥)',
|
||||
'maxImpndCpct': '최대저수용량(㎥)',
|
||||
'altdVl': '표고(EL.m)',
|
||||
'wtlvVl': '수위(m)',
|
||||
'exclnWpmpAbltVl': '우수양수능력(m²/d)'
|
||||
};
|
||||
// 오수펌프장
|
||||
export const DrtwSewagePumpCol = [
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'swage_pmstn_nm', title: '하수펌프장명' },
|
||||
{ id: 'plln_area', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'daily_prcs_cpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'max_impnd_cpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altd_vl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlv_vl', title: '수위(m)' },
|
||||
{ id: 'excln_wpmp_ablt_vl', title: '오수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const DrtwSewagePumpCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'swagePmstnNm', title: '하수펌프장명' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'pllnArea', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'dailyPrcsCpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'maxImpndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altdVl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlvVl', title: '수위(m)' },
|
||||
{ id: 'exclnWpmpAbltVl', title: '오수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
// 펌프장 용도 코드표
|
||||
export const DRTW_SEWAGE_PUMP_USE_CODES = [
|
||||
{ val: "SBE000", text: "미분류" },
|
||||
{ val: "SBE001", text: "빗물펌프장" },
|
||||
{ val: "SBE002", text: "오수펌프장" },
|
||||
{ val: "SBE003", text: "처리장내펌프장" },
|
||||
{ val: "SBE900", text: "오수배제" },
|
||||
{ val: "SBE901", text: "우수배제" },
|
||||
{ val: "SBE999", text: "기타" },
|
||||
];
|
||||
|
||||
// 우수토실
|
||||
export const stofcbrCol = [
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
{ id: 'worflw_wall_hgt', title: '월류벽높이(m)' },
|
||||
// { id: 'mhl_exst_yn_cd', title: '맨홀존재여부코드' },
|
||||
// { id: 'swage_mhl_ftre_smbol_cd', title: '하수맨홀지형지물부호코드' },
|
||||
{ id: 'swage_mhl_mng_no', title: '하수맨홀관리번호' },
|
||||
{ id: 'drtn_angl', title: '방향각도(°)' },
|
||||
]
|
||||
export const stofcbrCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
{ id: 'mapshtNo', title: '도엽번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
{ id: 'worflwWallHgt', title: '월류벽높이(m)' },
|
||||
// { id: 'mhlExstYnCd', title: '맨홀존재여부코드' },
|
||||
// { id: 'swageMhlFtreSmbolCd', title: '하수맨홀지형지물부호코드' },
|
||||
{ id: 'swageMhlMngNo', title: '하수맨홀관리번호' },
|
||||
{ id: 'drtnAngl', title: '방향각도(°)' },
|
||||
]
|
||||
export const stofcbrExcelCol = {
|
||||
'fcltMngNo': '시설관리번호',
|
||||
'mapshtNo': '도엽번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'instlYmd': '설치일자',
|
||||
'worflwWallHgt': '월류벽높이(m)',
|
||||
'swageMhlMngNo': '하수맨홀관리번호',
|
||||
'drtnAngl': '방향각도(°)'
|
||||
};
|
||||
|
||||
// 중점우수토실 측정지점
|
||||
export const untrtSwgRainCol1 = [
|
||||
{ id: 'manage_no', title: '관리번호' },
|
||||
{ id: 'swr_ppl_eqp_nm', title: '처리구역' },
|
||||
{ id: 'swr_ppl_trt_nm', title: '처리분구' },
|
||||
{ id: 'addr', title: '인근주소' },
|
||||
{ id: 'chmbr_xcnts', title: '토실 위도' },
|
||||
{ id: 'chmbr_ydnts', title: '토실 경도' }
|
||||
];
|
||||
|
||||
// 중점우수토실 측정지점 시설현황
|
||||
export const untrtSwgRainCol2 = [
|
||||
{ id: 'mesure_spot_se', title: '구분' }, // 유량계측정방식 기타
|
||||
{ id: 'discharge_pipe_spec', title: '방류관로 규격(m)' },
|
||||
{ id: 'swr_ppl_eqp_ar', title: '배수구역 면적(m²)' },
|
||||
{ id: 'swr_trt_popl', title: '하수처리 인구(인)' },
|
||||
{ id: 'screen_instl_at', title: '스크린설치 여부' },
|
||||
{ id: 'weir_hg', title: '위어 높이(m)' },
|
||||
{ id: 'weir_lt', title: '위어 길이(m)' }
|
||||
];
|
||||
|
||||
// 중점우수토실 강우정보 (id값 틀릴 수 있음)
|
||||
export const untrtSwgRainCol3 = [
|
||||
{ id: 'rainBeginDt', title: '강우시작일시' },
|
||||
{ id: 'rainEndDt', title: '강우종료일시' },
|
||||
{ id: 'precdngDry', title: '선행건기(h)' },
|
||||
{ id: 'rainInfo', title: '강우정보' },
|
||||
{ id: 'rainFall', title: '누적강우량(mm)' },
|
||||
{ id: 'cntncTime', title: '지속시간(h)' },
|
||||
{ id: 'rainIn', title: '강우강도(mm/h)' }
|
||||
];
|
||||
|
||||
// 중점우수토실 측정정보 (id값 틀릴 수 있음)
|
||||
export const untrtSwgRainCol4 = [
|
||||
{ id: 'mesureBeginDt', title: '측정시작일시' },
|
||||
{ id: 'mesureEndDt', title: '측정종료일시' },
|
||||
{ id: 'mesureIntrvl', title: '측정간격(분)' },
|
||||
{ id: 'mesureMhrlsInstlLc', title: '측정기기 설치위치' },
|
||||
{ id: 'flwmtrCombined', title: '유량계 종류 및 모델명' }, // 합쳐진 컬럼
|
||||
{ id: 'qltwtrBeginDt', title: '채수시작일시' },
|
||||
{ id: 'qltwtrEndDt', title: '채수종료일시' },
|
||||
{ id: 'qltwtrCo', title: '채수횟수' }
|
||||
];
|
||||
|
||||
// 중점우수토실 수량/수질측정 정보
|
||||
export const untrtSwgRainCol5 = [
|
||||
{ id: 'odr', title: '회차' },
|
||||
{ id: 'mesureDt', title: '측정일자' },
|
||||
{ id: 'mesureQy', title: '수량(m³)' },
|
||||
{ id: 'mesureBod', title: 'BOD(mg/L)' },
|
||||
{ id: 'mesureToc', title: 'TOC(mg/L)' },
|
||||
{ id: 'mesureLoad', title: '오염부하량(kg)' }
|
||||
];
|
||||
|
||||
// 🎯 날짜 필드들 (yyyymmdd -> yyyy년 mm월 dd일)
|
||||
export const dateFields = [
|
||||
'instl_ymd', 'last_drdgn_ymd', 'cmcn_ymd', 'oprtng_strt_ymd', 'clsg_ymd',
|
||||
'mesure_begin_dt', 'mesure_end_dt', 'qltwtr_begin_dt', 'qltwtr_end_dt',
|
||||
'rain_begin_dt', 'rain_end_dt', 'mesureDt',
|
||||
// 🎯 camelCase 버전 추가
|
||||
'instlYmd', 'lastDrdgnYmd', 'cmcnYmd', 'oprtngStrtYmd', 'clsgYmd',
|
||||
'mesureBeginDt', 'mesureEndDt', 'qltwtrBeginDt', 'qltwtrEndDt',
|
||||
'rainBeginDt', 'rainEndDt',
|
||||
'flfmtBgngYmd', 'flfmtEndYmd', 'flfmtYr',
|
||||
'bgng_ymd', 'end_ymd', 'bgngYmd', 'endYmd'
|
||||
];
|
||||
|
||||
// 🎯 연월 필드들 (yyyymm -> yyyy년 m월)
|
||||
export const yearMonthFields = ['aplcnYm',];
|
||||
|
||||
// 🎯 날짜시간 필드들 (yyyy-mm-dd hh:mm:ss -> yyyy년 m월 d일 hh:mm)
|
||||
export const dateTimeFields = ['regDt',];
|
||||
|
||||
// 🎯 숫자 필드들 (천 단위 구분자)
|
||||
export const numberFields = [
|
||||
'pdmt', 'prlg', 'avg_grdn', 'drtw_mltv_area', 'excln_mltv_area', 'tmpt_dpth', 'scend_dpth', 'trfln_cnt', 'vrtc_len', 'wdth_len',
|
||||
'fclt_cpct', 'plln_area', 'prcs_zone_area_sum', 'swtrmt_ppltn_cnt', 'stty_crtr_bod', 'stty_crtr_toc', 'stty_crtr_ss', 'stty_crtr_tn', 'stty_crtr_tph', 'stty_crtr_clbcl_cnt',
|
||||
'swage_mh_diam', 'wdth_len', 'vrtc_len', 'altd_vl', 'dpthlVl',
|
||||
'plln_area', 'daily_prcs_cpct', 'max_impnd_cpct', 'altd_vl', 'wtlv_vl', 'excln_wpmp_ablt',
|
||||
'worflw_wall_hgt',
|
||||
'swr_ppl_eqp_ar', 'swr_trt_popl', 'weir_hg', 'weir_lt',
|
||||
'precdng_dry', 'rain_fall', 'cntnc_time', 'rain_in',
|
||||
'mesure_intrvl', 'qltwtr_co', 'mesureQy', 'mesureBod', 'mesureToc', 'mesureLoad',
|
||||
// 🎯 camelCase 버전 추가
|
||||
'avgGrdn', 'drtwMltvArea', 'exclnMltvArea', 'tmptDpth', 'scendDpth', 'trflnCnt', 'vrtcLen', 'wdthLen',
|
||||
'fcltCpct', 'pllnArea', 'prcsZoneAreaSum', 'swtrmtPpltnCnt', 'sttyCltrBod', 'sttyCltrToc', 'sttyCltrSs', 'sttyCltrTn', 'sttyCltrTph', 'sttyCltrClbclCnt',
|
||||
'swageMhDiam', 'altdVl', 'dpthlVl',
|
||||
'dailyPrcsCpct', 'maxImpndCpct', 'wtlvVl', 'exclnWpmpAblt',
|
||||
'worflwWallHgt',
|
||||
'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt',
|
||||
'precdngDry', 'rainFall', 'cntncTime', 'rainIn',
|
||||
'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc',
|
||||
'bizAmt', 'count',
|
||||
'chmbr_xcnts', 'chmbr_ydnts',
|
||||
'fldng_hr', 'avg_fldng_wtlv', 'fldng_area', 'dam_amt',
|
||||
'fldngHr', 'avgFldngWtlv', 'fldngArea', 'damAmt'
|
||||
];
|
||||
+5
-2
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmnMapTypes";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmn-map-types";
|
||||
import LayerSettingCard from "../layer-setting/layer-setting-card";
|
||||
|
||||
type Props = {
|
||||
jobType: ExmnMapJobType;
|
||||
@@ -14,6 +14,7 @@ type Props = {
|
||||
enabled?: boolean;
|
||||
onEnabledChange?: (enabled: boolean) => void;
|
||||
onClose?: () => void;
|
||||
statusMessage?: string;
|
||||
};
|
||||
|
||||
const GRADE_OPTIONS = ["", "1", "2", "3", "4", "5"];
|
||||
@@ -26,6 +27,7 @@ export default function ExmnLayerSettingPanel({
|
||||
enabled = true,
|
||||
onEnabledChange = () => {},
|
||||
onClose = () => {},
|
||||
statusMessage = "",
|
||||
}: Props) {
|
||||
const updateFilter = (key: keyof ExmnMapFilters, value: string) => {
|
||||
onFiltersChange({
|
||||
@@ -46,6 +48,7 @@ export default function ExmnLayerSettingPanel({
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="space-y-[6px]">
|
||||
{statusMessage ? <p role="status" className="text-[1.1rem] text-muted-foreground">{statusMessage}</p> : null}
|
||||
<label className="flex min-h-[24px] flex-col items-stretch gap-1">
|
||||
<span className={`flex items-center gap-1 ${labelClass}`}>
|
||||
<span
|
||||
+9
-4
@@ -1,11 +1,13 @@
|
||||
/** 관로조사 지도 데이터 조회 API와 응답 변환 함수를 제공한다. */
|
||||
import { getRawJson } from "../../../../components/lib/api";
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmnMapTypes";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmn-map-types";
|
||||
|
||||
type ExmnLayerScope = {
|
||||
sggCd?: string;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
exmnBizId?: string;
|
||||
trtdstId?: string;
|
||||
};
|
||||
|
||||
/** 구버전 응답이 하류맨홀을 포함해도 업로드 관로조사의 상류맨홀 피처만 지도에 전달한다. */
|
||||
@@ -36,16 +38,19 @@ export const retainUpstreamManholeFeatures = (response: any) => {
|
||||
export const getExmnMapLayer = async (
|
||||
jobType: ExmnMapJobType,
|
||||
filters: ExmnMapFilters,
|
||||
scope: ExmnLayerScope,
|
||||
scope: ExmnLayerScope = {},
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const query = new URLSearchParams({ jobType });
|
||||
if (scope.sggCd?.trim()) query.set("sggCd", scope.sggCd.trim());
|
||||
if (scope.ctpvNm?.trim()) query.set("ctpvNm", scope.ctpvNm.trim());
|
||||
if (scope.sggNm?.trim()) query.set("sggNm", scope.sggNm.trim());
|
||||
if (scope.exmnBizId?.trim()) query.set("exmnBizId", scope.exmnBizId.trim());
|
||||
if (scope.trtdstId?.trim()) query.set("trtdstId", scope.trtdstId.trim());
|
||||
if (filters.bizCycl) query.set("bizCycl", filters.bizCycl);
|
||||
if (filters.structuralGrade) query.set("structuralGrade", filters.structuralGrade);
|
||||
if (filters.operationalGrade) query.set("operationalGrade", filters.operationalGrade);
|
||||
|
||||
const response = await getRawJson(`/sewerage-service/api/v1/exmnMap/layers?${query.toString()}`);
|
||||
const response = await getRawJson(`/sewerage-service/api/v1/exmnMap/layers?${query.toString()}`, { signal });
|
||||
return jobType === "MNHL" ? retainUpstreamManholeFeatures(response) : response;
|
||||
};
|
||||
+2
@@ -21,6 +21,7 @@ export type ExmnMapFilters = {
|
||||
|
||||
export type ExmnMapEntryContext = {
|
||||
exmnBizId: string;
|
||||
trtdstId: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
bizCycl: string;
|
||||
@@ -291,6 +292,7 @@ export const buildExmnMapEntryContext = (
|
||||
const rawJobType = String(searchParams.get("exmnJobType") ?? "").toUpperCase();
|
||||
return {
|
||||
exmnBizId: String(searchParams.get("exmnBizId") ?? "").trim(),
|
||||
trtdstId: String(searchParams.get("trtdstId") ?? "").trim(),
|
||||
ctpvNm: String(searchParams.get("ctpvNm") ?? "").trim(),
|
||||
sggNm: String(searchParams.get("sggNm") ?? "").trim(),
|
||||
bizCycl: String(searchParams.get("bizCycl") ?? "").replace(/[^0-9]/g, ""),
|
||||
@@ -0,0 +1,261 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import GeoJSON from "ol/format/GeoJSON";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import { getExmnMapLayer } from "./exmn-map-api";
|
||||
import {
|
||||
EMPTY_EXMN_MAP_FILTERS,
|
||||
EXMN_RESULT_PPL_LAYER_ID,
|
||||
EXMN_RESULT_MNHL_LAYER_ID,
|
||||
type ExmnMapEntryContext,
|
||||
type ExmnMapFilters,
|
||||
type ExmnMapJobType,
|
||||
} from "./exmn-map-types";
|
||||
|
||||
type Options = {
|
||||
gisApp: any;
|
||||
mapRef: RefObject<any>;
|
||||
ready: boolean;
|
||||
treeData: any[];
|
||||
entry: ExmnMapEntryContext;
|
||||
pipeChecked: boolean;
|
||||
manholeChecked: boolean;
|
||||
pipeEnabled: boolean;
|
||||
manholeEnabled: boolean;
|
||||
pipeFilters: ExmnMapFilters;
|
||||
manholeFilters: ExmnMapFilters;
|
||||
pipeStyle: any;
|
||||
manholeStyle: any;
|
||||
};
|
||||
|
||||
type LayerState = {
|
||||
layer?: VectorLayer<any>;
|
||||
controller?: AbortController;
|
||||
sequence: number;
|
||||
};
|
||||
|
||||
/** 전체 조사 레이어와 상세현황 위치 이동을 분리하며 유형별 요청을 취소/무효화한다. */
|
||||
export const useExmnResultMap = (options: Options) => {
|
||||
const {
|
||||
gisApp,
|
||||
mapRef,
|
||||
ready,
|
||||
treeData,
|
||||
entry,
|
||||
pipeChecked,
|
||||
manholeChecked,
|
||||
pipeEnabled,
|
||||
manholeEnabled,
|
||||
pipeFilters,
|
||||
manholeFilters,
|
||||
pipeStyle,
|
||||
manholeStyle,
|
||||
} = options;
|
||||
// 지도 객체/요청 수명은 React 렌더링과 분리하고 관로·맨홀을 독립적으로 관리한다.
|
||||
const layers = useRef<Record<ExmnMapJobType, LayerState>>({
|
||||
PPL: { sequence: 0 },
|
||||
MNHL: { sequence: 0 },
|
||||
});
|
||||
const fittedContext = useRef("");
|
||||
const [messages, setMessages] = useState<Record<ExmnMapJobType, string>>({
|
||||
PPL: "",
|
||||
MNHL: "",
|
||||
});
|
||||
|
||||
/** 체크 해제/언마운트 시 늦게 도착한 응답도 다시 표시되지 않게 한다. */
|
||||
const removeLayer = useCallback(
|
||||
(job: ExmnMapJobType) => {
|
||||
const state = layers.current[job];
|
||||
state.sequence++;
|
||||
state.controller?.abort();
|
||||
state.controller = undefined;
|
||||
if (state.layer) {
|
||||
gisApp.Module.select?.removeLayer?.(state.layer);
|
||||
mapRef.current?.removeLayer?.(state.layer);
|
||||
}
|
||||
state.layer = undefined;
|
||||
},
|
||||
[gisApp, mapRef],
|
||||
);
|
||||
|
||||
/** 지역 검색값 없이 전체를 요청하고, 선택 진입 context는 위치 이동 범위 조회에만 사용한다. */
|
||||
const loadLayer = useCallback(
|
||||
async (job: ExmnMapJobType, filters: ExmnMapFilters) => {
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
removeLayer(job);
|
||||
const state = layers.current[job];
|
||||
const sequence = state.sequence;
|
||||
const controller = new AbortController();
|
||||
state.controller = controller;
|
||||
const active = () =>
|
||||
state.sequence === sequence && !controller.signal.aborted;
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: "조사결과를 불러오는 중입니다.",
|
||||
}));
|
||||
try {
|
||||
const response = await getExmnMapLayer(
|
||||
job,
|
||||
filters,
|
||||
{},
|
||||
controller.signal,
|
||||
);
|
||||
if (!active()) return;
|
||||
const data = response?.data ?? response;
|
||||
const features = new GeoJSON().readFeatures(data, {
|
||||
dataProjection: "EPSG:5179",
|
||||
featureProjection: gisApp.TO_PROJ,
|
||||
});
|
||||
const source = new VectorSource({ features });
|
||||
const layer = new VectorLayer({
|
||||
source,
|
||||
zIndex: job === "PPL" ? 9988 : 9989,
|
||||
style: job === "PPL" ? pipeStyle : manholeStyle,
|
||||
});
|
||||
layer.setProperties({
|
||||
id:
|
||||
job === "PPL"
|
||||
? EXMN_RESULT_PPL_LAYER_ID
|
||||
: EXMN_RESULT_MNHL_LAYER_ID,
|
||||
lyrNm: job === "PPL" ? "관로조사" : "맨홀조사",
|
||||
lyrTyNm: job === "PPL" ? "tb_ms_swage_ppl" : "tb_ms_swage_mhl",
|
||||
clickPsblYn: "Y",
|
||||
chcYn: "Y",
|
||||
});
|
||||
state.layer = layer;
|
||||
map.addLayer(layer);
|
||||
gisApp.Module.select?.addLayer?.(layer);
|
||||
const missing = Number(data?.missingGeometryCount ?? 0);
|
||||
const diagnostics = data?.diagnostics ?? {};
|
||||
const reasons = [
|
||||
["지역 확인 불가", diagnostics.regionUnresolvedCount],
|
||||
["시설번호 없음", diagnostics.identifierMissingCount],
|
||||
["도형 없음", diagnostics.geometryNotFoundCount],
|
||||
["도형 중복", diagnostics.ambiguousGeometryCount],
|
||||
]
|
||||
.filter(([, count]) => Number(count) > 0)
|
||||
.map(([label, count]) => `${label} ${count}건`)
|
||||
.join(", ");
|
||||
const summary = features.length
|
||||
? `${features.length.toLocaleString()}개 표시`
|
||||
: "표시할 조사결과가 없습니다.";
|
||||
const status = missing
|
||||
? `${summary} · 공간정보 미연결 ${missing}건${reasons ? ` (${reasons})` : ""}`
|
||||
: summary;
|
||||
setMessages((current) => ({ ...current, [job]: status }));
|
||||
|
||||
if (entry.jobType !== job || !entry.exmnBizId || !entry.trtdstId)
|
||||
return;
|
||||
// 위치 조회 실패가 정상적으로 조회된 기본 레이어를 지우지 않도록 별도 처리한다.
|
||||
try {
|
||||
const selectedResponse = await getExmnMapLayer(
|
||||
job,
|
||||
filters,
|
||||
{ exmnBizId: entry.exmnBizId, trtdstId: entry.trtdstId },
|
||||
controller.signal,
|
||||
);
|
||||
if (!active()) return;
|
||||
const selectedData = selectedResponse?.data ?? selectedResponse;
|
||||
const selectedFeatures = new GeoJSON().readFeatures(selectedData, {
|
||||
dataProjection: "EPSG:5179",
|
||||
featureProjection: gisApp.TO_PROJ,
|
||||
});
|
||||
if (!selectedFeatures.length) {
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: `${status} · 선택한 처리분구에 현재 조건으로 표시할 공간정보가 없습니다.`,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const selectedSource = new VectorSource({
|
||||
features: selectedFeatures,
|
||||
});
|
||||
// 별도 강조선을 추가하지 않고 기존 조사 레이어 색상으로 표시한다.
|
||||
const contextKey = `${entry.exmnBizId}:${entry.trtdstId}:${job}`;
|
||||
const extent = selectedSource.getExtent();
|
||||
if (
|
||||
fittedContext.current !== contextKey &&
|
||||
extent.every(Number.isFinite)
|
||||
) {
|
||||
map
|
||||
.getView()
|
||||
.fit(extent, {
|
||||
duration: 300,
|
||||
padding: [80, 420, 80, 80],
|
||||
maxZoom: 18,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
fittedContext.current = contextKey;
|
||||
}
|
||||
} catch (error) {
|
||||
if (active())
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: `${status} · 선택 영역 조회 실패: ${error instanceof Error ? error.message : "다시 조회해 주세요."}`,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (active())
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: `조회 실패: ${error instanceof Error ? error.message : "다시 조회해 주세요."}`,
|
||||
}));
|
||||
}
|
||||
},
|
||||
[
|
||||
entry.exmnBizId,
|
||||
entry.trtdstId,
|
||||
entry.jobType,
|
||||
gisApp,
|
||||
mapRef,
|
||||
pipeStyle,
|
||||
manholeStyle,
|
||||
removeLayer,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && pipeChecked)
|
||||
void loadLayer("PPL", pipeEnabled ? pipeFilters : EMPTY_EXMN_MAP_FILTERS);
|
||||
else removeLayer("PPL");
|
||||
return () => removeLayer("PPL");
|
||||
}, [
|
||||
ready,
|
||||
pipeChecked,
|
||||
pipeEnabled,
|
||||
pipeFilters,
|
||||
treeData,
|
||||
loadLayer,
|
||||
removeLayer,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && manholeChecked)
|
||||
void loadLayer(
|
||||
"MNHL",
|
||||
manholeEnabled ? manholeFilters : EMPTY_EXMN_MAP_FILTERS,
|
||||
);
|
||||
else removeLayer("MNHL");
|
||||
return () => removeLayer("MNHL");
|
||||
}, [
|
||||
ready,
|
||||
manholeChecked,
|
||||
manholeEnabled,
|
||||
manholeFilters,
|
||||
treeData,
|
||||
loadLayer,
|
||||
removeLayer,
|
||||
]);
|
||||
|
||||
return { removeLayer, messages };
|
||||
};
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/** 침수흔적 강우분석과 연결된 심선·위선 목록 조회를 담당한다. */
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
|
||||
import type { FldngHstryDetailCacheValue } from "./fldngHstryTypes";
|
||||
import type { FldngHstryDetailCacheValue } from "@/entities/flood-history/model/flood-history-types";
|
||||
|
||||
const normalizeFldngHstryRainAnalysisPayload = (data: any) => {
|
||||
if (!data || typeof data !== "object") return {};
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import type { FldngHstryItem } from "./fldngHstryTypes";
|
||||
import { formatFldngHstryArea, formatFldngHstryDepth } from "./fldngHstryUtils";
|
||||
import type { FldngHstryItem } from "@/entities/flood-history/model/flood-history-types";
|
||||
import { formatFldngHstryArea, formatFldngHstryDepth } from "@/entities/flood-history/lib/flood-history-utils";
|
||||
|
||||
type Props = {
|
||||
detail: FldngHstryItem;
|
||||
+2
-6
@@ -10,12 +10,8 @@ import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import type Feature from "ol/Feature.js";
|
||||
import VectorSource from "ol/source/Vector.js";
|
||||
|
||||
import { loadFldngHstryDetail } from "./fldngHstryClient";
|
||||
import {
|
||||
FLDNG_HSTRY_DEPTH_LAYER_TYPE,
|
||||
FLDNG_HSTRY_LAYER_TYPE,
|
||||
type FldngHstryDetailCacheValue,
|
||||
} from "./fldngHstryTypes";
|
||||
import { loadFldngHstryDetail } from "./fldng-hstry-client";
|
||||
import { FLDNG_HSTRY_DEPTH_LAYER_TYPE, FLDNG_HSTRY_LAYER_TYPE, type FldngHstryDetailCacheValue } from "@/entities/flood-history/model/flood-history-types";
|
||||
|
||||
type FldngHstryGisLayer = {
|
||||
searchLayer?: { getSource?: () => VectorSource | null } | null;
|
||||
+3
-6
@@ -5,12 +5,9 @@
|
||||
"use client";
|
||||
|
||||
import { SelectBox } from "@common/business_components/ui";
|
||||
import {
|
||||
FLDNG_HSTRY_MIN_YEAR,
|
||||
type FldngHstryYearRange,
|
||||
} from "./fldngHstryTypes";
|
||||
import { normalizeFldngHstryYearRange } from "./fldngHstryUtils";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
import { FLDNG_HSTRY_MIN_YEAR, type FldngHstryYearRange } from "@/entities/flood-history/model/flood-history-types";
|
||||
import { normalizeFldngHstryYearRange } from "@/entities/flood-history/lib/flood-history-utils";
|
||||
import LayerSettingCard from "../layer-setting/layer-setting-card";
|
||||
|
||||
type Props = {
|
||||
yearRange: FldngHstryYearRange;
|
||||
+16
-60
@@ -7,34 +7,16 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import { requestGridExcel } from "@/shared/api/grid-export";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/window-popup/model/types";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import useStore from "@/store/useStore";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import {
|
||||
FLDNG_HSTRY_LAYER_TYPE,
|
||||
FLDNG_HSTRY_DEFAULT_FILTERS,
|
||||
FLDNG_HSTRY_DOWNLOAD_BATCH_SIZE,
|
||||
FLDNG_HSTRY_DOWNLOAD_HEADERS,
|
||||
FLDNG_HSTRY_LIST_HEADLIST,
|
||||
FLDNG_HSTRY_PAGE_SIZE,
|
||||
FLDNG_HSTRY_ROW_SELECT_MESSAGE,
|
||||
type FldngHstryApiItem,
|
||||
type FldngHstryFilters,
|
||||
type FldngHstryTableRow,
|
||||
} from "./fldngHstryTypes";
|
||||
import {
|
||||
attachFldngHstryGeometry,
|
||||
buildFldngHstryListQueryString,
|
||||
buildFldngHstryYearOptions,
|
||||
normalizeFldngHstryFilters,
|
||||
toFldngHstryTableRows,
|
||||
} from "./fldngHstryUtils";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { FLDNG_HSTRY_LAYER_TYPE, FLDNG_HSTRY_DEFAULT_FILTERS, FLDNG_HSTRY_LIST_HEADLIST, FLDNG_HSTRY_PAGE_SIZE, FLDNG_HSTRY_ROW_SELECT_MESSAGE, type FldngHstryApiItem, type FldngHstryFilters, type FldngHstryTableRow } from "@/entities/flood-history/model/flood-history-types";
|
||||
import { attachFldngHstryGeometry, buildFldngHstryListQueryString, buildFldngHstryYearOptions, normalizeFldngHstryFilters, toFldngHstryTableRows } from "@/entities/flood-history/lib/flood-history-utils";
|
||||
|
||||
const unwrapPayload = <T,>(response: any): T =>
|
||||
(response?.data ?? response) as T;
|
||||
@@ -58,26 +40,6 @@ const resolveFldngHstryMapFeatureFid = (row: FldngHstryTableRow) => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const buildDownloadRows = (rows: FldngHstryTableRow[]) =>
|
||||
rows.map((row) => ({
|
||||
rowNo: row.rowNo,
|
||||
floodYearDisplay: row.floodYearDisplay,
|
||||
ctpvNm: row.ctpvNm,
|
||||
sggNm: row.sggNm,
|
||||
disasterName: row.disasterName,
|
||||
rainBgngTmDisplay: row.rainBgngTmDisplay,
|
||||
rainEndTmDisplay: row.rainEndTmDisplay,
|
||||
rainTotDisplay: row.rainTotDisplay,
|
||||
rainStrDisplay: row.rainStrDisplay,
|
||||
floodHourDisplay: row.floodHourDisplay,
|
||||
districtName: row.districtName,
|
||||
causeDetailCode: row.causeDetailCode,
|
||||
causeDetailName: row.causeDetailName,
|
||||
avgFloodDepthDisplay: row.avgFloodDepthDisplay,
|
||||
floodAreaDisplay: row.floodAreaDisplay,
|
||||
exmnYearDisplay: row.exmnYearDisplay,
|
||||
damageAmountDisplay: row.damageAmountDisplay,
|
||||
}));
|
||||
|
||||
const resolveRegionNames = (
|
||||
row: FldngHstryTableRow,
|
||||
@@ -265,19 +227,13 @@ export default function FldngHstryPopupPage() {
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetchAllPages({
|
||||
batchSize: FLDNG_HSTRY_DOWNLOAD_BATCH_SIZE,
|
||||
fetchPage: (page, size) => fetchRows(page + 1, appliedFilters, size),
|
||||
getItems: (page) => page.rows,
|
||||
getTotal: (page) => page.total,
|
||||
});
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: buildDownloadRows(response.items) as unknown as JSON[],
|
||||
fileName: `침수발생이력_${new Date().toISOString().slice(0, 10)}.xlsx`,
|
||||
sheetNameArray: ["침수발생이력"],
|
||||
headerRange: "A1:Q1",
|
||||
headerMapping: FLDNG_HSTRY_DOWNLOAD_HEADERS,
|
||||
});
|
||||
const params = new URLSearchParams(buildFldngHstryListQueryString(appliedFilters, 1, FLDNG_HSTRY_PAGE_SIZE));
|
||||
params.delete("page"); params.delete("size");
|
||||
await requestGridExcel("v1/spatialGridExports/floodHistories", {
|
||||
history: Object.fromEntries(params),
|
||||
ctpvNames: Object.fromEntries(ctpv.map((item) => [String(item.val).trim(), item.text])),
|
||||
sggNames: Object.fromEntries(sgg.map((item) => [String(item.val).trim(), item.text])),
|
||||
}, `침수발생이력_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||
} catch (error) {
|
||||
console.error("침수발생이력 다운로드 중 오류:", error);
|
||||
alert(
|
||||
+7
-7
@@ -7,12 +7,12 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import useStore from "@/store/useStore";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/window-popup/model/types";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import {
|
||||
FLOOD_DNGR_MAP_DEFAULT_FILTERS,
|
||||
FLOOD_DNGR_MAP_DEPTH_OPTIONS,
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
type FloodDngrMapExtent,
|
||||
type FloodDngrMapFilters,
|
||||
type FloodDngrMapTableRow,
|
||||
} from "./floodDngrMapTypes";
|
||||
} from "./flood-dngr-map-types";
|
||||
|
||||
const FLOOD_DNGR_MAP_REQUEST_TIMEOUT_MS = 10_000;
|
||||
const FLOOD_DNGR_MAP_LOADING_WATCHDOG_MS = 12_000;
|
||||
+1
-1
@@ -159,7 +159,7 @@ export const toFloodDngrMapTableRows = (
|
||||
): FloodDngrMapTableRow[] =>
|
||||
items.map((item, index) => ({
|
||||
...item,
|
||||
rowNo: total - page * pageSize - index,
|
||||
rowNo: page * pageSize + index + 1,
|
||||
ctpvNm: resolveAdministrativeName(item.ctpvCd, ctpvOptions, 2),
|
||||
sggNm: resolveAdministrativeName(item.sggCd, sggOptions, 5),
|
||||
sbmrgDpthlDisplay: toDepthRangeDisplay(
|
||||
+61
-22
@@ -4,15 +4,19 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState, useRef } from "react";
|
||||
import { useFacilityCodeCatalog } from "@/entities/facility/lib/use-facility-code-catalog";
|
||||
|
||||
import { Button } from "@common/components/ui";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { VerticalTable } from "@/components/ui/verticalTable";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { VerticalTable } from "@/components/ui/vertical-table";
|
||||
|
||||
import type { GisEditChange, GisEditPipeBasicInfo } from "./gisEditTypes";
|
||||
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from "../common/table";
|
||||
import type { GisEditChange, GisEditPipeBasicInfo } from "./gis-edit-types";
|
||||
import {
|
||||
MS_SWAGE_PPL_CODES,
|
||||
MS_SWAGE_PPL_USE_CODES,
|
||||
} from "@/entities/facility/model/facility-code-fallbacks";
|
||||
|
||||
type Props = {
|
||||
change: GisEditChange;
|
||||
@@ -206,20 +210,25 @@ const normalizeBasicFormValues = (values: Record<string, any>) => {
|
||||
return nextValues;
|
||||
};
|
||||
|
||||
const buildBaseFeatureValues = (featureData: any) => {
|
||||
const buildBaseFeatureValues = (
|
||||
featureData: any,
|
||||
catalog?: ReturnType<typeof useFacilityCodeCatalog>,
|
||||
) => {
|
||||
const resolveUsageCode = (value: unknown) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
const matched = MS_SWAGE_PPL_USE_CODES.find(
|
||||
(item) => item.val === raw || item.text === raw,
|
||||
);
|
||||
const matched = [
|
||||
...(catalog?.MS_SWAGE_PPL_USE_CODES ?? []),
|
||||
...MS_SWAGE_PPL_USE_CODES,
|
||||
].find((item) => item.val === raw || item.text === raw);
|
||||
return matched?.val.trim() ?? raw;
|
||||
};
|
||||
|
||||
const resolveMaterialCode = (value: unknown) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
const matched = MS_SWAGE_PPL_CODES.find(
|
||||
(item) => item.val === raw || item.text === raw,
|
||||
);
|
||||
const matched = [
|
||||
...(catalog?.MS_SWAGE_PPL_CODES ?? []),
|
||||
...MS_SWAGE_PPL_CODES,
|
||||
].find((item) => item.val === raw || item.text === raw);
|
||||
return matched?.val.trim() ?? raw;
|
||||
};
|
||||
|
||||
@@ -243,13 +252,12 @@ const buildBaseFeatureValues = (featureData: any) => {
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getPipeUsageLabel = (value: unknown) => {
|
||||
const getPipeUsageLabel = (value: unknown, codes = MS_SWAGE_PPL_USE_CODES) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
if (!raw) return "";
|
||||
return (
|
||||
MS_SWAGE_PPL_USE_CODES.find(
|
||||
(item) => item.val.trim() === raw || item.text === raw,
|
||||
)?.text ?? raw
|
||||
codes.find((item) => item.val.trim() === raw || item.text === raw)?.text ??
|
||||
raw
|
||||
);
|
||||
};
|
||||
|
||||
@@ -269,10 +277,17 @@ export default function GisEditPipeSessionPopup({
|
||||
onClose,
|
||||
onSave,
|
||||
}: Props) {
|
||||
const catalog = useFacilityCodeCatalog();
|
||||
// CMS 명칭 갱신은 입력 중인 초안을 초기화할 이유가 아니다.
|
||||
const initializedSource = useRef<{
|
||||
featureData: any;
|
||||
changeId: string;
|
||||
isNewPipe: boolean;
|
||||
} | null>(null);
|
||||
const isNewPipe = change.editType === "ADD";
|
||||
const baseFeatureValues = useMemo(
|
||||
() => buildBaseFeatureValues(featureData),
|
||||
[featureData],
|
||||
() => buildBaseFeatureValues(featureData, catalog),
|
||||
[featureData, catalog],
|
||||
);
|
||||
const [basicInfoTableData, setBasicInfoTableData] = useState<
|
||||
Record<string, any>
|
||||
@@ -322,12 +337,36 @@ export default function GisEditPipeSessionPopup({
|
||||
PIPE_BASIC_INFO_FIELDS.map((field) =>
|
||||
field.id === "mngInstCd"
|
||||
? { ...field, options: mngInstSelectOptions }
|
||||
: field,
|
||||
: field.id === "usgCd" || field.id === "mqltCd"
|
||||
? {
|
||||
...field,
|
||||
options: [
|
||||
EMPTY_SELECT_OPTION,
|
||||
...(field.id === "usgCd"
|
||||
? catalog.MS_SWAGE_PPL_USE_CODES
|
||||
: catalog.MS_SWAGE_PPL_CODES
|
||||
).map((item) => ({ value: item.val, label: item.text })),
|
||||
],
|
||||
}
|
||||
: field,
|
||||
),
|
||||
[mngInstSelectOptions],
|
||||
[mngInstSelectOptions, catalog],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const previous = initializedSource.current;
|
||||
if (
|
||||
previous &&
|
||||
previous.featureData === featureData &&
|
||||
previous.changeId === change.changeId &&
|
||||
previous.isNewPipe === isNewPipe
|
||||
)
|
||||
return;
|
||||
initializedSource.current = {
|
||||
featureData,
|
||||
changeId: change.changeId,
|
||||
isNewPipe,
|
||||
};
|
||||
const initialBasicValues: Record<string, any> = {
|
||||
...baseFeatureValues,
|
||||
...normalizeBasicFormValues(change.basicInfo ?? {}),
|
||||
@@ -335,7 +374,7 @@ export default function GisEditPipeSessionPopup({
|
||||
|
||||
setBasicInfoTableData(initialBasicValues);
|
||||
setInputInfoTableData(buildInitialInputDraft(change));
|
||||
}, [baseFeatureValues, change.changeId, isNewPipe]);
|
||||
}, [baseFeatureValues, featureData, change.changeId, isNewPipe]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewPipe) return;
|
||||
@@ -429,7 +468,7 @@ export default function GisEditPipeSessionPopup({
|
||||
<h3 className="whitespace-nowrap text-2xl font-medium text-primary-foreground">
|
||||
하수관로
|
||||
{basicInfoTableData?.usgCd
|
||||
? `(${getPipeUsageLabel(basicInfoTableData.usgCd)})`
|
||||
? `(${getPipeUsageLabel(basicInfoTableData.usgCd, catalog.MS_SWAGE_PPL_USE_CODES)})`
|
||||
: ""}{" "}
|
||||
정보 <br />
|
||||
관리번호 : {displayManagementNo || "신규"}
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import type { GisEditSessionSnapshot, GisEditTool } from "./gisEditTypes";
|
||||
import { useGisEditOperationLabels } from "./useGisEditCommonCodes";
|
||||
import type { GisEditSessionSnapshot, GisEditTool } from "./gis-edit-types";
|
||||
import { useGisEditOperationLabels } from "./use-gis-edit-common-codes";
|
||||
|
||||
type Props = GisEditSessionSnapshot & {
|
||||
onRemoveFocusedChange: () => void;
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
import { Check, Move, PenTool, Plus, Trash2, X } from "lucide-react";
|
||||
import { Button } from "@common/components/ui";
|
||||
|
||||
import type { GisEditTool } from "./gisEditTypes";
|
||||
import { useGisEditOperationLabels } from "./useGisEditCommonCodes";
|
||||
import type { GisEditTool } from "./gis-edit-types";
|
||||
import { useGisEditOperationLabels } from "./use-gis-edit-common-codes";
|
||||
|
||||
type Props = {
|
||||
isEditMode: boolean;
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
/** GIS 편집 모델은 기능이 소유하고 기존 화면 import 경로는 유지한다. */
|
||||
export * from "@/features/gisEdit/model/types";
|
||||
export * from "@/features/gis-edit/model/types";
|
||||
+2
-2
@@ -3,9 +3,9 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getCommonCodes } from "@/components/lib/common/apiHelpers";
|
||||
import { getCommonCodes } from "@/entities/code/api/common-code-api";
|
||||
|
||||
import type { GisEditTool } from "./gisEditTypes";
|
||||
import type { GisEditTool } from "./gis-edit-types";
|
||||
|
||||
const GIS_EDIT_OPERATION_UP_CD = "GISEDITOPRT";
|
||||
|
||||
+3
-3
@@ -22,15 +22,15 @@ import {
|
||||
getFeatureProperties,
|
||||
normalizePipeGeometry,
|
||||
createChangeId,
|
||||
} from "@/features/gisEdit";
|
||||
export { EMPTY_GIS_EDIT_PIPE_BASIC_INFO } from "@/features/gisEdit";
|
||||
} from "@/features/gis-edit";
|
||||
export { EMPTY_GIS_EDIT_PIPE_BASIC_INFO } from "@/features/gis-edit";
|
||||
|
||||
import type {
|
||||
GisEditChange,
|
||||
GisEditPipeBasicInfo,
|
||||
GisEditSavePayload,
|
||||
GisEditTool,
|
||||
} from "./gisEditTypes";
|
||||
} from "./gis-edit-types";
|
||||
|
||||
export const useGisEdit = (treeData: any[]) => {
|
||||
const editableLayers = useMemo(() => collectEditableLayers(treeData), [treeData]);
|
||||
+14
-18
@@ -12,26 +12,22 @@ import {
|
||||
} from "@common/business_components";
|
||||
import { Button, Input } from "@common/components";
|
||||
|
||||
import {
|
||||
getBlobDataWithHeaders,
|
||||
getData,
|
||||
postMutiPart,
|
||||
postRawJson,
|
||||
} from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import { getBlobDataWithHeaders, postMutiPart } from "@/shared/api/file-api";
|
||||
import { getData, postRawJson } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useUserStore } from "@/shared/lib/use-user-store";
|
||||
import GisUploadPreviewMap from "./GisUploadPreviewMap";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { useGisManagementCommonCodes } from "./useGisManagementCommonCodes";
|
||||
import type { UploadFacilityType, MngInstOption, UploadHistoryItem, GisUploadResponse, ValidationErrorItem, ViewMode } from "@/features/gisManagement/model/upload-types";
|
||||
import { getLocalizedValidationErrorMessage, getFileExtension, formatDateTime, toScopeKey, buildManagementLabel, getErrorMessage } from "@/features/gisManagement/lib/upload-display";
|
||||
import { createGisDownloadClient, saveBlob } from "@/features/gisManagement/api/gis-download";
|
||||
import { postRefreshMessageToOpener } from "@/features/gisManagement/lib/popup-refresh";
|
||||
import GisUploadPreviewMap from "./gis-upload-preview-map";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { useGisManagementCommonCodes } from "./use-gis-management-common-codes";
|
||||
import type { UploadFacilityType, MngInstOption, UploadHistoryItem, GisUploadResponse, ValidationErrorItem, ViewMode } from "@/features/gis-management/model/upload-types";
|
||||
import { getLocalizedValidationErrorMessage, getFileExtension, formatDateTime, toScopeKey, buildManagementLabel, getErrorMessage } from "@/features/gis-management/lib/upload-display";
|
||||
import { createGisDownloadClient, saveBlob } from "@/features/gis-management/api/gis-download";
|
||||
import { postRefreshMessageToOpener } from "@/features/gis-management/lib/popup-refresh";
|
||||
|
||||
import { createGisUploadRetryGuard, getGisPartialSuccessNotice } from "@/features/gisManagement";
|
||||
import { createGisUploadRetryGuard, getGisPartialSuccessNotice } from "@/features/gis-management";
|
||||
|
||||
// 기존 전송 경로를 유지하며 다운로드 작업의 비동기 처리는 기능 모듈에 위임한다.
|
||||
const { downloadGeoPackage, startDownloadJob, waitForDownloadJob } =
|
||||
@@ -57,7 +53,7 @@ const GIS_UPLOAD_HISTORY_HEADLIST: UiHeadProps[] = [
|
||||
];
|
||||
|
||||
|
||||
/** 오류 원본 행번호와 공통 표의 역순 순번을 별도 열로 표시한다. */
|
||||
/** 오류 원본 행번호와 공통 표의 오름차순 표시 순번을 별도 열로 표시한다. */
|
||||
const GIS_VALIDATION_ERROR_HEADLIST: UiHeadProps[] = [
|
||||
{ id: "no", title: "순번", widthPercent: 8 },
|
||||
{ id: "sourceRowNo", title: "행번호", widthPercent: 10 },
|
||||
+1
-1
@@ -20,7 +20,7 @@ import { get as getProjection } from "ol/proj";
|
||||
import { register } from "ol/proj/proj4";
|
||||
import proj4 from "proj4";
|
||||
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
|
||||
type GisUploadPreviewMapProps = {
|
||||
uploadId: string;
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getCommonCodes } from "@/components/lib/common/apiHelpers";
|
||||
import { getCommonCodes } from "@/entities/code/api/common-code-api";
|
||||
|
||||
type CodeLabelMap = Record<string, string>;
|
||||
|
||||
+12
-35
@@ -11,26 +11,25 @@ import {
|
||||
type UiHeadProps,
|
||||
type UiTableSort,
|
||||
} from "@common/business_components";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import { downloadAnalysisExcel } from "@/shared/lib/download-analysis-excel";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox";
|
||||
import { formatDisplayData } from "@/components/lib/utils/formatDisplayValue";
|
||||
import useStore from "@/store/useStore";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpv-sgg-prcs-fclt-select-box";
|
||||
import { formatDisplayData } from "@/components/lib/utils/format-display-value";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
|
||||
import {
|
||||
INFTWT_RESULT_ROW_SELECT_MESSAGE,
|
||||
INFTWT_RESULT_ROW_SELECT_STORAGE_KEY,
|
||||
type InftwtResultRow,
|
||||
} from "./inftwtResultTypes";
|
||||
} from "./inftwt-result-types";
|
||||
|
||||
const INFTWT_RESULT_PAGE_SIZE = 10;
|
||||
|
||||
@@ -160,7 +159,7 @@ export default function InftwtResultPopupPage() {
|
||||
return {
|
||||
...(formattedItem as Record<string, unknown>),
|
||||
rowNumber: Math.max(
|
||||
totalElements - ((page - 1) * INFTWT_RESULT_PAGE_SIZE + index),
|
||||
(page - 1) * INFTWT_RESULT_PAGE_SIZE + index + 1,
|
||||
0,
|
||||
),
|
||||
ctpvNm: ctpvOption ? ctpvOption.text : String(item?.ctpvCd ?? "-"),
|
||||
@@ -241,29 +240,7 @@ export default function InftwtResultPopupPage() {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetchAllPages<any, Record<string, unknown>>({
|
||||
fetchPage: (page, size) =>
|
||||
getData(
|
||||
`v1/inftwtAnlsRslt/ppl/${selectedRow.inftwtAnlsSn}?page=${page}&size=${size}`,
|
||||
),
|
||||
getItems: (page) =>
|
||||
Array.isArray(page?.content) ? page.content : [],
|
||||
getTotal: (page) => Number(page?.totalElements ?? 0),
|
||||
});
|
||||
const excelRows = response.items;
|
||||
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: excelRows as unknown as JSON[],
|
||||
fileName: `침입수분석결과_${selectedRow.swtrmtFcltNm}_${selectedRow.aplcnYm}_${selectedRow.inftwtAnlsSn}`,
|
||||
sheetNameArray: ["침입수 분석결과"],
|
||||
headerRange: "A1:D1",
|
||||
headerMapping: {
|
||||
inftwtAnlsSn: "침입수분석\n분석번호",
|
||||
pplMngNo: "관로\n관리번호",
|
||||
anlsTm: "분석시각",
|
||||
predcOilq: "예측유량\n(m³/h)",
|
||||
},
|
||||
});
|
||||
await downloadAnalysisExcel("inftwt", selectedRow.inftwtAnlsSn, `침입수분석결과_${selectedRow.swtrmtFcltNm}_${selectedRow.aplcnYm}_${selectedRow.inftwtAnlsSn}`);
|
||||
} catch (error) {
|
||||
console.error("침입수 분석결과 다운로드 중 오류:", error);
|
||||
alert("침입수 분석결과 다운로드 중 오류가 발생했습니다.");
|
||||
+3
-3
@@ -10,8 +10,8 @@ import Fill from "ol/style/Fill.js";
|
||||
import Style from "ol/style/Style.js";
|
||||
import { intersects as intersectsExtent } from "ol/extent";
|
||||
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import { openWindowPopup } from "@/features/window-popup/lib/open-window-popup";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
INFTWT_RESULT_ROW_SELECT_STORAGE_KEY,
|
||||
type InftwtResultMapViewResponse,
|
||||
type InftwtResultRow,
|
||||
} from "./inftwtResultTypes";
|
||||
} from "./inftwt-result-types";
|
||||
|
||||
type UseInftwtResultMapParams = {
|
||||
gisMaps: MutableRefObject<any | null>;
|
||||
+51
-54
@@ -8,20 +8,18 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { requestGridExcel } from "@/shared/api/grid-export";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/store/useStore";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import { useUserStore } from "@/shared/lib/use-user-store";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from "../common/table";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { useFacilityCodeCatalog } from "@/entities/facility/lib/use-facility-code-catalog";
|
||||
import {
|
||||
PIPE_ANALYSIS_DETAIL_DEFAULT_FILTERS,
|
||||
PIPE_ANALYSIS_DETAIL_EXCEL_HEADERS,
|
||||
PIPE_ANALYSIS_DETAIL_HEADLIST,
|
||||
PIPE_ANALYSIS_DETAIL_ROW_SELECT_MESSAGE,
|
||||
applyScopeToDetailFilters,
|
||||
@@ -36,24 +34,11 @@ import {
|
||||
toPipeAnalysisDetailViewRows,
|
||||
type PipeAnalysisDetailFilters,
|
||||
type PipeAnalysisDetailRow,
|
||||
} from "./pipeAnalysisUtils";
|
||||
} from "./pipe-analysis-utils";
|
||||
|
||||
type DetailViewRow = ReturnType<typeof toPipeAnalysisDetailViewRows>[number];
|
||||
const PIPE_ANALYSIS_DETAIL_PAGE_SIZE = 10;
|
||||
const PIPE_ANALYSIS_MAP_WINDOW_NAME = "sn-sewerage-map-root";
|
||||
const PIPE_ANALYSIS_DETAIL_EXPORT_KEYS = [
|
||||
"rowNo",
|
||||
"ctpvNm",
|
||||
"sggNm",
|
||||
"fcltMngNo",
|
||||
"instMngNo",
|
||||
"swtrmtZoneNm",
|
||||
"instlYmd",
|
||||
"useNm",
|
||||
"pdmtVl",
|
||||
"prlgVl",
|
||||
"materialName",
|
||||
] as const;
|
||||
const PIPE_ANALYSIS_DETAIL_STANDARD_HEADLIST = PIPE_ANALYSIS_DETAIL_HEADLIST;
|
||||
|
||||
const resolveErrorMessage = (error: unknown, fallbackMessage: string) =>
|
||||
@@ -62,6 +47,8 @@ const resolveErrorMessage = (error: unknown, fallbackMessage: string) =>
|
||||
: fallbackMessage;
|
||||
|
||||
export default function PipeAnalysisDetailPopupPage() {
|
||||
const { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } =
|
||||
useFacilityCodeCatalog();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const searchParams = useSearchParams();
|
||||
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
|
||||
@@ -158,11 +145,20 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
responseRows as PipeAnalysisDetailRow[],
|
||||
ctpv,
|
||||
sgg,
|
||||
{ materials: MS_SWAGE_PPL_CODES, uses: MS_SWAGE_PPL_USE_CODES },
|
||||
),
|
||||
total: Number(response?.count ?? 0),
|
||||
};
|
||||
},
|
||||
[baseAnalysisType, baseElapsedYearBucket, ctpv, scope.regionCode, sgg],
|
||||
[
|
||||
baseAnalysisType,
|
||||
baseElapsedYearBucket,
|
||||
ctpv,
|
||||
scope.regionCode,
|
||||
sgg,
|
||||
MS_SWAGE_PPL_CODES,
|
||||
MS_SWAGE_PPL_USE_CODES,
|
||||
],
|
||||
);
|
||||
|
||||
const loadRows = useCallback(
|
||||
@@ -240,33 +236,31 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetchAllPages({
|
||||
fetchPage: (page, size) => fetchRows(page + 1, appliedFilters, size),
|
||||
getItems: (page) => page.rows,
|
||||
getTotal: (page) => page.total,
|
||||
});
|
||||
const exportRows = response.items.map((row) => ({
|
||||
rowNo: row.rowNo,
|
||||
ctpvNm: row.ctpvNm,
|
||||
sggNm: row.sggNm,
|
||||
fcltMngNo: row.fcltMngNo,
|
||||
instMngNo: row.instMngNo,
|
||||
swtrmtZoneNm: row.swtrmtZoneNm,
|
||||
instlYmd: row.instlYmd,
|
||||
useNm: row.useNm,
|
||||
pdmtVl: row.pdmtVl,
|
||||
prlgVl: row.prlgVl,
|
||||
materialName:
|
||||
resolveMaterialKoreanName(row.materialCode) || row.materialName,
|
||||
}));
|
||||
const detailHeaderRange = `A1:${String.fromCharCode(64 + PIPE_ANALYSIS_DETAIL_EXPORT_KEYS.length)}1`;
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: exportRows as unknown as JSON[],
|
||||
fileName: `관로분석_관로현황_${buildPipeAnalysisFileDate()}`,
|
||||
sheetNameArray: ["관로현황"],
|
||||
headerRange: detailHeaderRange,
|
||||
headerMapping: PIPE_ANALYSIS_DETAIL_EXCEL_HEADERS,
|
||||
const params = new URLSearchParams({ analysisType: baseAnalysisType });
|
||||
appendApiSearchParams(params, {
|
||||
ctpvCd: appliedFilters.ctpvCd,
|
||||
sggCd: appliedFilters.sggCd,
|
||||
swtrmtZoneNm: appliedFilters.swtrmtZoneNm,
|
||||
elapsedYearBucket: baseElapsedYearBucket,
|
||||
materialCode: appliedFilters.materialCode,
|
||||
useCd: appliedFilters.useCd,
|
||||
diameterFrom: appliedFilters.diameterFrom,
|
||||
diameterTo: appliedFilters.diameterTo,
|
||||
});
|
||||
if (scope.regionCode) params.set("scopeRegionCode", scope.regionCode);
|
||||
await requestGridExcel(
|
||||
"v1/spatialGridExports/pipes",
|
||||
{
|
||||
pipe: Object.fromEntries(params),
|
||||
ctpvNames: Object.fromEntries(
|
||||
ctpv.map((item) => [String(item.val).trim(), item.text]),
|
||||
),
|
||||
sggNames: Object.fromEntries(
|
||||
sgg.map((item) => [String(item.val).trim(), item.text]),
|
||||
),
|
||||
},
|
||||
`관로분석_관로현황_${buildPipeAnalysisFileDate()}.xlsx`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("관로현황 다운로드 중 오류:", error);
|
||||
alert(
|
||||
@@ -293,12 +287,12 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
) {
|
||||
options.push({
|
||||
value: baseMaterialCode,
|
||||
label: resolveMaterialName(baseMaterialCode),
|
||||
label: resolveMaterialName(baseMaterialCode, MS_SWAGE_PPL_CODES),
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [baseMaterialCode]);
|
||||
}, [baseMaterialCode, MS_SWAGE_PPL_CODES]);
|
||||
|
||||
const titleSuffix = useMemo(() => {
|
||||
const locationParts: string[] = [];
|
||||
@@ -329,7 +323,7 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
if (baseElapsedYearBucket)
|
||||
parts.push(resolveElapsedYearBucketLabel(baseElapsedYearBucket));
|
||||
if (baseMaterialCode && baseMaterialCode !== "0")
|
||||
parts.push(resolveMaterialName(baseMaterialCode));
|
||||
parts.push(resolveMaterialName(baseMaterialCode, MS_SWAGE_PPL_CODES));
|
||||
return parts.length > 0 ? ` (${parts.join(" / ")})` : "";
|
||||
}, [
|
||||
baseAnalysisType,
|
||||
@@ -339,6 +333,7 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
filters.ctpvCd,
|
||||
filters.sggCd,
|
||||
filters.swtrmtZoneNm,
|
||||
MS_SWAGE_PPL_CODES,
|
||||
rows,
|
||||
sgg,
|
||||
]);
|
||||
@@ -414,7 +409,9 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
if (event.key === "Enter") handleSearch();
|
||||
}}
|
||||
/>
|
||||
<span className="shrink-0 text-xl font-semibold text-foreground">~</span>
|
||||
<span className="shrink-0 text-xl font-semibold text-foreground">
|
||||
~
|
||||
</span>
|
||||
<Input
|
||||
className="sn-popup-input !h-[3.2rem] w-[12rem] !text-xl"
|
||||
aria-label="관경 최대값"
|
||||
+56
-53
@@ -8,16 +8,16 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/store/useStore";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useUserStore } from "@/shared/lib/use-user-store";
|
||||
import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import PipeAnalysisSummaryTable from "./PipeAnalysisSummaryTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { openWindowPopup } from "@/features/window-popup/lib/open-window-popup";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import PipeAnalysisSummaryTable from "./pipe-analysis-summary-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import {
|
||||
PIPE_ANALYSIS_SUMMARY_DEFAULT_FILTERS,
|
||||
PIPE_ANALYSIS_TYPE_OPTIONS,
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
type PipeAnalysisSummaryResponse,
|
||||
type PipeAnalysisSummaryViewRow,
|
||||
type PipeAnalysisYearMaterialGroup,
|
||||
} from "./pipeAnalysisUtils";
|
||||
} from "./pipe-analysis-utils";
|
||||
|
||||
const SUMMARY_FILE_PREFIX: Record<string, string> = {
|
||||
YEAR: "관로분석_경과년도별",
|
||||
@@ -83,8 +83,12 @@ export default function PipeAnalysisPopupPage() {
|
||||
Array<{ value: string; label: string }>
|
||||
>([]);
|
||||
const [summaryCurrentPage, setSummaryCurrentPage] = useState(1);
|
||||
// 복합표는 물리 행 수가 아니라 서버가 반환하는 처리구역 그룹 수를 사용한다.
|
||||
const [summaryTotal, setSummaryTotal] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const initializedRef = useRef(false);
|
||||
// 늦게 완료된 이전 페이지 응답이 최신 조회조건의 표를 덮어쓰지 않게 한다.
|
||||
const summaryRequestId = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (ctpv.length === 0) fetchCtpv();
|
||||
@@ -124,9 +128,13 @@ export default function PipeAnalysisPopupPage() {
|
||||
}, [draftFilters.ctpvCd, draftFilters.sggCd, scope.regionCode, sgg]);
|
||||
|
||||
const fetchSummary = useCallback(
|
||||
async (nextFilters: PipeAnalysisSummaryFilters) => {
|
||||
async (nextFilters: PipeAnalysisSummaryFilters, page = 1) => {
|
||||
const requestId = ++summaryRequestId.current;
|
||||
setIsLoading(true);
|
||||
const params = new URLSearchParams({
|
||||
analysisType: nextFilters.analysisType,
|
||||
page: String(page - 1),
|
||||
size: String(PIPE_ANALYSIS_SUMMARY_PAGE_SIZE),
|
||||
});
|
||||
|
||||
appendApiSearchParams(params, {
|
||||
@@ -137,22 +145,27 @@ export default function PipeAnalysisPopupPage() {
|
||||
if (nextFilters.scopeRegionCode)
|
||||
params.set("scopeRegionCode", nextFilters.scopeRegionCode);
|
||||
|
||||
const response = (await getData(
|
||||
`v1/fclts/pplAnalysis/summaries?${params.toString()}`,
|
||||
)) as PipeAnalysisSummaryResponse;
|
||||
|
||||
const nextMaterialColumns = Array.isArray(response?.materialColumns)
|
||||
? response.materialColumns
|
||||
: [];
|
||||
setMaterialColumns(nextMaterialColumns);
|
||||
setRows(buildPipeAnalysisSummaryViewRows(response, ctpv, sgg));
|
||||
try {
|
||||
const response = (await getData(
|
||||
`v1/fclts/pplAnalysis/summaryPage?${params.toString()}`,
|
||||
)) as PipeAnalysisSummaryResponse & { count: number };
|
||||
if (requestId !== summaryRequestId.current) return;
|
||||
const nextMaterialColumns = Array.isArray(response?.materialColumns) ? response.materialColumns : [];
|
||||
setMaterialColumns(nextMaterialColumns);
|
||||
setRows(buildPipeAnalysisSummaryViewRows(response, ctpv, sgg));
|
||||
setSummaryTotal(response.count);
|
||||
setSummaryCurrentPage(page);
|
||||
} catch (error) {
|
||||
if (requestId === summaryRequestId.current) throw error;
|
||||
} finally {
|
||||
if (requestId === summaryRequestId.current) setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[ctpv, sgg],
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
async (nextFilters = draftFilters) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const scopedFilters = applyScopeToSummaryFilters(nextFilters, scope);
|
||||
setDraftFilters(scopedFilters);
|
||||
@@ -168,9 +181,8 @@ export default function PipeAnalysisPopupPage() {
|
||||
),
|
||||
);
|
||||
setRows([]);
|
||||
setSummaryTotal(0);
|
||||
setMaterialColumns([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[alert, draftFilters, fetchSummary, scope],
|
||||
@@ -191,6 +203,18 @@ export default function PipeAnalysisPopupPage() {
|
||||
}
|
||||
|
||||
try {
|
||||
// 화면은 한 페이지뿐이므로 Excel만 별도 전체 집계 계약으로 조회한다.
|
||||
const params = new URLSearchParams({ analysisType: appliedFilters.analysisType });
|
||||
appendApiSearchParams(params, {
|
||||
ctpvCd: appliedFilters.ctpvCd, sggCd: appliedFilters.sggCd,
|
||||
swtrmtZoneNm: appliedFilters.swtrmtZoneNm,
|
||||
});
|
||||
if (appliedFilters.scopeRegionCode) params.set("scopeRegionCode", appliedFilters.scopeRegionCode);
|
||||
const response = await getData(`v1/fclts/pplAnalysis/summaries?${params}`) as PipeAnalysisSummaryResponse;
|
||||
const materialColumns = response.materialColumns ?? [];
|
||||
const rows = buildPipeAnalysisSummaryViewRows(response, ctpv, sgg);
|
||||
const orderedYearMaterialGroups = buildPipeAnalysisYearMaterialGroups(rows, materialColumns)
|
||||
.map((group, index) => ({ ...group, rowNo: index + 1 }));
|
||||
const exportHeadList = buildPipeAnalysisSummaryHeadList(
|
||||
appliedFilters.analysisType,
|
||||
materialColumns,
|
||||
@@ -334,31 +358,11 @@ export default function PipeAnalysisPopupPage() {
|
||||
() =>
|
||||
yearMaterialGroups.map((group, index) => ({
|
||||
...group,
|
||||
rowNo: yearMaterialGroups.length - index,
|
||||
rowNo: (summaryCurrentPage - 1) * PIPE_ANALYSIS_SUMMARY_PAGE_SIZE + index + 1,
|
||||
})),
|
||||
[yearMaterialGroups],
|
||||
[yearMaterialGroups, summaryCurrentPage],
|
||||
);
|
||||
|
||||
const summaryPageOffset =
|
||||
(summaryCurrentPage - 1) * PIPE_ANALYSIS_SUMMARY_PAGE_SIZE;
|
||||
const pagedRows = useMemo(
|
||||
() =>
|
||||
appliedFilters.analysisType === "YEAR_MATERIAL"
|
||||
? rows
|
||||
: rows.slice(
|
||||
summaryPageOffset,
|
||||
summaryPageOffset + PIPE_ANALYSIS_SUMMARY_PAGE_SIZE,
|
||||
),
|
||||
[appliedFilters.analysisType, rows, summaryPageOffset],
|
||||
);
|
||||
const visibleYearMaterialGroups = useMemo(
|
||||
() =>
|
||||
orderedYearMaterialGroups.slice(
|
||||
summaryPageOffset,
|
||||
summaryPageOffset + PIPE_ANALYSIS_SUMMARY_PAGE_SIZE,
|
||||
),
|
||||
[orderedYearMaterialGroups, summaryPageOffset],
|
||||
);
|
||||
const sggOptions = useMemo(
|
||||
() =>
|
||||
draftFilters.ctpvCd === "0"
|
||||
@@ -393,8 +397,8 @@ export default function PipeAnalysisPopupPage() {
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="0">전체</option>
|
||||
{ctpv.map((item) => (
|
||||
{/* 시도는 전체 조회 없이 개별 지역만 선택한다. */}
|
||||
{ctpv.filter((item) => String(item.val).trim() !== "0").map((item) => (
|
||||
<option key={item.val} value={String(item.val)}>
|
||||
{item.text}
|
||||
</option>
|
||||
@@ -498,19 +502,18 @@ export default function PipeAnalysisPopupPage() {
|
||||
</PopupSearchPanel>
|
||||
|
||||
<PipeAnalysisSummaryTable
|
||||
rows={pagedRows}
|
||||
groups={visibleYearMaterialGroups}
|
||||
rows={rows}
|
||||
groups={orderedYearMaterialGroups}
|
||||
grouped={appliedFilters.analysisType === "YEAR_MATERIAL"}
|
||||
heads={headList}
|
||||
title={`${resolvePipeAnalysisTypeLabel(appliedFilters.analysisType)} 목록`}
|
||||
total={
|
||||
appliedFilters.analysisType === "YEAR_MATERIAL"
|
||||
? orderedYearMaterialGroups.length
|
||||
: rows.length
|
||||
}
|
||||
total={summaryTotal}
|
||||
page={summaryCurrentPage}
|
||||
pageSize={PIPE_ANALYSIS_SUMMARY_PAGE_SIZE}
|
||||
onPageChange={setSummaryCurrentPage}
|
||||
onPageChange={(page) => {
|
||||
void fetchSummary(appliedFilters, page)
|
||||
.catch(() => alert("관로분석 페이지를 불러오지 못했습니다."));
|
||||
}}
|
||||
onCellClick={handleCellClick}
|
||||
onGroupedCellClick={handleYearMaterialCellClick}
|
||||
actions={
|
||||
+2
-2
@@ -15,7 +15,7 @@ import type {
|
||||
PipeAnalysisSummaryViewRow,
|
||||
PipeAnalysisYearMaterialGroup,
|
||||
PipeAnalysisYearMaterialGroupRow,
|
||||
} from "./pipeAnalysisUtils";
|
||||
} from "./pipe-analysis-utils";
|
||||
|
||||
type SummaryRow =
|
||||
| {
|
||||
@@ -158,7 +158,7 @@ export default function PipeAnalysisSummaryTable({
|
||||
if (summaryRow.kind === "flat") {
|
||||
const value =
|
||||
head.id === "rowNo"
|
||||
? total - ((page - 1) * pageSize + summaryRow.sourceIndex)
|
||||
? (page - 1) * pageSize + summaryRow.sourceIndex + 1
|
||||
: summaryRow.value[head.id];
|
||||
const onClick =
|
||||
summaryRow.value.__cellFilters[head.id] && Number(value) > 0
|
||||
+22
-15
@@ -3,7 +3,10 @@
|
||||
|
||||
import type { SelectDataType } from "@common/business_components";
|
||||
import type { IUserInfo } from "@/shared/store";
|
||||
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from "../common/table";
|
||||
import {
|
||||
MS_SWAGE_PPL_CODES,
|
||||
MS_SWAGE_PPL_USE_CODES,
|
||||
} from "@/entities/facility/model/facility-code-fallbacks";
|
||||
|
||||
export const PIPE_ANALYSIS_DETAIL_ROW_SELECT_MESSAGE =
|
||||
"SN_PIPE_ANALYSIS_DETAIL_ROW_SELECT";
|
||||
@@ -125,13 +128,6 @@ export type PipeAnalysisYearMaterialGroup = {
|
||||
rows: PipeAnalysisYearMaterialGroupRow[];
|
||||
};
|
||||
|
||||
const materialMap = new Map(
|
||||
MS_SWAGE_PPL_CODES.map((item) => [item.val.trim(), item.text]),
|
||||
);
|
||||
const useMap = new Map(
|
||||
MS_SWAGE_PPL_USE_CODES.map((item) => [item.val.trim(), item.text]),
|
||||
);
|
||||
|
||||
export const PIPE_ANALYSIS_TYPE_OPTIONS: Array<{
|
||||
value: PipeAnalysisType;
|
||||
label: string;
|
||||
@@ -144,7 +140,8 @@ export const PIPE_ANALYSIS_TYPE_OPTIONS: Array<{
|
||||
export const PIPE_ANALYSIS_SUMMARY_DEFAULT_FILTERS: PipeAnalysisSummaryFilters =
|
||||
{
|
||||
analysisType: "YEAR",
|
||||
ctpvCd: "0",
|
||||
// 관로분석 최초 조회·초기화의 기본 시도는 부산광역시다. 계정 지역 제한은 별도 적용한다.
|
||||
ctpvCd: "26",
|
||||
sggCd: "0",
|
||||
swtrmtZoneNm: "0",
|
||||
scopeRegionCode: "",
|
||||
@@ -486,13 +483,17 @@ export const toPipeAnalysisDetailViewRows = (
|
||||
rows: PipeAnalysisDetailRow[],
|
||||
ctpvOptions: SelectDataType[],
|
||||
sggOptions: SelectDataType[],
|
||||
catalog?: {
|
||||
materials: { val: string; text: string }[];
|
||||
uses: { val: string; text: string }[];
|
||||
},
|
||||
) =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
ctpvNm: row.ctpvNm || resolveRegionName(row.ctpvCd, ctpvOptions, 2),
|
||||
sggNm: row.sggNm || resolveRegionName(row.sggCd, sggOptions, 5),
|
||||
useNm: resolveUseName(row.useCd),
|
||||
materialName: resolveMaterialName(row.materialCode),
|
||||
useNm: resolveUseName(row.useCd, catalog?.uses),
|
||||
materialName: resolveMaterialName(row.materialCode, catalog?.materials),
|
||||
}));
|
||||
|
||||
export const buildPipeAnalysisSummaryDownloadHeaders = (
|
||||
@@ -538,11 +539,14 @@ export const PIPE_ANALYSIS_DETAIL_EXCEL_HEADERS = {
|
||||
materialName: "관재질",
|
||||
};
|
||||
|
||||
export const resolveMaterialName = (materialCode?: string) => {
|
||||
export const resolveMaterialName = (
|
||||
materialCode?: string,
|
||||
codes = MS_SWAGE_PPL_CODES,
|
||||
) => {
|
||||
const code = String(materialCode ?? "").trim();
|
||||
if (!code) return "-";
|
||||
if (code === "OTHER") return "기타";
|
||||
return materialMap.get(code) ?? code;
|
||||
return codes.find((item) => item.val.trim() === code)?.text ?? code;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -627,10 +631,13 @@ export const resolvePipeAnalysisTypeLabel = (analysisType?: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const resolveUseName = (useCode?: string) => {
|
||||
export const resolveUseName = (
|
||||
useCode?: string,
|
||||
codes = MS_SWAGE_PPL_USE_CODES,
|
||||
) => {
|
||||
const code = String(useCode ?? "").trim();
|
||||
if (!code) return "-";
|
||||
return useMap.get(code) ?? code;
|
||||
return codes.find((item) => item.val.trim() === code)?.text ?? code;
|
||||
};
|
||||
|
||||
export const resolveRegionName = (
|
||||
+12
-35
@@ -13,24 +13,23 @@ import {
|
||||
} from "@common/business_components";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import { downloadAnalysisExcel } from "@/shared/lib/download-analysis-excel";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox";
|
||||
import { formatDisplayData } from "@/components/lib/utils/formatDisplayValue";
|
||||
import useStore from "@/store/useStore";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpv-sgg-prcs-fclt-select-box";
|
||||
import { formatDisplayData } from "@/components/lib/utils/format-display-value";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
|
||||
import {
|
||||
RDII_RESULT_ROW_SELECT_MESSAGE,
|
||||
RDII_RESULT_ROW_SELECT_STORAGE_KEY,
|
||||
type RdiiResultRow,
|
||||
} from "./rdiiResultTypes";
|
||||
} from "./rdii-result-types";
|
||||
|
||||
const RDII_RESULT_PAGE_SIZE = 10;
|
||||
|
||||
@@ -162,7 +161,7 @@ export default function RdiiResultPopupPage() {
|
||||
return {
|
||||
...(formattedItem as Record<string, unknown>),
|
||||
rowNumber: Math.max(
|
||||
totalElements - ((page - 1) * RDII_RESULT_PAGE_SIZE + index),
|
||||
(page - 1) * RDII_RESULT_PAGE_SIZE + index + 1,
|
||||
0,
|
||||
),
|
||||
ctpvNm: ctpvOption ? ctpvOption.text : String(item?.ctpvCd ?? "-"),
|
||||
@@ -243,29 +242,7 @@ export default function RdiiResultPopupPage() {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetchAllPages<any, Record<string, unknown>>({
|
||||
fetchPage: (page, size) =>
|
||||
getData(
|
||||
`v1/rdiiAnlsRslt/ppl/${selectedRow.rainInfwtAnlsSn}?page=${page}&size=${size}`,
|
||||
),
|
||||
getItems: (page) =>
|
||||
Array.isArray(page?.content) ? page.content : [],
|
||||
getTotal: (page) => Number(page?.totalElements ?? 0),
|
||||
});
|
||||
const excelRows = response.items;
|
||||
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: excelRows as unknown as JSON[],
|
||||
fileName: `RDII분석결과_${selectedRow.swtrmtFcltNm}_${selectedRow.aplcnYm}_${selectedRow.rainInfwtAnlsSn}`,
|
||||
sheetNameArray: ["RDII 분석결과"],
|
||||
headerRange: "A1:D1",
|
||||
headerMapping: {
|
||||
rainInfwtAnlsSn: "RDII분석\n분석번호",
|
||||
pplMngNo: "관로\n관리번호",
|
||||
anlsTm: "분석시각",
|
||||
predcOilq: "예측유량\n(m³/h)",
|
||||
},
|
||||
});
|
||||
await downloadAnalysisExcel("rdii", selectedRow.rainInfwtAnlsSn, `RDII분석결과_${selectedRow.swtrmtFcltNm}_${selectedRow.aplcnYm}_${selectedRow.rainInfwtAnlsSn}`);
|
||||
} catch (error) {
|
||||
console.error("RDII 분석결과 다운로드 중 오류:", error);
|
||||
alert("RDII 분석결과 다운로드 중 오류가 발생했습니다.");
|
||||
+3
-3
@@ -8,8 +8,8 @@ import VectorSource from "ol/source/Vector.js";
|
||||
import Stroke from "ol/style/Stroke.js";
|
||||
import Style from "ol/style/Style.js";
|
||||
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import { openWindowPopup } from "@/features/window-popup/lib/open-window-popup";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
RDII_RESULT_ROW_SELECT_STORAGE_KEY,
|
||||
type RdiiResultMapViewResponse,
|
||||
type RdiiResultRow,
|
||||
} from "./rdiiResultTypes";
|
||||
} from "./rdii-result-types";
|
||||
|
||||
type UseRdiiResultMapParams = {
|
||||
gisMaps: MutableRefObject<any | null>;
|
||||
+66
-43
@@ -4,29 +4,30 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import {
|
||||
StandardPopupLayout,
|
||||
type UiHeadProps,
|
||||
} from "@common/business_components";
|
||||
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import {
|
||||
WINDOW_POPUP_MESSAGE_TYPE,
|
||||
type WindowPopupContentProps,
|
||||
type WindowPopupMessageType,
|
||||
} from "@/features/windowPopup/model/types";
|
||||
import { MS_SWAGE_PPL_CODES } from "../common/table";
|
||||
} from "@/features/window-popup/model/types";
|
||||
import { useFacilityCodeCatalog } from "@/entities/facility/lib/use-facility-code-catalog";
|
||||
import {
|
||||
downloadRepairHistoriesByZoneId,
|
||||
fetchAllRepairHistoriesByZoneId,
|
||||
fetchRepairHistoryPage,
|
||||
fetchRepairHistoryMapFeatures,
|
||||
type RepairHistoryFilters,
|
||||
type RepairHistoryRow,
|
||||
} from "../popupComponent/swtrmtPrcsZoneAnalysisApi";
|
||||
} from "@/features/facility-detail/api/treatment-zone-api";
|
||||
|
||||
const REPAIR_HISTORY_PAGE_SIZE = 10;
|
||||
const MAP_LAYER_SETTING_PATH =
|
||||
@@ -162,6 +163,7 @@ const formatYear = (value: unknown) => {
|
||||
export default function RepairHistoryPopupPage({
|
||||
searchParams,
|
||||
}: WindowPopupContentProps) {
|
||||
const { MS_SWAGE_PPL_CODES } = useFacilityCodeCatalog();
|
||||
const zoneId = useMemo(
|
||||
() => firstSearchParam(searchParams.zoneId),
|
||||
[searchParams.zoneId],
|
||||
@@ -175,17 +177,21 @@ export default function RepairHistoryPopupPage({
|
||||
useState<RepairHistoryFilters>(buildEmptyFilters);
|
||||
const [appliedFilters, setAppliedFilters] =
|
||||
useState<RepairHistoryFilters>(buildEmptyFilters);
|
||||
// 전체 조회 결과는 부모 지도 강조와 팝업 내 10건 페이지 표가 함께 사용한다.
|
||||
// 목록에는 현재 서버 페이지의 행만 저장하고 지도 전체 도형은 별도로 전달한다.
|
||||
const [rows, setRows] = useState<RepairHistoryRow[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
// 페이지 요청과 지도 요청을 분리해 페이지 이동이 진행 중인 전체 지도 조회를 취소하지 않는다.
|
||||
const pageRequestId = useRef(0);
|
||||
const mapRequestId = useRef(0);
|
||||
// 용도 선택지는 별도 하드코딩하지 않고 관망도 트리 설정 API의 sortOrdr를 따른다.
|
||||
const [pipeUseOptions, setPipeUseOptions] = useState<PipeUseOption[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
/** 조회 결과 전체를 부모 지도 레이어와 팝업 표에 동시에 반영한다. */
|
||||
/** 목록은 한 페이지만 조회하고 검색조건 변경 시에만 지도 전체 도형을 갱신한다. */
|
||||
const searchRepairHistories = useCallback(
|
||||
async (nextFilters: RepairHistoryFilters) => {
|
||||
async (nextFilters: RepairHistoryFilters, page = 1, refreshMap = true) => {
|
||||
if (!zoneId) {
|
||||
setErrorMessage("처리구역 식별값이 없습니다.");
|
||||
return;
|
||||
@@ -193,38 +199,59 @@ export default function RepairHistoryPopupPage({
|
||||
|
||||
setIsLoading(true);
|
||||
setErrorMessage("");
|
||||
const requestId = ++pageRequestId.current;
|
||||
if (refreshMap) {
|
||||
const mapId = ++mapRequestId.current;
|
||||
postRepairMessage(
|
||||
WINDOW_POPUP_MESSAGE_TYPE.REPAIR_HISTORY_SHOW_RESULTS,
|
||||
{ zoneId, rows: [] },
|
||||
);
|
||||
void fetchRepairHistoryMapFeatures(zoneId, nextFilters)
|
||||
.then((features) => {
|
||||
if (mapId !== mapRequestId.current) return;
|
||||
postRepairMessage(
|
||||
WINDOW_POPUP_MESSAGE_TYPE.REPAIR_HISTORY_SHOW_RESULTS,
|
||||
{ zoneId, rows: features },
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
if (mapId === mapRequestId.current)
|
||||
setErrorMessage(
|
||||
"보수이력 지도 도형을 불러오지 못했습니다. 조회를 다시 실행해주세요.",
|
||||
);
|
||||
});
|
||||
}
|
||||
try {
|
||||
const response = await fetchAllRepairHistoriesByZoneId(
|
||||
const response = await fetchRepairHistoryPage(
|
||||
zoneId,
|
||||
nextFilters,
|
||||
page - 1,
|
||||
REPAIR_HISTORY_PAGE_SIZE,
|
||||
);
|
||||
if (requestId !== pageRequestId.current) return;
|
||||
const nextRows = response.data ?? [];
|
||||
setRows(nextRows);
|
||||
setTotal(response.totalElements);
|
||||
setAppliedFilters({ ...nextFilters });
|
||||
setCurrentPage(1);
|
||||
postRepairMessage(
|
||||
WINDOW_POPUP_MESSAGE_TYPE.REPAIR_HISTORY_SHOW_RESULTS,
|
||||
{
|
||||
zoneId,
|
||||
rows: nextRows,
|
||||
},
|
||||
);
|
||||
setCurrentPage(page);
|
||||
} catch (error) {
|
||||
if (requestId !== pageRequestId.current) return;
|
||||
setRows([]);
|
||||
postRepairMessage(
|
||||
WINDOW_POPUP_MESSAGE_TYPE.REPAIR_HISTORY_SHOW_RESULTS,
|
||||
{
|
||||
zoneId,
|
||||
rows: [],
|
||||
},
|
||||
);
|
||||
setTotal(0);
|
||||
if (refreshMap) {
|
||||
mapRequestId.current += 1;
|
||||
postRepairMessage(
|
||||
WINDOW_POPUP_MESSAGE_TYPE.REPAIR_HISTORY_SHOW_RESULTS,
|
||||
{ zoneId, rows: [] },
|
||||
);
|
||||
}
|
||||
setErrorMessage(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "보수이력을 불러오지 못했습니다.",
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (requestId === pageRequestId.current) setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[zoneId],
|
||||
@@ -270,6 +297,8 @@ export default function RepairHistoryPopupPage({
|
||||
};
|
||||
window.addEventListener("beforeunload", clearParentLayer);
|
||||
return () => {
|
||||
pageRequestId.current += 1;
|
||||
mapRequestId.current += 1;
|
||||
window.removeEventListener("beforeunload", clearParentLayer);
|
||||
clearParentLayer();
|
||||
};
|
||||
@@ -277,7 +306,7 @@ export default function RepairHistoryPopupPage({
|
||||
|
||||
/** 화면에서 적용한 조건과 동일한 9개 목록 항목의 XLSX를 내려받는다. */
|
||||
const handleDownload = async () => {
|
||||
if (!zoneId || rows.length === 0) return;
|
||||
if (!zoneId || total === 0) return;
|
||||
setIsLoading(true);
|
||||
setErrorMessage("");
|
||||
try {
|
||||
@@ -306,18 +335,10 @@ export default function RepairHistoryPopupPage({
|
||||
});
|
||||
};
|
||||
|
||||
const visibleRows = useMemo(
|
||||
() =>
|
||||
rows.slice(
|
||||
(currentPage - 1) * REPAIR_HISTORY_PAGE_SIZE,
|
||||
currentPage * REPAIR_HISTORY_PAGE_SIZE,
|
||||
),
|
||||
[currentPage, rows],
|
||||
);
|
||||
// 공통 표에 전달할 표시값은 원본 API 행과 분리해 지도 연계 식별값을 보존한다.
|
||||
const tableRows = useMemo<RepairHistoryTableRow[]>(
|
||||
() =>
|
||||
visibleRows.map((row) => ({
|
||||
rows.map((row) => ({
|
||||
fid: row.fid,
|
||||
fcltMngNo: formatCellValue(row.fcltMngNo),
|
||||
useName: formatCellValue(row.useNm),
|
||||
@@ -329,7 +350,7 @@ export default function RepairHistoryPopupPage({
|
||||
installationYear: formatYear(row.installationYear),
|
||||
burialDepth: formatCellValue(row.burialDepth),
|
||||
})),
|
||||
[visibleRows],
|
||||
[rows],
|
||||
);
|
||||
return (
|
||||
<StandardPopupLayout title="보수이력 목록">
|
||||
@@ -459,12 +480,14 @@ export default function RepairHistoryPopupPage({
|
||||
}
|
||||
headlist={REPAIR_HISTORY_HEADLIST}
|
||||
title={`${zoneName || "처리구역"} 보수이력 목록`}
|
||||
total={rows.length}
|
||||
total={total}
|
||||
page={currentPage}
|
||||
pageSize={REPAIR_HISTORY_PAGE_SIZE}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageChange={(page) =>
|
||||
void searchRepairHistories(appliedFilters, page, false)
|
||||
}
|
||||
onRowClick={(_, index) => {
|
||||
const row = visibleRows[index];
|
||||
const row = rows[index];
|
||||
if (row) handleRowClick(row);
|
||||
}}
|
||||
rowId={(row, index) => String(row.fid ?? index)}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/** 우측 상세 탭의 표시·분류 모델을 지도 조회 수명에서 분리한다. */
|
||||
import type { ExmnMapJobType } from "../exmn/exmnMapTypes";
|
||||
import type { SinkholeDetail, SinkholeNearbyPipe } from "../sinkhole/sinkholeTypes";
|
||||
import type { ExmnMapJobType } from "../exmn/exmn-map-types";
|
||||
import type { SinkholeDetail, SinkholeNearbyPipe } from "../sinkhole/sinkhole-types";
|
||||
|
||||
export type RightPanelTabKind = "feature" | "sinkhole" | "gisEdit";
|
||||
export type RightPanelTabCategory =
|
||||
+7
-7
@@ -7,13 +7,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/window-popup/model/types";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import useStore from "@/store/useStore";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import {
|
||||
SBMRG_DNGR_MAP_DEFAULT_FILTERS,
|
||||
SBMRG_DNGR_MAP_FREQUENCY_OPTIONS,
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
type SbmrgDngrMapApiItem,
|
||||
type SbmrgDngrMapFilters,
|
||||
type SbmrgDngrMapTableRow,
|
||||
} from "./sbmrgDngrMapTypes";
|
||||
} from "./sbmrg-dngr-map-types";
|
||||
|
||||
const unwrapPayload = <T,>(response: any): T =>
|
||||
(response?.data ?? response) as T;
|
||||
+1
-1
@@ -139,7 +139,7 @@ export const toSbmrgDngrMapTableRows = (
|
||||
): SbmrgDngrMapTableRow[] =>
|
||||
items.map((item, index) => ({
|
||||
...item,
|
||||
rowNo: total - page * pageSize - index,
|
||||
rowNo: page * pageSize + index + 1,
|
||||
ctpvNm: resolveAdministrativeNames(item.ctpvCd, ctpvOptions, 2),
|
||||
sggNm: resolveAdministrativeNames(item.sggCd, sggOptions, 5),
|
||||
floodWtlvFrqVlDisplay: toDisplay(item.floodWtlvFrqVl),
|
||||
+4
-4
@@ -6,10 +6,10 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@common/components";
|
||||
import { VerticalTable } from "@/components/ui/verticalTable";
|
||||
import { getCmsData } from "@/components/lib/api";
|
||||
import type { SinkholeDetail, SinkholeNearbyPipe } from "./sinkholeTypes";
|
||||
import { useSinkholeCommonCodes } from "./useSinkholeCommonCodes";
|
||||
import { VerticalTable } from "@/components/ui/vertical-table";
|
||||
import { getCmsData } from "@/shared/api/external-service-api";
|
||||
import type { SinkholeDetail, SinkholeNearbyPipe } from "./sinkhole-types";
|
||||
import { useSinkholeCommonCodes } from "./use-sinkhole-common-codes";
|
||||
|
||||
type Props = {
|
||||
detail: SinkholeDetail;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user