diff --git a/apps/cs/app/(with-base-layout)/link/page.tsx b/apps/cs/app/(with-base-layout)/link/page.tsx new file mode 100644 index 000000000..c703d7fcf --- /dev/null +++ b/apps/cs/app/(with-base-layout)/link/page.tsx @@ -0,0 +1,2 @@ +import { LinkPage } from "@/pages/link"; +export default LinkPage; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/link/register/[id]/page.tsx b/apps/cs/app/(with-base-layout)/link/register/[id]/page.tsx new file mode 100644 index 000000000..b54d60cec --- /dev/null +++ b/apps/cs/app/(with-base-layout)/link/register/[id]/page.tsx @@ -0,0 +1,10 @@ +import { ModifyLinkPage as VModifyLinkPage } from "@/pages/link"; + +export default async function ModifyLinkPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + return ; +}; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/link/register/page.tsx b/apps/cs/app/(with-base-layout)/link/register/page.tsx new file mode 100644 index 000000000..a7be897af --- /dev/null +++ b/apps/cs/app/(with-base-layout)/link/register/page.tsx @@ -0,0 +1,2 @@ +import { RegistLinkPage } from "@/pages/link"; +export default RegistLinkPage; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/linkLog/page.tsx b/apps/cs/app/(with-base-layout)/linkLog/page.tsx new file mode 100644 index 000000000..bf10b7ba0 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/linkLog/page.tsx @@ -0,0 +1,2 @@ +import { LinkLogPage } from "@/pages/linkLog"; +export default LinkLogPage; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/linkParams/page.tsx b/apps/cs/app/(with-base-layout)/linkParams/page.tsx new file mode 100644 index 000000000..a9e250bac --- /dev/null +++ b/apps/cs/app/(with-base-layout)/linkParams/page.tsx @@ -0,0 +1,2 @@ +import { LinkParamsPage } from "@/pages/linkParams"; +export default LinkParamsPage; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/linkStats/page.tsx b/apps/cs/app/(with-base-layout)/linkStats/page.tsx new file mode 100644 index 000000000..78acc9fc3 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/linkStats/page.tsx @@ -0,0 +1,2 @@ +import { LinkStatsPage } from "@/pages/linkStats"; +export default LinkStatsPage; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/linkTable/page.tsx b/apps/cs/app/(with-base-layout)/linkTable/page.tsx new file mode 100644 index 000000000..df14e95e3 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/linkTable/page.tsx @@ -0,0 +1,2 @@ +import { LinkTablePage } from "@/pages/linkTable"; +export default LinkTablePage; \ No newline at end of file diff --git a/apps/cs/app/(with-base-layout)/user-statistics/page.tsx b/apps/cs/app/(with-base-layout)/user-statistics/page.tsx new file mode 100644 index 000000000..125900022 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/user-statistics/page.tsx @@ -0,0 +1,2 @@ +import { UserStatisticsPage } from "@/pages/user-statistics"; +export default UserStatisticsPage; diff --git a/apps/cs/src/entities/link/api/link-service.ts b/apps/cs/src/entities/link/api/link-service.ts new file mode 100644 index 000000000..c607dd2c7 --- /dev/null +++ b/apps/cs/src/entities/link/api/link-service.ts @@ -0,0 +1,191 @@ +import axios from "axios"; +import { + type LinkService, + type LinkSearchParam, + type LinkList, + type LinkFormData, + type LinkInfo, + type LinkLogList, + type LinkStatsList, + type LinkVrblList, + type LinkVrblFormData, + type LinkVrbl, + type LinkTable, + type LinkTableColumnsList, + type LinkTableColumns, + type LinkTableColData, + type LinkTableFormData, +} from "../model/link-interface"; + +export class LinkApiService implements LinkService { + public async getLinkList(params: LinkSearchParam): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, + { params } + ); + return response.data; + } + + public async getLinkInfo(linkSn: string): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo/${linkSn}`, + ); + return response.data; + } + + public async linkQuickStart(linkSn: string): Promise { + const response = await axios.patch( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/linkQuickStart/${linkSn}`, + ); + return response.data; + } + + public async updateLinkInfo(data: LinkFormData): Promise { + await axios.put( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkInfo`, + data, + ); + } + + public async insertLinkInfo(data: LinkFormData): Promise { + await axios.post( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkInfo`, + data, + ); + } + + public async deleteLink(linkSn: string): Promise { + await axios.delete( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/deleteLink/${linkSn}`, + ); + } + + public async getLinkLogList(params: LinkSearchParam): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/selectLinkLoglist`, + { params } + ); + return response.data; + } + + public async getLinkStatsList(params: LinkSearchParam): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/selectLinkStats`, + { params } + ); + return response.data; + } + + public async getLinkVrblList(linkSn: string): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkVrblList/${linkSn}`, + ); + return response.data; + } + + public async insertLinkVrbl(data: LinkVrblFormData): Promise { + const response = await axios.post( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkVrbl`, + data, + ); + + return response.data; + } + + public async deleteLinkVrbl(data: string[]): Promise { + await axios.delete( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/deleteLinkVrbl`, + { + data + } + ); + } + + public async getLinkVrblInfo(linkVrblSn: string): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkVrblInfo/${linkVrblSn}`, + ); + return response.data; + } + + public async updateLinkVrblInfo(data: LinkVrblFormData): Promise { + await axios.put( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkVrblInfo`, + data, + ); + } + + public async getLinkTableInfo(linkSn: string): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo/${linkSn}`, + ); + return response.data; + } + + public async getLinkTableColList(linkSn: string): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList/${linkSn}`, + ); + return response.data; + } + + public async getLinkTableColInfo(params: { colSn: string; tblSn:string; }): Promise { + const response = await axios.get( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColInfo`, + { params } + ); + return response.data; + } + + public async insertLinkTableCol(data: LinkTableColData): Promise { + await axios.post( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkTableCol`, + data + ); + } + + public async deleteLinkTableCol(data: string[]): Promise { + await axios.delete( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/deleteLinkTableCol`, + { + data + }, + ); + } + + public async updateLinkTableCol(data: LinkTableColData): Promise { + axios.put( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkTableCol`, + data, + ); + } + + public async insertLinkTableInfo(data: LinkTableFormData): Promise { + await axios.post( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/insertLinkTableInfo`, + data + ); + } + + public async updateLinkTableInfo(data: LinkTableFormData): Promise { + await axios.put( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkTableInfo`, + data + ); + } + + public async countLinkTblNm(tblNm: string): Promise<{count: number, tbl_sn: string}> { + const response = await axios.get<{count: number, tbl_sn: string}>( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/countLinkTblNm/${tblNm}`, + ); + return response.data; + } + + public async updateLinkResultCode(linkSn: string): Promise { + await axios.patch( + `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/updateLinkResultCode/${linkSn}`, + ); + } +} + +export const LinkApi = new LinkApiService(); \ No newline at end of file diff --git a/apps/cs/src/entities/link/index.ts b/apps/cs/src/entities/link/index.ts new file mode 100644 index 000000000..89d56aaa0 --- /dev/null +++ b/apps/cs/src/entities/link/index.ts @@ -0,0 +1,18 @@ +export { LinkApi } from "./api/link-service"; +export { + type LinkSearchParam, + type Link, + type LinkFormData, + type LinkLog, + type LinkStats, + type LinkVrbl, + type LinkVrblFormData, + type LinkTable, + type LinkTableColumns, + type LinkTableColData, + type LinkTableFormData, + linkFormSchema, + linkVrblFormSchema, + LinkTableColumnsSchema, + LinkTableSchema, +} from "./model/link-interface" \ No newline at end of file diff --git a/apps/cs/src/entities/link/model/link-interface.ts b/apps/cs/src/entities/link/model/link-interface.ts new file mode 100644 index 000000000..c4493a6c0 --- /dev/null +++ b/apps/cs/src/entities/link/model/link-interface.ts @@ -0,0 +1,235 @@ +import { z } from "zod"; +import type { PageableParams, PageableList } from "@/shared/model"; + +export interface Link { + link_sn: string; + link_nm: string; + link_clsf_cd: string; + link_url: string; + link_srvr_ip: string; + link_db_cd: string; + link_key: string; + link_id: string; + link_pswd: string; + link_cycle_stng: string; + srvc_usg: string; + last_link_rslt_cd: string; + last_link_excn_dt: string; + next_link_excn_dt: string; + last_link_err_cd: string; + mdfcn_dt: string; + mdfr: string; + link_batch_time: string; +} + +export interface LinkLog { + rownum: number; + link_sn: string; + link_nm: string; + link_rslt_cd: string; + err_cn: string; + link_err_cd: string; + log_crt_dt: string; +} + +export interface LinkInfo extends LinkFormData { + link_sn: string; + link_nm: string; + link_clsf_cd: string; + link_url: string; + link_srvr_ip: string; + link_db_cd: string; + link_key: string; + link_id: string; + link_pswd: string; + srvc_usg: string; + last_link_rslt_cd: string; + last_link_excn_dt: string; + next_link_excn_dt: string; + last_link_err_cd: string; + mdfcn_dt: string; + mdfr: string; + link_batch_time: number; +} + +export interface LinkStats { + link_sn: string; + link_nm: string; + count: number; +} + +export interface LinkVrbl { + link_vrbl_sn: string; + link_vrbl_nm: string; + link_vrbl_vl: string; + link_vrbl_cd: string; + link_vrbl_expln: string; + mdfcn_dt: string; + mdfr: string; +} + +export interface LinkTable { + link_nm: string; + link_sn: string; + tbl_sn: string; + sou_tbl_nm: string; + sou_tbl_korn_nm: string; + sou_tbl_expln: string; + trsf_tbl_nm: string; + trsf_tbl_korn_nm: string; + trsf_tbl_expln: string; +} + +export interface LinkTableColumns { + tbl_sn: string; + col_sn: string; + sou_col_nm: string; + sou_col_korn_nm: string; + sou_col_data_frm: string; + sou_col_sz: number; + trsf_col_nm: string; + trsf_col_korn_nm: string; + trsf_col_data_frm: string; + trsf_col_sz: number + pk_yn: string; + col_seq: number; + mdfcn_dt: string; + mdfr: string; +} + +export const linkFormSchema = z.object({ + link_clsf_cd: z.string().max(8).nullable().refine((val) => { + // 빈 값이나 "none" 값이면 false 반환 + return val && val !== "none"; + }, { + message: "연계 분류를 선택해주세요" + }), + link_sn: z.string().max(50).nullable().optional(), + link_nm: z.string().max(40, { message: '연계명은 40자 이하로 입력해 주세요.' }).nonempty({ message: '연계명은 필수입니다.' }), + link_url: z.string().max(1000, { message: '연계 URL은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '연계 URL은 필수입니다.' }), + link_srvr_ip: z.string().max(20).nullable().optional(), + link_db_cd: z.string().max(10).nullable().optional(), + link_key: z.string().max(100, { message: '연계 KEY는 100자 이하로 입력해 주세요.' }).nonempty({ message: '연계 KEY는 필수입니다.' }), + link_id: z.string().max(20).nullable().optional(), + link_pswd: z.string().max(128).nullable().optional(), + srvc_usg: z.string().max(40).nullable().optional(), + last_link_rslt_cd: z.string().max(4).nullable().optional(), + last_link_excn_dt: z.string().max(40).nullable().optional(), + next_link_excn_dt: z.string().max(40).nullable().optional(), + last_link_err_cd: z.string().max(10).nullable().optional(), + mdfcn_dt: z.string().nullable().optional(), + mdfr: z.string().nullable().optional(), + link_batch_time: z.coerce.number().nullable().optional().refine((val) => { + if (val == null) return true; + return val >= 5; + }, { + message: "연계 주기는 5분 이상이어야 합니다." + }), +}); + +export const linkVrblFormSchema = z.object({ + link_sn: z.string().max(50).nullable().optional(), + link_vrbl_sn: z.string().max(50).nullable().optional(), + link_vrbl_nm: z.string().max(40, { message: '변수명은 40자 이하로 입력해 주세요.' }).nonempty({ message: '변수명은 필수입니다.' }), + link_vrbl_expln: z.string().max(4000, { message: '설명은 400자 이하로 입력해 주세요.' }).nullable().optional(), + link_vrbl_cd: z.string().max(10, { message: '변수코드는 10자 이하로 입력해 주세요.' }).nullable().optional(), + link_vrbl_vl: z.string().max(1000, { message: '변수값은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '변수값은 필수입니다.' }), + mdfcn_dt: z.string().nullable().optional(), + mdfr: z.string().nullable().optional(), +}); + +export const LinkTableColumnsSchema = z.object({ + link_sn: z.string().max(50).nullable().optional(), + tbl_sn: z.string().max(50).nullable().optional(), + col_sn: z.string().max(50).nullable().optional(), + sou_col_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), + sou_col_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), + sou_col_data_frm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), + sou_col_sz: z.coerce.number().nullable().optional().refine((val) => { + if (val == null) return true; + return val.toString().replace('.', '').length <= 14; + }, { + message: '최대 14자리까지 입력 가능합니다.', + }), + pk_yn: z.string().max(1).nonempty(), + trsf_col_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼명은 필수입니다.' }), + trsf_col_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 한글명은 필수입니다.' }), + trsf_col_data_frm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 데이터 형식은 필수입니다.' }), + trsf_col_sz: z.coerce.number().nullable().optional().refine((val) => { + if (val == null) return true; + return val.toString().replace('.', '').length <= 14; + }, { + message: '최대 14자리까지 입력 가능합니다.', + }), + col_seq: z.coerce.number().nullable().optional().refine((val) => { + if (val == null) return true; + return val > 0; + }, { + message: "컬럼 매칭 순서는 0 이상이어야 합니다." + }), + mdfcn_dt: z.string().nullable().optional(), + mdfr: z.string().nullable().optional(), +}); + +export const LinkTableSchema = z.object({ + link_sn: z.string().max(50).nullable().optional(), + tbl_sn: z.string().max(50).nullable().optional(), + link_nm: z.string().max(50).nullable().optional(), + sou_tbl_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), + sou_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), + sou_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(), + trsf_tbl_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블명은 필수입니다.' }), + trsf_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블 한글명은 필수입니다.' }), + trsf_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(), + mdfcn_dt: z.string().nullable().optional(), + mdfr: z.string().nullable().optional(), +}); + +interface LinkStatsListObject { + linkSuccStats: LinkStats[] | null; + linkFailStats: LinkStats[] | null; +} + +export interface LinkSearchParam extends PageableParams { + linkSn?: string; + linkNm?: string; + startDate?: string; + endDate?: string; + isSucc?: string; +} + +export type LinkFormData = z.infer; +export type LinkVrblFormData = z.infer; +export type LinkTableColData = z.infer; +export type LinkTableFormData = z.infer; +export type LinkList = PageableList; +export type LinkLogList = PageableList; +export type LinkStatsList = LinkStatsListObject; +export type LinkVrblList = PageableList; +export type LinkTableColumnsList = PageableList; + +export interface LinkService { + getLinkList(params: LinkSearchParam): Promise; + getLinkInfo(params: string): Promise; + linkQuickStart(params: string): Promise; + updateLinkInfo(data: LinkFormData): Promise; + deleteLink(params: string): Promise; + getLinkLogList(params: LinkSearchParam): Promise; + getLinkStatsList(params: LinkSearchParam): Promise; + getLinkVrblList(params: string): Promise; + insertLinkVrbl(data: LinkVrblFormData): Promise; + deleteLinkVrbl(data: string[]): Promise; + getLinkVrblInfo(params: string): Promise; + updateLinkVrblInfo(data: LinkVrblFormData): Promise; + getLinkTableInfo(params: string): Promise; + getLinkTableColList(params: string): Promise; + getLinkTableColInfo(params: { colSn: string; tblSn: string; }): Promise; + insertLinkTableCol(data: LinkTableColData): Promise; + deleteLinkTableCol(data: string[]): Promise; + updateLinkTableCol(data: LinkTableColData): Promise; + insertLinkTableInfo(data: LinkTableFormData): Promise; + updateLinkTableInfo(data: LinkTableFormData): Promise; + insertLinkInfo(data: LinkFormData): Promise; + countLinkTblNm(tblNm: string): Promise<{ count: number, tbl_sn: string }>; + updateLinkResultCode(linkSn: string): Promise; +} \ No newline at end of file diff --git a/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx b/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx index 327473932..859af4058 100644 --- a/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx +++ b/apps/cs/src/pages/admin/ui/add-admin-dialog.tsx @@ -47,7 +47,7 @@ function AddAdminDialog() { const [open, setOpen] = useState(false); const [userSearchQuery, setUserSearchQuery] = useState({ - keywordType: "", + keywordType: "userName", keyword: "", }); const [selectedGroupId, setSelectedGroupId] = useState(""); @@ -193,6 +193,7 @@ function AddAdminDialog() {
{ + setDateRange(dateRange); + setPresetDateRange(""); + }; + return (
@@ -148,20 +153,9 @@ function ApiLogPage() { 3개월 - { - setDateRange({ ...dateRange, from: date }); - setPresetDateRange(""); - }} - /> - ~ - { - setDateRange({ ...dateRange, to: date }); - setPresetDateRange(""); - }} +

총 회원수

- {data?.memberTotCnt ?? "-"} + {data?.memberTotCnt + ? data.memberTotCnt.toLocaleString() + : "-"}

- 금일 신규 가입자 {data?.memberTodayCnt ?? "-"}명 + 금일 신규 가입자 + {data?.memberTodayCnt + ? data.memberTodayCnt.toLocaleString() + : "-"} + 명

{/* 차트 영역 */} @@ -131,10 +137,12 @@ function DashboardPage() {

전체 게시물

- {data?.pstTotCnt ?? "-"} + {data?.pstTotCnt ? data.pstTotCnt.toLocaleString() : "-"}

- 금일 +{data?.pstTodayCnt ?? "-"} 등록 + 금일 + + {data?.pstTodayCnt ? data.pstTodayCnt.toLocaleString() : "-"}{" "} + 등록

{/* 차트 영역 */} @@ -152,10 +160,16 @@ function DashboardPage() {

오늘 접속자 수

- {data?.accessTodayCnt ?? "-"} + {data?.accessTodayCnt + ? data.accessTodayCnt.toLocaleString() + : "-"}

- 전일 대비 +{data?.accessBefDayPer ?? "-"}% + 전일 대비 + + {data?.accessBefDayPer + ? data.accessBefDayPer.toLocaleString() + : "-"} + %

{/* 차트 영역 */} @@ -173,7 +187,7 @@ function DashboardPage() {

API 호출 건수

- {data?.apiTodayCnt ?? "-"} + {data?.apiTodayCnt ? data.apiTodayCnt.toLocaleString() : "-"}

금일 누적

@@ -221,7 +235,10 @@ function DashboardPage() {
- {data?.waitMemberCnt}건 + {data?.waitMemberCnt + ? data.waitMemberCnt.toLocaleString() + : "0"} + 건
신규 가입 승인 대기 @@ -253,7 +270,9 @@ function DashboardPage() { 메뉴 승인
-
3건
+
+ {(3).toLocaleString()}건 +
매뉴 수정 승인 대기
@@ -283,7 +302,10 @@ function DashboardPage() {
- {data?.waitCntnCnt}건 + {data?.waitCntnCnt + ? data.waitCntnCnt.toLocaleString() + : "0"} + 건
컨텐츠 수정 승인 대기 @@ -303,19 +325,19 @@ function DashboardPage() {
신규 사용자 가입 - 1분 전 + {(1).toLocaleString()}분 전
메뉴 수정 요청 - 5분 전 + {(5).toLocaleString()}분 전
게시물 신고 - 10분 전 + {(10).toLocaleString()}분 전
컨텐츠 승인 요청 - 15분 전 + {(15).toLocaleString()}분 전
@@ -328,20 +350,22 @@ function DashboardPage() {
- 3월 정기 시스템 점검 + + {(3).toLocaleString()}월 정기 시스템 점검 + 내일
서버 업데이트 예정 - 3일 후 + {(3).toLocaleString()}일 후
백업 스케줄 - 5일 후 + {(5).toLocaleString()}일 후
정기 보안 점검 - 7일 후 + {(7).toLocaleString()}일 후
diff --git a/apps/cs/src/pages/groups/ui/group-form.tsx b/apps/cs/src/pages/groups/ui/group-form.tsx index 6c1d8b67f..16c620388 100644 --- a/apps/cs/src/pages/groups/ui/group-form.tsx +++ b/apps/cs/src/pages/groups/ui/group-form.tsx @@ -161,11 +161,11 @@ function GroupForm({ form }: GroupFormProps) { >
- +
- +
diff --git a/apps/cs/src/pages/groups/ui/groups-dialog.tsx b/apps/cs/src/pages/groups/ui/groups-dialog.tsx index 427ba4b1b..9fca4ebdb 100644 --- a/apps/cs/src/pages/groups/ui/groups-dialog.tsx +++ b/apps/cs/src/pages/groups/ui/groups-dialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { memo, useState } from "react"; +import { memo, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; @@ -43,7 +43,7 @@ const GroupsDialog = memo( const { data: group } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, groupId], queryFn: () => GroupApi.findGroupById(groupId!), - enabled: !!groupId && open, // open 시점 groupId가 있을 때만 상세 조회 API 호출 + enabled: !!groupId && open, // open 시점 groupId가 있을 때만 상세 조회 API 호출 }); const { mutateAsync: createGroup } = useMutationWrapper({ @@ -77,7 +77,7 @@ const GroupsDialog = memo( delYn: group?.delYn ?? "", authrtId: group?.authrtId ?? "", siteId: group?.siteId ?? "", - mngrYn: group?.mngrYn ?? "", + mngrYn: group?.mngrYn ?? "Y", }, }); @@ -91,6 +91,10 @@ const GroupsDialog = memo( if (await confirm(confirmMessage)) callback(data); }; + useEffect(() => { + if (open) form.reset(); + }, [open]); + return ( {children} diff --git a/apps/cs/src/pages/image/ui/create-page.tsx b/apps/cs/src/pages/image/ui/create-page.tsx index e7dda81d5..3bb249d25 100644 --- a/apps/cs/src/pages/image/ui/create-page.tsx +++ b/apps/cs/src/pages/image/ui/create-page.tsx @@ -23,6 +23,7 @@ function CreatePage() { resolver: zodResolver(imageFormSchema), defaultValues: { atflYn: "Y", + useYn: "Y", }, }); diff --git a/apps/cs/src/pages/image/ui/image-form.tsx b/apps/cs/src/pages/image/ui/image-form.tsx index 1de7820c5..7430a21e1 100644 --- a/apps/cs/src/pages/image/ui/image-form.tsx +++ b/apps/cs/src/pages/image/ui/image-form.tsx @@ -124,6 +124,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) { {...field} type="number" min={0} + max={9999} value={field.value ?? ""} onChange={(e) => field.onChange(parseInt(e.target.value)) diff --git a/apps/cs/src/pages/link/index.ts b/apps/cs/src/pages/link/index.ts new file mode 100644 index 000000000..6eac7d15c --- /dev/null +++ b/apps/cs/src/pages/link/index.ts @@ -0,0 +1,3 @@ +export { ModifyLinkPage } from "./ui/modify-page"; +export { RegistLinkPage } from "./ui/regist-page"; +export { LinkPage } from "./ui/link-page"; \ No newline at end of file diff --git a/apps/cs/src/pages/link/model/table-columns.tsx b/apps/cs/src/pages/link/model/table-columns.tsx new file mode 100644 index 000000000..5b1710fec --- /dev/null +++ b/apps/cs/src/pages/link/model/table-columns.tsx @@ -0,0 +1,88 @@ +import dayjs from "dayjs"; +import { createColumnHelper } from "@tanstack/react-table"; +import { type Link } from "@/entities/link"; +import QuickStartButton from "../ui/QuickStartButton"; + +const columnHelper = createColumnHelper(); + +const convertMinutesToPeriod = (minutes: number): string => { + if (!minutes) return ''; + + const year = Math.floor(minutes / (525600)); // 365일 * 24시간 * 60분 + minutes = minutes % 525600; + + const month = Math.floor(minutes / (43200)); // 30일 * 24시간 * 60분 + minutes = minutes % 43200; + + const day = Math.floor(minutes / 1440); // 24시간 * 60분 + minutes = minutes % 1440; + + const hour = Math.floor(minutes / 60); + const minute = minutes % 60; + + const parts = []; + if (year > 0) parts.push(`${year}년`); + if (month > 0) parts.push(`${month}개월`); + if (day > 0) parts.push(`${day}일`); + if (hour > 0) parts.push(`${hour}시간`); + if (minute > 0) parts.push(`${minute}분`); + + return parts.join(' '); +}; + +const columns = [ + columnHelper.accessor((_, index) => index + 1, { + id: 'no', + header: 'NO', + }), + columnHelper.accessor('link_sn', { + id: 'link_sn', + header: '연계일련번호', + }), + columnHelper.accessor('link_nm', { + id: 'link_nm', + header: '연계 명', + }), + columnHelper.accessor('link_url', { + id: 'link_url', + header: '연계 URL', + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), + columnHelper.accessor('link_srvr_ip', { + id: 'link_srvr_ip', + header: '연계서버 IP', + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), + columnHelper.accessor('link_batch_time', { + id: 'link_batch_time', + header: '연계 주기', + cell: (info) => convertMinutesToPeriod(Number(info.getValue())), + }), + columnHelper.accessor('srvc_usg', { + id: 'srvc_usg', + header: '서비스용도', + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), + columnHelper.display({ + id: 'last_link_rslt_cd', + header: '연계상태', + cell: (info) => { + const resultCd = info.row.original.last_link_rslt_cd; + return {resultCd === 'SUCC' ? '성공' : (resultCd === 'FAIL' ? '실패' : '대기')} + }, + }), + columnHelper.accessor('last_link_excn_dt', { + id: 'last_link_excn_dt', + header: '최종연계일', + cell: (info) => (info.getValue() ? dayjs(info.getValue()).format('YYYY-MM-DD HH:mm') : '-'), + }), + columnHelper.display({ + id: 'quick_start_btn', + header: '즉시 실행', + cell: (info) => { + return + } + }) +]; + +export { columns }; \ No newline at end of file diff --git a/apps/cs/src/pages/link/ui/QuickStartButton.tsx b/apps/cs/src/pages/link/ui/QuickStartButton.tsx new file mode 100644 index 000000000..a322ee678 --- /dev/null +++ b/apps/cs/src/pages/link/ui/QuickStartButton.tsx @@ -0,0 +1,36 @@ +import { useMutation } from "@tanstack/react-query"; +import { LinkApi } from "@/entities/link"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { Button } from "@common/components"; + +const QuickStartButton: React.FC<{ linkSn: string }> = ({ linkSn }) => { + const { setMessage: alert } = useAlertStore((state) => state); + + const { mutateAsync: updateQuickStart } = useMutation({ + mutationFn: (linkSn: string) => LinkApi.linkQuickStart(linkSn), + onSuccess: (result) => { + if (result === 'succ') { + alert('즉시 실행 요청이 완료되었습니다.'); + } else { + alert('기존 요청이 실행 대기중입니다.'); + } + }, + onError: () => { + alert('즉시 실행 요청을 실패했습니다.'); + }, + }); + + return ( + + ); +}; + +export default QuickStartButton; \ No newline at end of file diff --git a/apps/cs/src/pages/link/ui/link-form.tsx b/apps/cs/src/pages/link/ui/link-form.tsx new file mode 100644 index 000000000..05a3da0f1 --- /dev/null +++ b/apps/cs/src/pages/link/ui/link-form.tsx @@ -0,0 +1,590 @@ +import { + Button, + Input, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "@common/components"; +import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import type { UseFormReturn } from "react-hook-form"; +import { type LinkFormData } from "@/entities/link"; +import dayjs from "dayjs"; +import { useRouter } from "next/navigation"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useState } from "react"; + + +type LinkFormProps = { + form: UseFormReturn; + linkSn?: string; + updateLinkInfo?: (data: LinkFormData) => void; + handleInsertLink?: (data: LinkFormData) => void; + deleteLink?: () => void; + updateLinkResultCode?: () => void; +} + +const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, updateLinkResultCode }: LinkFormProps) => { + const isModifyMode = !!linkSn; + const router = useRouter(); + + const { confirm } = useConfirm(); + const [onConfirm, setOnConfirm] = useState(false); + + const handleConfirm = async (callback: () => Promise) => { + setOnConfirm(true); + await callback(); + setOnConfirm(false); + }; + + const handleSubmit = async (data: LinkFormData) => { + const confirmMsg = !!linkSn ? '수정 하시겠습니까?' : '저장 하시겠습니까?'; + + await handleConfirm(async () => { + + if (await confirm(confirmMsg)) { + if (linkSn) { + updateLinkInfo?.(data); + } else { + handleInsertLink?.(data); + } + } + }); + }; + + const handleDelete = async () => { + await handleConfirm(async () => { + if (await confirm('삭제하시겠습니까?')) { + deleteLink?.(); + } + }) + }; + + const convertMinutesToPeriod = (minutes: number) => { + if (!minutes) return { + year: 0, + month: 0, + day: 0, + hour: 0, + minute: 0 + }; + + const year = Math.floor(minutes / (525600)); // 365일 * 24시간 * 60분 + minutes = minutes % 525600; + + const month = Math.floor(minutes / (43200)); // 30일 * 24시간 * 60분 + minutes = minutes % 43200; + + const day = Math.floor(minutes / 1440); // 24시간 * 60분 + minutes = minutes % 1440; + + const hour = Math.floor(minutes / 60); + const minute = minutes % 60; + + const timeParts = { + year: year, + month: month, + day: day, + hour: hour, + minute: minute, + }; + + return timeParts; + }; + + return ( + <> + + +
+ + + + + + + {isModifyMode && + + 연계 일련번호 + + ( + + + + + + )} + /> + + + } + + * 연계 명 + + ( + + + + + {form.formState.errors.link_nm && ( +

+ {form.formState.errors.link_nm.message} +

+ )} +
+ )} + /> +
+
+ + * 연계 분류 + + ( + + + + + {form.formState.errors.link_clsf_cd && ( +

+ {form.formState.errors.link_clsf_cd.message} +

+ )} +
+ )} + /> +
+
+ + * 연계 URL + + ( + + + + + {form.formState.errors.link_url && ( +

+ {form.formState.errors.link_url.message} +

+ )} +
+ )} + /> +
+
+ + 연계서버 IP + + ( + + + + + + )} + /> + + + + * 연계 KEY + + ( + + + + + {form.formState.errors.link_key && ( +

+ {form.formState.errors.link_key.message} +

+ )} +
+ )} + /> +
+
+ + 연계 ID + + ( + + + + + + )} + /> + + + + 연계 비밀번호 + + ( + + + + + + )} + /> + + + + 서비스 용도 + + ( + + + + + + )} + /> + + + + 연계 주기 + + { + const timeParts = convertMinutesToPeriod(Number(field.value)); + return ( + + +
+
+ { + const total = (Number(e.target.value) * 525600) + + (timeParts.month * 43200) + + (timeParts.day * 1440) + + (timeParts.hour * 60) + + timeParts.minute; + field.onChange(total); + }} + /> + 년 +
+
+ { + const total = (timeParts.year * 525600) + + (Number(e.target.value) * 43200) + + (timeParts.day * 1440) + + (timeParts.hour * 60) + + timeParts.minute; + field.onChange(total); + }} + /> + 월 +
+
+ { + const total = (timeParts.year * 525600) + + (timeParts.month * 43200) + + (Number(e.target.value) * 1440) + + (timeParts.hour * 60) + + timeParts.minute; + field.onChange(total); + }} + /> + 일 +
+
+ { + const total = (timeParts.year * 525600) + + (timeParts.month * 43200) + + (timeParts.day * 1440) + + (Number(e.target.value) * 60) + + timeParts.minute; + field.onChange(total); + }} + /> + 시간 +
+
+ { + const total = (timeParts.year * 525600) + + (timeParts.month * 43200) + + (timeParts.day * 1440) + + (timeParts.hour * 60) + + Number(e.target.value); + field.onChange(total); + }} + /> + 분 +
+
+
+ {form.formState.errors.link_batch_time && ( +

+ {form.formState.errors.link_batch_time.message} +

+ )} +
+ ); + }} + /> +
+
+ {isModifyMode && + <> + + 연계 상태 + + { + return ( + + +
+ + +
+
+
+ ); + }} + /> +
+
+ + 최종 연계 실행일 + + ( + + + + + + )} + /> + + + + 다음 연계 실행일 + + ( + + + + + + )} + /> + + + + 수정자 + + ( + + + + + + )} + /> + + + + 수정일시 + + ( + + + + + + )} + /> + + + + } +
+
+
+
+ + + {isModifyMode && ( + + )} +
+ + + + + ) +}; + +export default LinkForm; \ No newline at end of file diff --git a/apps/cs/src/pages/link/ui/link-page.tsx b/apps/cs/src/pages/link/ui/link-page.tsx new file mode 100644 index 000000000..95a9cc043 --- /dev/null +++ b/apps/cs/src/pages/link/ui/link-page.tsx @@ -0,0 +1,134 @@ +"use client"; + +import "@common/assets/styles/grid.css"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button, Input, Label, LoadingWithAnimation } from "@common/components"; +import { getCoreRowModel } from "@tanstack/react-table"; +import { LinkApi, type Link, type LinkSearchParam } from "@/entities/link"; +import { useQuery } from "@tanstack/react-query"; +import { columns } from "../model/table-columns"; +import { useQueryClient } from "@tanstack/react-query"; +import { useDataTable } from "@/shared/lib"; + +function LinkPage() { + const router = useRouter(); + const queryClient = useQueryClient(); + + const [linkSn, setLinkSn] = useState(''); + const [linkNm, setLinkNm] = useState(''); + + const [linkSearchParam, setLinkSearchParam] = useState({ + linkSn: '', + linkNm: '' + }); + + const query = useQuery({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam], + queryFn: () => LinkApi.getLinkList(linkSearchParam), + }); + + const handleResetSearchParam = () => { + setLinkSn(''); + setLinkNm(''); + setLinkSearchParam({ + ...linkSearchParam, + linkSn: '', + linkNm: '', + }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLinkSearchParam({ + ...linkSearchParam, + linkSn, + linkNm + }); + }; + + const handleRowClick = (link: Link) => { + queryClient.removeQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`], + }); + queryClient.removeQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`], + }); + router.push(`link/register/${link.link_sn}`); + }; + + const handleClickRegist = () => { + router.push('link/register'); + }; + + const { tableComponent } = useDataTable({ + query, + tableOptions: { + columns, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + }, + dataTableOptions: { + onRowClick: handleRowClick, + viewPagination: true, + }, + }); + + return ( +
+
+
+ + setLinkSn(e.target.value)} + autoComplete="off" + /> + + setLinkNm(e.target.value)} + autoComplete="off" + /> + + +
+
+
+
+

연계 목록

+
+ +
+
+
+ {!query.isLoading ? +
{tableComponent}
+ : +
+
+ +
+
+ } +
+
+
+ ); +} + +export { LinkPage }; \ No newline at end of file diff --git a/apps/cs/src/pages/link/ui/modify-page.tsx b/apps/cs/src/pages/link/ui/modify-page.tsx new file mode 100644 index 000000000..594cb4c49 --- /dev/null +++ b/apps/cs/src/pages/link/ui/modify-page.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { LinkApi, linkFormSchema, type LinkFormData } from "@/entities/link"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import LinkForm from "./link-form"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { useEffect } from "react"; +import { getUserInfo } from "../../../../../../packages/common/components/src/api"; +import { AUTH_USER_ID } from "@/shared/config"; +import { getCookie } from "@common/components"; + +function ModifyLinkPage({ linkSn }: { linkSn: string }) { + const router = useRouter(); + const queryClient = useQueryClient(); + const { setMessage: alert } = useAlertStore((state) => state); + const userUniqId = getCookie(AUTH_USER_ID) ?? ""; + + // 컴포넌트 마운트 시 캐시 초기화 + useEffect(() => { + queryClient.removeQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`], + }); + }, [queryClient]); + + const { data: linkInfo, refetch } = useQuery({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`], + queryFn: () => LinkApi.getLinkInfo(linkSn), + enabled: !!linkSn, + }); + + const { mutateAsync: updateLinkInfo } = useMutation({ + mutationFn: (data: LinkFormData) => LinkApi.updateLinkInfo({ + ...data, + link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null, + mdfr: userUniqId + }), + onSuccess: () => { + alert('수정되었습니다.'); + router.push('/link'); + } + }); + + const { mutateAsync: deleteLink } = useMutation({ + mutationFn: () => LinkApi.deleteLink(linkSn), + onSuccess: () => { + alert('삭제되었습니다.', router.back); + } + }); + + const { mutateAsync: updateLinkResultCode } = useMutation({ + mutationFn: () => LinkApi.updateLinkResultCode(linkSn), + onSuccess: () => { + alert('대기로 전환되었습니다.'); + refetch(); + } + }); + + const fetchUserInfo = async () => { + const userData = await getUserInfo(); + return userData.userName; + }; + + const form = useForm({ + resolver: zodResolver(linkFormSchema), + defaultValues: {}, + }); + + useEffect(() => { + const applyFormData = async () => { + if (linkInfo) { + let newData = { ...linkInfo }; + + if (linkInfo.mdfr) { + const userName = await fetchUserInfo(); + newData = { + ...linkInfo, + mdfr: userName, + }; + } + + form.reset(newData); + + } + }; + + applyFormData(); + }, [linkInfo]); + + return ( + <> +
+
+

연계정보 수정

+
+ +
+ + + ); +}; + +export { ModifyLinkPage }; \ No newline at end of file diff --git a/apps/cs/src/pages/link/ui/regist-page.tsx b/apps/cs/src/pages/link/ui/regist-page.tsx new file mode 100644 index 000000000..403aefa24 --- /dev/null +++ b/apps/cs/src/pages/link/ui/regist-page.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { LinkApi, type LinkFormData, linkFormSchema } from "@/entities/link"; +import LinkForm from "./link-form"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import dayjs from "dayjs"; +import { useMutation } from "@tanstack/react-query"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { useRouter } from "next/navigation"; +import { AUTH_USER_ID } from "@/shared/config"; +import { getCookie } from "@common/components"; + +function RegistLinkPage() { + const { setMessage: alert } = useAlertStore((state) => state); + const router = useRouter(); + const userUniqId = getCookie(AUTH_USER_ID) ?? ""; + + const form = useForm({ + resolver: zodResolver(linkFormSchema), + defaultValues: { + link_nm: '', + link_url: '', + link_srvr_ip: '', + link_db_cd: '', + link_key: '', + link_id: '', + link_pswd: '', + srvc_usg: '', + link_batch_time: 5, + link_clsf_cd: 'none', + }, + }); + + const { mutate: insertLinkInfo } = useMutation({ + mutationFn: (data: LinkFormData) => LinkApi.insertLinkInfo(data), + onSuccess: () => { + alert('저장되었습니다.'); + router.push('/link'); + } + }); + + const handleInsertLink = (data: LinkFormData) => { + const insertData = { + ...data, + link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null, + next_link_excn_dt: dayjs().add(Number(data.link_batch_time), 'minute').format('YYYY-MM-DD HH:mm:ss'), + mdfr: userUniqId, + }; + + insertLinkInfo(insertData); + }; + + return ( +
+
+

연계 등록

+
+ +
+ ); +}; + +export { RegistLinkPage }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkLog/index.ts b/apps/cs/src/pages/linkLog/index.ts new file mode 100644 index 000000000..53ef1798e --- /dev/null +++ b/apps/cs/src/pages/linkLog/index.ts @@ -0,0 +1 @@ +export { LinkLogPage } from './ui/link-log-page'; \ No newline at end of file diff --git a/apps/cs/src/pages/linkLog/model/table-columns.tsx b/apps/cs/src/pages/linkLog/model/table-columns.tsx new file mode 100644 index 000000000..30e7fa702 --- /dev/null +++ b/apps/cs/src/pages/linkLog/model/table-columns.tsx @@ -0,0 +1,55 @@ +import dayjs from "dayjs"; +import { createColumnHelper } from "@tanstack/react-table"; +import { type LinkLog } from "@/entities/link"; + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor('rownum', { + id: 'rownum', + header: 'NO', + }), + columnHelper.accessor('link_sn', { + id: 'link_sn', + header: '연계일련번호', + }), + columnHelper.accessor('link_nm', { + id: 'link_nm', + header: '연계 명', + }), + columnHelper.display({ + id: 'link_rslt_cd', + header: '연계실행결과', + cell: (info) => { + const isSucc = info.row.original.link_rslt_cd.trim() === 'SUCC'; + return {isSucc ? '성공' : '실패'} + }, + }), + columnHelper.accessor('link_err_cd', { + id: 'link_err_cd', + header: '연계오류코드', + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), + columnHelper.accessor('err_cn', { + id: 'err_cn', + header: '연계오류메세지', + cell: (info) => ( +
+ {info.getValue() || '-'} +
+ ), + }), + columnHelper.accessor('log_crt_dt', { + id: 'log_crt_dt', + header: '연계실행일자', + cell: (info) => { + const date = dayjs(info.getValue()); + return date.format('YYYY-MM-DD HH:mm'); + } + }), +]; + +export { columns }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkLog/ui/link-log-page.tsx b/apps/cs/src/pages/linkLog/ui/link-log-page.tsx new file mode 100644 index 000000000..dd39a4726 --- /dev/null +++ b/apps/cs/src/pages/linkLog/ui/link-log-page.tsx @@ -0,0 +1,189 @@ +"use client"; + +import "@common/assets/styles/grid.css"; + +import { useState } from "react"; +import { Button, Input, Label, DatePicker, Select, SelectTrigger, SelectContent, SelectItem, SelectValue, LoadingWithAnimation } from "@common/components"; +import { useQuery } from "@tanstack/react-query"; + +import { getCoreRowModel, type PaginationState, type RowSelectionState, } from "@tanstack/react-table"; +import { LinkApi, type LinkSearchParam } from "@/entities/link"; +import { columns } from "../model/table-columns"; +import dayjs from "dayjs"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { useDataTable } from "@/shared/lib"; + +function LinkLogPage() { + const { setMessage: alert } = useAlertStore((state) => state); + + const [linkSn, setLinkSn] = useState(''); + const [linkNm, setLinkNm] = useState(''); + const [isSucc, setIsSucc] = useState('ALL'); + const [startDate, setStartDate] = useState(dayjs().subtract(7, "day").format('YYYY-MM-DD')); + const [endDate, setEndDate] = useState(dayjs().format('YYYY-MM-DD')); + + const [linkSearchParam, setLinkSearchParam] = useState({ + linkSn: '', + linkNm: '', + isSucc: '', + startDate: '', + endDate: '', + }); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, + }); + + const [rowSelection, setRowSelection] = useState({}); + + const query = useQuery({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam, pagination], + queryFn: () => + LinkApi.getLinkLogList({ + ...linkSearchParam, + page: pagination.pageIndex, + size: pagination.pageSize, + isSucc, + startDate, + endDate, + }), + }); + + const { tableComponent } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + rowSelection, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + onRowSelectionChange: setRowSelection, + }, + dataTableOptions: { + viewPagination: true, + }, + }); + + const handleResetSearchParam = () => { + setIsSucc('ALL'); + setLinkNm(''); + setLinkSn(''); + setStartDate(dayjs().subtract(7, "day").format('YYYY-MM-DD')); + setEndDate(dayjs().format('YYYY-MM-DD')); + setLinkSearchParam({ + ...linkSearchParam, + linkSn: '', + linkNm: '', + isSucc: 'ALL', + startDate: dayjs().subtract(7, "day").format('YYYY-MM-DD'), + endDate: dayjs().format('YYYY-MM-DD'), + }); + }; + + const handleSubmit = async (e?: React.FormEvent) => { + e?.preventDefault(); + + if (startDate && endDate) { + + if (startDate > endDate) { + alert('종료일이 시작일보다 이전일 수 없습니다.'); + return; + } else { + setPagination({ + ...pagination, + pageIndex: 0, + }); + setLinkSearchParam({ + ...linkSearchParam, + linkSn, + linkNm, + isSucc, + startDate, + endDate, + }); + } + } else { + alert('조회 날짜를 선택해주세요.'); + return; + } + }; + + return ( +
+
+
+ + setLinkSn(e.target.value)} + autoComplete="off" + /> + + setLinkNm(e.target.value)} + autoComplete="off" + /> + + + + setStartDate(dayjs(e).format('YYYY-MM-DD'))} /> + + setEndDate(dayjs(e).format('YYYY-MM-DD'))} /> + + + +
+
+
+

로그 목록 (검색 조건에 따른 건수 : {query.data?.totalElements.toLocaleString('ko-KR') ?? 0} )

+
+
+ {query.isLoading ? +
+
+ +
+
+ : +
{tableComponent}
+ } +
+
+
+ ) +}; + +export { LinkLogPage }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkParams/index.ts b/apps/cs/src/pages/linkParams/index.ts new file mode 100644 index 000000000..bd233390f --- /dev/null +++ b/apps/cs/src/pages/linkParams/index.ts @@ -0,0 +1 @@ +export { LinkParamsPage } from './ui/link-params-page'; \ No newline at end of file diff --git a/apps/cs/src/pages/linkParams/model/table-columns.tsx b/apps/cs/src/pages/linkParams/model/table-columns.tsx new file mode 100644 index 000000000..a71a74e9d --- /dev/null +++ b/apps/cs/src/pages/linkParams/model/table-columns.tsx @@ -0,0 +1,26 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { type Link } from "@/entities/link"; + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor((_, index) => index + 1, { + id: 'no', + header: 'NO', + }), + columnHelper.accessor('link_sn', { + id: 'link_sn', + header: '연계 일련번호', + }), + columnHelper.accessor('link_nm', { + id: 'link_nm', + header: '연계 명', + }), + columnHelper.accessor('srvc_usg', { + id: 'srvc_usg', + header: '서비스용도', + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), +]; + +export { columns }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx b/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx new file mode 100644 index 000000000..26ccc7303 --- /dev/null +++ b/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx @@ -0,0 +1,98 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { type LinkVrbl } from "@/entities/link"; +import { Checkbox } from "@common/components"; + +const columnHelper = createColumnHelper(); + +function vrblColumns({ + selectedVrblIds, + setSelectedVrblIds, +}: { + selectedVrblIds: string[]; + setSelectedVrblIds: React.Dispatch>; +}) { + return [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + const allRows = table.getRowModel().rows; + const allSelected = + allRows.length > 0 && + allRows.every((row) => + selectedVrblIds.includes(row.original.link_vrbl_sn) + ); + + return ( +
+ { + if (checked) { + setSelectedVrblIds(allRows.map((row) => row.original.link_vrbl_sn)); + } else { + setSelectedVrblIds([]); + } + }} + /> +
+ ); + }, + cell: ({ row }) => { + const id = row.original.link_vrbl_sn; + const checked = selectedVrblIds.includes(id); + + return ( +
+ e.stopPropagation()} + onCheckedChange={(checked) => { + setSelectedVrblIds((prev) => + checked ? [...prev, id] : prev.filter((x) => x !== id) + ); + }} + /> +
+ ); + }, + }), + + columnHelper.accessor("link_vrbl_sn", { + id: "link_vrbl_sn", + header: "연계 변수 일련번호", + }), + columnHelper.accessor("link_vrbl_nm", { + id: "link_vrbl_nm", + header: "변수 명", + }), + columnHelper.accessor("link_vrbl_cd", { + id: "link_vrbl_cd", + header: "변수코드", + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), + columnHelper.accessor("link_vrbl_vl", { + id: "link_vrbl_vl", + header: "변수 값", + cell: (info) => (info.getValue() === 'now' ? '요청일자' : info.getValue() === 'nowMonth' ? '요청월' : info.getValue()), + }), + columnHelper.accessor("link_vrbl_expln", { + id: "link_vrbl_expln", + header: "설명", + cell: (info) => ( +
+ {info.getValue() || '-'} +
+ ), + }), + ]; +} + +export { vrblColumns }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx b/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx new file mode 100644 index 000000000..cff72a030 --- /dev/null +++ b/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx @@ -0,0 +1,351 @@ +'use client'; + +import { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogClose, + Button, + Input, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Checkbox, + RadioGroup, + RadioGroupItem, + Label +} from "@common/components"; +import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form"; +import { type LinkVrblFormData } from "@/entities/link"; +import dayjs from "dayjs"; +import type { UseFormReturn } from "react-hook-form"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useState, useEffect } from "react"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; + +type LinkParamsDialogProps = { + selectedLink: { + linkSn: string; + linkNm: string; + }; + form: UseFormReturn; + insertLinkVrbl: (data: LinkVrblFormData) => void; + dialogOpen: boolean; + onOpenChange: (val: boolean) => void; + updateLinkVrbl: (data: LinkVrblFormData) => void; +} + +function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOpenChange, updateLinkVrbl }: LinkParamsDialogProps) { + + const isModifyMode = !!form.getValues('link_vrbl_sn'); + + const { confirm } = useConfirm(); + const [onConfirm, setOnConfirm] = useState(false); + const [isToday, setIsToday] = useState(false); + const [checkNowMonth, setCheckNowMonth] = useState('now'); + + const handleConfirm = async (callback: () => Promise) => { + setOnConfirm(true); + await callback(); + setOnConfirm(false); + }; + + function replaceEmptyWithNull>(data: T): T { + const result = {} as T; + for (const [key, value] of Object.entries(data)) { + (result as Record)[key] = value === '' ? null : value; + } + return result as T; + }; + + const handleSubmit = async (data: LinkVrblFormData) => { + const confirmMsg = isModifyMode ? '수정 하시겠습니까?' : '저장 하시겠습니까?'; + + await handleConfirm(async () => { + const cleanData = replaceEmptyWithNull(data); + + if (await confirm(confirmMsg)) { + if (isModifyMode) { + updateLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl: checkNowMonth } : cleanData); + } else { + insertLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl: checkNowMonth } : cleanData); + } + } + }); + }; + + const handleCheckTodayValue = (check: boolean | string) => { + if (check) { + setIsToday(true); + form.setValue('link_vrbl_vl', (checkNowMonth === 'now' ? '요청일자' : '요청월')); + } else { + setIsToday(false); + form.setValue('link_vrbl_vl', ''); + } + }; + + useEffect(() => { + if (!dialogOpen) { + setIsToday(false); + setCheckNowMonth('now'); + form.reset({ + link_vrbl_nm: '', + link_vrbl_expln: '', + link_vrbl_cd: '', + link_vrbl_vl: '', + }); + } + + if (isModifyMode && (form.getValues('link_vrbl_vl') === 'now' || form.getValues('link_vrbl_vl') === 'nowMonth')) { + setIsToday(true); + setCheckNowMonth(form.getValues('link_vrbl_vl')); + } + + }, [dialogOpen]); + + return ( + { + onOpenChange(open); + }} + > + + + + + + + {selectedLink.linkNm} ({selectedLink.linkSn}) + + +
+ +
+ + + + + + + {isModifyMode && + + 변수 일련번호 + + ( + + + + + + )} + /> + + + } + + * 변수 명 + + ( + + + + + {form.formState.errors.link_vrbl_nm && ( +

+ {form.formState.errors.link_vrbl_nm.message} +

+ )} +
+ )} + /> +
+
+ + 변수 설명 + + ( + + + + + {form.formState.errors.link_vrbl_expln && ( +

+ {form.formState.errors.link_vrbl_expln.message} +

+ )} +
+ )} + /> +
+
+ + 변수 코드 + + ( + + + + + {form.formState.errors.link_vrbl_cd && ( +

+ {form.formState.errors.link_vrbl_cd.message} +

+ )} +
+ )} + /> +
+
+ + * 변수 값 + + ( + +
+ handleCheckTodayValue(check)} /> + +
+ {isToday && +
+ { + setCheckNowMonth(value); + form.setValue('link_vrbl_vl', (value === 'now' ? '요청일자' : '요청월')); + }} + > +
+ + +
+
+ + +
+
+
+ } + + + + {form.formState.errors.link_vrbl_vl && ( +

+ {form.formState.errors.link_vrbl_vl.message} +

+ )} +
+ )} + /> +
+
+ {isModifyMode && + <> + + 수정자 + + ( + + + + + + )} + /> + + + + 수정일시 + + ( + + + + + + )} + /> + + + + } +
+
+
+ + + + + + +
+ + +
+
+ ) +}; + +export { LinkParamsDialog }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx new file mode 100644 index 000000000..b3806e712 --- /dev/null +++ b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx @@ -0,0 +1,405 @@ +'use client'; + +import { useState, useMemo } from "react"; +import { + Button, + DataTable, + Input, + Label, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + LoadingWithAnimation, +} from "@common/components"; +import { getCoreRowModel, useReactTable, type PaginationState, type RowSelectionState, } from "@tanstack/react-table"; +import { LinkApi, type Link, type LinkSearchParam, type LinkVrblFormData, linkVrblFormSchema } from "@/entities/link"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { columns } from "../model/table-columns"; +import { vrblColumns } from "../model/vrbl-table-columns"; +import { type LinkVrbl } from "@/entities/link"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { LinkParamsDialog } from "./link-params-dialog"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { getUserInfo } from "../../../../../../packages/common/components/src/api"; +import { AUTH_USER_ID } from "@/shared/config"; +import { getCookie } from "@common/components"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { useDataTable } from "@/shared/lib"; + +function LinkParamsPage() { + const queryClient = useQueryClient(); + + const [linkSn, setLinkSn] = useState(''); + const [linkNm, setLinkNm] = useState(''); + + const [linkSearchParam, setLinkSearchParam] = useState({ + linkSn: '', + linkNm: '' + }); + + const [selectedLink, setSelectedLink] = useState<{ + linkSn: string; + linkNm: string; + }>({ + linkSn: '', + linkNm: '' + }); + + const [rowSelection, setRowSelection] = useState({}); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, + }); + + const { confirm } = useConfirm(); + + const [selectedVrblIds, setSelectedVrblIds] = useState([]); + const [isDBdata, setIsDBData] = useState(false); + const [dialogOpen, setDialogOpen] = useState(false); + const [onConfirm, setOnConfirm] = useState(false); + + const userUniqId = getCookie(AUTH_USER_ID) ?? ""; + const { setMessage: alert } = useAlertStore((state) => state); + + const dbIdList = ['link0001', 'link0002']; + + // Link 목록 쿼리 + const query = useQuery({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam], + queryFn: () => LinkApi.getLinkList({ + ...linkSearchParam, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + }); + + // LinkVrbl 목록 쿼리 - 별도 관리 + const linkVrblQuery = useQuery({ + queryKey: ['linkVrblList', selectedLink.linkSn], + queryFn: () => LinkApi.getLinkVrblList(selectedLink.linkSn), + enabled: !!selectedLink.linkSn && !dbIdList.includes(selectedLink.linkSn), + staleTime: 0, // 항상 최신 데이터를 가져오도록 설정 + }); + + const handleResetSearchParam = () => { + setSelectedLink({ + linkSn: '', + linkNm: '' + }); + + setLinkSn(''); + setLinkNm(''); + setLinkSearchParam({ + ...linkSearchParam, + linkSn: '', + linkNm: '' + }); + }; + + const handleConfirm = async (callback: () => Promise) => { + setOnConfirm(true); + await callback(); + setOnConfirm(false); + }; + + const { mutateAsync: insertLinkVrbl } = useMutation({ + mutationFn: async (data: LinkVrblFormData) => + await LinkApi.insertLinkVrbl({ + ...data, + link_sn: selectedLink.linkSn, + mdfr: userUniqId, + }), + onSuccess: async () => { + // 쿼리 무효화를 통한 최신 데이터 가져오기 + await queryClient.invalidateQueries({ + queryKey: ['linkVrblList', selectedLink.linkSn] + }); + setDialogOpen(false); + alert('저장되었습니다.'); + } + }); + + const { mutateAsync: updateLinkVrbl } = useMutation({ + mutationFn: async (data: LinkVrblFormData) => await LinkApi.updateLinkVrblInfo({ + ...data, + link_sn: selectedLink.linkSn, + mdfr: userUniqId, + }), + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['linkVrblList', selectedLink.linkSn] + }); + setDialogOpen(false); + alert('저장되었습니다.'); + } + }); + + const { mutateAsync: deleteLinkVrbl } = useMutation({ + mutationFn: async (data: string[]) => await LinkApi.deleteLinkVrbl(data), + onSuccess: async () => { + await queryClient.invalidateQueries({ + queryKey: ['linkVrblList', selectedLink.linkSn] + }); + setSelectedVrblIds([]); + alert('삭제되었습니다.'); + } + }); + + // linkVrblQuery의 데이터를 사용 + const tableData = useMemo(() => { + return linkVrblQuery.data?.content ?? []; + }, [linkVrblQuery.data]); + + const vrblCols = useMemo( + () => + vrblColumns({ + selectedVrblIds, + setSelectedVrblIds, + }), + [selectedVrblIds] + ); + + const vrblTable = useReactTable({ + data: tableData, + columns: vrblCols, + getCoreRowModel: getCoreRowModel(), + }); + + const form = useForm({ + resolver: zodResolver(linkVrblFormSchema), + defaultValues: {}, + }); + + const handleRowClick = async (link: Link) => { + setSelectedLink({ + linkSn: link.link_sn, + linkNm: link.link_nm, + }); + + setSelectedVrblIds([]); + + if (dbIdList.includes(link.link_clsf_cd)) { + setIsDBData(true); + } else { + setIsDBData(false); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + setSelectedLink({ + linkSn: '', + linkNm: '' + }); + + setLinkSearchParam({ + ...linkSearchParam, + linkSn, + linkNm + }); + }; + + const fetchUserInfo = async () => { + const userData = await getUserInfo(); + return userData.userName; + }; + + const handleDeleteParam = async () => { + if (selectedVrblIds.length === 0) { + alert('삭제할 파라미터를 선택해 주세요.'); + } else { + await handleConfirm(async () => { + if (await confirm('선택한 파라미터 정보를 삭제하시겠습니까?')) { + deleteLinkVrbl(selectedVrblIds); + } + }); + } + }; + + const handleVrblRowClick = async (data: LinkVrbl) => { + const linkVrblInfo = await LinkApi.getLinkVrblInfo(data.link_vrbl_sn); + + if (linkVrblInfo) { + let newData = { ...linkVrblInfo }; + + if (linkVrblInfo.mdfr) { + const userName = await fetchUserInfo(); + newData = { + ...linkVrblInfo, + mdfr: userName, + }; + } + + form.reset(newData); + } + setDialogOpen(true); + }; + + const { tableComponent } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + rowSelection, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + onRowSelectionChange: setRowSelection, + }, + dataTableOptions: { + onRowClick: handleRowClick, + viewPagination: true, + }, + }); + + // LinkVrbl 데이터 표시 로직 + const renderLinkVrblContent = () => { + if (isDBdata) { + return ( +
+ 변수를 지원하지 않는 연계 데이터입니다. +
+ ); + } + + if (!selectedLink.linkSn) { + return ( +
+ 연계 데이터를 선택해주세요. +
+ ); + } + + if (linkVrblQuery.isLoading) { + return ( +
+
+ +
+
+ ); + } + + return ( + <> +
+ {tableData.length > 0 && ( + + )} + +
+ {tableData.length > 0 ? ( +
+ +
+ ) : ( +
+ 등록된 연계 변수가 없습니다. +
+ )} + + ); + }; + + return ( +
+
+
+ + setLinkSn(e.target.value)} + autoComplete="off" + /> + + setLinkNm(e.target.value)} + autoComplete="off" + /> + + +
+
+
+
+
+

연계 목록

+
+
+ {!query.isLoading ? ( +
{tableComponent}
+ ) : ( +
+
+ +
+
+ )} +
+
+
+
+

연계 변수 정보

+
+
+ + + + + + + + 연계 일련번호 + + {selectedLink.linkSn ? selectedLink.linkSn : '-'} + + + + 연계 명 + + {selectedLink.linkNm ? selectedLink.linkNm : '-'} + + + +
+ {renderLinkVrblContent()} +
+
+
+ +
+ ); +} + +export { LinkParamsPage }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkStats/index.ts b/apps/cs/src/pages/linkStats/index.ts new file mode 100644 index 000000000..4e3241a79 --- /dev/null +++ b/apps/cs/src/pages/linkStats/index.ts @@ -0,0 +1 @@ +export { LinkStatsPage } from './ui/link-stats-page'; \ No newline at end of file diff --git a/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx b/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx new file mode 100644 index 000000000..f6168ba76 --- /dev/null +++ b/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx @@ -0,0 +1,378 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; +import dayjs from "dayjs"; +import { LinkApi, type LinkStats, type LinkSearchParam } from "@/entities/link"; +import { Button, Label, DatePicker, LoadingWithAnimation } from "@common/components"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Tooltip, + Legend, + type ChartOptions, + type Chart, +} from "chart.js"; +import { Line } from "react-chartjs-2"; +import { getCoreRowModel, type PaginationState, type RowSelectionState, } from "@tanstack/react-table"; +import { columns } from "@/pages/linkLog/model/table-columns"; +import { useDataTable } from "@/shared/lib"; +import { useQuery } from "@tanstack/react-query"; + +interface LinkStatsListObject { + linkSuccStats: LinkStats[] | null; + linkFailStats: LinkStats[] | null; +} + +function LinkStatsPage() { + + const succChartRef = useRef | null>(null); + const failChartRef = useRef | null>(null); + + const [startDate, setStartDate] = useState(dayjs().subtract(7, "day").format('YYYY-MM-DD')); + const [endDate, setEndDate] = useState(dayjs().format('YYYY-MM-DD')); + + const [rowSelection, setRowSelection] = useState({}); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, + }); + + const [linkStatsList, setLinkStatsList] = useState(null); + const [logQueryParam, setLogQueryParam] = useState<{ + isSucc: 'SUCC' | 'FAIL'; + linkSn?: string; + linkNm?: string; + } | null>(null); + + const query = useQuery({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, logQueryParam, pagination], + queryFn: () => + LinkApi.getLinkLogList({ + ...logQueryParam, + page: pagination.pageIndex, + size: pagination.pageSize, + startDate, + endDate, + }), + enabled: !!logQueryParam, + }); + + + const { tableComponent } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + rowSelection, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + onRowSelectionChange: setRowSelection, + }, + dataTableOptions: { + viewPagination: true, + }, + }); + + const [linkSearchParam, setLinkSearchParam] = useState({ + linkSn: '', + linkNm: '', + startDate: '', + endDate: '', + }); + + ChartJS.register( + LineElement, + CategoryScale, + LinearScale, + PointElement, + Tooltip, + Legend + ); + + const succData = { + labels: linkStatsList?.linkSuccStats?.map((e) => e.link_nm), + datasets: [ + { + label: '연계 성공', + data: linkStatsList?.linkSuccStats?.map((e) => e.count), + fill: false, + borderColor: 'rgb(53, 162, 235)', + backgroundColor: 'rgb(53, 162, 235)', + tension: 0.1, + pointRadius: 6, + pointHoverRadius: 8, + }, + ], + }; + + const succOptions: ChartOptions<'line'> = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top', + }, + }, + scales: { + y: { + min: 0, + }, + }, + }; + + const failData = { + labels: linkStatsList?.linkFailStats?.map((e) => e.link_nm), + datasets: [ + { + label: '연계 실패', + data: linkStatsList?.linkFailStats?.map((e) => e.count), + fill: false, + borderColor: 'rgb(255, 99, 132)', + backgroundColor: 'rgb(255, 99, 132)', + tension: 0.1, + pointRadius: 6, + pointHoverRadius: 8, + }, + ], + }; + + const failOptions: ChartOptions<'line'> = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top', + }, + }, + scales: { + y: { + min: 0, + }, + }, + }; + + const handleResetSearchParam = async () => { + setLinkSearchParam({ + linkSn: '', + linkNm: '', + startDate: dayjs().subtract(7, "day").format('YYYY-MM-DD'), + endDate: dayjs().format('YYYY-MM-DD'), + }); + + setLogQueryParam({ + isSucc: 'SUCC', + linkNm: '', + linkSn: '', + }); + + setStartDate(dayjs().subtract(7, "day").format('YYYY-MM-DD')); + setEndDate(dayjs().format('YYYY-MM-DD')); + + const data = await LinkApi.getLinkStatsList({ + ...linkSearchParam, + linkSn: '', + linkNm: '', + startDate: dayjs().subtract(7, "day").format('YYYY-MM-DD'), + endDate: dayjs().format('YYYY-MM-DD'), + }); + setLinkStatsList(data); + }; + + const handleSubmit = async (e?: React.FormEvent) => { + e?.preventDefault(); + + if (startDate && endDate) { + + if (startDate > endDate) { + alert('종료일이 시작일보다 이전일 수 없습니다.'); + return; + } else { + const data = await LinkApi.getLinkStatsList({ + ...linkSearchParam, + startDate, + endDate + }); + setLinkStatsList(data); + setLogQueryParam({ + isSucc: 'SUCC', + linkNm: '', + linkSn: '', + }); + setLinkSearchParam({ + ...linkSearchParam, + startDate, + endDate + }); + } + } else { + alert('조회 날짜를 선택해주세요.'); + return; + } + }; + + const handleSuccChartClick = async (event: React.MouseEvent) => { + if (!succChartRef.current) return; + + const chart = succChartRef.current; + + const points = chart.getElementsAtEventForMode( + event.nativeEvent, + "nearest", + { intersect: true }, + false + ); + + if (points[0] && linkStatsList?.linkSuccStats) { + const index = points[0].index; + setPagination({ + ...pagination, + pageIndex: 0, + pageSize: 100, + }); + setLogQueryParam({ + isSucc: 'SUCC', + linkNm: linkStatsList.linkSuccStats[index]?.link_nm, + linkSn: linkStatsList.linkSuccStats[index]?.link_sn, + }); + } + }; + + const handleFailChartClick = async (event: React.MouseEvent) => { + if (!failChartRef.current) return; + + const chart = failChartRef.current; + + const points = chart.getElementsAtEventForMode( + event.nativeEvent, + "nearest", + { intersect: true }, + false + ); + + if (points[0] && linkStatsList?.linkFailStats) { + const index = points[0].index; + setPagination({ + ...pagination, + pageIndex: 0, + pageSize: 100, + }); + setLogQueryParam({ + isSucc: 'FAIL', + linkNm: linkStatsList.linkFailStats[index]?.link_nm, + linkSn: linkStatsList.linkFailStats[index]?.link_sn, + }); + } + }; + + const handleSubmitToday = async () => { + const data = await LinkApi.getLinkStatsList({ + ...linkSearchParam, + startDate: dayjs().format('YYYY-MM-DD'), + endDate: dayjs().format('YYYY-MM-DD'), + }); + setEndDate(dayjs().format('YYYY-MM-DD')); + setStartDate(dayjs().format('YYYY-MM-DD')); + setLinkStatsList(data); + setLinkSearchParam({ + ...linkSearchParam, + startDate: dayjs().format('YYYY-MM-DD'), + endDate: dayjs().format('YYYY-MM-DD'), + }); + setLogQueryParam({ + isSucc: 'SUCC', + linkNm: '', + linkSn: '', + }); + }; + + useEffect(() => { + const fetchData = async () => { + const data = await LinkApi.getLinkStatsList({ + ...linkSearchParam, + startDate, + endDate + }); + setLinkStatsList(data); + }; + + setLinkSearchParam({ + ...linkSearchParam, + startDate, + endDate + }); + + fetchData(); + }, []); + + return ( +
+
+
+ + setStartDate(dayjs(e).format('YYYY-MM-DD'))} /> + + setEndDate(dayjs(e).format('YYYY-MM-DD'))} /> + + + + +
+
+
+
+

조회 일자 : {`${linkSearchParam.startDate} ~ ${linkSearchParam.endDate}`}

+
+
+ +
+
+ +
+
+
+
+

+ {logQueryParam?.linkNm ? `${logQueryParam.linkNm} 연계 ${logQueryParam.isSucc === 'FAIL' ? '실패' : '성공'} 로그 목록 (${linkSearchParam.startDate} ~ ${linkSearchParam.endDate}) - ${query.data?.totalElements.toLocaleString('ko-KR') ?? 0}건` : '로그 목록'} +

+
+
+ {logQueryParam?.linkNm ? + query.isLoading ? +
+
+ +
+
+ : +
{tableComponent}
+ : +
+ 차트 데이터를 선택해주세요. +
+ } +
+
+
+
+ ) +}; + +export { LinkStatsPage }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkTable/index.ts b/apps/cs/src/pages/linkTable/index.ts new file mode 100644 index 000000000..4677f1b52 --- /dev/null +++ b/apps/cs/src/pages/linkTable/index.ts @@ -0,0 +1 @@ +export { LinkTablePage} from "./ui/link-table-page"; \ No newline at end of file diff --git a/apps/cs/src/pages/linkTable/model/table-columns.tsx b/apps/cs/src/pages/linkTable/model/table-columns.tsx new file mode 100644 index 000000000..8c15ecd4b --- /dev/null +++ b/apps/cs/src/pages/linkTable/model/table-columns.tsx @@ -0,0 +1,103 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { type LinkTableColumns } from "@/entities/link"; +import { Checkbox } from "@common/components"; + +const columnHelper = createColumnHelper(); + +function tableColumns({ selectedColIds, setSelectedColIds }: { + selectedColIds: string[]; + setSelectedColIds: React.Dispatch>; +}) { + return [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + const allRows = table.getRowModel().rows; + const allSelected = + allRows.length > 0 && + allRows.every((row) => + selectedColIds.includes(row.original.col_sn) + ); + + return ( +
+ { + if (checked) { + setSelectedColIds(allRows.map((row) => row.original.col_sn)); + } else { + setSelectedColIds([]); + } + }} + /> +
+ ); + }, + cell: ({ row }) => { + const id = row.original.col_sn; + const checked = selectedColIds.includes(id); + + return ( +
+ e.stopPropagation()} + onCheckedChange={(checked) => { + setSelectedColIds((prev) => + checked ? [...prev, id] : prev.filter((x) => x !== id) + ); + }} + /> +
+ ); + }, + }), + columnHelper.accessor('col_sn', { + id: 'col_sn', + header: '컬럼 일련번호' + }), + columnHelper.accessor('sou_col_nm', { + id: 'sou_col_nm', + header: '원천컬럼명', + cell: (info) => info.getValue() ? info.getValue() : '-', + }), + columnHelper.accessor('sou_col_korn_nm', { + id: 'sou_col_korn_nm', + header: '원천컬럼 한글명', + cell: (info) => info.getValue() ? info.getValue() : '-', + }), + columnHelper.accessor('sou_col_sz', { + id: 'sou_col_sz', + header: '원천컬럼 크기', + cell: (info) => info.getValue() ? info.getValue() : '-', + }), + columnHelper.accessor('trsf_col_nm', { + id: 'trsf_col_nm', + header: '변환컬럼명', + cell: (info) => info.getValue() ? info.getValue() : '-', + }), + columnHelper.accessor('trsf_col_korn_nm', { + id: 'trsf_col_korn_nm', + header: '변환컬럼 한글명', + cell: (info) => info.getValue() ? info.getValue() : '-', + }), + columnHelper.accessor('trsf_col_sz', { + id: 'trsf_col_sz', + header: '변환컬럼 크기', + cell: (info) => info.getValue() ? info.getValue() : '-', + }), + columnHelper.accessor('pk_yn', { + id: 'pk_yn', + header: 'PK 여부', + cell: (info) => info.getValue() === 'Y' ? '예' : '아니오', + }), + ] +} + +export { tableColumns }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx b/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx new file mode 100644 index 000000000..a566a7d95 --- /dev/null +++ b/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx @@ -0,0 +1,503 @@ +'use client'; + +import { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogClose, + Button, + Input, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, + Checkbox +} from "@common/components"; +import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form"; +import { type LinkTableColData, type LinkTable } from "@/entities/link"; +import dayjs from "dayjs"; +import type { UseFormReturn } from "react-hook-form"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useState, useEffect } from "react"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; + +interface LinkTableDialogProps { + dialogOpen: boolean; + onOpenChange: (val: boolean) => void; + form: UseFormReturn; + tableInfo: LinkTable; + insertLinkTableCol: (data: LinkTableColData) => void; + updateLinkTableCol: (data: LinkTableColData) => void; +} + +function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLinkTableCol, updateLinkTableCol }: LinkTableDialogProps) { + + const isModifyMode = !!form.getValues('col_sn'); + + const { confirm } = useConfirm(); + const [onConfirm, setOnConfirm] = useState(false); + const [useColSeq, setUseColSeq] = useState(false); + + const handleConfirm = async (callback: () => Promise) => { + setOnConfirm(true); + await callback(); + setOnConfirm(false); + }; + + const handleSubmit = async (data: LinkTableColData) => { + const confirmMsg = isModifyMode ? '수정 하시겠습니까?' : '저장 하시겠습니까?'; + + await handleConfirm(async () => { + if (await confirm(confirmMsg)) { + if (!isModifyMode) { + insertLinkTableCol({ + ...data, + link_sn: tableInfo.link_sn, + tbl_sn: tableInfo.tbl_sn, + }); + } else { + updateLinkTableCol({ + ...data, + link_sn: tableInfo.link_sn, + tbl_sn: tableInfo.tbl_sn, + }); + } + } + }); + }; + + const handleCheckColSeqValue = (check: boolean | string) => { + if (check) { + setUseColSeq(false); + form.setValue('col_seq', null); + } else { + setUseColSeq(true); + } + }; + + useEffect(() => { + if (!dialogOpen) { + setUseColSeq(false); + form.reset({ + tbl_sn: '', + col_sn: '', + sou_col_nm: '', + sou_col_korn_nm: '', + sou_col_data_frm: '', + sou_col_sz: null, + trsf_col_nm: '', + trsf_col_korn_nm: '', + trsf_col_data_frm: '', + trsf_col_sz: null, + col_seq: null, + pk_yn: 'N', + mdfcn_dt: '', + mdfr: '', + }); + } + + if (isModifyMode && form.getValues('col_seq')) { + setUseColSeq(true); + } + + }, [dialogOpen]); + + return ( + { + onOpenChange(open); + }} + > + + + + + + + {tableInfo.trsf_tbl_korn_nm} ({tableInfo.tbl_sn}) + + +
+ +
+ + + + + + + {isModifyMode && + + 컬럼 일련번호 + + ( + + + + + + )} + /> + + + } + + * 원천컬럼명 + + ( + + + + + {form.formState.errors.sou_col_nm && ( +

+ {form.formState.errors.sou_col_nm.message} +

+ )} +
+ )} + /> +
+
+ + * 원천컬럼 한글명 + + ( + + + + + {form.formState.errors.sou_col_korn_nm && ( +

+ {form.formState.errors.sou_col_korn_nm.message} +

+ )} +
+ )} + /> +
+
+ + * 원천컬럼 데이터 형식 + + ( + + + + + {form.formState.errors.sou_col_data_frm && ( +

+ {form.formState.errors.sou_col_data_frm.message} +

+ )} +
+ )} + /> +
+
+ + 원천컬럼 크기 + + ( + + + { + const onlyDigits = e.target.value.replace(/\D/g, "").slice(0, 14); + field.onChange(onlyDigits); // react-hook-form에 반영 + }} + /> + +

+ ※ 14자리까지 입력 가능합니다. +

+
+ )} + /> +
+
+ + PK 여부 + + ( + + + + + + )} + /> + + + + * 변환컬럼명 + + ( + + + + + {form.formState.errors.trsf_col_nm && ( +

+ {form.formState.errors.trsf_col_nm.message} +

+ )} +
+ )} + /> +
+
+ + * 변환컬럼 한글명 + + ( + + + + + {form.formState.errors.trsf_col_korn_nm && ( +

+ {form.formState.errors.trsf_col_korn_nm.message} +

+ )} +
+ )} + /> +
+
+ + * 변환컬럼 데이터 형식 + + ( + + + + + {form.formState.errors.trsf_col_data_frm && ( +

+ {form.formState.errors.trsf_col_data_frm.message} +

+ )} +
+ )} + /> +
+
+ + 변환컬럼 크기 + + ( + + + { + const onlyDigits = e.target.value.replace(/\D/g, "").slice(0, 14); + field.onChange(onlyDigits); // react-hook-form에 반영 + }} + /> + +

+ ※ 14자리까지 입력 가능합니다. +

+
+ )} + /> +
+
+ + 컬럼 매칭 순서 + + ( + +
+ handleCheckColSeqValue(check)} /> + +
+ + { + const onlyDigits = e.target.value.replace(/\D/g, "").slice(0, 3); + field.onChange(onlyDigits); // react-hook-form에 반영 + }} + /> + +

+ ※ 3자리까지 입력 가능합니다. +

+
+ )} + /> +
+
+ {isModifyMode && + <> + + 수정자 + + ( + + + + + + )} + /> + + + + 수정일시 + + ( + + + + + + )} + /> + + + + } +
+
+
+ + + + + + +
+ + +
+
+ ) +} + +export { LinkTableDialog }; \ No newline at end of file diff --git a/apps/cs/src/pages/linkTable/ui/link-table-page.tsx b/apps/cs/src/pages/linkTable/ui/link-table-page.tsx new file mode 100644 index 000000000..11f478b71 --- /dev/null +++ b/apps/cs/src/pages/linkTable/ui/link-table-page.tsx @@ -0,0 +1,710 @@ +'use client'; + +import { useState, useMemo } from "react"; +import { + Button, + DataTable, + Input, + Label, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + LoadingWithAnimation, +} from "@common/components"; +import { getCoreRowModel, type PaginationState, type RowSelectionState, useReactTable } from "@tanstack/react-table"; +import { LinkApi, type Link, type LinkSearchParam, type LinkTable, type LinkTableColumns, type LinkTableColData, type LinkTableFormData, LinkTableColumnsSchema, LinkTableSchema } from "@/entities/link"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { columns } from "../../linkParams/model/table-columns"; +import { tableColumns } from "../model/table-columns"; +import { LinkTableDialog } from "./link-table-dialog"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { getUserInfo } from "../../../../../../packages/common/components/src/api"; +import { AUTH_USER_ID } from "@/shared/config"; +import { getCookie } from "@common/components"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form"; +import { useDataTable } from "@/shared/lib"; + +function LinkTablePage() { + const queryClient = useQueryClient(); + + const userUniqId = getCookie(AUTH_USER_ID) ?? ""; + + const [linkSn, setLinkSn] = useState(''); + const [linkNm, setLinkNm] = useState(''); + + const [linkSearchParam, setLinkSearchParam] = useState({ + linkSn: '', + linkNm: '' + }); + + const [selectedTableInfo, setSelectedTableInfo] = useState(null); + const [tableColList, setTableColList] = useState([]); + const [dialogOpen, setDialogOpen] = useState(false); + const [selectedColIds, setSelectedColIds] = useState([]); + + const [rowSelection, setRowSelection] = useState({}); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 100, + }); + + const { setMessage: alert } = useAlertStore((state) => state); + + const { confirm } = useConfirm(); + const [onConfirm, setOnConfirm] = useState(false); + + const handleConfirm = async (callback: () => Promise) => { + setOnConfirm(true); + await callback(); + setOnConfirm(false); + }; + + const query = useQuery({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkList`, linkSearchParam], + queryFn: () => LinkApi.getLinkList({ + ...linkSearchParam, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + }); + + const { mutateAsync: insertLinkTableCol } = useMutation({ + mutationFn: async (data: LinkTableColData) => await LinkApi.insertLinkTableCol({ + ...data, + mdfr: userUniqId, + }), + onSuccess: async () => { + if (selectedTableInfo) { + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`] + }); + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`] + }); + + const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn); + const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn); + + setSelectedTableInfo(tableData); + setTableColList(tableColList.content); + setSelectedColIds([]); + setDialogOpen(false); + alert('저장되었습니다.'); + } + } + }); + + const { mutateAsync: updateLinkTableCol } = useMutation({ + mutationFn: async (data: LinkTableColData) => await LinkApi.updateLinkTableCol({ + ...data, + mdfr: userUniqId, + }), + onSuccess: async () => { + if (selectedTableInfo) { + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`] + }); + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`] + }); + + const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn); + const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn); + + setSelectedTableInfo(tableData); + setTableColList(tableColList.content); + setSelectedColIds([]); + setDialogOpen(false); + alert('수정되었습니다.'); + } + } + }); + + const { mutateAsync: deleteLinkTableCol } = useMutation({ + mutationFn: async (data: string[]) => await LinkApi.deleteLinkTableCol(data), + onSuccess: async () => { + if (selectedTableInfo) { + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`] + }); + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`] + }); + + const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn); + const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn); + + setSelectedTableInfo(tableData); + setTableColList(tableColList.content); + setSelectedColIds([]); + setDialogOpen(false); + alert('삭제되었습니다.'); + } + } + }); + + const { mutateAsync: insertLinkTableInfo } = useMutation({ + mutationFn: async (data: LinkTableFormData) => await LinkApi.insertLinkTableInfo(data), + onSuccess: async () => { + if (selectedTableInfo) { + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`] + }); + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`] + }); + + const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn); + const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn); + + tableForm.reset({ + link_sn: tableData.link_sn, + link_nm: tableData.link_nm, + tbl_sn: tableData.tbl_sn ?? "", + sou_tbl_nm: tableData.sou_tbl_nm ?? "", + sou_tbl_korn_nm: tableData.sou_tbl_korn_nm ?? "", + sou_tbl_expln: tableData.sou_tbl_expln ?? "", + trsf_tbl_expln: tableData.trsf_tbl_expln ?? "", + trsf_tbl_nm: tableData.trsf_tbl_nm ?? "", + trsf_tbl_korn_nm: tableData.trsf_tbl_korn_nm ?? "", + }); + setSelectedTableInfo(tableData); + setTableColList(tableColList.content); + setSelectedColIds([]); + alert('저장되었습니다.'); + } + } + }); + + const { mutateAsync: updateLinkTableInfo } = useMutation({ + mutationFn: async (data: LinkTableFormData) => await LinkApi.updateLinkTableInfo(data), + onSuccess: async () => { + if (selectedTableInfo) { + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableInfo`] + }); + await queryClient.invalidateQueries({ + queryKey: [`${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkTableColList`] + }); + + const tableData = await LinkApi.getLinkTableInfo(selectedTableInfo.link_sn); + const tableColList = await LinkApi.getLinkTableColList(selectedTableInfo.link_sn); + + setSelectedTableInfo(tableData); + setTableColList(tableColList.content); + setSelectedColIds([]); + alert('수정되었습니다.'); + } + } + }); + + const tableCol = useMemo( + () => + tableColumns({ selectedColIds, setSelectedColIds }), + [selectedColIds] + ); + + const colTable = useReactTable({ + data: tableColList, + columns: tableCol, + getCoreRowModel: getCoreRowModel(), + }); + + const form = useForm({ + resolver: zodResolver(LinkTableColumnsSchema), + defaultValues: { + pk_yn: "N" + } + }); + + const tableForm = useForm({ + resolver: zodResolver(LinkTableSchema), + defaultValues: {}, + }); + + const handleResetSearchParam = async () => { + setSelectedTableInfo(null); + setTableColList([]); + setLinkSn(''); + setLinkNm(''); + setLinkSearchParam({ + ...linkSearchParam, + linkSn: '', + linkNm: '', + }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setSelectedTableInfo(null); + setTableColList([]); + + setLinkSearchParam({ + ...linkSearchParam, + linkSn, + linkNm + }); + }; + + const handleRowClick = async (data: Link) => { + const tableData = await LinkApi.getLinkTableInfo(data.link_sn); + const tableColList = await LinkApi.getLinkTableColList(data.link_sn); + + setSelectedTableInfo(tableData); + setTableColList(tableColList.content); + setSelectedColIds([]); + + tableForm.reset({ + link_sn: tableData.link_sn, + link_nm: tableData.link_nm, + tbl_sn: tableData.tbl_sn ?? "", + sou_tbl_nm: tableData.sou_tbl_nm ?? "", + sou_tbl_korn_nm: tableData.sou_tbl_korn_nm ?? "", + sou_tbl_expln: tableData.sou_tbl_expln ?? "", + trsf_tbl_expln: tableData.trsf_tbl_expln ?? "", + trsf_tbl_nm: tableData.trsf_tbl_nm ?? "", + trsf_tbl_korn_nm: tableData.trsf_tbl_korn_nm ?? "", + }); + }; + + const handleColumnRowClick = async (data: LinkTableColumns) => { + const colInfo = await LinkApi.getLinkTableColInfo({ colSn: data.col_sn, tblSn: data.tbl_sn }); + + if (colInfo) { + let newData = { ...colInfo }; + + if (colInfo.mdfr) { + const userName = await fetchUserInfo(); + newData = { + ...colInfo, + mdfr: userName, + }; + } + + form.reset(newData); + + } + setDialogOpen(true) + }; + + const fetchUserInfo = async () => { + const userData = await getUserInfo(); + return userData.userName; + }; + + const handleDeleteParam = async () => { + if (selectedColIds.length === 0) { + alert('삭제할 컬럼을 선택해 주세요.'); + } else { + await handleConfirm(async () => { + if (await confirm('선택한 컬럼 정보를 삭제하시겠습니까?')) { + deleteLinkTableCol(selectedColIds); + } + }); + } + }; + + const handleTableSubmit = async (data: LinkTableFormData) => { + const regTblInfo = await LinkApi.countLinkTblNm(data.trsf_tbl_nm); + const confirmMsg = (regTblInfo.count > 0 && data.trsf_tbl_nm !== selectedTableInfo?.trsf_tbl_nm) ? `이미 사용중인 테이블 명입니다. 해당 테이블 명으로 ${!data.tbl_sn ? '저장' : '수정'}하시겠습니까?` : `${!data.tbl_sn ? '저장' : '수정'}하시겠습니까?`; + if (!data.tbl_sn) { + await handleConfirm(async () => { + if (await confirm(confirmMsg)) { + insertLinkTableInfo({ + ...data, + mdfr: userUniqId, + tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "", + }); + } + }); + } else { + await handleConfirm(async () => { + if (await confirm(confirmMsg)) { + updateLinkTableInfo({ + ...data, + tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "", + mdfr: userUniqId, + }); + } + }); + } + + }; + + const { tableComponent } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + rowSelection, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + onRowSelectionChange: setRowSelection, + }, + dataTableOptions: { + onRowClick: handleRowClick, + viewPagination: true, + }, + }); + + return ( +
+
+
+ + setLinkSn(e.target.value)} + autoComplete="off" + /> + + setLinkNm(e.target.value)} + autoComplete="off" + /> + + +
+
+
+
+
+

연계 목록

+
+
+ {!query.isLoading ? +
{tableComponent}
+ : +
+
+ +
+
+ } +
+
+
+
+

연계 테이블 정보

+
+
+
+ + {selectedTableInfo && +
+ +
+ } + + + + + + + + 연계 일련번호 + + {selectedTableInfo ? + ( + + + + + + )} + /> + : + - + } + + + + 연계 명 + + {selectedTableInfo ? + ( + + + + + + )} + /> + : + - + } + + + + 원천 테이블 명 + + {selectedTableInfo ? + ( + + + + + {tableForm.formState.errors.sou_tbl_nm && ( +

+ {tableForm.formState.errors.sou_tbl_nm.message} +

+ )} +
+ )} + /> + : + - + } +
+
+ + 원천 테이블 한글명 + + {selectedTableInfo ? + ( + + + + + {tableForm.formState.errors.sou_tbl_korn_nm && ( +

+ {tableForm.formState.errors.sou_tbl_korn_nm.message} +

+ )} +
+ )} + /> + : + - + } +
+
+ + 원천 테이블 설명 + + {selectedTableInfo ? + ( + + + + + {tableForm.formState.errors.sou_tbl_expln && ( +

+ {tableForm.formState.errors.sou_tbl_expln.message} +

+ )} +
+ )} + /> + : + - + } +
+
+ + * 변환 테이블 명 + + {selectedTableInfo ? + ( + + + + + {tableForm.formState.errors.trsf_tbl_nm && ( +

+ {tableForm.formState.errors.trsf_tbl_nm.message} +

+ )} +
+ )} + /> + : + - + } +
+
+ + * 변환 테이블 한글명 + + {selectedTableInfo ? + ( + + + + + {tableForm.formState.errors.trsf_tbl_korn_nm && ( +

+ {tableForm.formState.errors.trsf_tbl_korn_nm.message} +

+ )} +
+ )} + /> + : + - + } +
+
+ + 변환 테이블 설명 + + {selectedTableInfo ? + ( + + + + + {tableForm.formState.errors.trsf_tbl_expln && ( +

+ {tableForm.formState.errors.trsf_tbl_expln.message} +

+ )} +
+ )} + /> + : + - + } +
+
+
+
+
+ + {selectedTableInfo ? + <> +
+ {tableColList.length > 0 && + + } + {selectedTableInfo.tbl_sn && + + } +
+ {selectedTableInfo.tbl_sn ? + (tableColList.length > 0 ? +
+ +
+ : +
+ 등록된 테이블 컬럼이 없습니다. +
) + : +
+ 테이블 정보를 등록해주세요. +
+ } + + : +
+ 연계 데이터를 선택해주세요. +
+ } +
+
+
+ +
+ ) +}; + +export { LinkTablePage }; \ No newline at end of file diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx index a9748b28d..7ae7990a1 100644 --- a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -7,7 +7,6 @@ import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; import { Button, Input, - InputDatePicker, Label, Select, SelectContent, @@ -16,10 +15,11 @@ import { SelectValue, LoadingWithImage, } from "@common/components"; +import { columns } from "@/pages/loginlog/model/table-columns"; import { LoginlogApi, type LoginlogParams } from "@/entities/loginlog"; import { useDataTable } from "@/shared/lib"; import { ENDPOINT } from "@/shared/config"; -import { columns } from "@/pages/loginlog/model/table-columns"; +import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker"; type DateRange = { from?: Date | undefined; @@ -141,6 +141,11 @@ function LoginlogPage() { setDateRange(presetDateRange || defaultDateRange); }; + const handleDateRangeChange = (dateRange: DateRange) => { + setDateRange(dateRange); + setPresetDateRange(""); + }; + return (
@@ -156,20 +161,9 @@ function LoginlogPage() { 3개월 - { - setDateRange({ ...dateRange, from: date }); - setPresetDateRange(""); - }} - /> - ~ - { - setDateRange({ ...dateRange, to: date }); - setPresetDateRange(""); - }} + { + setDateRange(dateRange); + setPresetDateRange(""); + }; + return (
@@ -147,20 +152,9 @@ function PrivacyLogPage() { 3개월 - { - setDateRange({ ...dateRange, from: date }); - setPresetDateRange(""); - }} - /> - ~ - { - setDateRange({ ...dateRange, to: date }); - setPresetDateRange(""); - }} + ({}); const [presetDateRange, setPresetDateRange] = useState(""); - const [dateRange, setDateRange] = useState({ + const [dateRange, setDateRange] = useState({ from: undefined, to: undefined, }); @@ -169,7 +169,7 @@ function RecentPage() { }; const handlePresetDateRange = (value: string) => { - let presetDateRange: DateRange | undefined; + let presetDateRange: DateRange; switch (value) { case "1W": presetDateRange = { @@ -190,12 +190,20 @@ function RecentPage() { }; break; default: - presetDateRange = undefined; + presetDateRange = { + from: undefined, + to: undefined, + }; } setPresetDateRange(value); setDateRange(presetDateRange); }; + const handleDateRangeChange = (dateRange: DateRange) => { + setDateRange(dateRange); + setPresetDateRange(""); + }; + const onSoftDeletePost = async () => { const { resolved, value: delRsn } = await prompt("삭제 사유를 입력해주세요."); @@ -260,20 +268,9 @@ function RecentPage() { 3개월 - { - setDateRange({ ...dateRange, from: date }); - setPresetDateRange(""); - }} - /> - ~ - { - setDateRange({ ...dateRange, to: date }); - setPresetDateRange(""); - }} + + + + + + 일주일 + 1개월 + 3개월 + + + + + + + + setKeyword(e.target.value)} + className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" + /> + + + +
+ + {/* 차트 영역 */} +
+ {/* 일별 접속 현황 */} +
+
+

일별 접속 현황

+
+
+ } + options={chartData.monthlyStatisticsOptions} + /> +
+
+ +
+ {/* 사이트별 접속자 */} +
+
+

사이트별 접속자

+
+
+ +
+
+ + {/* 브라우저별 접속자 */} +
+
+

브라우저별 접속자

+
+
+ +
+
+
+
+ + {/* 접속 로그 상세 */} +
+
+
+

접속 로그 상세

+
+ +
+
+
+
{tableComponent}
+
+
+ ); +} + +export { UserStatisticsPage }; diff --git a/apps/cs/src/pages/users/ui/users-page.tsx b/apps/cs/src/pages/users/ui/users-page.tsx index e119738fa..5362b8b42 100644 --- a/apps/cs/src/pages/users/ui/users-page.tsx +++ b/apps/cs/src/pages/users/ui/users-page.tsx @@ -9,7 +9,6 @@ import { Button, Checkbox, Input, - InputDatePicker, Label, Select, SelectContent, @@ -25,6 +24,7 @@ import { } from "@/entities/user"; import { ENDPOINT } from "@/shared/config"; import { useDataTable } from "@/shared/lib"; +import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker"; import { columns } from "../model/table-columns"; type DateRange = { @@ -40,7 +40,7 @@ function UsersPage() { pageSize: 10, }); const [presetDateRange, setPresetDateRange] = useState(""); - const [dateRange, setDateRange] = useState({ + const [dateRange, setDateRange] = useState({ from: undefined, to: undefined, }); @@ -62,7 +62,7 @@ function UsersPage() { }); const { data: userSttsStats } = useQuery({ - queryKey: [ENDPOINT.USER_SERVICE.USERS], + queryKey: [ENDPOINT.USER_SERVICE.USERS, "userStatusStatistics"], queryFn: () => UserApi.selectUserSttsStats(), }); @@ -145,7 +145,7 @@ function UsersPage() { }; const handlePresetDateRange = (value: string) => { - let presetDateRange: DateRange | undefined; + let presetDateRange: DateRange; switch (value) { case "1M": presetDateRange = { @@ -166,15 +166,24 @@ function UsersPage() { }; break; default: - presetDateRange = undefined; + presetDateRange = { + from: undefined, + to: undefined, + }; } setPresetDateRange(value); setDateRange(presetDateRange); }; - const getUserSttsStatsCnt = (code: string): number => { - if (!Array.isArray(userSttsStats)) return 0; - return userSttsStats.find((item) => item.sttsCd === code)?.cnt ?? 0; + const handleDateRangeChange = (dateRange: DateRange) => { + setDateRange(dateRange); + setPresetDateRange(""); + }; + + const getUserSttsStatsCnt = (code: string): string => { + if (!Array.isArray(userSttsStats)) return "0"; + const count = userSttsStats.find((item) => item.sttsCd === code)?.cnt ?? 0; + return count.toLocaleString(); }; const handleUserStateCodeChange = (code: string, checked: boolean) => { @@ -292,20 +301,9 @@ function UsersPage() { 6개월 - { - setDateRange({ ...dateRange, from: date }); - setPresetDateRange(""); - }} - /> - ~ - { - setDateRange({ ...dateRange, to: date }); - setPresetDateRange(""); - }} +
diff --git a/apps/cs/src/shared/ui/input-date-range-picker.tsx b/apps/cs/src/shared/ui/input-date-range-picker.tsx new file mode 100644 index 000000000..9f574661e --- /dev/null +++ b/apps/cs/src/shared/ui/input-date-range-picker.tsx @@ -0,0 +1,47 @@ +import { InputDatePicker } from "@common/components"; +import { useEffect } from "react"; +import { useAlertStore } from "../lib"; + +type DateRange = { + from?: Date; + to?: Date; +}; + +type InputDateRangePickerProps = { + dateRange: DateRange; + setDateRange: (dateRange: DateRange) => void; +}; + +function InputDateRangePicker({ + dateRange, + setDateRange, +}: InputDateRangePickerProps) { + const alert = useAlertStore((state) => state.setMessage); + + useEffect(() => { + if (dateRange.from && dateRange.to && dateRange.from > dateRange.to) { + alert("시작일보다 종료일이 커야 합니다."); + setDateRange({ ...dateRange, to: dateRange.from }); + } + }, [dateRange]); + + return ( + <> + { + setDateRange({ ...dateRange, from: date }); + }} + /> + ~ + { + setDateRange({ ...dateRange, to: date }); + }} + /> + + ); +} + +export { InputDateRangePicker }; diff --git a/apps/dm/app/base.tsx b/apps/dm/app/base.tsx index 3905c9952..bae02acb2 100644 --- a/apps/dm/app/base.tsx +++ b/apps/dm/app/base.tsx @@ -3,7 +3,7 @@ import { BaseLayout } from "@common/components"; import type { MenuItem } from "./store/common"; import { useDmStore } from "./store"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; export default function Base({ children, @@ -13,10 +13,16 @@ export default function Base({ const { getMenuAuth } = useDmStore((state) => state); const router = useRouter(); - const handleClick = (id:MenuItem) =>{ + const pathname = usePathname(); + + const handleClick = (id: MenuItem) => { getMenuAuth(id.menuCd); router.push(id.urlPath); - } + }; - return {children}; + return ( + + {children} + + ); } diff --git a/apps/fo/.env.local b/apps/fo/.env.local index 94488f0cd..76fb868cd 100644 --- a/apps/fo/.env.local +++ b/apps/fo/.env.local @@ -1,6 +1,6 @@ NEXT_PUBLIC_SITE_ID = FO -NEXT_PUBLIC_PORT = 11300 -NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300 +NEXT_PUBLIC_PORT = 3000 +NEXT_PUBLIC_PROXY_HOST = http://localhost:3000 NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 diff --git a/apps/fo/app/base.tsx b/apps/fo/app/base.tsx index 7bdf4f3e0..a01ce8643 100644 --- a/apps/fo/app/base.tsx +++ b/apps/fo/app/base.tsx @@ -3,7 +3,7 @@ import { BaseLayout } from "@common/components"; import type { MenuItem } from "./store/common"; import { useFoStore } from "./store"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; export default function Base({ children, @@ -11,12 +11,17 @@ export default function Base({ children: React.ReactNode; }>) { const { getMenuAuth } = useFoStore((state) => state); - + const pathname = usePathname(); const router = useRouter(); - const handleClick = (id:MenuItem) =>{ + + const handleClick = (id: MenuItem) => { getMenuAuth(id.menuCd); router.push(id.urlPath); - } + }; - return {children}; + return ( + + {children} + + ); } diff --git a/apps/fo/app/pages/dashboard/facilityInfo/page.tsx b/apps/fo/app/pages/dashboard/facilityInfo/page.tsx index 7a88755ce..767ed54e4 100644 --- a/apps/fo/app/pages/dashboard/facilityInfo/page.tsx +++ b/apps/fo/app/pages/dashboard/facilityInfo/page.tsx @@ -2,7 +2,7 @@ import { useFoStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/processFacility"; import { ContentsDiv, UiModal, UiTable } from "@common/business_components"; -import { cn, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; import { useEffect, useState } from "react"; import DashBoardChartModal from "./modal/page"; @@ -30,20 +30,20 @@ const headMakeColSpanPlanFclty = (upHeadList: TableUpperProps[], planYearList: s const headMakeColSpanOperationStatus = (upHeadList: TableUpperProps[], searchYear: string) => { return ( <> - {searchYear+'년도 운영실태'} + {searchYear+'년도 운영실태'} { upHeadList.map((head: TableUpperProps) => (head.upName === 'upChangeRe1'&&head.upSequnce===1?{head.title}: - !head.upSequnce?{head.title}: + !head.upSequnce?{head.title}: head.upName === 'upChangeRe2'&&head.upSequnce===1?{'연간 운영실태'}: head.upName === 'upChangeRe3'&&head.upSequnce===1?{'하절기 운영실태\n(6~8월)'}: head.upName === 'upChangeRe4'&&head.upSequnce===1?{'동절기 운영실태\n(1~2월)'}: - head.upName === 'upChangeRe5'&&head.upSequnce===1?{'지하수유입량'}:null))} + head.upName === 'upChangeRe5'&&head.upSequnce===1?{'지하수 유입량 추정'}:null))} {upHeadList.map((head: TableUpperProps) => - (head.upName === 'upChangeRe2'||head.upName === 'upChangeRe3'||head.upName === 'upChangeRe4'||head.upName === 'upChangeRe5' ?{head.title}:null))} + (head.upName === 'upChangeRe2'||head.upName === 'upChangeRe3'||head.upName === 'upChangeRe4'||head.upName === 'upChangeRe5'?{head.title}:null))} ) } @@ -238,10 +238,10 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}: (head.id === 'flowRate' && index === 11)?{el[head.id]}: (head.id === 'groundWaterAvg'||head.id === 'groundWaterSummer'||head.id === 'groundWaterWinter')&&(index === 0)?{el[head.id]}: (head.id === 'groundWaterAvg'||head.id === 'groundWaterSummer'||head.id === 'groundWaterWinter')&&(index === 7)?{el[head.id]}: - (head.id === 'rdi'&&index === 0)?{el[head.id]}: - (head.id === 'rdi'&&index === 2)?{'RDII추정시\n일강수량(mm)'}: - (head.id === 'rdi'&&index === 5)?{el['rdiRain']}: + (head.id === 'rdi'&&index === 0)?{el[head.id]}: (head.id === 'rdi'&&index === 7)?: + (head.id === 'rdiRain'&&index === 0)?{el['rdiRain']}: + (head.id === 'rdiRain'&&index === 7)?: (head.id === 'waterQlty')?{el[head.id]}: ((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=0 && index<=4))?{fncLoadModal('flowRate', 'year', el['waterQlty'] as string);}}>{el[head.id]}: ((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=5 && index<=10))?{fncLoadModal('waterQlty', 'year', el['waterQlty'] as string);}}>{el[head.id]}: diff --git a/apps/fo/app/pages/dashboard/page.tsx b/apps/fo/app/pages/dashboard/page.tsx index 97b45fd06..09c452da4 100644 --- a/apps/fo/app/pages/dashboard/page.tsx +++ b/apps/fo/app/pages/dashboard/page.tsx @@ -14,7 +14,7 @@ const tabList = [ { value: 'facilityInfo', label: '시설정보 및 운영현황' }, { value: 'mainGraph', label: '주요그래프' }, { value: 'rainManage', label: '강우일 운영현황' }, - { value: 'drainageEquipment', label: '배수설비 정비현황' } + //{ value: 'drainageEquipment', label: '배수설비 정비현황' } ]; export default function DashBoard() { @@ -57,9 +57,18 @@ export default function DashBoard() { /******************선택된 데이터******************/ // 연도 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || '0'); + //const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || '0'); + const [searchYear, setSearchYear] = useState('2023'); // 시설명 const [fcltyList, setFcltyList] = useState<{bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | ''}>({ + bscFctCd: '26260PB001R', + bscFctNm: '수영', + fcltyCpc: 452000, + trtFrfNm: '표준활성슬러지법, MLE, MBR', + efflWqltStndrdNm: '500톤 이상(Ⅳ지역)' + }); + + const [searchFcltyList, setSearchFcltyList] = useState<{bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | ''}>({ bscFctCd: '', bscFctNm: '', fcltyCpc: '', @@ -70,12 +79,12 @@ export default function DashBoard() { useEffect(() => { if (!isInit) { initializeCommonData(); - setFcltyList({ - bscFctCd: '11000SW001R', - bscFctNm: '난지', - fcltyCpc: 860000, - trtFrfNm: '표준활성슬러지법, MLE, A2O', - efflWqltStndrdNm: '500톤 이상(Ⅲ지역)' + setSearchFcltyList({ + bscFctCd: '26260PB001R', + bscFctNm: '수영', + fcltyCpc: 452000, + trtFrfNm: '표준활성슬러지법, MLE, MBR', + efflWqltStndrdNm: '500톤 이상(Ⅳ지역)' }); } }, [isInit, initializeCommonData]); @@ -257,12 +266,29 @@ export default function DashBoard() { const initVal = () => { setFcltyList({ - bscFctCd: '11000SW001R', - bscFctNm: '난지', - fcltyCpc: 860000, - trtFrfNm: '표준활성슬러지법, MLE, A2O', - efflWqltStndrdNm: '500톤 이상(Ⅲ지역)' + bscFctCd: '26260PB001R', + bscFctNm: '수영', + fcltyCpc: 452000, + trtFrfNm: '표준활성슬러지법, MLE, MBR', + efflWqltStndrdNm: '500톤 이상(Ⅳ지역)' }); + + setSearchFcltyList({ + bscFctCd: '26260PB001R', + bscFctNm: '수영', + fcltyCpc: 452000, + trtFrfNm: '표준활성슬러지법, MLE, MBR', + efflWqltStndrdNm: '500톤 이상(Ⅳ지역)' + }); + setSearchYear('2023'); + searchFcltyList.bscFctNm = '수영'; + } + + const searchVal = () => { + setSearchFcltyList(fcltyList); + if(searchFcltyList.bscFctCd) { + searchOperationStatus(false, searchYear, fcltyList.bscFctCd); + } } return ( @@ -274,10 +300,13 @@ export default function DashBoard() { onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={undefined}/> onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={undefined}/>
- +
+ @@ -288,16 +317,16 @@ export default function DashBoard() {
- + - + - + - + diff --git a/apps/fo/app/pages/dashboardMain/page.tsx b/apps/fo/app/pages/dashboardMain/page.tsx index ccbf42c9d..143a78acd 100644 --- a/apps/fo/app/pages/dashboardMain/page.tsx +++ b/apps/fo/app/pages/dashboardMain/page.tsx @@ -3,7 +3,7 @@ import { useFoStore } from "@/app/store"; import { useEffect, useState } from "react"; import SearchInput from "../../components/common/SearchInput"; -export default function DashBoardFclty({ onFacilityChange, sidoSigun, facilityNm }: { onFacilityChange?: (facility: { bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | '' }) => void, sidoSigun?: string, facilityNm?: string }) { +export default function DashBoardFclty({ onFacilityChange, sidoSigun, facilityNm, }: { onFacilityChange?: (facility: { bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | '' }) => void, sidoSigun?: string, facilityNm?: string }) { // 입력값 상태 관리 const { Common, initializeCommonData } = useFoStore((state) => state); @@ -12,7 +12,7 @@ export default function DashBoardFclty({ onFacilityChange, sidoSigun, facilityNm } = Common; // 시설명 - const [bscFctNm, setFcltyName] = useState(facilityNm || '난지'); + const [bscFctNm, setFcltyName] = useState(facilityNm || '수영'); const [bscFctCd, setbscFctCd] = useState('11000SW001R'); const [fcltyCpc, setFcltyCpc] = useState(860000); const [trtFrfNm, setTrtFrfNm] = useState('표준활성슬러지법, MLE, A2O'); @@ -48,8 +48,6 @@ export default function DashBoardFclty({ onFacilityChange, sidoSigun, facilityNm } } - console.log('bscFctNm : ', bscFctNm); - return (
diff --git a/apps/fo/app/pages/facilityPrecipitation/page.tsx b/apps/fo/app/pages/facilityPrecipitation/page.tsx index c9403fd14..96456a71b 100644 --- a/apps/fo/app/pages/facilityPrecipitation/page.tsx +++ b/apps/fo/app/pages/facilityPrecipitation/page.tsx @@ -81,7 +81,8 @@ export default function FacilityPrecipitation() { // 시설상태 const [operationValue, setOperationValue] = useState("0"); // 조회기간 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + //const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState("2023"); /** * 페이지 함수 @@ -125,9 +126,9 @@ export default function FacilityPrecipitation() { useEffect(() => { if (!isInit) { initializeCommonData(); - searchFacilityPrecipitationList(1, 10, selectSearchYear[0]?.val || "0"); + searchFacilityPrecipitationList(1, 10, searchYear || "0"); } - }, [isInit, initializeCommonData, searchFacilityPrecipitationList, selectSearchYear]); + }, [isInit, initializeCommonData, searchFacilityPrecipitationList, searchYear]); useEffect(() => { if (excelList && excelList.length > 0) { diff --git a/apps/fo/app/pages/facilityWaterQlty/modal/page.tsx b/apps/fo/app/pages/facilityWaterQlty/modal/page.tsx index 19129762e..eadfa9ba8 100644 --- a/apps/fo/app/pages/facilityWaterQlty/modal/page.tsx +++ b/apps/fo/app/pages/facilityWaterQlty/modal/page.tsx @@ -63,7 +63,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe let maxEfflWqltBod = 0; let maxEfflWqltTn = 0; let maxEfflWqltTp = 0; - let maxefflWqltCod = 0; + let maxefflWqltToc = 0; let maxEfflWqltSs = 0; let maxInflAssctAftWqltBod = 0; @@ -80,7 +80,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe let minEfflWqltBod = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltBod) : 0; let minEfflWqltTn = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltTn) : 0; let minEfflWqltTp = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltTp) : 0; - let minefflWqltCod = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltCod) : 0; + let minefflWqltToc = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltToc) : 0; let minEfflWqltSs = filteredData.length > 0 ? parseNumber(filteredData[0]?.efflWqltSs) : 0; let minInflAssctAftWqltBod = filteredData.length > 0 ? parseNumber(filteredData[0]?.inflAssctAftWqltBod) : 0; @@ -97,7 +97,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe let avgEfflWqltBod = 0; let avgEfflWqltTn = 0; let avgEfflWqltTp = 0; - let avgefflWqltCod = 0; + let avgefflWqltToc = 0; let avgEfflWqltSs = 0; let avgInflAssctAftWqltBod = 0; @@ -115,7 +115,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe const efflWqltBod = parseNumber(item.efflWqltBod); const efflWqltTn = parseNumber(item.efflWqltTn); const efflWqltTp = parseNumber(item.efflWqltTp); - const efflWqltCod = parseNumber(item.efflWqltCod); + const efflWqltToc = parseNumber(item.efflWqltToc); const efflWqltSs = parseNumber(item.efflWqltSs); const inflAssctAftWqltBod = parseNumber(item.inflAssctAftWqltBod); @@ -133,7 +133,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe maxEfflWqltBod = Math.max(maxEfflWqltBod, efflWqltBod); maxEfflWqltTn = Math.max(maxEfflWqltTn, efflWqltTn); maxEfflWqltTp = Math.max(maxEfflWqltTp, efflWqltTp); - maxefflWqltCod = Math.max(maxefflWqltCod, efflWqltCod); + maxefflWqltToc = Math.max(maxefflWqltToc, efflWqltToc); maxEfflWqltSs = Math.max(maxEfflWqltSs, efflWqltSs); maxInflAssctAftWqltBod = Math.max(maxInflAssctAftWqltBod, inflAssctAftWqltBod); @@ -150,7 +150,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe minEfflWqltBod = Math.min(minEfflWqltBod, efflWqltBod); minEfflWqltTn = Math.min(minEfflWqltTn, efflWqltTn); minEfflWqltTp = Math.min(minEfflWqltTp, efflWqltTp); - minefflWqltCod = Math.min(minefflWqltCod, efflWqltCod); + minefflWqltToc = Math.min(minefflWqltToc, efflWqltToc); minEfflWqltSs = Math.min(minEfflWqltSs, efflWqltSs); minInflAssctAftWqltBod = Math.min(minInflAssctAftWqltBod, inflAssctAftWqltBod); @@ -167,7 +167,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe avgEfflWqltBod += efflWqltBod; avgEfflWqltTn += efflWqltTn; avgEfflWqltTp += efflWqltTp; - avgefflWqltCod += efflWqltCod; + avgefflWqltToc += efflWqltToc; avgEfflWqltSs += efflWqltSs; avgInflAssctAftWqltBod += inflAssctAftWqltBod; @@ -186,7 +186,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe { msrDt: '평균', efflWqltBod: filteredData.length==0?'0.0':(Math.round(avgEfflWqltBod / filteredData.length * 10) / 10).toFixed(1), - efflWqltCod: filteredData.length==0?'0.0':(Math.round(avgefflWqltCod / filteredData.length * 10) / 10).toFixed(1), + efflWqltToc: filteredData.length==0?'0.0':(Math.round(avgefflWqltToc / filteredData.length * 10) / 10).toFixed(1), efflWqltSs: filteredData.length==0?'0.0':(Math.round(avgEfflWqltSs / filteredData.length * 10) / 10).toFixed(1), efflWqltTn: filteredData.length==0?'0.000':(Math.round(avgEfflWqltTn / filteredData.length * 1000) / 1000).toFixed(3), efflWqltTp: filteredData.length==0?'0.000':(Math.round(avgEfflWqltTp / filteredData.length * 1000) / 1000).toFixed(3), @@ -209,7 +209,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe { msrDt : '최대', efflWqltBod : Math.round(maxEfflWqltBod * 10) / 10?(Math.round(maxEfflWqltBod * 10) / 10).toFixed(1):'0.0', - efflWqltCod : Math.round(maxefflWqltCod * 10) / 10?(Math.round(maxefflWqltCod * 10) / 10).toFixed(1):'0.0', + efflWqltToc : Math.round(maxefflWqltToc * 10) / 10?(Math.round(maxefflWqltToc * 10) / 10).toFixed(1):'0.0', efflWqltSs : Math.round(maxEfflWqltSs * 10) / 10?(Math.round(maxEfflWqltSs * 10) / 10).toFixed(1):'0.0', efflWqltTn : Math.round(maxEfflWqltTn * 1000) / 1000?(Math.round(maxEfflWqltTn * 1000) / 1000).toFixed(3):'0.000', efflWqltTp : Math.round(maxEfflWqltTp * 1000) / 1000?(Math.round(maxEfflWqltTp * 1000) / 1000).toFixed(3):'0.000', @@ -232,7 +232,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe { msrDt : '최소', efflWqltBod : Math.round(minEfflWqltBod * 10) / 10?(Math.round(minEfflWqltBod?minEfflWqltBod:0 * 10) / 10).toFixed(1) : '0.0', - efflWqltCod : Math.round(minefflWqltCod * 10) / 10?(Math.round(minefflWqltCod?minefflWqltCod:0 * 10) / 10).toFixed(1) : '0.0', + efflWqltToc : Math.round(minefflWqltToc * 10) / 10?(Math.round(minefflWqltToc?minefflWqltToc:0 * 10) / 10).toFixed(1) : '0.0', efflWqltSs : Math.round(minEfflWqltSs * 10) / 10?(Math.round(minEfflWqltSs * 10) / 10).toFixed(1) : '0.0', efflWqltTn : Math.round(minEfflWqltTn * 1000) / 1000?(Math.round(minEfflWqltTn * 1000) / 1000).toFixed(3) : '0.000', efflWqltTp : Math.round(minEfflWqltTp * 1000) / 1000?(Math.round(minEfflWqltTp * 1000) / 1000).toFixed(3) : '0.000', @@ -299,16 +299,16 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe "유입TN(mg/L)": item.inflAssctAftWqltTn, "유입TP(mg/L)": item.inflAssctBfrWqltTp, "유출BOD(mg/L)": item.efflWqltBod, - "유출TOC(COD)(mg/L)": item.efflWqltCod, + "유출TOC(COD)(mg/L)": item.efflWqltToc, "유출SS(mg/L)": item.efflWqltSs, "유출TN(mg/L)": item.efflWqltTn, "유출TP(mg/L)": item.efflWqltTp, - "강우량(일)": item.infoRnDay?item.infoRnDay.toString():"0.0", +/* "강우량(mm/일)": item.infoRnDay?item.infoRnDay.toString():"0.0", */ }); }); } if (excelDownList && excelDownList.length > 0) { - downloadExcel({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: searchYear+'년 '+fcltyName + ' 수질현황', sheetNameArray: [fcltyName + ' 수질현황'], headerRange: 'A1:L1' }); + downloadExcel({ data: JSON.parse(JSON.stringify(excelDownList)), fileName: searchYear+'년 '+fcltyName + ' 수질현황', sheetNameArray: [fcltyName + ' 수질현황'], headerRange: 'A1:K1' }); } else if (prevExcelListRef.current && prevExcelListRef.current.length > 0) { alert('다운받을 데이터가 없습니다.'); } @@ -351,7 +351,7 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe

수질 TMS

-

({msrmtDate?msrmtDate.substring(0,4)+'-'+msrmtDate.substring(4,6)+'-'+msrmtDate.substring(6,8):yesterDayStr})

+ {/*

({msrmtDate?msrmtDate.substring(0,4)+'-'+msrmtDate.substring(4,6)+'-'+msrmtDate.substring(6,8):yesterDayStr})

*/}
+
+ ☞ 청천시 : 강우량이 3mm미만의 날 +
@@ -476,6 +479,9 @@ export default function WaterQualityModal({fcltyName='', facilityCd='', searchYe
+
+ ☞ 강우량 3mm 이상만 날과 강우영향일(강우일+2일)의 날 +
diff --git a/apps/fo/app/pages/facilityWaterQlty/page.tsx b/apps/fo/app/pages/facilityWaterQlty/page.tsx index 2c728f679..7d571a69f 100644 --- a/apps/fo/app/pages/facilityWaterQlty/page.tsx +++ b/apps/fo/app/pages/facilityWaterQlty/page.tsx @@ -48,7 +48,8 @@ export default function ProcessFacilitySearch() { const [upheadList, setUpheadList] = useState(upHeadList1); const [fcltyName, setFcltyName] = useState(""); const [operationValue, setOperationValue] = useState("0"); - const [searchYearValue, setSearchYearValue] = useState(selectSearchYear[0]?.val || "0"); + //const [searchYearValue, setSearchYearValue] = useState(selectSearchYear[0]?.val || "0"); + const [searchYearValue, setSearchYearValue] = useState("2023"); const [fcltCpct, setFcltCpct] = useState("0"); // 모달 상태 추가 @@ -196,7 +197,7 @@ export default function ProcessFacilitySearch() { {selectedRow !== null && ( } - bodyClass="sm:max-w-[90vw] w-[1200px] h-[920px]" + bodyClass="sm:max-w-[90vw] w-[1300px] h-[920px]" open={selectedRow !== null} onOpenChange={(open) => !open && closeModal()} noHeaderYn="Y" diff --git a/apps/fo/app/pages/flowRate/modal/page.tsx b/apps/fo/app/pages/flowRate/modal/page.tsx index c3815911a..409dab7eb 100644 --- a/apps/fo/app/pages/flowRate/modal/page.tsx +++ b/apps/fo/app/pages/flowRate/modal/page.tsx @@ -52,6 +52,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st return Number(value.replace(/,/g, '')); }; + console.log('filteredData : ', filteredData); let maxtotInflAmnt = 0; let maxinflAmnt = 0; let maxsumFlow = 0; @@ -65,7 +66,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st let minsumFlow = parseNumber(filteredData[0]?.sumFlow); let minreturnAmnt = parseNumber(filteredData[0]?.returnAmnt); let minefflAmnt = parseNumber(filteredData[0]?.efflAmnt); - let mininfoRnDay = parseNumber(filteredData[0]?.infoRnDay); + let mininfoRnDay = parseNumber(filteredData[0]?.infoRnDay && filteredData[0]?.infoRnDay.toString().trim() !== 'x' ? filteredData[0]?.infoRnDay.toString().trim() : 0); let avgtotInflAmnt = 0; let avginflAmnt = 0; @@ -80,7 +81,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st const sumFlow = parseNumber(item.sumFlow); const returnAmnt = parseNumber(item.returnAmnt); const efflAmnt = parseNumber(item.efflAmnt); - const infoRnDay = parseNumber(item.infoRnDay); + const infoRnDay = parseNumber(item.infoRnDay && item.infoRnDay.toString().trim() !== 'x' ? item.infoRnDay.toString().trim() : 0); maxtotInflAmnt = Math.max(maxtotInflAmnt, totInflAmnt); maxinflAmnt = Math.max(maxinflAmnt, inflAmnt); maxsumFlow = Math.max(maxsumFlow, sumFlow); @@ -166,7 +167,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st "연계처리량(m³/일)": item.sumFlow, "총인반류수량(m³/일)": item.returnAmnt, "방류량(m³/일)": item.efflAmnt, - "강우량": item.infoRnDay, + /* "강우량(mm/일)": item.infoRnDay, */ }); }); } @@ -194,20 +195,18 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st ref={ref} queryClick={filterData} /> -
-
- {}} - className="h-[370px]" - boldArray={[0,1,2]} - /> -
+
+ {}} + className="h-[370px]" + boldArray={[0,1,2]} + />
@@ -220,20 +219,18 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st ref={ref} queryClick={filterData} /> -
-
- {}} - className="h-[370px]" - boldArray={[0,1,2]} - /> -
+
+ {}} + className="h-[370px]" + boldArray={[0,1,2]} + />
@@ -246,20 +243,18 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st ref={ref} queryClick={filterData} /> -
-
- {}} - className="h-[370px]" - boldArray={[0,1,2]} - /> -
+
+ {}} + className="h-[370px]" + boldArray={[0,1,2]} + />
@@ -272,20 +267,21 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st ref={ref} queryClick={filterData} /> -
-
- {}} - className="h-[370px]" - boldArray={[0,1,2]} - /> -
+
+ {}} + className="h-[370px]" + boldArray={[0,1,2]} + /> +
+
+ ☞ 청천시 : 강우량이 3mm미만의 날
@@ -298,10 +294,11 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st ref={ref} queryClick={filterData} /> -
-
+
{}} className="h-[370px]" boldArray={[0,1,2]}/> -
+
+
+ ☞ 강우량 3mm 이상만 날과 강우영향일(강우일+2일)의 날
diff --git a/apps/fo/app/pages/flowRate/page.tsx b/apps/fo/app/pages/flowRate/page.tsx index 3910944cf..0c4f503e9 100644 --- a/apps/fo/app/pages/flowRate/page.tsx +++ b/apps/fo/app/pages/flowRate/page.tsx @@ -49,7 +49,8 @@ export default function ProcessFacilitySearch() { const [fcltCpct, setFcltCpct] = useState("0"); const [fcltyName, setFcltyName] = useState(""); const [operationValue, setOperationValue] = useState("0"); - const [searchYearValue, setSearchYearValue] = useState(selectSearchYear[0]?.val || "0"); + //const [searchYearValue, setSearchYearValue] = useState(selectSearchYear[0]?.val || "0"); + const [searchYearValue, setSearchYearValue] = useState("2023"); const [widthSize, setWidthSize] = useState("w-[120%]"); const [upheadList, setUpheadList] = useState(upHeadList1); const [resetPage, setResetPage] = useState(false); @@ -125,10 +126,11 @@ export default function ProcessFacilitySearch() { // 시도 데이터 초기화 useEffect(() => { if(!isInit) { + console.log('searchYearValue', searchYearValue, isInit); initializeCommonData(); - searchFlowRateList(1, 10, selectSearchYear[0]?.val || "0"); + searchFlowRateList(1, 10, searchYearValue || "0"); } - }, [isInit, initializeCommonData, searchFlowRateList, selectSearchYear]); + }, [isInit, initializeCommonData, searchFlowRateList, searchYearValue]); useEffect(() => { if (excelList && excelList.length > 0) { @@ -197,7 +199,7 @@ export default function ProcessFacilitySearch() { {selectedRow !== null && ( } - bodyClass="sm:max-w-[90vw] w-[1050px] h-[700px]" + bodyClass="sm:max-w-[90vw] w-[1250px] h-[780px]" open={selectedRow !== null} onOpenChange={(open: boolean) => !open && closeModal()} /> diff --git a/apps/fo/app/pages/planSewage/page.tsx b/apps/fo/app/pages/planSewage/page.tsx index 8d7de5d80..c9324c246 100644 --- a/apps/fo/app/pages/planSewage/page.tsx +++ b/apps/fo/app/pages/planSewage/page.tsx @@ -70,7 +70,8 @@ export default function PlanSewage() { // 시설상태 const [operationValue, setOperationValue] = useState("0"); // 조회기간 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + //const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState("2023"); /** * 페이지 함수 @@ -190,6 +191,11 @@ export default function PlanSewage() {
+
+ ☞ 여유용량 = 하수처리시설 시설용량 – 건기시 유입유량(평균)​
+ ☞ 변동부하율 = 건기시 일최대 유량/일평균​유량(기준 최근 3개년)​
+ ☞ 분류식화율 = 분류식지역 면적(㎢) / 처리​구역 면적합계(㎢) ×100
+
diff --git a/apps/fo/app/pages/sewageOccur/modal/page.tsx b/apps/fo/app/pages/sewageOccur/modal/page.tsx index e8a90651e..23c890160 100644 --- a/apps/fo/app/pages/sewageOccur/modal/page.tsx +++ b/apps/fo/app/pages/sewageOccur/modal/page.tsx @@ -1,103 +1,34 @@ "use client" import { useFoStore } from "@/app/store"; -import type { SewageOccurDetailProps } from "@/app/store/sewageOccur"; +import type { modalTableType } from "@/app/store/sewageOccur"; import { DateMonthPicker, UiTable } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Table, TableCell, TableHead, TableRow } from "@common/components/ui"; import { useEffect, useState } from "react"; + export default function SewageOccurModal(props: { fcltyName: string; facilityCd : string; searchYear : string}) { // 그리드 관련 - const {SewageOccurDetail} = useFoStore((state) => state); + const {SewageOccurDetail, searchSewageOccurDetailList} = useFoStore((state) => state); // searchYear 유효성 검사 및 기본값 설정 const validSearchYear = props.searchYear && /^\d{4}$/.test(props.searchYear) ? props.searchYear : new Date().getFullYear().toString(); const [startYearMonth, setStartYearMonth] = useState(validSearchYear+'01'); const [endYearMonth, setEndYearMonth] = useState(validSearchYear+'12'); - const [detailList, setDetailList] = useState([]); + const [detailList, setDetailList] = useState([]); const { upHeadListSewageOccurDetail, gridListSewageOccurDetail } = SewageOccurDetail; useEffect(() => { - setDetailList(gridListSewageOccurDetail); - }, [gridListSewageOccurDetail]); + searchSewageOccurDetailList(props.facilityCd, validSearchYear); + }, [searchSewageOccurDetailList, props.facilityCd, validSearchYear]); useEffect(() => { - const filteredList = gridListSewageOccurDetail.filter(item => item.msrDt.replace(/-/g, '') >= startYearMonth && item.msrDt.replace(/-/g, '') <= endYearMonth); - let maxWaterUsage = 0; - let maxSwageChangePercent = 0; - let maxEstimationSewage = 0; - - let minWaterUsage = filteredList[0]?.waterUsage || 0; - let minSwageChangePercent = filteredList[0]?.swageChangePercent || 0; - let minEstimationSewage = filteredList[0]?.estimationSewage || 0; - - let totalWaterUsage = 0; - let totalSwageChangePercent = 0; - let totalEstimationSewage = 0; - - let avgWaterUsage = 0; - let avgSwageChangePercent = 0; - let avgEstimationSewage = 0; - - filteredList.forEach(item => { - if (item.waterUsage > maxWaterUsage) { - maxWaterUsage = item.waterUsage; - } - if (item.swageChangePercent > maxSwageChangePercent) { - maxSwageChangePercent = item.swageChangePercent; - } - if (item.estimationSewage > maxEstimationSewage) { - maxEstimationSewage = item.estimationSewage; - } - - if (item.waterUsage < minWaterUsage) { - minWaterUsage = item.waterUsage; - } - if (item.swageChangePercent < minSwageChangePercent) { - minSwageChangePercent = item.swageChangePercent; - } - if (item.estimationSewage < minEstimationSewage) { - minEstimationSewage = item.estimationSewage; - } - - totalWaterUsage += item.waterUsage; - totalSwageChangePercent += item.swageChangePercent; - totalEstimationSewage += item.estimationSewage; - }); - - avgWaterUsage = totalWaterUsage?totalWaterUsage / filteredList.length:0; - avgSwageChangePercent = totalSwageChangePercent?totalSwageChangePercent / filteredList.length:0; - avgEstimationSewage = totalEstimationSewage?totalEstimationSewage / filteredList.length:0; - - const newData = [ - { - msrDt: '평균', - waterUsage: Math.round(avgWaterUsage * 100) / 100, - swageChangePercent: Math.round(avgSwageChangePercent * 100) / 100, - estimationSewage: Math.round(avgEstimationSewage * 100) / 100 - }, - { - msrDt: '최대', - waterUsage: maxWaterUsage, - swageChangePercent: maxSwageChangePercent, - estimationSewage: maxEstimationSewage - }, - { - msrDt: '최소', - waterUsage: minWaterUsage, - swageChangePercent: minSwageChangePercent, - estimationSewage: minEstimationSewage - }, - ...filteredList - ] - setDetailList(newData); - - - }, [startYearMonth, endYearMonth, gridListSewageOccurDetail]); + setDetailList(gridListSewageOccurDetail); + }, [gridListSewageOccurDetail]); return ( <> diff --git a/apps/fo/app/pages/sewageOccur/page.tsx b/apps/fo/app/pages/sewageOccur/page.tsx index a137d9ebe..8b004e681 100644 --- a/apps/fo/app/pages/sewageOccur/page.tsx +++ b/apps/fo/app/pages/sewageOccur/page.tsx @@ -18,7 +18,8 @@ export default function PlanSewage() { SewageOccur: { sewageOccurList: [], sewageOccurCount: 0, excelList: [], upHeadList1: [], upHeadList2: [], upHeadList3: [] }, SewageOccurSearch: { labelArray: [] }, initializeCommonData: () => {}, - searchSewageOccurList: () => {} + searchSewageOccurList: () => {}, + calculateModalDataForAllFacilities: async () => [] }; const { @@ -83,7 +84,8 @@ export default function PlanSewage() { // 시설상태 const [operationValue, setOperationValue] = useState("0"); // 조회기간 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + // const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState("2023"); /** * 페이지 함수 @@ -126,9 +128,9 @@ export default function PlanSewage() { useEffect(() => { if (!isInit) { initializeCommonData(); - searchSewageOccurList(1, 10, selectSearchYear[0]?.val || "0"); + searchSewageOccurList(1, 10, searchYear || "0"); } - }, [isInit, initializeCommonData, searchSewageOccurList, selectSearchYear]); + }, [isInit, initializeCommonData, searchSewageOccurList, searchYear]); useEffect(() => { if (excelList && excelList.length > 0) { @@ -224,7 +226,11 @@ export default function PlanSewage() { {selectedRow !== null && ( } + body={} bodyClass="sm:max-w-[90vw] w-[1000px] h-[770px]" open={selectedRow !== null} onOpenChange={(open) => !open && closeModal()} diff --git a/apps/fo/app/pages/underWaterInput/modal/page.tsx b/apps/fo/app/pages/underWaterInput/modal/page.tsx index 9e49fb1bc..145612431 100644 --- a/apps/fo/app/pages/underWaterInput/modal/page.tsx +++ b/apps/fo/app/pages/underWaterInput/modal/page.tsx @@ -1,6 +1,6 @@ "use client" import { useFoStore } from "@/app/store"; -import type { UnderWaterInputModalProps } from "@/app/store/underWaterInput"; +import type { UnderWaterInputModalProps } from "@/app/store/underWaterInput/types"; import { DateMonthPicker, UITab, UiTable } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Table, TableCell, TableHead, TableRow, TabsContent } from "@common/components/ui"; @@ -19,7 +19,7 @@ const tabList1 = [ export default function UnderWaterInputModal({fcltyName='', searchYear=new Date().getFullYear().toString()} : ModalProps) { // 그리드 관련 - const {UnderWaterDetail} = useFoStore((state) => state); + const {UnderWaterDetail, unserWaterModal} = useFoStore((state) => state); const [startYmDate, setStartYmDate] = useState(searchYear+'01'); const [endYmDate, setEndYmDate] = useState(searchYear+'12'); @@ -115,7 +115,10 @@ export default function UnderWaterInputModal({fcltyName='', searchYear=new Date( setPrevEndYmDate(searchYear+'12'); } }, [searchYear, tabTerm]); - + useEffect(()=>{ + console.log(fcltyName, searchYear); + unserWaterModal(fcltyName, searchYear); + }, []) useEffect(() => { if(endYmDate < startYmDate) { alert('종료일이 시작일보다 작습니다.') diff --git a/apps/fo/app/pages/underWaterInput/page.tsx b/apps/fo/app/pages/underWaterInput/page.tsx index 1e821734b..d57c894dd 100644 --- a/apps/fo/app/pages/underWaterInput/page.tsx +++ b/apps/fo/app/pages/underWaterInput/page.tsx @@ -5,7 +5,7 @@ import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, Selec import ListDiv from "@common/business_components/ui/list_div"; import { useCallback, useEffect, useRef, useState } from "react"; import UnderWaterInputModal from "./modal/page"; -import type { UnderWaterProps } from "@/app/store/underWaterInput"; +import type { UnderWaterProps } from "@/app/store/underWaterInput/types"; export default function UnderWaterInput() { const {Common @@ -82,7 +82,8 @@ export default function UnderWaterInput() { // 시설상태 const [operationValue, setOperationValue] = useState("0"); // 조회기간 - const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + //const [searchYear, setSearchYear] = useState(selectSearchYear[0]?.val || "0"); + const [searchYear, setSearchYear] = useState("2023"); /** * 페이지 함수 @@ -114,7 +115,7 @@ export default function UnderWaterInput() { // 셀 클릭 시 모달 표시 const cellClick = (index: number) => { if (underWater[index]) { - setFcltyNm(underWater[index].bscFctNm); + setFcltyNm(underWater[index].bscFctCd); setSelectedRow(index); // 클릭한 행의 index 저장 } }; diff --git a/apps/fo/app/store/common/index.tsx b/apps/fo/app/store/common/index.tsx index 5c07bcd6e..3e15e2162 100644 --- a/apps/fo/app/store/common/index.tsx +++ b/apps/fo/app/store/common/index.tsx @@ -3,8 +3,6 @@ ====================================**/ import { getFetch } from "@/app/components/api"; -import { LoginApi } from "@/src/entities/auth"; -import type { ILogin } from "@/src/entities/auth/model/login-interface"; /** * STORE - TYPE @@ -413,15 +411,6 @@ export const CommonReducer:(set:(fn: (state: CommonType) => CommonType) => void) // 시도 변경 이벤트 initializeCommonData: async () => { - const payLoad : ILogin = { - userId: "tuser10003", - password: "test1234!" - }; - - console.log(payLoad.userId) - console.log(payLoad.password) - - LoginApi.login(payLoad); const promiseSet = await Promise.all([fetchSidoData(), fetchSigunData(), fetchRegionData(), fetchUnitWaterShedData(), fetchMiddleRegionData(), fetchEnvPartData()]); const [sidoData, sggData, regionData, unitData, midData, envData] = promiseSet; set((state: CommonType) => ({ diff --git a/apps/fo/app/store/dashboard/index.tsx b/apps/fo/app/store/dashboard/index.tsx index 09c7acec9..c0720b9fa 100644 --- a/apps/fo/app/store/dashboard/index.tsx +++ b/apps/fo/app/store/dashboard/index.tsx @@ -347,6 +347,7 @@ const upHeadListOperationStatus : TableUpperProps[] = [ { id: "groundWaterSummer", title: "하절기\n평균", upName:"upChangeRe5", upSequnce:2 }, { id: "groundWaterWinter", title: "동절기\n평균", upName:"upChangeRe5", upSequnce:3 }, { id: "rdi", title: "RDII 추정" }, + { id: "rdiRain", title: "RDII 추정시\n일강수량(mm)" }, ] const upHeadListRainManage : TableUpperProps[] = [ @@ -688,12 +689,12 @@ const drainageEquipmentList : DrainageEquipmentListProps[] = [ /* 시설정보 및 운영현황 - 계획 */ const fetchFacilityPlan = async (targetYear : string | undefined, fcltyCd : string | undefined) => { try { - targetYear = targetYear ? targetYear : "2024"; + targetYear = targetYear ? targetYear : "2023"; const baseUrl = 'https://apigateway.rsonesoft.com/dc-service/api/v1/nowDmgMtncGnsvy'; const queryParams = new URLSearchParams(); if (fcltyCd) queryParams.append('swtrmtFcltCd', fcltyCd); - if (targetYear) queryParams.append('targetYear',targetYear); + //if (targetYear) queryParams.append('targetYear',targetYear); const queryString = queryParams.toString(); const url = queryString ? `${baseUrl}?${queryString}` : baseUrl; const res = await getFetchAreaList(url); @@ -707,7 +708,7 @@ const fetchFacilityPlan = async (targetYear : string | undefined, fcltyCd : stri /* 시설정보 및 운영현황 - 운영실태 */ const fetchOperationStatus = async (searchYear: string|undefined, fcltyCd: string|undefined) => { try{ - const url = '/dashBoard/mngInfo/' + (searchYear?searchYear:'2024') + '/' + (fcltyCd?fcltyCd:''); + const url = '/dashBoard/mngInfo/' + (searchYear?searchYear:'2023') + '/' + (fcltyCd?fcltyCd:''); const res = await getFetch('fo', url); return { data: res.data, count: res.count } } catch (error) { @@ -719,7 +720,7 @@ const fetchOperationStatus = async (searchYear: string|undefined, fcltyCd: strin // 주요그래프 데이터 const fetchMainGraph = async (searchYear: string|undefined, fcltyCd: string|undefined) => { try { - const url = '/fclt/qlty/hist/' + (searchYear?searchYear:'2024') + '/' + (fcltyCd?fcltyCd:'11680PB001R'); + const url = '/fclt/qlty/hist/' + (searchYear?searchYear:'2023') + '/' + (fcltyCd?fcltyCd:'11680PB001R'); const res = await getFetch('fo', url); return { data: res.data, count: res.count } } catch (error) { @@ -954,41 +955,51 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) /** 시설정보 및 운영현황 - 계획정보 */ searchPlanInfo : async (targetYear: string, fcltyCd: string) => { const res = await fetchFacilityPlan(targetYear, fcltyCd); + let minDiff=9999999; + let minIndex; + res.forEach((el:any, idx) => { + if(minDiff>Math.abs(Number(targetYear) - Number(el.yr))){ + minDiff=Math.abs(Number(targetYear) - Number(el.yr)); + minIndex=idx; + } + }); + + const rtnPlanData = res[minIndex]; planFcltyList.map((el, index) => { planYearList = []; - planYearList.push(res[0]?res[0].yrDt4.toString():""); - planYearList.push(res[0]?res[0].yrDc1.toString():""); - planYearList.push(res[0]?res[0].yrDc2.toString():""); - planYearList.push(res[0]?res[0].yrDc3.toString():""); - planYearList.push(res[0]?res[0].yrDc4.toString():""); + planYearList.push(rtnPlanData?rtnPlanData.yrDt4.toString():""); + planYearList.push(rtnPlanData?rtnPlanData.yrDc1.toString():""); + planYearList.push(rtnPlanData?rtnPlanData.yrDc2.toString():""); + planYearList.push(rtnPlanData?rtnPlanData.yrDc3.toString():""); + planYearList.push(rtnPlanData?rtnPlanData.yrDc4.toString():""); if(index === 0){ - el.provisionalApproval = res&&res.length>0?res[0].planSwageQntSumDt4:''; - el.now = res&&res.length>0?res[0].planSwageQntSum:''; - el.stepOne = res&&res.length>0?res[0].planSwageQntSumDc1:''; - el.stepTwo = res&&res.length>0?res[0].planSwageQntSumDc2:''; - el.stepThree = res&&res.length>0?res[0].planSwageQntSumDc3:''; - el.stepFour = res&&res.length>0?res[0].planSwageQntSumDc4:''; + el.provisionalApproval = rtnPlanData?rtnPlanData.planSwageQntSumDt4:''; + el.now = rtnPlanData?rtnPlanData.planSwageQntSum:''; + el.stepOne = rtnPlanData?rtnPlanData.planSwageQntSumDc1:''; + el.stepTwo = rtnPlanData?rtnPlanData.planSwageQntSumDc2:''; + el.stepThree = rtnPlanData?rtnPlanData.planSwageQntSumDc3:''; + el.stepFour = rtnPlanData?rtnPlanData.planSwageQntSumDc4:''; } if(index === 1){ - el.provisionalApproval = res&&res.length>0?res[0].swtrmtPpltnCntDt4:''; - el.now = res&&res.length>0?res[0].swtrmtPpltnCnt:''; - el.stepOne = res&&res.length>0?res[0].swtrmtPpltnCntDc1:''; - el.stepTwo = res&&res.length>0?res[0].swtrmtPpltnCntDc2:''; - el.stepThree = res&&res.length>0?res[0].swtrmtPpltnCntDc3:''; - el.stepFour = res&&res.length>0?res[0].swtrmtPpltnCntDc4:''; + el.provisionalApproval = rtnPlanData?rtnPlanData.swtrmtPpltnCntDt4:''; + el.now = rtnPlanData?rtnPlanData.swtrmtPpltnCnt:''; + el.stepOne = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc1:''; + el.stepTwo = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc2:''; + el.stepThree = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc3:''; + el.stepFour = rtnPlanData?rtnPlanData.swtrmtPpltnCntDc4:''; } if(index === 2){ - el.provisionalApproval = res&&res.length>0?res[0].swtrmtZoneAreaDt4:''; - el.now = res&&res.length>0?res[0].swtrmtZoneArea:''; - el.stepOne = res&&res.length>0?res[0].swtrmtZoneAreaDc1:''; - el.stepTwo = res&&res.length>0?res[0].swtrmtZoneAreaDc2:''; - el.stepThree = res&&res.length>0?res[0].swtrmtZoneAreaDc3:''; - el.stepFour = res&&res.length>0?res[0].swtrmtZoneAreaDc4:''; + el.provisionalApproval = rtnPlanData?rtnPlanData.swtrmtZoneAreaDt4:''; + el.now = rtnPlanData?rtnPlanData.swtrmtZoneArea:''; + el.stepOne = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc1:''; + el.stepTwo = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc2:''; + el.stepThree = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc3:''; + el.stepFour = rtnPlanData?rtnPlanData.swtrmtZoneAreaDc4:''; } if(index === 4){ - el.provisionalApproval = res&&res.length>0?res[0].ugwtUseSum:''; + el.provisionalApproval = rtnPlanData?rtnPlanData.ugwtUseSum:''; } // 임시시 @@ -1040,9 +1051,10 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) operationalStatusList[0].winterSunnyAvg = data.winterSunny === null ? '' : (data.winterSunny.AVG_INFL_AMNT ? data.winterSunny.AVG_INFL_AMNT : 0) + (data.winterSunny.AVG_SUM_FLOW ? data.winterSunny.AVG_SUM_FLOW : 0) + (data.winterSunny.AVG_TP_INFL_WQLT_TP ? data.winterSunny.AVG_TP_INFL_WQLT_TP : 0); operationalStatusList[0].winterSunnyMax = data.winterSunny === null ? '' : (data.winterSunny.MAX_INFL_AMNT ? data.winterSunny.MAX_INFL_AMNT : 0) + (data.winterSunny.MAX_SUM_FLOW ? data.winterSunny.MAX_SUM_FLOW : 0) + (data.winterSunny.MAX_TP_INFL_WQLT_TP ? data.winterSunny.MAX_TP_INFL_WQLT_TP : 0); // 지하수 - operationalStatusList[0].groundWaterAvg = 10; - operationalStatusList[0].groundWaterSummer = 11; - operationalStatusList[0].groundWaterWinter = 12; + operationalStatusList[0].groundWaterAvg = data.yearData.AVG_INFL_AMNT === null ? '' : Number(data.yearData.AVG_INFL_AMNT) - (Number(data.yearData.AVG_INFL_AMNT)*0.65*0.8); + operationalStatusList[0].groundWaterSummer = data.summer.AVG_INFL_AMNT === null ? '' : Number(data.summer.AVG_INFL_AMNT) - (Number(data.summer.AVG_INFL_AMNT)*0.65*0.8); + operationalStatusList[0].groundWaterWinter = data.winter.AVG_INFL_AMNT === null ? '' : Number(data.winter.AVG_INFL_AMNT) - (Number(data.winter.AVG_INFL_AMNT)*0.65*0.8); + // 오염물질 // RDII 추정 = 하절기(6~8월) 중 최저 유입 BOD일때 유입유량 x ((동절기 청천시 유입BOD - 하절기(6~8월) 중 최저 유입BOD) / (동절기 청천시 유입BOD)) const summerLowFlow = data.summerBod&&data.summerBod.INFL_AMNT&&data.summerBod.INFL_AMNT!='null'?Number(data.summerBod.INFL_AMNT):0; @@ -1053,7 +1065,20 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) operationalStatusList[0].rdiRain = summerLowRain; dataFormSetting(operationalStatusList[0]); } - + + if(operationalStatusList[0] && operationalStatusList[7]){ + console.log(Number(operationalStatusList[0].groundWaterAvg)+' '+Number(data.yearData.AVG_INFL_AMNT)); + console.log(Number(operationalStatusList[0].groundWaterSummer)+' '+Number(data.summer.AVG_INFL_AMNT)); + console.log(Number(operationalStatusList[0].groundWaterWinter)+' '+Number(data.winter.AVG_INFL_AMNT)); + operationalStatusList[7].groundWaterAvg = Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT) * 100; + operationalStatusList[7].groundWaterSummer = Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT) * 100; + operationalStatusList[7].groundWaterWinter = Number(operationalStatusList[0].groundWaterWinter) / Number(data.winter.AVG_INFL_AMNT) * 100; + /* operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : Number(Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT)) * 100; + operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : Number(Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT)) * 100; + operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : Number(Number(operationalStatusList[0].groundWaterWinter) / Number(data.winter.AVG_INFL_AMNT)) * 100;*/ + dataFormSetting(operationalStatusList[0]); + } + for(let i=1; i DashBoardType) } const fcltyCpcValue = res.data.wqltObjs[0].fctCpc; - const itemList = ['계', '3mm 미만', '3~10mm 미만', '10~20mm 미만', '20~30mm 미만', '30~40mm 미만', '40~50mm 미만', '50mm~60mm 미만', '60mm 이상']; - const rainRate = ['0', '0', '0', '0', '0', '0', '0', '0', '0']; - const rainDate = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAmtCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAmnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const fcltyCpct = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const dsInflWqltBod: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const dsInflWqltCod: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const dsInflWqltSs: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const dsInflWqltTn: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const dsInflWqltTp: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltBodCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltBod = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltTocCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltToc = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltSsCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltSs = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltTnCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltTn = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltTpCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const inflAssctAftWqltTp = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltBodCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltBod = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltCodCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltCod = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltSsCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltSs = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltTnCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltTn = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltTpCnt = [0, 0, 0, 0, 0, 0, 0, 0, 0]; - const efflWqltTp = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + const itemList = ['계', '3~10mm 미만', '10~20mm 미만', '20~30mm 미만', '30~40mm 미만', '40~50mm 미만', '50mm~60mm 미만', '60mm 이상']; + const rainRate = ['0', '0', '0', '0', '0', '0', '0', '0']; + const rainDate = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAmtCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAmnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const fcltyCpct = [0, 0, 0, 0, 0, 0, 0, 0]; + const dsInflWqltBod: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const dsInflWqltCod: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const dsInflWqltSs: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const dsInflWqltTn: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const dsInflWqltTp: number[] = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltBodCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltBod = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltTocCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltToc = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltSsCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltSs = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltTnCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltTn = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltTpCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const inflAssctAftWqltTp = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltBodCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltBod = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltCodCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltCod = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltSsCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltSs = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltTnCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltTn = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltTpCnt = [0, 0, 0, 0, 0, 0, 0, 0]; + const efflWqltTp = [0, 0, 0, 0, 0, 0, 0, 0]; // Process the data and create rain management list res.data.wqltObjs.forEach((item: RainManageListProps) => { // 강우일 - if(Number(item.infoRnDay) < 3){ + /*if(Number(item.infoRnDay) < 3){ rainDate[1] = Number(rainDate[1] || 0) + 1; inflAmtCnt[1] = Number(inflAmtCnt[1] || 0) + 1; inflAmnt[1] = Number(inflAmnt[1] || 0) + Number(item.inflAmnt || 0); @@ -1267,7 +1292,33 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[1] = Number(efflWqltTn[1] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[1] = Number(efflWqltTpCnt[1] || 0) + 1; efflWqltTp[1] = Number(efflWqltTp[1] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 3 && Number(item.infoRnDay) < 10){ + }*/ + if(Number(item.infoRnDay) >= 3 && Number(item.infoRnDay) < 10){ + rainDate[1] = Number(rainDate[1] || 0) + 1; + inflAmtCnt[1] = Number(inflAmtCnt[1] || 0) + 1; + inflAmnt[1] = Number(inflAmnt[1] || 0) + Number(item.inflAmnt || 0); + fcltyCpct[1] = 0; + inflAssctAftWqltBodCnt[1] = Number(inflAssctAftWqltBodCnt[1] || 0) + 1; + inflAssctAftWqltBod[1] = Number(inflAssctAftWqltBod[1] || 0) + Number(item.inflAssctAftWqltBod || 0); + inflAssctAftWqltTocCnt[1] = Number(inflAssctAftWqltTocCnt[1] || 0) + 1; + inflAssctAftWqltToc[1] = Number(inflAssctAftWqltToc[1] || 0) + Number(item.inflAssctAftWqltToc || 0); + inflAssctAftWqltSsCnt[1] = Number(inflAssctAftWqltSsCnt[1] || 0) + 1; + inflAssctAftWqltSs[1] = Number(inflAssctAftWqltSs[1] || 0) + Number(item.inflAssctAftWqltSs || 0); + inflAssctAftWqltTnCnt[1] = Number(inflAssctAftWqltTnCnt[1] || 0) + 1; + inflAssctAftWqltTn[1] = Number(inflAssctAftWqltTn[1] || 0) + Number(item.inflAssctAftWqltTn || 0); + inflAssctAftWqltTpCnt[1] = Number(inflAssctAftWqltTpCnt[1] || 0) + 1; + inflAssctAftWqltTp[1] = Number(inflAssctAftWqltTp[1] || 0) + Number(item.inflAssctAftWqltTp || 0); + efflWqltBodCnt[1] = Number(efflWqltBodCnt[1] || 0) + 1; + efflWqltBod[1] = Number(efflWqltBod[1] || 0) + Number(item.efflWqltBod || 0); + efflWqltCodCnt[1] = Number(efflWqltCodCnt[1] || 0) + 1; + efflWqltCod[1] = Number(efflWqltCod[1] || 0) + Number(item.efflWqltCod || 0); + efflWqltSsCnt[1] = Number(efflWqltSsCnt[1] || 0) + 1; + efflWqltSs[1] = Number(efflWqltSs[1] || 0) + Number(item.efflWqltSs || 0); + efflWqltTnCnt[1] = Number(efflWqltTnCnt[1] || 0) + 1; + efflWqltTn[1] = Number(efflWqltTn[1] || 0) + Number(item.efflWqltTn || 0); + efflWqltTpCnt[1] = Number(efflWqltTpCnt[1] || 0) + 1; + efflWqltTp[1] = Number(efflWqltTp[1] || 0) + Number(item.efflWqltTp || 0); + }else if(Number(item.infoRnDay) >= 10 && Number(item.infoRnDay) < 20){ rainDate[2] = Number(rainDate[2] || 0) + 1; inflAmtCnt[2] = Number(inflAmtCnt[2] || 0) + 1; inflAmnt[2] = Number(inflAmnt[2] || 0) + Number(item.inflAmnt || 0); @@ -1283,7 +1334,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) inflAssctAftWqltTpCnt[2] = Number(inflAssctAftWqltTpCnt[2] || 0) + 1; inflAssctAftWqltTp[2] = Number(inflAssctAftWqltTp[2] || 0) + Number(item.inflAssctAftWqltTp || 0); efflWqltBodCnt[2] = Number(efflWqltBodCnt[2] || 0) + 1; - efflWqltBod[2] = Number(efflWqltBod[2] || 0) + Number(item.efflWqltBod || 0); + efflWqltBod[2] = Number(efflWqltBod[2] || 0) + Number(item.efflWqltBod || 0); efflWqltCodCnt[2] = Number(efflWqltCodCnt[2] || 0) + 1; efflWqltCod[2] = Number(efflWqltCod[2] || 0) + Number(item.efflWqltCod || 0); efflWqltSsCnt[2] = Number(efflWqltSsCnt[2] || 0) + 1; @@ -1292,7 +1343,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[2] = Number(efflWqltTn[2] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[2] = Number(efflWqltTpCnt[2] || 0) + 1; efflWqltTp[2] = Number(efflWqltTp[2] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 10 && Number(item.infoRnDay) < 20){ + }else if(Number(item.infoRnDay) >= 20 && Number(item.infoRnDay) < 30){ rainDate[3] = Number(rainDate[3] || 0) + 1; inflAmtCnt[3] = Number(inflAmtCnt[3] || 0) + 1; inflAmnt[3] = Number(inflAmnt[3] || 0) + Number(item.inflAmnt || 0); @@ -1317,11 +1368,11 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[3] = Number(efflWqltTn[3] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[3] = Number(efflWqltTpCnt[3] || 0) + 1; efflWqltTp[3] = Number(efflWqltTp[3] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 20 && Number(item.infoRnDay) < 30){ + }else if(Number(item.infoRnDay) >= 30 && Number(item.infoRnDay) < 40){ rainDate[4] = Number(rainDate[4] || 0) + 1; inflAmtCnt[4] = Number(inflAmtCnt[4] || 0) + 1; inflAmnt[4] = Number(inflAmnt[4] || 0) + Number(item.inflAmnt || 0); - fcltyCpct[4] = 0; + fcltyCpct[4] = item.inflAmnt&&Number(item.inflAmnt) !== 0 ? Number(Math.round(Number(item.inflAmnt) / Number(item.fctCpc) * 10) / 10) : 0; inflAssctAftWqltBodCnt[4] = Number(inflAssctAftWqltBodCnt[4] || 0) + 1; inflAssctAftWqltBod[4] = Number(inflAssctAftWqltBod[4] || 0) + Number(item.inflAssctAftWqltBod || 0); inflAssctAftWqltTocCnt[4] = Number(inflAssctAftWqltTocCnt[4] || 0) + 1; @@ -1329,7 +1380,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) inflAssctAftWqltSsCnt[4] = Number(inflAssctAftWqltSsCnt[4] || 0) + 1; inflAssctAftWqltSs[4] = Number(inflAssctAftWqltSs[4] || 0) + Number(item.inflAssctAftWqltSs || 0); inflAssctAftWqltTnCnt[4] = Number(inflAssctAftWqltTnCnt[4] || 0) + 1; - inflAssctAftWqltTn[4] = Number(inflAssctAftWqltTn[4] || 0) + Number(item.inflAssctAftWqltTn || 0); + inflAssctAftWqltTn[4] = Number(inflAssctAftWqltTn[5] || 0) + Number(item.inflAssctAftWqltTn || 0); inflAssctAftWqltTpCnt[4] = Number(inflAssctAftWqltTpCnt[4] || 0) + 1; inflAssctAftWqltTp[4] = Number(inflAssctAftWqltTp[4] || 0) + Number(item.inflAssctAftWqltTp || 0); efflWqltBodCnt[4] = Number(efflWqltBodCnt[4] || 0) + 1; @@ -1338,15 +1389,15 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltCod[4] = Number(efflWqltCod[4] || 0) + Number(item.efflWqltCod || 0); efflWqltSsCnt[4] = Number(efflWqltSsCnt[4] || 0) + 1; efflWqltSs[4] = Number(efflWqltSs[4] || 0) + Number(item.efflWqltSs || 0); - efflWqltTnCnt[4] = Number(efflWqltTnCnt[4] || 0) + 1; + efflWqltTnCnt[4] = Number(efflWqltTnCnt[5] || 0) + 1; efflWqltTn[4] = Number(efflWqltTn[4] || 0) + Number(item.efflWqltTn || 0); - efflWqltTpCnt[4] = Number(efflWqltTpCnt[4] || 0) + 1; + efflWqltTpCnt[4] = Number(efflWqltTpCnt[5] || 0) + 1; efflWqltTp[4] = Number(efflWqltTp[4] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 30 && Number(item.infoRnDay) < 40){ + }else if(Number(item.infoRnDay) >= 40 && Number(item.infoRnDay) < 50){ rainDate[5] = Number(rainDate[5] || 0) + 1; inflAmtCnt[5] = Number(inflAmtCnt[5] || 0) + 1; inflAmnt[5] = Number(inflAmnt[5] || 0) + Number(item.inflAmnt || 0); - fcltyCpct[5] = item.inflAmnt&&Number(item.inflAmnt) !== 0 ? Number(Math.round(Number(item.inflAmnt) / Number(item.fctCpc) * 10) / 10) : 0; + fcltyCpct[5] = 0; inflAssctAftWqltBodCnt[5] = Number(inflAssctAftWqltBodCnt[5] || 0) + 1; inflAssctAftWqltBod[5] = Number(inflAssctAftWqltBod[5] || 0) + Number(item.inflAssctAftWqltBod || 0); inflAssctAftWqltTocCnt[5] = Number(inflAssctAftWqltTocCnt[5] || 0) + 1; @@ -1367,7 +1418,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[5] = Number(efflWqltTn[5] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[5] = Number(efflWqltTpCnt[5] || 0) + 1; efflWqltTp[5] = Number(efflWqltTp[5] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 40 && Number(item.infoRnDay) < 50){ + }else if(Number(item.infoRnDay) >= 50 && Number(item.infoRnDay) < 60){ rainDate[6] = Number(rainDate[6] || 0) + 1; inflAmtCnt[6] = Number(inflAmtCnt[6] || 0) + 1; inflAmnt[6] = Number(inflAmnt[6] || 0) + Number(item.inflAmnt || 0); @@ -1392,7 +1443,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[6] = Number(efflWqltTn[6] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[6] = Number(efflWqltTpCnt[6] || 0) + 1; efflWqltTp[6] = Number(efflWqltTp[6] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 50 && Number(item.infoRnDay) < 60){ + }else if(Number(item.infoRnDay) >= 60){ rainDate[7] = Number(rainDate[7] || 0) + 1; inflAmtCnt[7] = Number(inflAmtCnt[7] || 0) + 1; inflAmnt[7] = Number(inflAmnt[7] || 0) + Number(item.inflAmnt || 0); @@ -1417,31 +1468,6 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) efflWqltTn[7] = Number(efflWqltTn[7] || 0) + Number(item.efflWqltTn || 0); efflWqltTpCnt[7] = Number(efflWqltTpCnt[7] || 0) + 1; efflWqltTp[7] = Number(efflWqltTp[7] || 0) + Number(item.efflWqltTp || 0); - }else if(Number(item.infoRnDay) >= 60){ - rainDate[8] = Number(rainDate[8] || 0) + 1; - inflAmtCnt[8] = Number(inflAmtCnt[8] || 0) + 1; - inflAmnt[8] = Number(inflAmnt[8] || 0) + Number(item.inflAmnt || 0); - fcltyCpct[8] = 0; - inflAssctAftWqltBodCnt[8] = Number(inflAssctAftWqltBodCnt[8] || 0) + 1; - inflAssctAftWqltBod[8] = Number(inflAssctAftWqltBod[8] || 0) + Number(item.inflAssctAftWqltBod || 0); - inflAssctAftWqltTocCnt[8] = Number(inflAssctAftWqltTocCnt[8] || 0) + 1; - inflAssctAftWqltToc[8] = Number(inflAssctAftWqltToc[8] || 0) + Number(item.inflAssctAftWqltToc || 0); - inflAssctAftWqltSsCnt[8] = Number(inflAssctAftWqltSsCnt[8] || 0) + 1; - inflAssctAftWqltSs[8] = Number(inflAssctAftWqltSs[8] || 0) + Number(item.inflAssctAftWqltSs || 0); - inflAssctAftWqltTnCnt[8] = Number(inflAssctAftWqltTnCnt[8] || 0) + 1; - inflAssctAftWqltTn[8] = Number(inflAssctAftWqltTn[8] || 0) + Number(item.inflAssctAftWqltTn || 0); - inflAssctAftWqltTpCnt[8] = Number(inflAssctAftWqltTpCnt[8] || 0) + 1; - inflAssctAftWqltTp[8] = Number(inflAssctAftWqltTp[8] || 0) + Number(item.inflAssctAftWqltTp || 0); - efflWqltBodCnt[8] = Number(efflWqltBodCnt[8] || 0) + 1; - efflWqltBod[8] = Number(efflWqltBod[8] || 0) + Number(item.efflWqltBod || 0); - efflWqltCodCnt[8] = Number(efflWqltCodCnt[8] || 0) + 1; - efflWqltCod[8] = Number(efflWqltCod[8] || 0) + Number(item.efflWqltCod || 0); - efflWqltSsCnt[8] = Number(efflWqltSsCnt[8] || 0) + 1; - efflWqltSs[8] = Number(efflWqltSs[8] || 0) + Number(item.efflWqltSs || 0); - efflWqltTnCnt[8] = Number(efflWqltTnCnt[8] || 0) + 1; - efflWqltTn[8] = Number(efflWqltTn[8] || 0) + Number(item.efflWqltTn || 0); - efflWqltTpCnt[8] = Number(efflWqltTpCnt[8] || 0) + 1; - efflWqltTp[8] = Number(efflWqltTp[8] || 0) + Number(item.efflWqltTp || 0); } }); @@ -1488,7 +1514,7 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType) rainDate[0] = (Number(rainDate[1]) || 0) + (Number(rainDate[2]) || 0) + (Number(rainDate[3]) || 0) + (Number(rainDate[4]) || 0) + (Number(rainDate[5]) || 0) + (Number(rainDate[6]) || 0) + - (Number(rainDate[7]) || 0) + (Number(rainDate[8]) || 0); + (Number(rainDate[7]) || 0); // 강수비율 for(let idx=0; idx DashBoardType) inflAmnt[0] = calculateAverage(calculateTotal(inflAmnt), dataIsCount); fcltyCpct[0] = calculateTotal(fcltyCpct); - inflAssctAftWqltBod[0] = Number((calculateTotal(inflAssctAftWqltBod)/8).toFixed(1)); - inflAssctAftWqltToc[0] = Number((calculateTotal(inflAssctAftWqltToc)/8).toFixed(1)); - inflAssctAftWqltSs[0] = Number((calculateTotal(inflAssctAftWqltSs)/8).toFixed(1)); - inflAssctAftWqltTn[0] = Number((calculateTotal(inflAssctAftWqltTn)/8).toFixed(3)); - inflAssctAftWqltTp[0] = Number((calculateTotal(inflAssctAftWqltTp)/8).toFixed(3)); - efflWqltBod[0] = Number((calculateTotal(efflWqltBod)/8).toFixed(1)); - efflWqltCod[0] = Number((calculateTotal(efflWqltCod)/8).toFixed(1)); - efflWqltSs[0] = Number((calculateTotal(efflWqltSs as number[])/8).toFixed(1)); - efflWqltTn[0] = Number((calculateTotal(efflWqltTn as number[])/8).toFixed(3)); - efflWqltTp[0] = Number((calculateTotal(efflWqltTp as number[])/8).toFixed(3)); + inflAssctAftWqltBod[0] = Number((calculateTotal(inflAssctAftWqltBod)/7).toFixed(1)); + inflAssctAftWqltToc[0] = Number((calculateTotal(inflAssctAftWqltToc)/7).toFixed(1)); + inflAssctAftWqltSs[0] = Number((calculateTotal(inflAssctAftWqltSs)/7).toFixed(1)); + inflAssctAftWqltTn[0] = Number((calculateTotal(inflAssctAftWqltTn)/7).toFixed(3)); + inflAssctAftWqltTp[0] = Number((calculateTotal(inflAssctAftWqltTp)/7).toFixed(3)); + efflWqltBod[0] = Number((calculateTotal(efflWqltBod)/7).toFixed(1)); + efflWqltCod[0] = Number((calculateTotal(efflWqltCod)/7).toFixed(1)); + efflWqltSs[0] = Number((calculateTotal(efflWqltSs as number[])/7).toFixed(1)); + efflWqltTn[0] = Number((calculateTotal(efflWqltTn as number[])/7).toFixed(3)); + efflWqltTp[0] = Number((calculateTotal(efflWqltTp as number[])/7).toFixed(3)); // 최종 리스트 const filteredData = itemList.map((item, index) => ({ diff --git a/apps/fo/app/store/facilityWaterQlty/index.tsx b/apps/fo/app/store/facilityWaterQlty/index.tsx index 6430522f4..565c251d2 100644 --- a/apps/fo/app/store/facilityWaterQlty/index.tsx +++ b/apps/fo/app/store/facilityWaterQlty/index.tsx @@ -28,7 +28,7 @@ export interface FacilityWaterQltyProps { inflAssctAftWqltTp: string | '', inflAssctBfrWqltTp: string | '', efflWqltBod: string | '', - efflWqltCod: string | '', + efflWqltToc: string | '', efflWqltSs: string | '', efflWqltTn: string | '', efflWqltTp: string | '', @@ -104,7 +104,7 @@ export type modalExcelType = { "유출SS(mg/L)": string; "유출TN(mg/L)": string; "유출TP(mg/L)": string; - "강우량(일)": string; + /* "강우량(mm/일)": string; */ } /**==================================== @@ -133,7 +133,7 @@ const upHeadList1: TableUpperProps[] = [ { id: "inflAssctAftWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 4, align: "right", row: 1 }, { id: "inflAssctAftWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row: 1 }, { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row: 1 }, - { id: "efflWqltCod", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1 }, + { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row: 1 }, { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1 }, { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 4, align: "right", row: 1 }, { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row: 1 }, @@ -157,7 +157,7 @@ const upHeadList2: TableUpperProps[] = [ { id: "inflAssctAftWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row:1 }, { id: "inflAssctAftWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row:1 }, { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row:1 }, - { id: "efflWqltCod", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1 }, + { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1 }, { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1 }, { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1 }, { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1 }, @@ -185,7 +185,7 @@ const upHeadList3: TableUpperProps[] = [ { id: "inflAssctAftWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe3", upSequnce: 2, align: "right", row:1 }, { id: "inflAssctAftWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe3", upSequnce: 3, align: "right", row:1 }, { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe4", upSequnce: 1, align: "right", row:1 }, - { id: "efflWqltCod", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1 }, + { id: "efflWqltToc", title: "TOC\n(COD)\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1 }, { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1 }, { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe4", upSequnce: 2, align: "right", row:1 }, { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe4", upSequnce: 3, align: "right", row:1 }, @@ -203,11 +203,11 @@ const upHeadListModalYear: TableUpperProps[] = [ { id: "inflAssctAftWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe1", upSequnce: 2, align: "right" }, { id: "inflAssctBfrWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe1", upSequnce: 3, align: "right" }, { id: "efflWqltBod", title: "BOD\n(mg/L)", upName: "upChangeRe2", upSequnce: 1, align: "right" }, - { id: "efflWqltCod", title: "TOC(COD)\n(mg/L)", upName: "upChangeRe2", upSequnce: 2, align: "right" }, + { id: "efflWqltToc", title: "TOC(COD)\n(mg/L)", upName: "upChangeRe2", upSequnce: 2, align: "right" }, { id: "efflWqltSs", title: "SS\n(mg/L)", upName: "upChangeRe2", upSequnce: 3, align: "right" }, { id: "efflWqltTn", title: "T-N\n(mg/L)", upName: "upChangeRe2", upSequnce: 2, align: "right" }, { id: "efflWqltTp", title: "T-P\n(mg/L)", upName: "upChangeRe2", upSequnce: 3, align: "right" }, - { id: "infoRnDay", title: "강우량", align: "right" }, + /* { id: "infoRnDay", title: "강우량(mm/일)", align: "right" }, */ ]; // 처리시설 수질현황 헤더(모달-수질TMS) @@ -409,8 +409,8 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType if(item.inflAssctAftWqltToc){ item.inflAssctAftWqltToc = Number(Math.round(Number(item.inflAssctAftWqltToc) * 10)/10).toFixed(1); } - if(item.efflWqltCod){ - item.efflWqltCod = Number(Math.round(Number(item.efflWqltCod) * 10)/10).toFixed(1); + if(item.efflWqltToc){ + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); } if(item.inflAssctAftWqltBod){ item.inflAssctAftWqltBod = Number(Math.round(Number(item.inflAssctAftWqltBod) * 10)/10).toFixed(1); @@ -427,8 +427,8 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType if(item.inflAssctAftWqltToc){ item.inflAssctAftWqltToc = Number(Math.round(Number(item.inflAssctAftWqltToc) * 10)/10).toFixed(1); } - if(item.efflWqltCod){ - item.efflWqltCod = Number(Math.round(Number(item.efflWqltCod) * 10)/10).toFixed(1); + if(item.efflWqltToc){ + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); } return item; }); @@ -456,7 +456,7 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType 유입TN: item.inflAssctAftWqltTn, 유입TP: item.inflAssctAftWqltTp, 유출BOD: item.efflWqltBod, - 유출TOC: item.efflWqltCod, + 유출TOC: item.efflWqltToc, 유출SS: item.efflWqltSs, 유출TN: item.efflWqltTn, 유출TP: item.efflWqltTp, @@ -610,8 +610,8 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType if(item.inflAssctAftWqltToc){ item.inflAssctAftWqltToc = Number(Math.round(Number(item.inflAssctAftWqltToc) * 10)/10).toFixed(1); } - if(item.efflWqltCod){ - item.efflWqltCod = Number(Math.round(Number(item.efflWqltCod) * 10)/10).toFixed(1); + if(item.efflWqltToc){ + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); } if(item.inflAssctAftWqltBod){ item.inflAssctAftWqltBod = Number(Math.round(Number(item.inflAssctAftWqltBod) * 10)/10).toFixed(1); @@ -628,8 +628,8 @@ export const facilityWaterQltyReducer = (set: (fn: (state: FacilityWaterQltyType if(item.inflAssctAftWqltToc){ item.inflAssctAftWqltToc = Number(Math.round(Number(item.inflAssctAftWqltToc) * 10)/10).toFixed(1); } - if(item.efflWqltCod){ - item.efflWqltCod = Number(Math.round(Number(item.efflWqltCod) * 10)/10).toFixed(1); + if(item.efflWqltToc){ + item.efflWqltToc = Number(Math.round(Number(item.efflWqltToc) * 10)/10).toFixed(1); } if(item.tpInflWqltBod){ diff --git a/apps/fo/app/store/flowRate/index.tsx b/apps/fo/app/store/flowRate/index.tsx index a619ddf85..38cb4e2e6 100644 --- a/apps/fo/app/store/flowRate/index.tsx +++ b/apps/fo/app/store/flowRate/index.tsx @@ -13,8 +13,8 @@ const FlowRateSearchLabelArray:string[] = ['구분', '시도', '시군구', '용 export type flowRateListProps = { bscFctCd : string, - sidoNm : string, - sggNm : string, + sido : string, + gugun : string, bscFctNm : string, daddr : string, fctCpc : number, @@ -50,13 +50,13 @@ export interface ExcelType { } export type modalExcelType = { - 날짜: string, + "날짜": string, "총유입량(m³/일)": number | '', "하수유입량(m³/일)": number | '', "연계처리량(m³/일)": number | '', "총인반류수량(m³/일)": number | '', "방류량(m³/일)": number | '', - "강우량": number | string | '', + /* "강우량(mm/일)": number | string | '', */ } /** * (2) 모달 @@ -93,8 +93,8 @@ export type FlowRateModalProps = { const upHeadList1:TableUpperProps[] = [ { id: "bscFctCd", title: "시설코드", row:0, rowSpan:2}, { id: "A", title: "행정명", row: 0 ,colSpan: 2 }, - { id: "sidoNm", title: "시도", upName:"upChangeRe", row:1 }, - { id: "sggNm", title: "시군구", upName:"upChangeRe", row:1 }, + { id: "sido", title: "시도", upName:"upChangeRe", row:1 }, + { id: "gugun", title: "시군구", upName:"upChangeRe", row:1 }, { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2}, { id: "daddr", title: "주소", row: 0, rowSpan: 2}, { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row: 0, rowSpan: 2}, @@ -131,8 +131,8 @@ const upHeadList3:TableUpperProps[] = [ { id: "mdmbsn", title: "중권역", upName:"upChangeRe1", row:1 }, { id: "rgnNm", title: "환경청", row:0, rowSpan:2 }, { id: "B", title: "행정명", row: 0 ,colSpan: 2 }, - { id: "ctpvNm", title: "시도", upName:"upChangeRe2", row:1 }, - { id: "sggNm", title: "시군구", upName:"upChangeRe2", row:1 }, + { id: "sido", title: "시도", upName:"upChangeRe2", row:1 }, + { id: "gugun", title: "시군구", upName:"upChangeRe2", row:1 }, { id: "bscFctNm", title: "시설명", row: 0, rowSpan: 2 }, { id: "daddr", title: "주소", row: 0, rowSpan: 2 }, { id: "fctCpc", title: "시설용량\n(m³/일)", align:"right", row: 0, rowSpan: 2 }, @@ -157,7 +157,7 @@ const upHeadListFlowRateModal:TableUpperProps[] = [ { id: "sumFlow", title: "연계처리량(m³/일)", align:"right"}, { id: "returnAmnt", title: "총인 반류수량(m³/일)", align:"right"}, { id: "efflAmnt", title: "방류량(m³/일)", align:"right"}, - { id: "infoRnDay", title: "강우량", align:"right"} + /* { id: "infoRnDay", title: "강우량(mm/일)", align:"right"} */ ] const modalFlowRateListYear:FlowRateModalProps[] = [ @@ -268,11 +268,8 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow const res = await fetchFlowRateList(pageNo, pageSize, searchYear, partCd, nameCd, fcltCpct, operationValue, fcltyNm); const data = res.data; const excelDownList: ExcelType[] = []; - const rtnData = data.map((item: flowRateListProps) => { item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; item.totInflAmnt = Number(item.inflAmnt || 0) + Number(item.sumFlow || 0) + Number(item.returnAmnt || 0) dataFormSetting(item); return item; @@ -286,8 +283,8 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow 단위유역: item.lgbsn, 중권역: item.mdmbsn, 환경청: item.rgnNm, - 시도: item.sidoNm, - 시군구: item.sggNm, + 시도: item.sido, + 시군구: item.gugun, 시설이름: item.bscFctNm, 주소: item.daddr, 용량: item.fctCpc, @@ -300,7 +297,6 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow }); } - console.log('excelDownList' , excelDownList); set((state: FlowRateSearchType) => ({ ...state, FlowRateList: pageSize<5000?{ @@ -316,8 +312,14 @@ export const flowRateSearchReducer:(set:(fn: (state: FlowRateSearchType) => Flow searchFlowRateModalList: async (bscFctCd: string, searchYear: string) => { const res = await fetchFlowRateModalList(bscFctCd, searchYear); const data = res.data; - const tmpData:FlowRateModalProps[] = []; + + data.forEach((item: FlowRateModalProps) => { + if(item.infoRnDay && item.infoRnDay.toString().trim() === 'x') { + item.infoRnDay = ''; + } + }); + // facilityPrecipitation의 rainEffectDay 계산 로직을 적용 for(let idx = 0; idx < data.length; idx++) { const item = data[idx]; diff --git a/apps/fo/app/store/sewageOccur/index.tsx b/apps/fo/app/store/sewageOccur/index.tsx index 93de7ca94..f5d300aa1 100644 --- a/apps/fo/app/store/sewageOccur/index.tsx +++ b/apps/fo/app/store/sewageOccur/index.tsx @@ -1,4 +1,3 @@ -import DataForm from "@/app/components/common/DataForm"; import type { TableUpperProps } from "../dashboard"; import { getFetch } from "@/app/components/api"; export type { TableUpperProps }; @@ -29,21 +28,14 @@ export type SewageOccurProps = { mdmbsn : string, msrDt : string, } - +export type modalTableType = { msrDt: string,waterUsage: number,swageChangePercent: number,estimationSewage: number}; export type SewageOccurDetailProps = { - msrDt : string, - waterUsage : number, - swageChangePercent : number, - estimationSewage : number, + cnt : number, + sum : number, + yr:string, + usege:number } -/**==================================== - * Store - data - ====================================*/ - -/** - * (1) 메인페이지 - */ // 검색단 const LabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; @@ -120,159 +112,13 @@ const upHeadList3:TableUpperProps[] = [ const upHeadListSewageOccurDetail:TableUpperProps[] = [ { id: "msrDt", title: "년월" }, { id: "waterUsage", title: "물사용량(m³/일)" }, - { id: "swageChangePercent", title: "오수전환율" }, + { id: "swageChangePercent", title: "오수전환율(%)" }, { id: "estimationSewage", title: "오수발생량 추정(m³/일)", row:0, rowSpan:2 }, ]; -const sewageOccurList:SewageOccurProps[] = [ -] +const sewageOccurList:SewageOccurProps[] = [] -const tempSewageOccurList:SewageOccurDetailProps[] = [ - { - msrDt: '2024-01', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 60, - }, - { - msrDt: '2024-02', - waterUsage: 106, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-03', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-04', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-05', - waterUsage: 100, - swageChangePercent: 10, - estimationSewage: 80, - }, - { - msrDt: '2024-06', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-07', - waterUsage: 100, - swageChangePercent: 580, - estimationSewage: 80, - }, - { - msrDt: '2024-08', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-09', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-10', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-11', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2024-12', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-01', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-02', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-03', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-04', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-05', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-06', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-07', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-08', - waterUsage: 100, - swageChangePercent: 0.8, - estimationSewage: 80, - }, - { - msrDt: '2025-09', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-10', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-11', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, - { - msrDt: '2025-12', - waterUsage: 100, - swageChangePercent: 80, - estimationSewage: 80, - }, -] +const tempSewageOccurList:modalTableType[] = [] /**==================================== * Store - API @@ -280,11 +126,6 @@ const tempSewageOccurList:SewageOccurDetailProps[] = [ const fetchSewageOccurList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { // 20250701임시시 - console.log("partCd", partCd); - console.log("nameCd", nameCd); - console.log("fcltCpct", fcltCpct); - console.log("operationValue", operationValue); - console.log("fcltyNm", fcltyNm); console.log("searchYear", searchYear); const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : ''; const url = '/fclt/flow/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr; @@ -304,6 +145,7 @@ const fetchSewageOccurList = async (pageNo: number, pageSize: number, searchYear ====================================*/ export type SewageOccurActions = { searchSewageOccurList: (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => void; + searchSewageOccurDetailList: (bscFctCd: string, searchYear: string) => void; } export type SewageOccurType = { @@ -325,7 +167,7 @@ export type SewageOccurType = { SewageOccurDetail : { isInit: boolean; upHeadListSewageOccurDetail: TableUpperProps[]; - gridListSewageOccurDetail: SewageOccurDetailProps[]; + gridListSewageOccurDetail: modalTableType[]; } } @@ -352,14 +194,6 @@ export const sewageOccurInitState: SewageOccurType = { } } -const dataFormSetting=(item : SewageOccurProps)=> { - DataForm({ type: 'float1', column: 'fctCpc', item: item as unknown as Record }); - DataForm({ type: 'float1', column: 'waterUsage', item: item as unknown as Record}); - DataForm({ type: 'float2', column: 'estimationSewage', item: item as unknown as Record}); - DataForm({ type: 'float2', column: 'sewageInputYear', item: item as unknown as Record}); - DataForm({ type: 'float2', column: 'sewageInputChungchun', item: item as unknown as Record}); -} - export type SewageOccurStore = SewageOccurType & SewageOccurActions; // Add type definitions for API responses @@ -375,9 +209,7 @@ type FlowResponse = { count: number; } -type SetStateFunction = (state: SewageOccurType | ((state: SewageOccurType) => SewageOccurType)) => void; - -export const sewageOccurReducer:(set:SetStateFunction)=>SewageOccurActions=(set: SetStateFunction) => { +export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => { return { searchSewageOccurList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => { const res = await fetchSewageOccurList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm); @@ -388,67 +220,31 @@ export const sewageOccurReducer:(set:SetStateFunction)=>SewageOccurActions=(set: item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - item.waterUsage = item.fctCpc * 0.6; item.swageChangePercent = 80; - item.estimationSewage = item.waterUsage * (item.swageChangePercent/100); + const bscFctCd = item.bscFctCd; - const url2 = '/fclt/flow/' + bscFctCd + '/' + searchYear; - const res2 = await getFetch('fo', url2) as FlowResponse; - const sumFlow = res2.data.reduce((acc: number, curr: FlowDataItem) => Number(acc) + Number(curr.inflAmnt), 0); + const res2 = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear) as FlowResponse; let sumChungcunDay = 0; let chungcunSumFlow = 0; - + let totWaterUsage = 0; res2.data.forEach((item: FlowDataItem) => { if(Number(item.infoRnDay) ===0){ chungcunSumFlow += Number(item.inflAmnt); sumChungcunDay += 1; - }else{ - console.log(item.bscFctCd +' day : ' +' '+item.msrDt); } + totWaterUsage += Number(item.inflAmnt); }); - - item.sewageInputYear = sumFlow/res2.count; - item.sewageInputChungchun = chungcunSumFlow===0?0:(chungcunSumFlow / sumChungcunDay); + const avg = totWaterUsage /res2.count; + item.waterUsage = parseFloat((avg * 0.65).toFixed(2)); + item.sewageInputYear = parseFloat(avg.toFixed(2)); + item.sewageInputChungchun = parseFloat((chungcunSumFlow===0?0:(chungcunSumFlow / sumChungcunDay)).toFixed(2)); + item.estimationSewage = parseFloat((item.waterUsage * (item.swageChangePercent/100)).toFixed(2)); return item; })); - - console.log('calData : ', calData); - - const excelDownList: ExcelType[] = []; - if(pageSize>5000){ - calData.forEach(item => { - dataFormSetting(item) - item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); - /* item.ctpvNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; */ - excelDownList.push({ - 시설코드: item.bscFctCd, - 권역: item.rvbsn, - 단위유역: item.lgbsn, - 중권역: item.mdmbsn, - 환경청: item.rgnNm, - 시도: item.sidoNm, - 시군구: item.sggNm, - 시설명: item.bscFctNm, - 주소: item.daddr, - 시설용량: item.fctCpc, - 물사용량: item.waterUsage, - 오수전환율: item.swageChangePercent, - 오수발생량추정: item.estimationSewage, - 연간하수유입량: item.sewageInputYear, - 청천시하수유입량: item.sewageInputChungchun, - }); - }); - }else{ - calData.forEach((item: SewageOccurProps) => { - dataFormSetting(item) - /* item.sidoNmNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || ''; - item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; */ - }); - } - set((state: SewageOccurType) => ({ + // 조회 데이터 설정 + set((state: any) => ({ ...state, SewageOccur: pageSize<5000?{ ...state.SewageOccur, @@ -456,7 +252,81 @@ export const sewageOccurReducer:(set:SetStateFunction)=>SewageOccurActions=(set: sewageOccurCount: res.count, }:{ ...state.SewageOccur, - excelList: excelDownList, + excelList: [], + } + })) + }, + + searchSewageOccurDetailList: async (bscFctCd: string, searchYear: string) => { + const res = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear); + const monthData: SewageOccurDetailProps[] = []; + let tot = 0, cnt= 0 + res.data.forEach((item: FlowDataItem) => { + const month = item.msrDt?.substring(4, 6); + const monthIdx = month ? Number(month) - 1 : -1; + if (monthIdx >= 0) { + if (typeof monthData[monthIdx] === 'object') { + monthData[monthIdx].sum += Number(item.inflAmnt); + monthData[monthIdx].cnt++; + } else { + monthData[monthIdx] = {cnt:1, sum:Number(item.inflAmnt), yr:item.msrDt.substring(0,4), usege:0}; + } + } + tot += Number(item.inflAmnt) + cnt++ + }) + console.log('tot', tot, cnt, tot/cnt, tot/cnt*0.65); + const tableData:modalTableType[] = [] + + let maxWaterUsage = 0; + let minWaterUsage = 9999999999; + let avgWaterUsage = 0; + monthData.forEach((item, index) => { + item.usege = item.sum/item.cnt *0.65 + tableData.push({ + msrDt: item.yr+'-'+(index+1>10?String(index+1):'0'+(index+1)), + waterUsage: Math.round(item.usege*100) /100, + swageChangePercent: (Math.round((item.usege/item.usege*0.8) * 100) / 100) * 100, + estimationSewage: Math.round(item.usege*0.8 * 100) / 100 + }) + if (item.usege > maxWaterUsage) { + maxWaterUsage = item.usege; + } + + if (item.usege < minWaterUsage) { + minWaterUsage = item.usege; + } + + }); + avgWaterUsage = tot/cnt*0.65 + + const newData= [ + { + msrDt: '평균', + waterUsage: Math.round(avgWaterUsage*100) /100, + swageChangePercent: 80, + estimationSewage: Math.round(avgWaterUsage*0.8*100) /100, + }, + { + msrDt: '최대', + waterUsage: Math.round(maxWaterUsage*100) /100, + swageChangePercent: 80, + estimationSewage: Math.round(maxWaterUsage*0.8*100) /100, + }, + { + msrDt: '최소', + waterUsage: Math.round(minWaterUsage*100) /100, + swageChangePercent: 80, + estimationSewage: Math.round(minWaterUsage*0.8*100) /100, + }, + ...tableData + ] + + set((state: SewageOccurType) => ({ + ...state, + SewageOccurDetail: { + ...state.SewageOccurDetail, + gridListSewageOccurDetail: newData, } })) }, diff --git a/apps/fo/app/store/store.tsx b/apps/fo/app/store/store.tsx index f17801d1f..512458c90 100644 --- a/apps/fo/app/store/store.tsx +++ b/apps/fo/app/store/store.tsx @@ -1,13 +1,19 @@ +import { LoginApi } from '@/src/entities/auth/api/login-service'; import { createStore } from 'zustand/vanilla'; import { CommonReducer, commonState, type CommonStore, type CommonType } from './common'; -import { dashBoardReducer, dashBoardInitState, type DashBoardStore, type DashBoardType } from './dashboard'; +import { dashBoardInitState, dashBoardReducer, type DashBoardStore, type DashBoardType } from './dashboard'; import { facilityPrecipitationInitState, facilityPrecipitationReducer, type FacilityPrecipitationStore, type FacilityPrecipitationType } from './facilityPrecipitation'; -import { planSewageInitState, planSewageReducer, type PlanSewageStore, type PlanSewageType } from './planSewage'; +import { + facilityWaterQltyInitState, + facilityWaterQltyReducer, + type FacilityWaterQltyStore, type FacilityWaterQltyType +} from './facilityWaterQlty'; import { flowRateSearchInitState, flowRateSearchReducer, type FlowRateSearchStore, type FlowRateSearchType } from './flowRate'; +import { planSewageInitState, planSewageReducer, type PlanSewageStore, type PlanSewageType } from './planSewage'; import { processAreaInitState, processAreaReducer, @@ -18,11 +24,12 @@ import { processFacilityReducer, type ProcessFacilityStore, type ProcessFacilityType } from './processFacility'; +import { tableActionsExport, tableStateExport, type tableState, type tableType } from './publicReuseFaciltyInsert/index'; import { - facilityWaterQltyInitState, - facilityWaterQltyReducer, - type FacilityWaterQltyStore, type FacilityWaterQltyType -} from './facilityWaterQlty'; + rdiInitState, + rdiReducer, + type RdiStore, type RdiType +} from './rdi'; import { sewageOccurInitState, sewageOccurReducer, @@ -33,12 +40,6 @@ import { underWaterReducer, type UnderWaterStore, type UnderWaterType } from './underWaterInput'; -import { - rdiInitState, - rdiReducer, - type RdiStore, type RdiType -} from './rdi'; -import { tableActionsExport, tableStateExport, type tableState, type tableType } from './publicReuseFaciltyInsert/index'; export type FoStore = CommonStore & tableType & DashBoardStore & ProcessFacilityStore & ProcessAreaStore & FlowRateSearchStore & FacilityPrecipitationStore & FacilityWaterQltyStore & PlanSewageStore & SewageOccurStore & UnderWaterStore & RdiStore; export type FoState = CommonType & tableState & ProcessFacilityType & ProcessAreaType & FacilityPrecipitationType & FlowRateSearchType & DashBoardType & FacilityWaterQltyType & PlanSewageType & SewageOccurType & UnderWaterType & RdiType; @@ -58,6 +59,7 @@ export const StoreZus = } ) => { return createStore()((set) => { + LoginApi.login({userId: "admin1gmail", password: "test1234!"}) return { ...initState, ...CommonReducer(set), diff --git a/apps/fo/app/store/underWaterInput/index.tsx b/apps/fo/app/store/underWaterInput/index.tsx index ea6f4575b..912d8673e 100644 --- a/apps/fo/app/store/underWaterInput/index.tsx +++ b/apps/fo/app/store/underWaterInput/index.tsx @@ -1,6 +1,8 @@ import DataForm from "@/app/components/common/DataForm"; import type { TableUpperProps } from "../dashboard"; import { getFetch } from "@/app/components/api"; +import type { ExcelType, UnderWaterInputModalProps, UnderWaterProps } from "./types"; +import type { SewageOccurDetailProps } from "../sewageOccur"; export type { TableUpperProps }; /**==================================== * Store - 타입 @@ -10,47 +12,6 @@ export type { TableUpperProps }; * (1) 메인페이지 */ // 시설별 강수현황 -export type UnderWaterProps = { - bscFctCd : string, - lclgvrnmntNm : string, - sidoNm : string, - sggNm : string, - bscFctNm : string, - daddr : string, - fctCpc : number, - underWaterInputYear : number, - underWaterInputSummer : number, - underWaterInputWinter : number, - infoRnDay : number, - rgnNm : string, - rvbsn : string, - lgbsn : string, - mdmbsn : string, -} -export interface UnderWaterInterface { - bscFctCd : string, - sidoNm : string, - sggNm : string, - bscFctNm : string, - daddr : string, - fctCpc : number, - underWaterInputYear : number, - underWaterInputSummer : number, - underWaterInputWinter : number, - infoRnDay : number, - rgnNm : string, - rvbsn : string, - lgbsn : string, - mdmbsn : string, -} - -// 모달 -export interface UnderWaterInputModalProps { - msrDt : string, - realSewageInput : number | '', - sewageOccur : number | '', - underWaterInput : number | '', -} /**==================================== * Store - data @@ -62,22 +23,6 @@ export interface UnderWaterInputModalProps { // 검색단 const LabelArray:string[] = ['구분', '시도', '시군구','용량별', '시설명', '조회기간', '시설상태', '권역', '단위유역', '중권역', '환경청별']; -export interface ExcelType { - 시설코드: string, - 권역:string|'', - 단위유역:string|'', - 중권역:string|'', - 환경청:string|'', - 시도: string, - 시군구: string, - 시설명: string, - 주소: string, - 시설용량: number, - 연간지하수유입량: number | '', - 하절기지하수유입량: number | '', - 동절기지하수유입량: number | '', -} - // 리스트 헤더 const upHeadList1:TableUpperProps[] = [ { id: "bscFctCd", title: "시설코드", row:0, rowSpan:2 }, @@ -247,6 +192,7 @@ const fetchUnderWaterList = async (pageNo: number, pageSize: number, searchYear: ====================================*/ export type UnderWaterActions = { searchUnderWaterList: (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => void; + unserWaterModal:(bscFctCd:string, searchYear:string)=>void } export type UnderWaterType = { @@ -315,8 +261,7 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, '')); const bscFctCd = item.bscFctCd; - const url2 = '/fclt/flow/' + bscFctCd + '/' + searchYear; - const res2 = await getFetch('fo', url2) as ApiResponse; + const res2 = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear) as ApiResponse; let sumChungcunDay = 0; let chungcunSumFlow = 0; @@ -393,5 +338,77 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy } })) }, + unserWaterModal:async (bscFctCd, searchYear)=>{ + const res = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear); + const monthData: SewageOccurDetailProps[] = []; + let tot = 0, cnt= 0 + res.data.forEach((item: FlowDataItem) => { + const month = item.msrDt?.substring(4, 6); + const monthIdx = month ? Number(month) - 1 : -1; + if (monthIdx >= 0) { + if (typeof monthData[monthIdx] === 'object') { + monthData[monthIdx].sum += Number(item.inflAmnt); + monthData[monthIdx].cnt++; + } else { + monthData[monthIdx] = {cnt:1, sum:Number(item.inflAmnt), yr:item.msrDt.substring(0,4), usege:0}; + } + } + tot += Number(item.inflAmnt) + cnt++ + }) + const tableData:UnderWaterInputModalProps[] = [] + + let maxWaterUsage = 0; + let minWaterUsage = 9999999999; + let avgWaterUsage = 0; + monthData.forEach((item, index) => { + item.usege = item.sum/item.cnt + tableData.push({ + msrDt: item.yr+'-'+(index+1>10?String(index+1):'0'+(index+1)), + realSewageInput:Math.round(item.usege*100) /100, + sewageOccur:Math.round(item.usege*0.65*0.8 * 100) / 100, + underWaterInput:Math.round((item.usege-(item.usege*0.65*0.8)) * 100) / 100, + }) + if (item.usege > maxWaterUsage) { + maxWaterUsage = item.usege; + } + + if (item.usege < minWaterUsage) { + minWaterUsage = item.usege; + } + + }); + avgWaterUsage = tot/cnt + + const newData= [ + { + msrDt: '평균', + realSewageInput:Math.round(avgWaterUsage*100) /100, + sewageOccur:Math.round(avgWaterUsage*0.65*0.8 * 100) / 100, + underWaterInput:Math.round((avgWaterUsage-(avgWaterUsage*0.65*0.8)) * 100) / 100, + }, + { + msrDt: '최대', + realSewageInput: Math.round(maxWaterUsage*100) /100, + sewageOccur: Math.round(maxWaterUsage*0.65*0.8 * 100) / 100, + underWaterInput: Math.round((maxWaterUsage-(maxWaterUsage*0.65*0.8)) * 100) / 100, + }, + { + msrDt: '최소', + realSewageInput: Math.round(minWaterUsage*100) /100, + sewageOccur: Math.round(minWaterUsage*0.65*0.8 * 100) / 100, + underWaterInput: Math.round((minWaterUsage-(minWaterUsage*0.65*0.8)) * 100) / 100, + }, + ...tableData + ] + + set((state: UnderWaterType) => ({ + ...state, + UnderWaterDetail: { + ...state.UnderWaterDetail, + gridListUnderWaterDetail:newData + } + })) + } } } \ No newline at end of file diff --git a/apps/fo/app/store/underWaterInput/types.tsx b/apps/fo/app/store/underWaterInput/types.tsx new file mode 100644 index 000000000..c90c654ad --- /dev/null +++ b/apps/fo/app/store/underWaterInput/types.tsx @@ -0,0 +1,58 @@ +export type UnderWaterProps = { + bscFctCd : string, + lclgvrnmntNm : string, + sidoNm : string, + sggNm : string, + bscFctNm : string, + daddr : string, + fctCpc : number, + underWaterInputYear : number, + underWaterInputSummer : number, + underWaterInputWinter : number, + infoRnDay : number, + rgnNm : string, + rvbsn : string, + lgbsn : string, + mdmbsn : string, +} +export interface UnderWaterInterface { + bscFctCd : string, + sidoNm : string, + sggNm : string, + bscFctNm : string, + daddr : string, + fctCpc : number, + underWaterInputYear : number, + underWaterInputSummer : number, + underWaterInputWinter : number, + infoRnDay : number, + rgnNm : string, + rvbsn : string, + lgbsn : string, + mdmbsn : string, +} + +// 모달 +export interface UnderWaterInputModalProps { + msrDt : string, + realSewageInput : number | '', + sewageOccur : number | '', + underWaterInput : number | '', +} + + +export interface ExcelType { + 시설코드: string, + 권역:string|'', + 단위유역:string|'', + 중권역:string|'', + 환경청:string|'', + 시도: string, + 시군구: string, + 시설명: string, + 주소: string, + 시설용량: number, + 연간지하수유입량: number | '', + 하절기지하수유입량: number | '', + 동절기지하수유입량: number | '', +} \ No newline at end of file diff --git a/apps/fo/eslint.config.mjs b/apps/fo/eslint.config.mjs index c85fb67c4..29e63f653 100644 --- a/apps/fo/eslint.config.mjs +++ b/apps/fo/eslint.config.mjs @@ -11,6 +11,10 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + {rules:{ + // other rules.. + "@typescript-eslint/no-explicit-any": "off", + }}, ]; export default eslintConfig; diff --git a/apps/fo/src/entities/auth/lib/token-manager.ts b/apps/fo/src/entities/auth/lib/token-manager.ts index 40d32f33e..e86dba5f2 100644 --- a/apps/fo/src/entities/auth/lib/token-manager.ts +++ b/apps/fo/src/entities/auth/lib/token-manager.ts @@ -20,7 +20,7 @@ export class TokenManager { public setToken(key: string, value: string): void { setCookie(key, value, { - secure: window.location.protocol === "https:", + secure: location.protocol === "https:", sameSite: "lax", }); } diff --git a/apps/sn/app/(map-layout)/map/components/InputSection.tsx b/apps/sn/app/(map-layout)/map/components/InputSection.tsx index 539122d44..e35165bd2 100644 --- a/apps/sn/app/(map-layout)/map/components/InputSection.tsx +++ b/apps/sn/app/(map-layout)/map/components/InputSection.tsx @@ -21,7 +21,7 @@ const InputSection: React.FC = ({ title, data, sectionKey, on 구분 - 년도 + 진단연도 사업명 유형 @@ -40,7 +40,7 @@ const InputSection: React.FC = ({ title, data, sectionKey, on className="w-full text-xl border-radius-0 px-3 py-2 text-base text-center leading-normal" /> - + {row.bizNm} diff --git a/apps/sn/app/(map-layout)/map/components/common/index.ts b/apps/sn/app/(map-layout)/map/components/common/index.ts index 23602b632..9860b211a 100644 --- a/apps/sn/app/(map-layout)/map/components/common/index.ts +++ b/apps/sn/app/(map-layout)/map/components/common/index.ts @@ -22,7 +22,7 @@ type LayerContentMap = { // 레이어 이름과 컴포넌트를 매핑 // 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업 const layerContentMap: LayerContentMap = { - "하수처리시설": { component: SwtrmtFclt, }, + // "하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리 "하수처리구역": {}, "우수토실": { component: Ssc, }, "중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정 diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index 68448af71..3148b7ea2 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -28,10 +28,11 @@ export const MsSwagePplCol = [ // { id: 'lyrTypeNm', title: '레이어타입명' }, ] export const MsSwagePplCol2 = [ + { id: 'rowNumber', title: '일련번호' }, { id: 'fcltMngNo', title: '시설 관리번호' }, { id: 'instMngNo', title: '기관 관리번호' }, { id: 'instlYmd', title: '설치일자' }, - { id: 'mapshtNo', title: '도엽번호' }, + // { id: 'mapshtNo', title: '도엽번호' }, { id: 'pdmt', title: '관경(mm)' }, { id: 'prlg', title: '연장(m)' }, { id: 'avgGrdn', title: '평균구배(m/m)' }, @@ -296,10 +297,11 @@ export const SwtrmtFcltCol = [ // { id: 'obsrvnSptCd', title: '관측지점코드' }, ]; export const SwtrmtFcltCol2 = [ + { id: 'rowNumber', title: '일련번호' }, { id: 'swtrmtFcltNm', title: '하수처리시설명' }, { id: 'fcltCpct', title: '시설용량(㎥/d)' }, { id: 'rgnExpln', title: '지역설명' }, - { id: 'cmcnYmd', title: '준공일자' }, + // { id: 'cmcnYmd', title: '준공일자' }, { id: 'oprtngStrtYmd', title: '가동개시일자' }, // { id: 'clsgYmd', title: '폐쇄일자' }, // { id: 'prcsMsrcCd', title: '처리공법코드' }, @@ -319,7 +321,7 @@ export const SwtrmtFcltCol2 = [ // { id: 'pllnArea', title: '부지면적(m²)' }, // { id: 'mngRprtvBzentyNm', title: '관리대행업체명' }, { id: 'swtrmtPpltnCnt', title: '하수처리인구수(인)' }, - { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' }, + // { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' }, // { id: 'xcrd', title: 'X좌표' }, // { id: 'ycrd', title: 'Y좌표' }, // { id: 'oprtngSttsCd', title: '가동상태코드' }, @@ -346,6 +348,7 @@ export const SwtrmtPrcsZoneCol = [ { id: 'swtrmt_fclt_nm', title: '하수처리시설명' }, ]; export const SwtrmtPrcsZoneCol2 = [ + { id: 'rowNumber', title: '일련번호' }, // { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' }, { id: 'swtrmtZoneNm', title: '하수처리구역명' }, // { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' }, @@ -380,12 +383,13 @@ export const MsSwageMhlCol = [ // { 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: 'pbadmsEmdCd', title: '행정읍면동코드' }, - { id: 'mapshtNo', title: '도엽번호' }, + // { id: 'mapshtNo', title: '도엽번호' }, // { id: 'mngInstCd', title: '관리기관코드' }, { id: 'instlYmd', title: '설치일자' }, // { id: 'lastDrdgnYmd', title: '최종준설일자' }, @@ -487,12 +491,13 @@ export const ExclnSewagePumpCol = [ // { id: 'instMngNo', title: '기관관리번호' }, ] export const ExclnSewagePumpCol2 = [ + { id: 'rowNumber', title: '일련번호' }, // { id: 'fid', title: 'FID' }, // { id: 'geom', title: 'geometry' }, // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, { id: 'fcltMngNo', title: '시설관리번호' }, // { id: 'pbadmsEmdCd', title: '행정읍면동코드' }, - { id: 'mapshtNo', title: '도엽번호' }, + // { id: 'mapshtNo', title: '도엽번호' }, // { id: 'mngInstCd', title: '관리기관코드' }, { id: 'instlYmd', title: '설치일자' }, { id: 'swagePmstnNm', title: '하수펌프장명' }, @@ -528,6 +533,7 @@ export const DrtwSewagePumpCol = [ // { id: 'instMngNo', title: '기관관리번호' }, ] export const DrtwSewagePumpCol2 = [ + { id: 'rowNumber', title: '일련번호' }, // { id: 'fid', title: 'FID' }, // { id: 'geom', title: 'geometry' }, // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, @@ -574,6 +580,7 @@ export const stofcbrCol = [ { id: 'drtn_angl', title: '방향각도(°)' }, ] export const stofcbrCol2 = [ + { id: 'rowNumber', title: '일련번호' }, // { id: 'ftreSmbolCd', title: '지형지물부호코드' }, { id: 'fcltMngNo', title: '시설관리번호' }, // { id: 'pbadmsEmdCd', title: '행정읍면동코드' }, @@ -650,7 +657,8 @@ export const dateFields = [ // 🎯 camelCase 버전 추가 'instlYmd', 'lastDrdgnYmd', 'cmcnYmd', 'oprtngStrtYmd', 'clsgYmd', 'mesureBeginDt', 'mesureEndDt', 'qltwtrBeginDt', 'qltwtrEndDt', - 'rainBeginDt', 'rainEndDt' + 'rainBeginDt', 'rainEndDt', + 'flfmtBgngYmd', 'flfmtEndYmd', 'flfmtYr' ]; // 🎯 연월 필드들 (yyyymm -> yyyy년 m월) @@ -677,5 +685,6 @@ export const numberFields = [ 'worflwWallHgt', 'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt', 'precdngDry', 'rainFall', 'cntncTime', 'rainIn', - 'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc' + 'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc', + 'bizAmt', 'count' ]; \ No newline at end of file diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx index a87cfcad1..08443c73f 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/MsSwagePpl.tsx @@ -107,7 +107,7 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic console.log('repairArr', repairArr); }; fetchAll(); - }, []); + }, [featureData]); // 폼 데이터가 변경될 때 호출될 핸들러 const handleFormDataChange = (newFormData: any) => { @@ -122,18 +122,6 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic } }; - // 변환 함수: 객체 → [{ year, type }] - // function convertToYearTypeArray(obj: any): { year: string, type: string }[] { - // if (!obj || typeof obj !== "object") return []; - // // 이미 배열이면 그대로 반환 - // if (Array.isArray(obj)) return obj; - // // 객체의 각 key를 type, value를 year로 변환 - // return Object.entries(obj).map(([type, year]) => ({ - // year: String(year ?? ""), - // type: String(type ?? ""), - // })); - // } - return ( <> diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts index 81daed0ed..857749bf1 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts @@ -1,5 +1,5 @@ import {useState, useEffect} from 'react'; -import { getData, getDcData } from '@/components/lib/api'; +import { getData } from '@/components/lib/api'; import moment from "moment"; export type TInftwtAnls = { @@ -64,9 +64,19 @@ export type TOilqData = { mtrngDt: string; // 계측일시 oilq: number; // 유량 } +export type TRainData = { + rainInfwtAnlsSn: number; + mtrngDt: string; + amrf: number; // 강우량 +} + +function roundTo(n: number, digits: number): number { + const factor = Math.pow(10, digits); + return Math.round(n * factor) / factor; +} const getOptionsAplcnYm = (yyyymms: string[]): { text: string; val: string, hidden?: boolean }[] => { - const yyyymms1 = yyyymms.map(dateStr => { + const yyyymms1 : { text: string; val: string; hidden?: boolean }[] = yyyymms.map(dateStr => { const year = dateStr.slice(0, 4); const month = dateStr.slice(4, 6); return { @@ -104,7 +114,7 @@ export const useAplcnYms = (gubun:string) => { return data; }; export const getOptionsInftwtAnlsInfo = (anlsArr: TInftwtAnls[]): { text: string; val: number, hidden?: boolean }[] => { - const options = anlsArr.map(anls => ({ + const options : { text: string; val: number; hidden?: boolean }[] = anlsArr.map(anls => ({ text: anls.anlsInfo, val: anls.inftwtAnlsSn, })); @@ -112,7 +122,7 @@ export const getOptionsInftwtAnlsInfo = (anlsArr: TInftwtAnls[]): { text: string return options; }; -export const selectInftwtAnls = async(aplcnYm:String) => { // 침입수분석 +export const selectInftwtAnls = async(aplcnYm:string) => { // 침입수분석 try { const queryString = aplcnYm ? `aplcnYmFrom=${aplcnYm}&aplcnYmTo=${aplcnYm}` : ''; const result = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`); @@ -127,7 +137,7 @@ export const selectInftwtAnls = async(aplcnYm:String) => { // 침입수분석 export const getChartDataInftwtAnlsRslt = (arr: TInftwtAnlsRslt[]): { label: string; val: number }[] => { const options = arr.map(anlsRslt => ({ label: moment(anlsRslt.anlsTm, "YYYYMMDDHHmmss").format("HH시"), - val: anlsRslt.predcOilq, + val: roundTo(anlsRslt.predcOilq, 2), })); return options; }; @@ -149,7 +159,7 @@ export const selectInftwtAnlsRslt = async(inftwtAnlsSn:number, pplMngNo:string) export const getChartDataOilq = (arr: TOilqData[]): { label: string; val: number }[] => { const options = arr.map(anlsRslt => ({ label: anlsRslt.mtrngDt, - val: anlsRslt.oilq, + val: roundTo(anlsRslt.oilq, 2), })); return options; }; @@ -203,7 +213,7 @@ export const selectPplRdiiQty = async(rainInfwtAnlsSn:number, pplMngNo:string) = }; export const getOptionsRdiiAnlsInfo = (anlsArr: TRdiiAnls[]): { text: string; val: number, hidden?: boolean }[] => { - const options = anlsArr.map(anls => ({ + const options : { text: string; val: number; hidden?: boolean }[] = anlsArr.map(anls => ({ text: anls.anlsInfo, val: anls.rainInfwtAnlsSn, })); @@ -211,7 +221,7 @@ export const getOptionsRdiiAnlsInfo = (anlsArr: TRdiiAnls[]): { text: string; va return options; }; -export const selectRdiiAnls = async(aplcnYm:String) => { // 침입수분석 +export const selectRdiiAnls = async(aplcnYm:string) => { // 침입수분석 try { const queryString = aplcnYm ? `aplcnYmFrom=${aplcnYm}&aplcnYmTo=${aplcnYm}` : ''; const result = await getData(`v1/rdiiAnls${queryString ? `?${queryString}` : ''}`); @@ -223,21 +233,22 @@ export const selectRdiiAnls = async(aplcnYm:String) => { // 침입수분석 console.error(error); } }; -export const getChartDataRdiiAnlsRslt = (arr: TRdiiAnlsRslt[]): { label: string; val: number }[] => { +export const getChartDataRdiiAnlsRslt = (arr: TRdiiAnlsRslt[]): { label: string; lineVal0: number, barVal0:number }[] => { const options = arr.map(anlsRslt => ({ label: moment(anlsRslt.anlsTm, "YYYYMMDDHHmmss").format("MM-DD HH시"), - val: anlsRslt.predcOilq, + lineVal0: roundTo(anlsRslt.predcOilq, 2), + barVal0: 0 })); return options; }; export const getChartDataRdiiAnlsRsltMinq = (arr: TRdiiAnlsRslt[]): { label: string; val: number }[] => { const options = arr.map(anlsRslt => ({ label: moment(anlsRslt.anlsTm, "YYYYMMDDHHmmss").format("MM-DD HH시"), - val: anlsRslt.minq, + val: roundTo(anlsRslt.minq, 2), })); return options; }; -export const selectRdiiAnlsRslt = async(rainInfwtAnlsSn:number, pplMngNo:string) => { // 침입수분석 결과 +export const selectRdiiAnlsRslt = async(rainInfwtAnlsSn:number, pplMngNo:string) => { // RDII분석 결과 try { if ((rainInfwtAnlsSn === undefined || rainInfwtAnlsSn === 0) || !pplMngNo) { console.warn("RdiiAnlsSn or pplMngNo is not provided"); @@ -245,10 +256,32 @@ export const selectRdiiAnlsRslt = async(rainInfwtAnlsSn:number, pplMngNo:string) } const result = await getData(`v1/rdiiAnlsRslt/ppl/${rainInfwtAnlsSn}/${pplMngNo}`); if (result && result.numberOfElements > 0) { - console.log("selectRdiiAnlsRslt", result.content); return result.content; } } catch (error) { console.error(error); } }; +export const selectRainData = async(rainInfwtAnlsSn:number) => { // 강우데이터 + try { + if ((rainInfwtAnlsSn === undefined || rainInfwtAnlsSn === 0)) { + console.warn("RdiiAnlsSn or pplMngNo is not provided"); + return []; + } + const result = await getData(`v1/rdiiAnlsRslt/rainData/${rainInfwtAnlsSn}`); + if (result && result.numberOfElements > 0) { + console.log("selectRainData", result.content); + return result.content; + } + } catch (error) { + console.error(error); + } +}; +export const getChartDataRainData = (arr: TRainData[]): { label: string; lineVal0: number, barVal0: number }[] => { + const options = arr.map(data => ({ + label: moment(data.mtrngDt, "YYYY-MM-DD HH:mm:ss").format("MM-DD HH시"), + lineVal0: 0, + barVal0: roundTo(data.amrf,2), + })); + return options; +}; \ No newline at end of file diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx index 345aade49..111b8f1af 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx @@ -5,7 +5,7 @@ import { Button, Input, Label } from '@common/components'; import React, { useEffect, useState } from 'react'; import moment from "moment"; -import type { TInftwtAnls, TOilqData } from './aplcnYms'; +import type { TInftwtAnls } from './aplcnYms'; import * as fn from './aplcnYms'; import { useInfilChartModalStore } from '../chartModalStore'; // 모달 상태 관리 스토어 type ModalOpenProp = { @@ -20,13 +20,13 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 const [selectedAplcnYm, setSelectedAplcnYm] = useState(""); const [inftwtAnlss, setInftwtAnlss] = useState([]); - const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState([{ val: 0, text: '선택' }]); // 침입수분석정보 options + const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 침입수분석정보 options const [inftwtAnls, setInftwtAnls] = useState(null); // 침입수분석정보 const [selectedInftwtAnls, setSelectedInftwtAnls] = useState(0); // 침입수분석 정보 선택시 사용 const [chartData, setChartData] = useState<{ chartData1: DataType[]; chartData2: DataType[] }>({ chartData1: [], chartData2: [], }); - const [inftwtQty, setInftwtQty] = useState(0); // 침입수량 + const [inftwtQty, setInftwtQty] = useState("0"); // 침입수량 const aplcnYms = fn.useAplcnYms('inftwtAnlsRslt'); // 상수사용월 데이터 가져오기 pplMngNo = "L" + pplMngNo; // inp파일에 하수관로와 맨홀의 번호가 같으면 분석 오류가 발행하여 @@ -56,7 +56,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { setInftwtAnls(null); // 침입수분석정보 초기화 setSelectedInftwtAnls(0); // 침입수분석 일련번호 초기화 setChartData({ chartData1: [], chartData2: [], }); // 차트 데이터 초기화 - setInftwtQty(0); // 침입수량 초기화 + setInftwtQty("0"); // 침입수량 초기화 } }, [isModalOpen]); @@ -133,7 +133,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { }); fn.selectPplInftwtQty(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수량 조회 if (data > 0) { - setInftwtQty(data); + setInftwtQty(data.toLocaleString('ko-KR')); } }); setPopupOpen(false); diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx index 9735c772f..0270b8dec 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx @@ -5,13 +5,20 @@ import { Button, Input, Label } from '@common/components'; import React, { useEffect, useState } from 'react'; import moment from "moment"; -import type { TRdiiAnls, TRdiiAnlsRslt, TOilqData } from './aplcnYms'; +import type { TRdiiAnls } from './aplcnYms'; import * as fn from './aplcnYms'; import { useRdiiChartModalStore } from '../chartModalStore'; // 모달 상태 관리 스토어 + type ModalOpenProp = { pplMngNo: string, // 관로관리번호 } -type DataType = {label: string; val: number}; +// 차트 +type lineBarChartData = { + label : string; + lineVal0 : number; + barVal0 : number; +} + const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useRdiiChartModalStore(); // 모달 상태 관리 스토어 @@ -20,17 +27,16 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 const [aplcnYm, setAplcnYm] = useState(""); const [rdiiAnlss, setRdiiAnlss] = useState([]); - const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState([{ val: 0, text: '선택' }]); // 강우유입수분석정보 options + const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 강우유입수분석정보 options const [rdiiAnls, setRdiiAnls] = useState(null); // 강우유입수분석정보 const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState(0); // 강우유입수분석 정보 선택시 사용 - const [chartData, setChartData] = useState<{ chartData1: DataType[]; }>({ - chartData1: [], //chartData2: [], - }); - const [rdiiQty, setRdiiQty] = useState(0); // 강우유입수량 - const [totalVolume, setTotalVolume] = useState(0); // 총체적 m³/mm + const [chartData, setChartData] = useState([]); + const [rdiiQty, setRdiiQty] = useState("0"); // 강우유입수량 + const [rainTotal, setRainTotal] = useState("0"); // 적용강우량 + const [totalVolume, setTotalVolume] = useState("0"); // 총체적 m³/mm const aplcnYms = fn.useAplcnYms('rdiiAnlsRslt'); // 상수사용월 데이터 가져오기 pplMngNo = "L"+pplMngNo; // "L13101003"; - console.log("pplMngNo", pplMngNo); + const openPopup = () => { if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 @@ -42,10 +48,16 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { setPopupOpen(true); // 팝업 열기 }; - const chartForm = CHART_FORM.LINE; + const chartForm = CHART_FORM.LINEBAR2; const chartConfig = { - chartData1: { label: 'RDII', color: "red", }, - //chartData2: { label: '순오수량(m³/h)', color: "orange", }, + barVal0: { + label: '강수량', + color: "#33AAFF", + }, + lineVal0: { + label: 'RDII', + color: "#FF0000", + }, } as ChartConfig; useEffect(() => { @@ -56,9 +68,10 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { setRdiiAnlsInfos([{ val: 0, text: '선택' }]); // 강우유입수분석정보 초기화 setRdiiAnls(null); // 강우유입수분석정보 초기화 setRainInfwtAnlsSn(0); // 강우유입수분석 일련번호 초기화 - setChartData({chartData1: [], }); // 차트 데이터 초기화 - setRdiiQty(0); // 강우유입수량 초기화 - setTotalVolume(0); // 총체적 m³/mm 초기화 + setChartData([]); // 차트 데이터 초기화 + setRdiiQty("0"); // 강우유입수량 초기화 + setRainTotal("0"); // 적용강우량 초기화 + setTotalVolume("0"); // 총체적 m³/mm 초기화 } }, [isModalOpen]); @@ -70,7 +83,6 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { setRdiiAnlsInfos([]); return; } - console.log(moment(aplcnYm).format("YYYY-MM")); fn.selectRdiiAnls(aplcnYm) .then((data) => { setRdiiAnlss(data ?? []); // 선택한 월의 분석성공한 RDII분석데이터[] @@ -93,29 +105,38 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { if (selectedAnls) { setRdiiAnls(selectedAnls); } - }, [rainInfwtAnlsSn]); + }, [rainInfwtAnlsSn, rdiiAnlss]); const closePopup = () => { // 팝업 닫기 setPopupOpen(false); } const rdiiRsltChart = () => { // 그래프 & 팝업 닫기 - fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, pplMngNo).then((data) => {// 강우유입수분석 결과 조회 - const rsltData1 = fn.getChartDataRdiiAnlsRslt(data ?? []); - //const rsltData2 = fn.getChartDataRdiiAnlsRsltMinq(data ?? []); - setChartData(prev => ({ - chartData1: rsltData1, // 순오수량(m³/h) - // chartData2: rsltData2 // RDII(m³/h) 강우유입수분석결과 - })); - }); + fetchFilteredData(); fn.selectPplRdiiQty(rainInfwtAnlsSn, pplMngNo).then((data) => {// 강우유입수량 조회 - setRdiiQty(Math.round(data.rdiih*24)); // cmd: m³/d 단위로 변환 + setRdiiQty(Math.round(data.rdiih*24).toLocaleString('ko-KR')); // cmd: m³/d 단위로 변환 if (data.raintotal && data.rainTotal !== 0) { - setTotalVolume(Math.round(data.rdiih * data.sustaintime / data.raintotal)); // 총체적 m³/mm + setRainTotal(data.raintotal.toLocaleString('ko-KR')); // 총강우량mm + setTotalVolume(Math.round(data.rdiih * data.sustaintime / data.raintotal).toLocaleString('ko-KR')); // 총체적 m³/mm } }); setPopupOpen(false); }; + const fetchFilteredData: () => Promise = async () => { + const rsltData = await fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, pplMngNo); + const rsltDataformatted = fn.getChartDataRdiiAnlsRslt(rsltData ?? []); // format 변경 + const rainData = await fn.selectRainData(rainInfwtAnlsSn); + const rainDataformatted = fn.getChartDataRainData(rainData ?? []); // format 변경 + const rainMap = new Map(rainDataformatted.map(item => [item.label, item.barVal0])); + + rsltDataformatted.forEach(item => { + const matchedVal = rainMap.get(item.label); + if (matchedVal !== undefined) { + item.barVal0 = matchedVal; + } + }); + setChartData(rsltDataformatted); // 차트 데이터 설정 + } const renderBody = () => ( <>
RDII분석결과
@@ -143,6 +164,11 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { {rdiiQty} m³/d
+
+ 적용강우량 : + {rainTotal} + mm +
강우량대비RDII : {totalVolume} @@ -153,7 +179,7 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { {/* 차트 */}
+ yFieldText2="강수량(mm)" yFieldText="유량(m³/h)" />
{popupOpen && ( diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx index 18cac4c00..2cde31b64 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx @@ -8,8 +8,8 @@ const exclnInputColumn = [ ] // 오수펌프 자체 입력 정보 const drtwInputColumn = [ - { id: "swtrmt_zone_cd", title: "배수구역", type: "input" }, - { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, + { id: "swtrmt_zone_cd", title: "처리구역", type: "input" }, + { id: "mltv_dtdv_nm", title: "처리분구", type: "input" }, ] // 하수펌프장 diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx index d601aeafd..f1cda25c1 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/smh.tsx @@ -1,6 +1,7 @@ // 하수맨홀 import { VerticalTable } from '@/components/ui/verticalTable'; -import React, { useState, useEffect } from 'react' +import { useEffect, useState } from 'react'; + const inputColumn = [ { id: "swtrmtZoneCd", title: "처리구역", type: "input" }, { id: "mltvZoneNm", title: "배수구역", type: "input" }, diff --git a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx index 079557972..5259fdab6 100644 --- a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx @@ -12,6 +12,8 @@ import RdiiChart from './components/popupComponent/chart/rdiiChart'; import { useInfilChartModalStore, useRdiiChartModalStore } from "./components/popupComponent/chartModalStore"; import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; import { codeMapper } from "./components/common/codeMapper"; +import { searchUpperPpls } from '@common/business_components/ui/map/Gis'; +import LoadingOverlay from '@/components/ui/LoadingBar'; const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => { @@ -33,6 +35,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, const { setIsOpen: setIsModalRdii } = useRdiiChartModalStore(); const [curPage, setCurPage] = useState(1); // 현재 페이지 상태 const [isClicked, setIsClicked] = useState(true); // 클릭 여부 상태 + const [isLoading, setIsLoading] = useState(false); // 로딩 상태 // 🎯 관로관리번호 추출 const pplMngNo = featureData?.values_?.fclt_mng_no || ""; @@ -92,13 +95,13 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, else if (lyrTyNm === 'tb_ms_swage_pmstn') { // 하수펌프장 const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장'); if (drtwPumpCode && featureData.values_.usg_cd.trim() === drtwPumpCode.val) { - setIsDrtwSewagePump(false); - setHeadlist(ExclnSewagePumpCol); - } - else { setIsDrtwSewagePump(true); setHeadlist(DrtwSewagePumpCol); } + else { + setIsDrtwSewagePump(false); + setHeadlist(ExclnSewagePumpCol); + } setLayerSpecificNm(featureData.values_.fclt_mng_no); result = '하수펌프장'; } @@ -469,42 +472,59 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, return (