Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/wr/sttn
@@ -0,0 +1,19 @@
|
||||
name: iipbs_sn
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [iipbs/sn/dev]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: appleboy/ssh-action@master
|
||||
with:
|
||||
port: 11022
|
||||
host: con.mind-one.co.kr
|
||||
username: mindone
|
||||
password: mindone!23
|
||||
script: |
|
||||
cd ./iipbs/front
|
||||
sh ./frontStartSn.sh
|
||||
@@ -0,0 +1,44 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
.next/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
#.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
pnpm-lock.yaml
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { BaseLayout } from "@common/components";
|
||||
|
||||
export function ClientLayout({ children }: { children: React.ReactNode }) {
|
||||
return <BaseLayout>{children}</BaseLayout>;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || '';
|
||||
|
||||
export const postData = async (url: string, data: object): Promise<any> => {
|
||||
const options: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
const res = await fetch(`${BASE_URL}/${url}`, options);
|
||||
const resData = await res.json();
|
||||
return resData;
|
||||
};
|
||||
|
||||
export const postMutiPart = async (url: string, data: FormData): Promise<any> => {
|
||||
const options: RequestInit = {
|
||||
method: "POST",
|
||||
body: data,
|
||||
};
|
||||
const res = await fetch(`${BASE_URL}/${url}`, options);
|
||||
const resData = await res.json();
|
||||
return resData;
|
||||
};
|
||||
|
||||
export const putData = async (url: string, data: object): Promise<any> => {
|
||||
const options: RequestInit = {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
const res = await fetch(`${BASE_URL}/${url}`, options);
|
||||
const resData = await res.json();
|
||||
return resData;
|
||||
};
|
||||
|
||||
export const getData = async (url: string): Promise<any> => {
|
||||
const res = await fetch(`${BASE_URL}/${url}`, {
|
||||
method: "GET",
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
return data;
|
||||
}
|
||||
throw new Error('Failed to fetch data');
|
||||
};
|
||||
|
||||
export const deleteData = async (url: string, data?: object): Promise<any> => {
|
||||
const options: RequestInit = {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
};
|
||||
const res = await fetch(`${BASE_URL}/${url}`, options);
|
||||
const resData = await res.json();
|
||||
return resData;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ContentsDiv, MainDiv, Title } from '@common/business_components/ui'
|
||||
import React, { type ReactNode } from 'react'
|
||||
type LayoutNode = {
|
||||
children : ReactNode
|
||||
title : string
|
||||
}
|
||||
const ContentLayout = ({children, title} : LayoutNode) => {
|
||||
return (
|
||||
<MainDiv>
|
||||
<Title title={title} />
|
||||
<ContentsDiv>
|
||||
{children}
|
||||
</ContentsDiv>
|
||||
</MainDiv>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentLayout
|
||||
@@ -0,0 +1,64 @@
|
||||
"use client"
|
||||
import { FileUpload, SelectBox } from '@common/business_components/ui';
|
||||
|
||||
import { Button, Input } from '@common/components/ui';
|
||||
import React, { useState } from 'react'
|
||||
|
||||
const FileUploadForm = ({ title }: { title: string }) => {
|
||||
const [files, setFiles] = useState<File[] | null>(null);
|
||||
const [selectedYear, setSelectedYear] = useState<string>('');
|
||||
|
||||
// 현재 년도부터 10년 전까지의 년도 옵션 생성
|
||||
const yearOptions = Array.from({ length: 10 }, (_, i) => {
|
||||
const year = new Date().getFullYear() - i;
|
||||
return { text: `${year}년`, val: year.toString() };
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='card-header'>
|
||||
<h3 className="text-2xl font-medium">{title}</h3>
|
||||
</div>
|
||||
<div className="card p-6">
|
||||
<div className="grid grid-col-2 items-center space-x-4">
|
||||
<div className='flex'>
|
||||
<SelectBox
|
||||
className="w-50 mr-3 ml-4"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="년도"
|
||||
selectArray={yearOptions}
|
||||
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
|
||||
/><SelectBox
|
||||
className="w-50"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="분기"
|
||||
selectArray={yearOptions}
|
||||
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex mt-5 items-center'>
|
||||
<div className="w-[200px] mr-4">
|
||||
<Input id="file" type="file" />
|
||||
</div>
|
||||
<Button
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (!files?.[0] || !selectedYear) {
|
||||
alert('파일과 등록년도를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
// TODO: 파일 업로드 처리
|
||||
console.log('Upload file:', files[0], 'Year:', selectedYear);
|
||||
}}
|
||||
>
|
||||
등록
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileUploadForm
|
||||
@@ -0,0 +1,24 @@
|
||||
import { FormField } from '@common/business_components/ui'
|
||||
import React, { type ReactElement } from 'react'
|
||||
|
||||
interface FormSectionProps {
|
||||
children: ReactElement
|
||||
titleNm: string
|
||||
form: any
|
||||
name: string
|
||||
}
|
||||
|
||||
const FormSection = ({ children, titleNm, form, name }: FormSectionProps) => {
|
||||
return (
|
||||
<div className="grid grid-cols-[130px_1fr] items-center">
|
||||
<div className="text-xl font-medium">{titleNm}</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => children}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormSection
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose, DialogOverlay } from '@common/components/ui'
|
||||
import React from 'react'
|
||||
|
||||
interface ModalProps {
|
||||
isModalOpen: boolean;
|
||||
setIsModalOpen: (open: boolean) => void;
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ isModalOpen, setIsModalOpen, title, children, closeModal }) => {
|
||||
return (
|
||||
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||
<DialogOverlay className='z-[200]'/>
|
||||
<DialogContent className="w-auto max-w-[1200px] h-auto max-h-[450px] overflow-auto z-[1000]">
|
||||
{title ? <DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader> : null}
|
||||
{children}
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closeModal}>닫기</button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
||||
@@ -0,0 +1,127 @@
|
||||
import UiPageNations from "@common/business_components/ui/table/uiPageNations";
|
||||
import { Table, TableBody, TableCell, TableRow } from "@common/components";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type UiTableProps = {
|
||||
headName: React.ReactNode | undefined;
|
||||
tableData: { [key: string]: string | number | boolean | object }[];
|
||||
headlist: UiHeadProps[];
|
||||
pageSize: number;
|
||||
total: number;
|
||||
children: React.ReactNode | undefined;
|
||||
cellClick: ((index: number | undefined, headId: string) => void) | undefined;
|
||||
onPageChange: (page: number) => void;
|
||||
className?: string;
|
||||
resetPageNo?: boolean;
|
||||
isEditable?: boolean;
|
||||
};
|
||||
|
||||
type UiHeadProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
align?: string;
|
||||
type?: string;
|
||||
options?: string[];
|
||||
};
|
||||
|
||||
export function VerticalTable({
|
||||
tableData,
|
||||
headlist,
|
||||
pageSize,
|
||||
total,
|
||||
children,
|
||||
headName,
|
||||
cellClick,
|
||||
onPageChange,
|
||||
className = "",
|
||||
resetPageNo = false,
|
||||
isEditable = false,
|
||||
}: UiTableProps) {
|
||||
const [cur, setCur] = useState<number>(1);
|
||||
const [formData, setFormData] = useState<{ [key: string]: string | number | boolean }>({});
|
||||
useEffect(() => {
|
||||
if (resetPageNo) {
|
||||
setCur(1);
|
||||
onPageChange(1);
|
||||
}
|
||||
}, [onPageChange, resetPageNo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableData.length > 0 && tableData[0]) {
|
||||
setFormData(tableData[0] as { [key: string]: string | number | boolean });
|
||||
}
|
||||
}, [tableData]);
|
||||
|
||||
const handleInputChange = (id: string, value: string | number | boolean) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[id]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCur(page);
|
||||
onPageChange(page);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{headName ? (
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">{headName}</h3>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
|
||||
<Table variant="secondary">
|
||||
<TableBody className="text-center">
|
||||
{headlist?.map((head) => (
|
||||
<TableRow key={head.id}>
|
||||
{/* 컬럼 제목 */}
|
||||
<TableCell className="font-bold bg-blue-100 text-xs">
|
||||
{head.title}
|
||||
</TableCell>
|
||||
{/* 데이터 값 또는 입력 필드 */}
|
||||
<TableCell className="text-xs">
|
||||
{isEditable ? (
|
||||
head.type === "select" ? (
|
||||
<select
|
||||
value={formData[head.id] ?? "-"}
|
||||
onChange={(e) => handleInputChange(head.id, e.target.value)}
|
||||
className="w-full border border-gray-300 rounded-sm px-2 py-1 text-xl border-radius-0"
|
||||
>
|
||||
{head.options?.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : head.type === "input" ? (
|
||||
<input
|
||||
type="text"
|
||||
value={formData[head.id] ?? ""}
|
||||
onChange={(e) => handleInputChange(head.id, e.target.value)}
|
||||
className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1"
|
||||
/>
|
||||
) : (
|
||||
<span>{formData[head.id] ?? "-"}</span>
|
||||
)
|
||||
) : (
|
||||
<span>{formData[head.id] ?? "-"}</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{total > 0 && (
|
||||
<UiPageNations
|
||||
total={total}
|
||||
cur={cur}
|
||||
pageSize={pageSize}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
'use client'
|
||||
import ContentLayout from '@/components/ui/contentLayout'
|
||||
import { SearchDiv, SearchFormLeft, SelectBox, SearchFormRight, Form, UiTable } from '@common/business_components/ui'
|
||||
import ListDiv from '@common/business_components/ui/list_div'
|
||||
import { Button } from '@common/components/ui'
|
||||
import React, { type ReactNode } from 'react'
|
||||
type TitleName = {
|
||||
mainTitle: string
|
||||
firstCardTitle: string
|
||||
secondCardTitle?: string
|
||||
}
|
||||
type SelectBox = {
|
||||
labelNm?:Label
|
||||
searchChk?:boolean
|
||||
}
|
||||
type Label= {
|
||||
first: string
|
||||
second: string
|
||||
third:string
|
||||
fourth:string
|
||||
}
|
||||
type BaseLayout = {
|
||||
children?: ReactNode[]
|
||||
titleName: TitleName
|
||||
form: any
|
||||
onSubmitEvent: Function
|
||||
graphBtn?: boolean
|
||||
firstDivBtn?:boolean
|
||||
searchBox?:SelectBox
|
||||
}
|
||||
// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ?
|
||||
/**
|
||||
*
|
||||
* @param children
|
||||
* @param titleName : 메인 타이틀, 첫번째 요소 타이틀, 두번째 요소 타이틀
|
||||
* @param form : zod 스키마
|
||||
* @param onSubmitEvent : 폼 이벤트 처리
|
||||
* @param graphBtn : 하단에 그래프 버튼 유무 처리
|
||||
* @returns
|
||||
*/
|
||||
const BaseLayout = ({ children,titleName, form, onSubmitEvent, graphBtn=false, searchBox }: BaseLayout) => {
|
||||
return (
|
||||
<ContentLayout title={titleName.mainTitle}>
|
||||
<SearchDiv>
|
||||
<SearchFormLeft>
|
||||
<SelectBox className="col-span-2" label={searchBox?.labelNm?.first ?? ''} selectArray={[{ text: '한강 유역청', val: '1' }]} onSelectValue={(val) => { }} selectClass={undefined} />
|
||||
<SelectBox label={searchBox?.labelNm?.second ?? ''} selectArray={[{ text: '경기도', val: '1' }]} className="col-span-2" onSelectValue={() => { }} selectClass='' />
|
||||
<SelectBox label={searchBox?.labelNm?.third ?? ''} selectArray={[{ text: '양평군', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
|
||||
<SelectBox label={searchBox?.labelNm?.fourth ?? ''} selectArray={[{ text: '양평하수처리장', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
|
||||
</SearchFormLeft>
|
||||
{searchBox?.searchChk ? <SearchFormRight><Button>조회</Button></SearchFormRight> : null}
|
||||
</SearchDiv>
|
||||
<ListDiv>
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">{titleName.firstCardTitle}</h3>
|
||||
<div>
|
||||
{graphBtn ? <Button className="!mr-[1.2rem]">다운로드</Button> : null}
|
||||
{/* 그래프 있을 시 */}
|
||||
{graphBtn ? <Button className="!mr-[1.2rem]">삭제</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmitEvent)}>
|
||||
<div className="card !m-[1.2rem]">
|
||||
{/* 폼 정보 입력 칸 */}
|
||||
{children?.[0]}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</ListDiv>
|
||||
{children?.[1] != null ? <ListDiv>
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-medium">{titleName.secondCardTitle}</h3>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem]">{children?.[1]}</div>
|
||||
</ListDiv> : null}
|
||||
</ContentLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default BaseLayout
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import { FormField, SelectBox } from '@common/business_components/ui'
|
||||
import { Button, DatePicker, Input } from '@common/components/ui'
|
||||
import { z } from 'zod'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import FormSection from '@/components/ui/formSection'
|
||||
import BaseLayout from '../BaseLayout'
|
||||
const formSchema = z.object({
|
||||
managenm: z.string(),
|
||||
region: z.string(),
|
||||
suyeonga: z.number(),
|
||||
inputdate: z.coerce.date(),
|
||||
rtkSource: z.string(),
|
||||
suyeongaPlace: z.string()
|
||||
})
|
||||
const region: { text: string, val: number }[] = [{ text: '양평', val: 1 }, { text: '강남', val: 2 }, { text: 'test', val: 3 }]
|
||||
const IrSetting = () => {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
managenm: "",
|
||||
region: "",
|
||||
suyeonga: 0,
|
||||
inputdate: new Date(),
|
||||
rtkSource: "",
|
||||
suyeongaPlace: ""
|
||||
}
|
||||
})
|
||||
const [selectedDate, setSelectedDate] = useState(new Date())
|
||||
return (
|
||||
<BaseLayout titleName={{ mainTitle: '침입수 산정', firstCardTitle: '실측자료 등록', secondCardTitle: '침입수 분석자료 입력' }} form={form} onSubmitEvent={() => { }} graphBtn={false}>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name='managenm' form={form} titleNm='○ 관로 관리번호'>
|
||||
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date);}} mode={"single"} />
|
||||
</FormSection>
|
||||
<FormSection name='region' form={form} titleNm='○ 파일위치'>
|
||||
<div className="w-[150px]">
|
||||
<Input id="fileNm" type="file" />
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<FormSection name='suyeongaPlace' form={form} titleNm='○ 오수관망도'><div className="w-[150px]">
|
||||
<Input id="fileNm" type="file" />
|
||||
</div></FormSection>
|
||||
<FormSection name='suyeonga' form={form} titleNm='○ 수용가-맨홀 매칭결과'><div className="w-[150px]">
|
||||
<Input id="fileNm" type="file" />
|
||||
</div></FormSection>
|
||||
<FormSection name='inputdate' form={form} titleNm='○ 실수사용량 적용기간'>
|
||||
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} mode={"single"} />
|
||||
</FormSection>
|
||||
<FormSection name='rtkSource' form={form} titleNm='○ 오수전환율'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
|
||||
<div className="flex justify-end mt-1">
|
||||
<Button type="submit">침입수분석 실행</Button>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default IrSetting
|
||||
@@ -0,0 +1,156 @@
|
||||
"use client"
|
||||
|
||||
import { date, z } from "zod"
|
||||
import { useForm, useFieldArray } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, Button, DatePicker } from "@common/components/ui"
|
||||
import { UiTable } from "@common/business_components/ui"
|
||||
import React, { useState } from "react"
|
||||
import BaseLayout from "@/ii/BaseLayout"
|
||||
import FormSection from "@/components/ui/formSection"
|
||||
|
||||
// TODO: 컴포넌트 호출 ~ 필요 코드 수정해야댐
|
||||
// zod 스키마 정의
|
||||
const formSchema = z.object({
|
||||
region: z.string(),
|
||||
rdiiArea: z.string(),
|
||||
rtkSource: z.string(),
|
||||
unitFactors: z.array(z.object({
|
||||
response: z.string(),
|
||||
R: z.string(),
|
||||
T: z.string(),
|
||||
K: z.string(),
|
||||
})),
|
||||
initialDepths: z.array(z.object({
|
||||
response: z.string(),
|
||||
Dmax: z.string(),
|
||||
Drec: z.string(),
|
||||
Do: z.string(),
|
||||
})),
|
||||
suyeonga: z.string(),
|
||||
rate: z.string(),
|
||||
date: z.coerce.date(),
|
||||
rain: z.string(),
|
||||
})
|
||||
|
||||
export default function RTKForm() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
region: "",
|
||||
rdiiArea: "",
|
||||
rtkSource: "",
|
||||
unitFactors: [
|
||||
{ response: "Short-Term", R: "", T: "", K: "" },
|
||||
{ response: "Medium-Term", R: "", T: "", K: "" },
|
||||
{ response: "Long-Term", R: "", T: "", K: "" },
|
||||
],
|
||||
initialDepths: [
|
||||
{ response: "Short-Term", Dmax: "", Drec: "", Do: "" },
|
||||
{ response: "Medium-Term", Dmax: "", Drec: "", Do: "" },
|
||||
{ response: "Long-Term", Dmax: "", Drec: "", Do: "" },
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const { fields: unitFactorFields } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "unitFactors"
|
||||
})
|
||||
|
||||
const { fields: initialDepthFields } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "initialDepths"
|
||||
})
|
||||
|
||||
const onSubmit = (data: z.infer<typeof formSchema>) => {
|
||||
console.log("✅ 제출 데이터:", data)
|
||||
}
|
||||
|
||||
// 단위도 계수 테이블 설정
|
||||
const unitFactorHeadlist = [
|
||||
{ id: 'response', title: 'Response' },
|
||||
{ id: 'R', title: 'R' },
|
||||
{ id: 'T', title: 'T' },
|
||||
{ id: 'K', title: 'K' }
|
||||
]
|
||||
|
||||
// 초기 차단 깊이 테이블 설정
|
||||
const initialDepthHeadlist = [
|
||||
{ id: 'response', title: 'Response' },
|
||||
{ id: 'Dmax', title: 'Dmax' },
|
||||
{ id: 'Drec', title: 'Drec' },
|
||||
{ id: 'Do', title: 'Do' }
|
||||
]
|
||||
// 테이블 페이지 변경 핸들러
|
||||
const handlePageChange = (page: number) => {
|
||||
console.log('Page changed:', page)
|
||||
}
|
||||
const [selectedDate, setSelectedDate] = useState(new Date())
|
||||
return (
|
||||
<BaseLayout titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력' }} form={form} onSubmitEvent={() => { }} graphBtn={false}>
|
||||
{[<div className="space-y-[1.2rem]" key="form-section">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처"><Input
|
||||
className="h-10 text-center text-xl w-[150px]"
|
||||
placeholder=""
|
||||
/></FormSection>
|
||||
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)"><Input
|
||||
className="h-10 text-center text-xl w-[150px]"
|
||||
placeholder=""
|
||||
/></FormSection></div>
|
||||
<div className="grid grid-cols-2">
|
||||
<div>
|
||||
<div className="mb-[1.2rem] text-xl font-medium">○ 단위도 계수</div>
|
||||
<UiTable
|
||||
headName=""
|
||||
className="w-[400px]"
|
||||
tableData={unitFactorFields}
|
||||
headlist={unitFactorHeadlist}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
onPageChange={handlePageChange}
|
||||
cellClick={undefined}
|
||||
>
|
||||
{null}
|
||||
</UiTable>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-[1.2rem] text-xl font-medium">○ 초기 차단 깊이</div>
|
||||
<UiTable
|
||||
headName=""
|
||||
className="w-[400px]"
|
||||
tableData={initialDepthFields}
|
||||
headlist={initialDepthHeadlist}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
onPageChange={handlePageChange}
|
||||
cellClick={undefined}
|
||||
>
|
||||
{null}
|
||||
</UiTable>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[150px]">
|
||||
<Input id="fileNm" type="file" />
|
||||
</div></FormSection>
|
||||
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[150px]">
|
||||
<Input id="fileNm2" type="file" />
|
||||
</div></FormSection>
|
||||
<FormSection name='date' form={form} titleNm='○ 실수사용량 적용기간'>
|
||||
<DatePicker selected={selectedDate} onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} mode={"single"} />
|
||||
</FormSection>
|
||||
<FormSection name='rate' form={form} titleNm='○ 오수전환율'><Input className="h-[3.2rem] text-center text-xl w-[150px]" /></FormSection>
|
||||
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[150px]">
|
||||
<Input id="fileNm3" type="file" />
|
||||
</div></FormSection>
|
||||
</div>
|
||||
<div className="flex justify-end mt-1">
|
||||
<Button type="submit">RDII분석 실행</Button>
|
||||
</div>
|
||||
</div>
|
||||
]}
|
||||
</BaseLayout >
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
'use client'
|
||||
import React, { useState } from 'react'
|
||||
import { FormField, SelectBox, UiTable } from '@common/business_components/ui'
|
||||
import { Button, DatePicker, Input } from '@common/components/ui'
|
||||
import { z } from 'zod'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import FormSection from '@/components/ui/formSection'
|
||||
import BaseLayout from '../BaseLayout'
|
||||
import { title } from 'process'
|
||||
const formSchema = z.object({
|
||||
managenm: z.string(),
|
||||
region: z.string(),
|
||||
suyeonga: z.number(),
|
||||
inputdate: z.coerce.date(),
|
||||
rtkSource: z.string(),
|
||||
suyeongaPlace: z.string()
|
||||
})
|
||||
// TODO: 분석구분에 따라서 테이블 컬럼 변경필요
|
||||
const tableColumn = [
|
||||
{ id: 'no', title: '번호' },
|
||||
{ id: 'hasu', title: '하수처리장' },
|
||||
{ id: 'guyeok', title: '처리구역' },
|
||||
{ id: 'sangsu', title: '상수 사용량' },
|
||||
{ id: 'rate', title: '오수 전환율' },
|
||||
{ id: 'water', title: '순오수량' },
|
||||
{ id: 'measureWater', title: '측정하수량' },
|
||||
{ id: 'ii', title: '침입수량' },
|
||||
{ id: 'date', title: '분석일자' },
|
||||
{ id: 'usr', title: '분석자' }
|
||||
]
|
||||
const tableInfoData = [{ 'no': '1', 'hasu': '양평하수처리장', 'guyeok': '양평', 'sangsu': '1000', 'rate': '0.5', 'water': '500', 'measureWater': '1000', 'ii': '500', 'date': '2023-10-01', 'usr': '홍길동' }]
|
||||
const Result = () => {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
managenm: "",
|
||||
region: "",
|
||||
suyeonga: 0,
|
||||
inputdate: new Date(),
|
||||
rtkSource: "",
|
||||
suyeongaPlace: ""
|
||||
}
|
||||
})
|
||||
return (
|
||||
<BaseLayout searchBox={{labelNm: {first: "분석기간",second: "침입수",third: "처리장",fourth: "처리구역",},searchChk: true,}} titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력' }} form={form} onSubmitEvent={() => { }} graphBtn={true}>
|
||||
{[<div key='table1'>
|
||||
<div className="mb-[1.2rem] text-xl font-medium">○ 침입수 산정결과 현황</div>
|
||||
<UiTable
|
||||
headName=''
|
||||
headlist={tableColumn}
|
||||
tableData={tableInfoData}
|
||||
total={0}
|
||||
pageSize={10}
|
||||
children={undefined}
|
||||
cellClick={(e) => { }}
|
||||
onPageChange={(e) => { }}
|
||||
/>
|
||||
</div>]}
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Result
|
||||
@@ -0,0 +1,37 @@
|
||||
import './styles/base.css'
|
||||
import './styles/components.css'
|
||||
import type { Metadata } from "next";
|
||||
import { cookies } from "next/headers";
|
||||
import { StoreProvider } from './store'
|
||||
import { ClientLayout } from './clientLayout'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "디지털 하수관로",
|
||||
description: "디지털 하수관로 시스템입니다",
|
||||
keywords: ["하수관로"],
|
||||
openGraph: {
|
||||
title: "디지털 하수관로",
|
||||
description: "디지털 하수관로 시스템입니다"
|
||||
}
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
|
||||
const theme = (await cookies()).get("site-theme-key");
|
||||
|
||||
return (
|
||||
<html lang="en" data-theme={theme?.value}>
|
||||
<body>
|
||||
<StoreProvider>
|
||||
<ClientLayout>
|
||||
{children}
|
||||
</ClientLayout>
|
||||
</StoreProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,796 @@
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import { UiModal } from '@common/business_components/ui/dialog_ui';
|
||||
import { Button } from '@common/components';
|
||||
import { UiTable2 } from '@common/business_components/ui/table/index-2';
|
||||
import { CommonTableColumn } from "./table";
|
||||
|
||||
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
const TABS = [
|
||||
{ key: 'basic', label: '기본정보' },
|
||||
{ key: 'operation', label: '운영정보' },
|
||||
{ key: 'report', label: '하수도정비기본계획보고서' },
|
||||
];
|
||||
|
||||
const WORK_TYPES = [
|
||||
{ key: 'maintenance', label: '보수 및 갱신' },
|
||||
{ key: 'cleaning', label: '청소 및 준설' },
|
||||
{ key: 'inspection', label: '점검 및 진단' },
|
||||
];
|
||||
|
||||
const YEARS = Array.from({ length: 100 }, (_, i) => `${2025 - i}`);
|
||||
|
||||
// 하수처리구역 테이블 헤더 리스트
|
||||
const swageZoneTableHeadlist = [
|
||||
// { id: 'ctpvCd', title: '시도코드' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
// { id: 'sggCd', title: '시군구코드' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
// { id: 'swtrmtZoneSn', title: '하수처리구역번호' },
|
||||
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
|
||||
{ id: 'swtrmtFcltCd', title: '하수처리시설번호' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
]
|
||||
// 하수처리시설 테이블 헤더 리스트
|
||||
const swageFcltTableHeadlist = [
|
||||
{ id: 'swtrmtFcltCd', title: '하수처리시설코드' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
{ id: 'fcltCpct', title: '시설용량' },
|
||||
{ id: 'rgnExpln', title: '지역설명' },
|
||||
{ id: 'cmcnYmd', title: '준공일자' },
|
||||
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
|
||||
{ id: 'clsgYmd', title: '폐쇄일자' },
|
||||
{ id: 'laeNm', title: '지자체명' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
{ id: 'sareaNm', title: '권역명' },
|
||||
{ id: 'lrgeSareaNm', title: '대권역명' },
|
||||
{ id: 'rvrNm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'daddr', title: '상세주소' },
|
||||
{ id: 'rdnmAddr', title: '도로명주소' },
|
||||
{ id: 'dtlRdnmAddr', title: '상세도로명주소' },
|
||||
{ id: 'dschgWtstNm', title: '방류수계명' },
|
||||
{ id: 'pllnArea', title: '부지면적' },
|
||||
{ id: 'mngRprtvBzentyNm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수' },
|
||||
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계' },
|
||||
{ id: 'geom', title: 'geometry' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
{ id: 'rprsMsrcCn', title: '대표공법내용' },
|
||||
{ id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' },
|
||||
{ id: 'sttyCrtrToc', title: '법정기준TOC' },
|
||||
{ id: 'sttyCrtrSs', title: '법정기준SS' },
|
||||
{ id: 'sttyCrtrTn', title: '법정기준총질소' },
|
||||
{ id: 'sttyCrtrTph', title: '법정기준총인' },
|
||||
{ id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' },
|
||||
{ id: 'bsnNm', title: '유역명' },
|
||||
];
|
||||
|
||||
// '구분' 선택 목록
|
||||
const BASIC_TYPES = [
|
||||
{ key: 'SEWER_PIPE', label: '하수관로' },
|
||||
{ key: 'tb_il_swage_prcs_fclt', label: '하수처리시설' },
|
||||
{ key: 'tb_ms_swage_prcs_zone', label: '하수처리구역' },
|
||||
{ key: 'MANHOLE', label: '하수맨홀' },
|
||||
{ key: 'WASTEWATER_PUMP', label: '오수펌프' },
|
||||
{ key: 'RAINWATER_PUMP', label: '빗물펌프' },
|
||||
{ key: 'OUTFALL', label: '우수토실' },
|
||||
{ key: 'RETENTION_FACILITY', label: '하수저류시설' },
|
||||
{ key: 'PRIORITY_OUTFALL', label: '중점우수토실' },
|
||||
];
|
||||
|
||||
const sidoList = [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 26, name: '부산광역시' },
|
||||
{ id: 28, name: '인천광역시' },
|
||||
{ id: 30, name: '대전광역시' },
|
||||
{ id: 41, name: '경기도' },
|
||||
{ id: 42, name: '강원도' },
|
||||
{ id: 44, name: '충청남도' },
|
||||
{ id: 45, name: '전라북도' },
|
||||
{ id: 46, name: '전라남도' },
|
||||
{ id: 47, name: '경상북도' },
|
||||
{ id: 48, name: '경상남도' },
|
||||
];
|
||||
|
||||
// 시도별 시군구 리스트 매핑
|
||||
const sigunguMap: Record<number, { id: number; name: string }[]> = {
|
||||
26: [
|
||||
{ id: 0, name: '선택' },
|
||||
// ...부산산광역시의 시군구 추가
|
||||
],
|
||||
28: [
|
||||
{ id: 0, name: '선택' },
|
||||
// ...인천광역시의 시군구 추가
|
||||
],
|
||||
30: [
|
||||
{ id: 0, name: '선택' },
|
||||
// ...대전광역시의 시군구 추가
|
||||
],
|
||||
41: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 41820, name: '가평군' },
|
||||
{ id: 41550, name: '안성시' },
|
||||
{ id: 41830, name: '양평군' },
|
||||
{ id: 41670, name: '여주시' },
|
||||
{ id: 41500, name: '이천시' },
|
||||
{ id: 41430, name: '의왕시' },
|
||||
{ id: 41150, name: '의정부시' },
|
||||
{ id: 41480, name: '파주시' },
|
||||
{ id: 41650, name: '포천시' },
|
||||
// ...경기도의 시군구 추가
|
||||
],
|
||||
42: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 42170, name: '동해시' },
|
||||
{ id: 42110, name: '춘천시' },
|
||||
{ id: 42720, name: '홍천군' },
|
||||
// ...강원도의 시군구 추가
|
||||
],
|
||||
44: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 44770, name: '서천군' },
|
||||
// ...충청남도의 시군구 추가
|
||||
],
|
||||
45: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 45130, name: '군산시' },
|
||||
{ id: 45190, name: '남원시' },
|
||||
{ id: 45730, name: '무주군' },
|
||||
{ id: 45800, name: '부안군' },
|
||||
{ id: 45770, name: '순창군' },
|
||||
{ id: 45140, name: '익산시' },
|
||||
{ id: 45750, name: '임실군' },
|
||||
{ id: 45710, name: '완주군' },
|
||||
{ id: 45110, name: '전주시' },
|
||||
// ...전라북도의 시군구 추가
|
||||
],
|
||||
46: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 46810, name: "강진군" },
|
||||
{ id: 46770, name: "고흥군" },
|
||||
{ id: 46720, name: "곡성군" },
|
||||
{ id: 46730, name: "구례군" },
|
||||
{ id: 46230, name: "광양시" },
|
||||
{ id: 46170, name: "나주시" },
|
||||
{ id: 46110, name: "목포시" },
|
||||
{ id: 46840, name: "무안군" },
|
||||
{ id: 46150, name: "순천시" },
|
||||
{ id: 46910, name: "신안군" },
|
||||
{ id: 46130, name: "여수시" },
|
||||
{ id: 46890, name: "완도군" },
|
||||
// ...전라남도의 시군구 추가
|
||||
],
|
||||
47: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 47130, name: "경주시" },
|
||||
{ id: 47830, name: "고령군" },
|
||||
{ id: 47190, name: "구미시" },
|
||||
{ id: 47720, name: "군위군" },
|
||||
{ id: 47150, name: "김천시" },
|
||||
{ id: 47280, name: "문경시" },
|
||||
{ id: 47920, name: "봉화군" },
|
||||
{ id: 47250, name: "상주시" },
|
||||
{ id: 47770, name: "영덕군" },
|
||||
{ id: 47760, name: "영양군" },
|
||||
{ id: 47210, name: "영주시" },
|
||||
{ id: 47900, name: "예천군" },
|
||||
{ id: 47930, name: "울진군" },
|
||||
{ id: 47730, name: "의성군" },
|
||||
{ id: 47750, name: "청송군" },
|
||||
{ id: 47850, name: "칠곡군" },
|
||||
{ id: 47110, name: "포항시" },
|
||||
// ...경상북도의 시군구 추가
|
||||
],
|
||||
48: [
|
||||
{ id: 0, name: '선택' },
|
||||
{ id: 48310, name: "거제시" },
|
||||
{ id: 48250, name: "김해시" },
|
||||
{ id: 48270, name: "밀양시" },
|
||||
{ id: 48220, name: "통영시" },
|
||||
// ...경상남도의 시군구 추가
|
||||
],
|
||||
// ...나머지 시도별 시군구 추가
|
||||
};
|
||||
|
||||
const 처리구역List = ['선택', 'A', 'B'];
|
||||
const 설치년도List = ['선택', ...YEARS];
|
||||
|
||||
// swageZoneTableHeadlist, tableData 타입 예시 (실제 타입에 맞게 수정)
|
||||
type TableHead = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
type TableRow = Record<string, any>;
|
||||
|
||||
interface SearchModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
// filterParams 타입 정의 추가
|
||||
type FilterParams = {
|
||||
swtrmt_zone_nm?: string;
|
||||
ctpv_cd?: number;
|
||||
sgg_cd?: number;
|
||||
swtrmt_fclt_nm?: string;
|
||||
// 추가 필터 속성들...
|
||||
};
|
||||
|
||||
// 해당 년/월의 마지막 일 구하는 함수
|
||||
function getLastDayOfMonth(year: string, month: string) {
|
||||
if (!year || !month) return 31;
|
||||
const y = parseInt(year, 10);
|
||||
const m = parseInt(month, 10);
|
||||
if (isNaN(y) || isNaN(m)) return 31;
|
||||
return new Date(y, m, 0).getDate();
|
||||
}
|
||||
// 날짜 조합 함수
|
||||
const getDateString = (year: string, month: string, day: string, defaultDate: string) => {
|
||||
if (!year && !month && !day) return defaultDate;
|
||||
const y = year || defaultDate.slice(0, 4);
|
||||
const m = month || defaultDate.slice(4, 6);
|
||||
const d = day || defaultDate.slice(6, 8);
|
||||
return `${y}${m}${d}`;
|
||||
};
|
||||
|
||||
const SearchModal: React.FC<SearchModalProps> = ({ open, onOpenChange }) => {
|
||||
const [activeTab, setActiveTab] = React.useState('basic');
|
||||
const [workType, setWorkType] = React.useState(WORK_TYPES[0]?.key ?? '');
|
||||
const [startYear, setStartYear] = React.useState('');
|
||||
const [endYear, setEndYear] = React.useState('');
|
||||
const [showTable, setShowTable] = React.useState(false);
|
||||
const [curPage, setCurPage] = React.useState(1);
|
||||
const [basicType, setBasicType] = React.useState(BASIC_TYPES[0]?.key ?? '');
|
||||
const [하수처리구역명, set하수처리구역명] = React.useState('');
|
||||
const [하수처리시설명, set하수처리시설명] = React.useState('');
|
||||
const [시도, set시도] = React.useState(0);
|
||||
const [시군구, set시군구] = React.useState(0);
|
||||
const [처리구역, set처리구역] = React.useState('');
|
||||
const [설치년도1, set설치년도1] = React.useState('');
|
||||
const [설치년도2, set설치년도2] = React.useState('');
|
||||
const [tableData, setTableData] = React.useState<any[]>([]);
|
||||
const [시설용량최소, set시설용량최소] = React.useState('');
|
||||
const [시설용량최대, set시설용량최대] = React.useState('');
|
||||
// 날짜 상태 선언
|
||||
const [가동개시_시작_년, set가동개시_시작_년] = React.useState('');
|
||||
const [가동개시_시작_월, set가동개시_시작_월] = React.useState('');
|
||||
const [가동개시_시작_일, set가동개시_시작_일] = React.useState('');
|
||||
const [가동개시_종료_년, set가동개시_종료_년] = React.useState('');
|
||||
const [가동개시_종료_월, set가동개시_종료_월] = React.useState('');
|
||||
const [가동개시_종료_일, set가동개시_종료_일] = React.useState('');
|
||||
|
||||
// 오늘 날짜 구하기
|
||||
const today = new Date();
|
||||
const todayYear = today.getFullYear().toString();
|
||||
const todayMonth = (today.getMonth() + 1).toString().padStart(2, '0');
|
||||
const todayDate = today.getDate().toString().padStart(2, '0');
|
||||
|
||||
// UiTable2에 넘길 headlist를 BASIC_TYPES 값에 따라 분기 처리
|
||||
let dynamicHeadlist = swageZoneTableHeadlist;
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') {
|
||||
dynamicHeadlist = swageZoneTableHeadlist; // 하수처리구역
|
||||
} else if (basicType === 'tb_il_swage_prcs_fclt') {
|
||||
dynamicHeadlist = swageFcltTableHeadlist; // 하수처리시설
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 선택된 시도에 따라 시군구 리스트 가져오기
|
||||
const selectedSigunguList = sigunguMap[시도] || [{ id: 0, name: '선택' }];
|
||||
|
||||
// 조회 버튼 클릭 시 1페이지로 이동하도록 수정
|
||||
const handleSearch = () => {
|
||||
setCurPage(1); // 페이지를 1로 초기화
|
||||
fetchFilteredData();
|
||||
setShowTable(true);
|
||||
};
|
||||
|
||||
// 선택된 필터 값을 저장할 상태 추가
|
||||
const [filterParams, setFilterParams] = React.useState<FilterParams>({});
|
||||
|
||||
// cellClick 함수 타입 지정
|
||||
const handleCellClick = (row: TableRow, col: TableHead) => {
|
||||
// 원하는 동작 구현
|
||||
console.log(row, col);
|
||||
};
|
||||
|
||||
const modalMinWidth = 'min-w-[900px]';
|
||||
const modalMaxWidth = 'max-w-6xl';
|
||||
|
||||
// 모달 높이 동적 계산
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [dynamicHeight, setDynamicHeight] = React.useState<number | undefined>(undefined);
|
||||
|
||||
// 구분(basicType)이 바뀔 때마다 queryParams, filterParams 초기화
|
||||
React.useEffect(() => {
|
||||
// filterParams 초기화
|
||||
setFilterParams({});
|
||||
set시설용량최소('');
|
||||
set시설용량최대('');
|
||||
set가동개시_시작_년('');
|
||||
set가동개시_시작_월('');
|
||||
set가동개시_시작_일('');
|
||||
set가동개시_종료_년('');
|
||||
set가동개시_종료_월('');
|
||||
set가동개시_종료_일('');
|
||||
set하수처리구역명('');
|
||||
set하수처리시설명('');
|
||||
set시도(0);
|
||||
set시군구(0);
|
||||
set처리구역('');
|
||||
set설치년도1('');
|
||||
set설치년도2('');
|
||||
// queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음
|
||||
}, [basicType]);
|
||||
|
||||
// 필터 값이 변경될 때 쿼리 파라미터 업데이트
|
||||
React.useEffect(() => {
|
||||
const params: FilterParams = {};
|
||||
|
||||
if (하수처리구역명) {
|
||||
params.swtrmt_zone_nm = 하수처리구역명;
|
||||
}
|
||||
|
||||
// 시도 선택 시 (0이 아닌 경우)
|
||||
if (시도 !== 0) {
|
||||
const selectedSido = sidoList.find(item => item.id === 시도);
|
||||
if (selectedSido) {
|
||||
params.ctpv_cd = selectedSido.id;
|
||||
}
|
||||
}
|
||||
|
||||
// 시군구 선택 시 (0이 아닌 경우)
|
||||
if (시군구 !== 0) {
|
||||
const selectedSigungu = (sigunguMap[시도] || []).find((item: { id: number; name: string }) => item.id === 시군구);
|
||||
if (selectedSigungu) {
|
||||
params.sgg_cd = selectedSigungu.id;
|
||||
}
|
||||
}
|
||||
|
||||
// 하수처리시설명이 있으면 추가
|
||||
if (하수처리시설명) {
|
||||
params.swtrmt_fclt_nm = 하수처리시설명;
|
||||
}
|
||||
|
||||
setFilterParams(params);
|
||||
}, [하수처리구역명, 시도, 시군구, 하수처리시설명]);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setDynamicHeight(contentRef.current.scrollHeight + 60); // 여유 padding
|
||||
}
|
||||
}, [activeTab, showTable, curPage, tableData.length]);
|
||||
|
||||
|
||||
// 검색 API 호출 함수
|
||||
const fetchFilteredData = () => {
|
||||
const queryParams = new URLSearchParams({
|
||||
lyrTypeNm: basicType
|
||||
});
|
||||
|
||||
const cqlFilters: string[] = [];
|
||||
|
||||
// 구분별 분기 처리
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') {
|
||||
// 하수처리구역
|
||||
// 시도
|
||||
if (filterParams.ctpv_cd) {
|
||||
cqlFilters.push(`ctpv_cd='${filterParams.ctpv_cd}'`);
|
||||
}
|
||||
// 시군구
|
||||
if (filterParams.sgg_cd) {
|
||||
cqlFilters.push(`sgg_cd='${filterParams.sgg_cd}'`);
|
||||
}
|
||||
// 하수처리구역명
|
||||
if (filterParams.swtrmt_zone_nm) {
|
||||
cqlFilters.push(`swtrmt_zone_nm ILIKE '%${filterParams.swtrmt_zone_nm}%'`);
|
||||
}
|
||||
// 하수처리시설명
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
} else if (basicType === 'tb_il_swage_prcs_fclt') {
|
||||
// 하수처리시설
|
||||
// 가동개시일자 범위 필터 추가
|
||||
// 기본값: 19000101 ~ 오늘
|
||||
const startDate = getDateString(가동개시_시작_년, 가동개시_시작_월, 가동개시_시작_일, '19000101');
|
||||
const endDate = getDateString(가동개시_종료_년, 가동개시_종료_월, 가동개시_종료_일, `${todayYear}${todayMonth}${todayDate}`);
|
||||
cqlFilters.push(`oprtng_strt_ymd >= '${startDate}' and oprtng_strt_ymd <= '${endDate}'`);
|
||||
// 시설용량 범위(fclt_cpct) 필터 추가
|
||||
if (시설용량최소 && 시설용량최대) cqlFilters.push(`fclt_cpct BETWEEN ${시설용량최소} AND ${시설용량최대}`);
|
||||
else if (시설용량최소) cqlFilters.push(`fclt_cpct >= ${시설용량최소}`);
|
||||
else if (시설용량최대) cqlFilters.push(`fclt_cpct <= ${시설용량최대}`);
|
||||
// 하수처리시설명
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
} else {
|
||||
// 기타 구분(예: 하수관로, 맨홀 등) - 필요시 추가
|
||||
// 예시: 이름으로 검색
|
||||
if (filterParams.swtrmt_fclt_nm) {
|
||||
cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${filterParams.swtrmt_fclt_nm}%'`);
|
||||
}
|
||||
}
|
||||
|
||||
// 파라미터 이름 오타 주의! (Swagger와 동일하게)
|
||||
if (cqlFilters.length > 0) {
|
||||
queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
}
|
||||
|
||||
console.log('API URL:', `${API_URL}/v1/map/features?${queryParams.toString()}`);
|
||||
axios.get(`${API_URL}/v1/map/features?${queryParams.toString()}`)
|
||||
.then(res => {
|
||||
// 결과값을 표에 사용할 수 있도록 상태에 저장
|
||||
console.log('필터링된 API 결과:', res.data.length);
|
||||
console.log('필터링된 API 결과:', res.data);
|
||||
setTableData(res.data || []); // res.data 구조에 따라 조정 필요
|
||||
setShowTable(true);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('API ERROR:', err);
|
||||
setTableData([]); // 에러 시 빈 배열로 초기화
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const renderBody = () => (
|
||||
<>
|
||||
{/* 탭 영역 */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.key}
|
||||
className={`px-4 py-2 rounded-t border border-b-0 text-base font-semibold
|
||||
${activeTab === tab.key
|
||||
? 'bg-[#bfcfe6] text-black'
|
||||
: 'bg-[#e6e6e6] text-gray-700'
|
||||
}`}
|
||||
style={{
|
||||
borderColor: '#888',
|
||||
borderBottom: activeTab === tab.key ? '2px solid white' : undefined,
|
||||
minWidth: 140,
|
||||
fontSize: '1.1rem',
|
||||
height: '2.4rem', // 글자 크기에서 약간의 여백만 있도록 높이 조정
|
||||
}}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 기본정보 탭 */}
|
||||
{activeTab === 'basic' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 구분 */}
|
||||
<div className="flex justify-between items-center mb-3 gap-4">
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">구분</label>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1"
|
||||
value={basicType}
|
||||
onChange={e => setBasicType(e.target.value)}
|
||||
>
|
||||
{BASIC_TYPES.map(opt => (
|
||||
<option key={opt.key} value={opt.key}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/* 분기처리: 하수처리구역 */}
|
||||
{basicType === 'tb_ms_swage_prcs_zone' && (
|
||||
<div className="mb-3">
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">시도</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-4" value={시도} onChange={e => set시도(parseInt(e.target.value))}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold text-xl mr-2">시군구</label>
|
||||
<select className="border rounded text-xl px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(parseInt(e.target.value))}>
|
||||
{selectedSigunguList.map(opt => (
|
||||
<option key={opt.id} value={opt.id}>{opt.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리구역명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리구역명} onChange={e => set하수처리구역명(e.target.value)} />
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 분기처리: 하수처리시설 */}
|
||||
{basicType === 'tb_il_swage_prcs_fclt' && (
|
||||
<div className="mb-3">
|
||||
{/* 가동개시일자 */}
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">가동개시일자</label>
|
||||
{/* 시작일자 */}
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_시작_년}
|
||||
onChange={e => set가동개시_시작_년(e.target.value)}
|
||||
>
|
||||
<option value="">년</option>
|
||||
{Array.from({ length: today.getFullYear() - 1900 + 1 }, (_, i) => {
|
||||
const y = (today.getFullYear() - i).toString();
|
||||
return <option key={y} value={y}>{y}</option>;
|
||||
})}
|
||||
</select>
|
||||
{/* <select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_시작_월}
|
||||
onChange={e => set가동개시_시작_월(e.target.value)}
|
||||
>
|
||||
<option value="">월</option>
|
||||
{[...Array(12)].map((_, i) => {
|
||||
const m = (i + 1).toString().padStart(2, '0');
|
||||
return <option key={m} value={m}>{m}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-4"
|
||||
value={가동개시_시작_일}
|
||||
onChange={e => set가동개시_시작_일(e.target.value)}
|
||||
>
|
||||
<option value="">일</option>
|
||||
{[...Array(31)].map((_, i) => {
|
||||
const d = (i + 1).toString().padStart(2, '0');
|
||||
return <option key={d} value={d}>{d}</option>;
|
||||
})}
|
||||
</select> */}
|
||||
<span className="mx-1">~</span>
|
||||
{/* 종료일자 */}
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_종료_년}
|
||||
onChange={e => set가동개시_종료_년(e.target.value)}
|
||||
>
|
||||
<option value="">년</option>
|
||||
{Array.from({ length: today.getFullYear() - 1900 + 1 }, (_, i) => {
|
||||
const y = (today.getFullYear() - i).toString();
|
||||
// 시작년이 선택된 경우, 종료년은 시작년 이상만 표시
|
||||
if (가동개시_시작_년 && parseInt(y) < parseInt(가동개시_시작_년)) return null;
|
||||
return <option key={y} value={y}>{y}</option>;
|
||||
})}
|
||||
</select>
|
||||
{/* <select
|
||||
className="border rounded text-xl px-2 py-1 mr-1"
|
||||
value={가동개시_종료_월}
|
||||
onChange={e => set가동개시_종료_월(e.target.value)}
|
||||
>
|
||||
<option value="">월</option>
|
||||
{[...Array(12)].map((_, i) => {
|
||||
const m = (i + 1).toString().padStart(2, '0');
|
||||
// 시작년/월이 선택된 경우, 종료년이 시작년과 같으면 시작월 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
parseInt(m) < parseInt(가동개시_시작_월)
|
||||
) return null;
|
||||
return <option key={m} value={m}>{m}</option>;
|
||||
})}
|
||||
</select>
|
||||
<select
|
||||
className="border rounded text-xl px-2 py-1 mr-4"
|
||||
value={가동개시_종료_일}
|
||||
onChange={e => set가동개시_종료_일(e.target.value)}
|
||||
>
|
||||
<option value="">일</option>
|
||||
{[...Array(31)].map((_, i) => {
|
||||
const d = (i + 1).toString().padStart(2, '0');
|
||||
// 시작년/월/일이 선택된 경우, 종료년/월이 시작년/월과 같으면 시작일 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
가동개시_종료_월 === 가동개시_시작_월 &&
|
||||
가동개시_시작_일 &&
|
||||
parseInt(d) < parseInt(가동개시_시작_일)
|
||||
) return null;
|
||||
// 시작년/월이 선택된 경우, 종료년/월이 시작년/월과 같고 시작일이 선택되었으면 시작일 이상만 표시
|
||||
if (
|
||||
가동개시_시작_년 &&
|
||||
가동개시_종료_년 === 가동개시_시작_년 &&
|
||||
가동개시_시작_월 &&
|
||||
가동개시_종료_월 === 가동개시_시작_월 &&
|
||||
가동개시_시작_일 &&
|
||||
parseInt(d) < parseInt(가동개시_시작_일)
|
||||
) return null;
|
||||
// 시작년/월만 선택된 경우, 종료년/월이 시작년/월과 같으면 모든 일 표시(위 조건에 포함)
|
||||
// 시작년만 선택된 경우, 종료년이 시작년과 같으면 모든 월/일 표시(위 조건에 포함)
|
||||
return <option key={d} value={d}>{d}</option>;
|
||||
})}
|
||||
</select> */}
|
||||
</div>
|
||||
{/* 시설용량 */}
|
||||
<div className="flex items-center mb-2">
|
||||
<label className="font-semibold text-xl mr-2">시설용량</label>
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded text-xl px-2 py-1 mr-2 w-40 placeholder:text-center"
|
||||
placeholder="최소"
|
||||
value={시설용량최소}
|
||||
onChange={e => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, '');
|
||||
set시설용량최소(val);
|
||||
}}
|
||||
/>
|
||||
<span className="text-xl mr-2">~</span>
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded text-xl px-2 py-1 mr-4 w-40 placeholder:text-center"
|
||||
placeholder="최대"
|
||||
value={시설용량최대}
|
||||
onChange={e => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, '');
|
||||
set시설용량최대(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* 하수처리시설명 */}
|
||||
<div className="flex items-center">
|
||||
<label className="font-semibold text-xl mr-2">하수처리시설명</label>
|
||||
<input className="border rounded text-xl px-2 py-1 mr-4" value={하수처리시설명} onChange={e => set하수처리시설명(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 운영정보 탭 */}
|
||||
{activeTab === 'operation' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
<div className="flex items-center gap-6 mb-6">
|
||||
<label className="w-24 font-semibold text-base">업무구분</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={workType}
|
||||
onChange={e => setWorkType(e.target.value)}
|
||||
style={{ minWidth: 140 }}
|
||||
>
|
||||
{WORK_TYPES.map(w => (
|
||||
<option key={w.key} value={w.key}>{w.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className="w-16 text-center font-semibold text-base">년도</label>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={startYear}
|
||||
onChange={e => setStartYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-2 text-lg">~</span>
|
||||
<select
|
||||
className="border rounded px-3 py-2 text-base"
|
||||
value={endYear}
|
||||
onChange={e => setEndYear(e.target.value)}
|
||||
style={{ minWidth: 100 }}
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{YEARS.map(y => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2" onClick={() => setShowTable(true)}>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 하수도정비기본계획보고서서 탭 */}
|
||||
{activeTab === 'report' && (
|
||||
<div className="border rounded p-6 mb-4">
|
||||
{/* 시도, 시군구, 하수처리구역 한 줄 */}
|
||||
<div className="flex items-center mb-3">
|
||||
<label className="font-semibold mr-2">시도</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시도} onChange={e => set시도(e.target.value)}>
|
||||
{sidoList.map(opt => (
|
||||
<option key={opt.id} value={opt.name}>{opt.id}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">시군구</label>
|
||||
<select className="border rounded px-2 py-1 mr-4" value={시군구} onChange={e => set시군구(e.target.value)}>
|
||||
{/* {sigunguMap.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))} */}
|
||||
</select>
|
||||
<label className="font-semibold mr-2">하수처리구역</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={처리구역} onChange={e => set처리구역(e.target.value)}>
|
||||
{처리구역List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<input className="border rounded px-2 py-1 w-24" placeholder="0000" />
|
||||
</div>
|
||||
{/* 승인년도 한 줄 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<label className="font-semibold mr-2">승인년도</label>
|
||||
<select className="border rounded px-2 py-1 mr-2" value={설치년도1} onChange={e => set설치년도1(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="mx-1">~</span>
|
||||
<select className="border rounded px-2 py-1" value={설치년도2} onChange={e => set설치년도2(e.target.value)}>
|
||||
{설치년도List.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="flex justify-center">
|
||||
<Button className="w-40 text-base py-2">조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 📋 결과 리스트 */}
|
||||
<div className="border-t pt-6 space-y-2" style={{ maxWidth: '100vw', maxHeight: '60vh', overflowX: 'auto'}}>
|
||||
{/* 선택된 탭의 label */}
|
||||
<div className="text-2xl font-bold mb-2">
|
||||
{TABS.find(tab => tab.key === activeTab)?.label}
|
||||
</div>
|
||||
{/* "▶" + 선택된 탭 label + " 조회 결과" */}
|
||||
<div className="text-lg font-semibold mb-6">
|
||||
▶ {TABS.find(tab => tab.key === activeTab)?.label} 조회 결과
|
||||
</div>
|
||||
<UiTable2 headName='' tableData={tableData} headlist={dynamicHeadlist}
|
||||
pageSize={tableData.length < 10 ? tableData.length : 10}
|
||||
total={tableData.length} children={undefined}
|
||||
cellClick={() => { }} onPageChange={setCurPage}
|
||||
resetPageNo={false}
|
||||
cur={curPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
{/* </DialogContent>
|
||||
</Dialog>
|
||||
); */}
|
||||
|
||||
return (
|
||||
<UiModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
bodyClass={`
|
||||
${modalMaxWidth} ${modalMinWidth}
|
||||
transition-all duration-300
|
||||
w-full overflow-hidden
|
||||
`}
|
||||
body={renderBody()}
|
||||
headerTitle="검색"
|
||||
// 필요시 footerContent 등 추가
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(SearchModal);
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@common/components/ui";
|
||||
import React from "react";
|
||||
|
||||
interface InputSectionProps {
|
||||
title: string;
|
||||
data: { year: string; type: string }[];
|
||||
sectionKey: string;
|
||||
onInputChange: (section: string, index: number, field: string, value: string) => void;
|
||||
}
|
||||
|
||||
const InputSection: React.FC<InputSectionProps> = ({ title, data, sectionKey, onInputChange }) => {
|
||||
return (
|
||||
<div>
|
||||
{/* 섹션 제목 */}
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">{title}</h3>
|
||||
</div>
|
||||
{/* 테이블 */}
|
||||
<div className="card !m-[1.2rem] !p-0r">
|
||||
<Table variant="secondary" className="w-full">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="border border-gray-300 px-2 py-2 text-sm">년도</TableHead>
|
||||
<TableHead className="border border-gray-300 px-2 py-2 text-sm">구분</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="border border-gray-300 px-2 py-2 text-sm">
|
||||
<input
|
||||
type="text"
|
||||
value={row.year}
|
||||
onChange={(e) => onInputChange(sectionKey, index, "year", e.target.value)}
|
||||
className="w-full text-xl border-radius-0 px-3 py-2 text-base text-center leading-normal"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="border border-gray-300 px-2 py-2 text-sm text-center leading-normal">
|
||||
{row.type}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputSection;
|
||||
@@ -0,0 +1,46 @@
|
||||
import smh from "../popupComponent/smh";
|
||||
import Ssc from "../popupComponent/Ssc";
|
||||
import SwtrmtFclt from "../popupComponent/SwtrmtFclt";
|
||||
|
||||
// 레이어 이름과 컴포넌트를 매핑
|
||||
const layerContentMap: { [key: string]: { component?: React.FC<any> ; props?: any; } } = {
|
||||
// TODO: gis데이터 추가되면 추가 작업 필요
|
||||
"하수처리시설": {
|
||||
component: SwtrmtFclt,
|
||||
},
|
||||
"하수처리구역": { },
|
||||
"중점우수토실":{component: Ssc,},
|
||||
"하수관로":{component: SwtrmtFclt,},
|
||||
"하수맨홀":{component: smh,},
|
||||
};
|
||||
|
||||
// 동적 컴포넌트 반환 함수
|
||||
export const getLayerContentComponent = (layerNm: string): React.FC => {
|
||||
const component = layerContentMap[layerNm];
|
||||
if (!component || !component.component) {
|
||||
// component가 없으면 null 반환
|
||||
return () => null;
|
||||
}
|
||||
return component.component;
|
||||
};
|
||||
// 사용자 입력 필드 (보수 및 갱신, 청소 및 준설, 점검 및 진단)
|
||||
export const handleInputChange = (section: string,
|
||||
index: number,
|
||||
field: string,
|
||||
value: string,
|
||||
dataMap: {
|
||||
[key: string]: {
|
||||
data: { year: string; type: string }[];
|
||||
setData: React.Dispatch<React.SetStateAction<{ year: string; type: string }[]>>;
|
||||
};
|
||||
}
|
||||
) => {
|
||||
const target = dataMap[section];
|
||||
if (!target) return;
|
||||
|
||||
const updated = [...target.data];
|
||||
if (updated[index]) {
|
||||
updated[index][field as keyof typeof updated[number]] = value;
|
||||
}
|
||||
target.setData(updated);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
// 하수처리시설
|
||||
export const SwtrmtFcltCol = [
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
{ id: 'fcltCpct', title: '시설용량' },
|
||||
{ id: 'rgnExpln', title: '지역설명' },
|
||||
{ id: 'cmcnYmd', title: '준공일자' },
|
||||
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
|
||||
{ id: 'clsgYmd', title: '폐쇄일자' },
|
||||
{ id: 'prcsMsrcCd', title: '처리공법코드' },
|
||||
{ id: 'laeCd', title: '지자체코드' },
|
||||
{ id: 'laeNm', title: '지자체명' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
{ id: 'sareaCd', title: '권역코드' },
|
||||
{ id: 'sareaNm', title: '권역명' },
|
||||
{ id: 'lrgeSareaNm', title: '대권역명' },
|
||||
{ id: 'rvrNm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'daddr', title: '상세주소' },
|
||||
{ id: 'rdnmAddr', title: '도로명주소' },
|
||||
{ id: 'dtlRdnmAddr', title: '상세도로명주소' },
|
||||
{ id: 'dschgWtstNm', title: '방류수계명' },
|
||||
{ id: 'pllnArea', title: '부지면적' },
|
||||
{ id: 'mngRprtvBzentyNm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수' },
|
||||
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
{ id: 'oprtngSttsCd', title: '가동상태코드' },
|
||||
{ id: 'rprsMsrcCn', title: '대표공법내용' },
|
||||
{ id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' },
|
||||
{ id: 'sttyCrtrToc', title: '법정기준TOC' },
|
||||
{ id: 'sttyCrtrSs', title: '법정기준SS' },
|
||||
{ id: 'sttyCrtrTn', title: '법정기준총질소' },
|
||||
{ id: 'sttyCrtrTph', title: '법정기준총인' },
|
||||
{ id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' },
|
||||
{ id: 'bsnNm', title: '유역명' },
|
||||
{ id: 'obsrvnSptCd', title: '관측지점코드' },
|
||||
];
|
||||
|
||||
// 하수처리구역
|
||||
export const SwtrmtPrcsZoneCol = [
|
||||
{ id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
|
||||
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
|
||||
{ id: 'swtrmtFcltCd', title: '하수처리시설코드' },
|
||||
{ id: 'ctpvCd', title: '시도코드' },
|
||||
{ id: 'ctpvNm', title: '시도명' },
|
||||
{ id: 'sggCd', title: '시군구코드' },
|
||||
{ id: 'sggNm', title: '시군구명' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
// 우수토실
|
||||
import Modal from '@/components/ui/modal';
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import { UiTable } from '@common/business_components/ui';
|
||||
import { Button, TableHead, TableRow } from '@common/components/ui';
|
||||
import React from 'react'
|
||||
const inputColumn = [
|
||||
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
|
||||
{ id: "mainSscYn", title: "중점우수토실 대상여부", type: "select", options: ['Y', 'N'] },
|
||||
];
|
||||
const headlist = [
|
||||
{ id: 'startrain', title: '강우시작일시' }, { id: 'endrain', title: '강우종료일시' }, { id: '선행건기', title: '선행건기(h)' }, { id: '강우정보', title: '강우정보' }, { id: '누적강우량', title: '누적강우량(mm)' }, { id: '지속시간', title: '지속시간(h)' }, { id: '강우강도', title: '강우강도(mm/h)' },
|
||||
]
|
||||
const headlist3 = [{ id: 'nm', title: '회차' }, { id: 'date', title: '측정일자' }, { id: 'measure', title: '수량(m)' }, { id: 'bod', title: 'BOD(mg/L)' }, { id: 'toc', title: 'TOC(mg/L)' }, { id: '오염부하량', title: '오염부하량(kg)' }]
|
||||
const Ssc = () => {
|
||||
const [isModalOpen, setIsModalOpen] = React.useState(false);
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
return (
|
||||
<div><VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={0} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}} />
|
||||
<div>
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl mx-3 font-medium text-label">중점우수토실 운영정보</h3>
|
||||
</div>
|
||||
<Button className='mx-auto block w-64 mt-3 py-2 text-white rounded-sm text-sm font-medium hover:bg-blue-700 bg-blue-600' onClick={openModal}>상세보기</Button>
|
||||
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
|
||||
<div className='grid grid-col-3'><UiTable headName='강우정보' tableData={[]} headlist={headlist} pageSize={0} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}></UiTable>
|
||||
<UiTable headName='측정정보' tableData={[]} headlist={[]} pageSize={0} total={0} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}> {/* 커스텀 헤더 */}
|
||||
<TableRow>
|
||||
<TableHead colSpan={5} className="text-center bg-blue-100 font-bold">
|
||||
수량
|
||||
</TableHead>
|
||||
<TableHead colSpan={3} className="text-center bg-blue-100 font-bold">
|
||||
수질
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead className="text-center">측정시작일시</TableHead>
|
||||
<TableHead className="text-center">측정종료일시</TableHead>
|
||||
<TableHead className="text-center">측정간격(분)</TableHead>
|
||||
<TableHead className="text-center">측정기기설치위치</TableHead>
|
||||
<TableHead className="text-center">유량계 종류 및 모델명</TableHead>
|
||||
<TableHead className="text-center">채수시작일시</TableHead>
|
||||
<TableHead className="text-center">채수종료일시</TableHead>
|
||||
<TableHead className="text-center">채수횟수</TableHead>
|
||||
</TableRow></UiTable>
|
||||
<UiTable headName='수량/수질측정 정보' tableData={[]} headlist={headlist3} pageSize={0} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}></UiTable></div>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Ssc
|
||||
@@ -0,0 +1,45 @@
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import React, { useState } from 'react'
|
||||
import InputSection from '../InputSection';
|
||||
import { handleInputChange } from '../common';
|
||||
// 하수처리시설
|
||||
// TODO: 컬럼에 보고서 엉 ? 이거 버튼누르면 팝업으로 상세화면 띄워주는 건지 확인 필요
|
||||
const inputColumn = [
|
||||
{ id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] },
|
||||
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
|
||||
{ id: "basu", title: "배수구역", type: "input" },
|
||||
{ id: "bungu", title: "배수분구", type: "input" },
|
||||
{ id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] },
|
||||
{ id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] },
|
||||
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
|
||||
];
|
||||
const SwtrmtFclt = () => {
|
||||
const [repairData, setRepairData] = useState([
|
||||
{ year: "2025", type: "개대체" },
|
||||
{ year: "2025", type: "보수" },
|
||||
]);
|
||||
const [cleaningData, setCleaningData] = useState([
|
||||
{ year: "2025", type: "청소" },
|
||||
{ year: "2025", type: "준설" },
|
||||
]);
|
||||
const [inspectionData, setInspectionData] = useState([
|
||||
{ year: "2025", type: "관로기술진단" },
|
||||
{ year: "2025", type: "관로기술진단" },
|
||||
]);
|
||||
const dataMap = {
|
||||
repair: { data: repairData, setData: setRepairData },
|
||||
cleaning: { data: cleaningData, setData: setCleaningData },
|
||||
inspection: { data: inspectionData, setData: setInspectionData },
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
<InputSection title="보수 및 갱신" data={repairData} sectionKey="repair" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
|
||||
<InputSection title="청소 및 준설" data={cleaningData} sectionKey="cleaning" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
|
||||
<InputSection title="점검 및 진단" data={inspectionData} sectionKey="inspection" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} /></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SwtrmtFclt
|
||||
@@ -0,0 +1,21 @@
|
||||
// 하수맨홀
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import React from 'react'
|
||||
const inputColumn = [
|
||||
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
|
||||
{ id: "basu", title: "배수구역", type: "input" },
|
||||
{ id: "bungu", title: "배수분구", type: "input" },
|
||||
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
|
||||
{id:'spec', title:'규격', type:'input'},
|
||||
];
|
||||
const Smh = () => {
|
||||
return (
|
||||
<div>
|
||||
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Smh;
|
||||
@@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
import { VerticalTable } from "@/components/ui/verticalTable";
|
||||
import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SwtrmtFcltCol, SwtrmtPrcsZoneCol } from "./components/common/table";
|
||||
import { useGisContext } from "@common/business_components/ui/map/Gis";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getLayerContentComponent } from "./components/common";
|
||||
// TODO:
|
||||
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
|
||||
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
|
||||
const featureId = featureData.getId(); // 피처의 id 가져오기
|
||||
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
|
||||
const [tableData, setTableData] = useState<any>(null); // 피처 데이터 상태
|
||||
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
|
||||
let [buttonChk,setButtonChk] = useState(true)
|
||||
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
|
||||
const getSelectedLayers = () => {
|
||||
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
|
||||
let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수
|
||||
layers.forEach((layer: any) => {
|
||||
const source = layer.getSource();
|
||||
if (source && source.hasFeature && source.hasFeature(featureData)) {
|
||||
featureLayer = layer; // 피처가 속한 레이어 찾기
|
||||
}
|
||||
});
|
||||
return featureLayer.get('lyrNm'); // 레이어 이름 출력
|
||||
};
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (featureId) {
|
||||
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
|
||||
setLayerNm(selectedLayerName); // 상태 업데이트
|
||||
|
||||
const data = await getData(`v1/map/features/${featureId}`);
|
||||
switch (selectedLayerName) {
|
||||
case '하수처리시설':
|
||||
setHeadlist(SwtrmtFcltCol);
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '하수처리구역':
|
||||
setHeadlist(SwtrmtPrcsZoneCol)
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '중점우수토실':
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '우수토실':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수관로':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수맨홀':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setTableData(data);
|
||||
};
|
||||
}
|
||||
fetchData();
|
||||
}, [featureId]);
|
||||
const ContentComponent = layerNm ? getLayerContentComponent(layerNm) : null;
|
||||
return (
|
||||
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[200]">
|
||||
<div className="card-header bg-primary flex items-center justify-between p-4">
|
||||
<div className="flex mx-3 flex-col">
|
||||
<h3 className="text-2xl font-medium text-white text-label">{layerNm} 정보</h3>
|
||||
<div className="font-medium text-white text-label">(00구 00리 하수관로)</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-white hover:text-gray-700 focus:outline-none"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1">
|
||||
<VerticalTable headName='LX 연계정보' tableData={[tableData]} headlist={headlist} pageSize={20} total={0} children={undefined} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
<div className="w-full mx-5 flex items-center justify-end space-x-2">
|
||||
{buttonChk ? <Button className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
|
||||
저장
|
||||
</Button> : null}
|
||||
</div>
|
||||
{ContentComponent && <ContentComponent />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default FeatureClickEventPopup;
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
|
||||
import Gis from '@common/business_components/ui/map/Gis';
|
||||
import { SearchDiv, SelectBox, UiTable, MainDiv, Title, ContentsDiv, SearchFormLeft, SearchFormRight } from '@common/business_components';
|
||||
import ListDiv from '@common/business_components/ui/list_div';
|
||||
import { Button, Input } from '@common/components';
|
||||
import { useEffect, useState } from "react";
|
||||
import { mapService } from "@service";
|
||||
|
||||
const Map = () => {
|
||||
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const [selectedFeature,setSelectedFeature] = useState(null); // 선택된 피처 상태
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await mapService.getLyrStngs();
|
||||
setTreeData(response.data); // API 응답 저장
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis treeData={treeData} isSearch={true} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
|
||||
import { SearchDiv, SelectBox, UiTable } from '@common/business_components';
|
||||
import { Button, TableRow, Input, Label } from '@common/components';
|
||||
import { TableHead } from '@common/components';
|
||||
import { useSnStore } from '../../store';
|
||||
import type { TableUpperProps } from '../../store/table';
|
||||
|
||||
const headMakeColSpan = (upHeadList: TableUpperProps[]) => {
|
||||
return ( <>
|
||||
<TableRow>{
|
||||
upHeadList.map((head: TableUpperProps) =>
|
||||
(!head.upSequnce?<TableHead rowSpan={2}key={head.id}>{head.title}</TableHead>:
|
||||
head.upName === 'upChangeRe'&& head.upSequnce=== 1?<TableHead key={head.id} rowSpan={1} colSpan={3}>{'중축 개축 증축 '}</TableHead>:null))}
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
{upHeadList.map((head: TableUpperProps) =>
|
||||
(head.upName === 'upChangeRe'?<TableHead key={head.id}>{head.title}</TableHead>:null))}
|
||||
</TableRow>
|
||||
</> )
|
||||
}
|
||||
|
||||
export default function PublicReuseFaciltyInsert() {
|
||||
const { upHeadList, publicReuseFacilityUper} = useSnStore((state) => state);
|
||||
const nodeList= headMakeColSpan(upHeadList);
|
||||
const total = 100;
|
||||
const headName= ()=>{
|
||||
return(
|
||||
<p className="pl-3 text-xl">점검 및 진단 현황</p>
|
||||
)
|
||||
}
|
||||
return ( <>
|
||||
<div>
|
||||
<p className="pl-3 text-2xl"> 점검 및 진단 조회</p>
|
||||
<SearchDiv>
|
||||
<div className='flex'>
|
||||
<SelectBox label='시설물' selectArray={[{text:'관로', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='사용여부' selectArray={[{text:'사용', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='경과년도' selectArray={[{text:'10년', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='경사' selectArray={[{text:'', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='흐름방식' selectArray={[{text:'자연유하', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='용도' selectArray={[{text:'오수관', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='관종' selectArray={[{text:'PE관관', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<SelectBox label='관경' selectArray={[{text:'50', val:'1'}]} selectClass='w-24' className='mr-4' onSelectValue={()=>{}} />
|
||||
<Button className='mr-4' size='sm'>조회</Button>
|
||||
</div>
|
||||
</SearchDiv>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="colsapn-1">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</> )
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
'use client'
|
||||
import { ContentsDiv, MainDiv, SearchDiv, SearchFormLeft, SearchFormRight, SearchInput, Title, UiTable } from '@common/business_components/ui'
|
||||
import { Button, DatePicker, Input, Label } from '@common/components/ui'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import FileUploadForm from '../../components/ui/fileUploadForm'
|
||||
import ListDiv from '@common/business_components/ui/list_div'
|
||||
import ContentLayout from '@/components/ui/contentLayout'
|
||||
import { tableData, tableHeadlist } from '../oldPp/page'
|
||||
|
||||
const Page = () => {
|
||||
const [selectedDate, setSelectedDate] = useState(new Date())
|
||||
const [company, setCompany] = useState('')
|
||||
const [pjNm, setpjNm] = useState('')
|
||||
return (
|
||||
<ContentLayout title='도시침수 예방사업'>
|
||||
<SearchDiv>
|
||||
<SearchFormLeft>
|
||||
<div className='flex items-center gap-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label className="w-20 text-xl text-gray-600">등록기간</Label>
|
||||
<DatePicker mode={'single'}
|
||||
selected={selectedDate}
|
||||
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} />
|
||||
<span>~</span>
|
||||
<DatePicker mode={'single'}
|
||||
selected={selectedDate}
|
||||
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} />
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Label className="w-20 text-xl text-gray-600">등록자</Label>
|
||||
<Input
|
||||
className='w-[100px]'
|
||||
value={company}
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Label className="w-20 text-xl text-gray-600">파일명</Label>
|
||||
<Input
|
||||
className='w-[100px]'
|
||||
value={pjNm}
|
||||
onChange={(e) => setpjNm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SearchFormLeft>
|
||||
<SearchFormRight>
|
||||
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm">조회</Button>
|
||||
</SearchFormRight>
|
||||
</SearchDiv>
|
||||
<ListDiv className="mt-4">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-medium">사업정보 등록현황</h3>
|
||||
<div>
|
||||
<Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" >파일 다운로드</Button>
|
||||
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" >삭제</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable headName='' tableData={tableData} headlist={tableHeadlist} pageSize={10} total={20} children={undefined} cellClick={() => { }} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.')
|
||||
}}></UiTable>
|
||||
</ListDiv>
|
||||
<ListDiv className="mt-4">
|
||||
<FileUploadForm title='도시침수 예방사업 사업추진현황 등록' />
|
||||
</ListDiv>
|
||||
</ContentLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const ExcelDown = () => {
|
||||
return (
|
||||
<div>excelDown</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExcelDown
|
||||
@@ -0,0 +1,90 @@
|
||||
'use client'
|
||||
import { ContentsDiv, MainDiv, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiTable } from '@common/business_components/ui'
|
||||
import { Button, DatePicker, Input, Label } from '@common/components/ui'
|
||||
import React, { useState } from 'react'
|
||||
import FileUploadForm from '../../components/ui/fileUploadForm'
|
||||
import ListDiv from '@common/business_components/ui/list_div'
|
||||
import ContentLayout from '@/components/ui/contentLayout'
|
||||
// TODO: 임시 id
|
||||
export const tableHeadlist = [
|
||||
{ id: 'no', title: 'NO' },
|
||||
{ id: 'fileNm', title: '파일명' },
|
||||
{ id: 'manageYear', title: '년도' },
|
||||
{ id: 'bungi', title: '분기' },
|
||||
{ id: 'registDt', title: '등록 일자' },
|
||||
{ id: 'registUsr', title: '등록자' }
|
||||
|
||||
]
|
||||
export const tableData = [
|
||||
{ 'no': '1', 'fileNm': '파일명', 'manageYear': '2023', 'registDt': '2025-04-16', 'registUsr': 'me', 'bungi': '1분기' },
|
||||
{ 'no': '2', 'fileNm': '파일명', 'manageYear': '2024', 'registDt': '2025-04-16', 'registUsr': 'me2', 'bungi': '1분기' },
|
||||
{ 'no': '3', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '4', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '5', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '6', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '7', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '8', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '9', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' },
|
||||
{ 'no': '10', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3', 'bungi': '1분기' }
|
||||
]
|
||||
const Page = () => {
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
const [company, setCompany] = useState('')
|
||||
const [pjNm, setpjNm] = useState('')
|
||||
return (
|
||||
<ContentLayout title='노후 하수관로 정비사업'>
|
||||
<SearchDiv>
|
||||
<SearchFormLeft>
|
||||
<div className='flex items-center gap-4'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Label className="w-20 text-xl text-gray-600">등록기간</Label>
|
||||
<DatePicker mode={'single'}
|
||||
selected={selectedDate}
|
||||
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} />
|
||||
<span>~</span>
|
||||
<DatePicker mode={'single'}
|
||||
selected={selectedDate}
|
||||
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }} />
|
||||
<div className='flex items-center'>
|
||||
<Label className="w-20 text-xl text-gray-600">등록자</Label>
|
||||
<Input
|
||||
className='w-[100px]'
|
||||
value={company}
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<Label className="w-20 text-xl text-gray-600">파일명</Label>
|
||||
<Input
|
||||
className='w-[100px]'
|
||||
value={pjNm}
|
||||
onChange={(e) => setpjNm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SearchFormLeft>
|
||||
<SearchFormRight>
|
||||
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 mr-2" size="sm" >조회</Button>
|
||||
</SearchFormRight>
|
||||
</SearchDiv>
|
||||
<ListDiv className="mt-4">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-medium">사업정보 등록현황</h3>
|
||||
<div>
|
||||
<Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" >파일 다운로드</Button>
|
||||
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" >삭제</Button>
|
||||
</div>
|
||||
</div>
|
||||
<UiTable headName='' tableData={tableData} headlist={tableHeadlist} pageSize={10} total={20} children={undefined} cellClick={() => { }} onPageChange={function (page: number): void {
|
||||
throw new Error('Function not implemented.')
|
||||
}}></UiTable>
|
||||
</ListDiv>
|
||||
<ListDiv className="mt-4">
|
||||
<FileUploadForm title='노후 하수관로 정비사업 주진현황 등록' />
|
||||
</ListDiv>
|
||||
</ContentLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,65 @@
|
||||
'use client'
|
||||
import "@common/assets/styles/grid.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SearchDiv, SelectBox, UiTable, MainDiv, Title, ContentsDiv, SearchFormLeft, SearchFormRight } from '@common/business_components';
|
||||
import ListDiv from '@common/business_components/ui/list_div';
|
||||
import { Button, Input } from '@common/components';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { pplinesService } from "@service";
|
||||
|
||||
const tableHeadlist = [
|
||||
{ id: "ftreSmbol", title: "지형지물부호"},
|
||||
{ id: "swageLnkgPplPk", title: "하수연결관로기본키"},
|
||||
{ id: "pbadmsEmd", title: "행정읍면동"},
|
||||
{ id: "sttyEmd", title: "법정읍면동"}
|
||||
];
|
||||
|
||||
export default function Pplines() {
|
||||
|
||||
const router = useRouter()
|
||||
const total = 20
|
||||
const pageSize = 20
|
||||
|
||||
const [data, setData] = useState<{ content: any[] }>({ content: [] }); // 초기값을 객체 또는 null로 설정
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
try {
|
||||
const response = await pplinesService.getPplines();
|
||||
setData(response.data); // API 응답 저장
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MainDiv>
|
||||
<Title title="관로시설" />
|
||||
<ContentsDiv>
|
||||
<SearchDiv>
|
||||
<SearchFormLeft>
|
||||
<SelectBox label='시설물' selectArray={[{text:'관로', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='사용여부' selectArray={[{text:'사용', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='경과년도' selectArray={[{text:'10년', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='경사' selectArray={[{text:'', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='흐름방식' selectArray={[{text:'자연유하', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='용도' selectArray={[{text:'오수관', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='관종' selectArray={[{text:'PE관관', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
<SelectBox label='관경' selectArray={[{text:'50', val:'1'}]} className='col-span-3' onSelectValue={()=>{}} selectClass='w-24' />
|
||||
</SearchFormLeft>
|
||||
<SearchFormRight>
|
||||
<Button className="px-4 py2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={fetchData}>조회</Button>
|
||||
</SearchFormRight>
|
||||
</SearchDiv>
|
||||
|
||||
<ListDiv>
|
||||
<UiTable tableData={data.content} headlist={tableHeadlist} pageSize={pageSize} total={total} children={null} headName="현황정보" cellClick={()=>{}} />
|
||||
</ListDiv>
|
||||
</ContentsDiv>
|
||||
</MainDiv> );
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
'use client'
|
||||
|
||||
import { SearchDiv, SelectBox, UiTable } from '@common/business_components';
|
||||
import { Button } from '@common/components';
|
||||
import { useSnStore } from '@store';
|
||||
import { type TableUpperProps } from '@/store/table';
|
||||
import { StoreZus } from '@/store/store';
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const upHeadList: TableUpperProps[] = [
|
||||
{ id: "sido", title: "자산ID111"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
]
|
||||
|
||||
const useAStore = StoreZus();
|
||||
|
||||
export default function PublicReuseFaciltyInsert() {
|
||||
|
||||
//const upHeadList = useSnStore((state) => state.upHeadList)
|
||||
//const publicReuseFacilityUper = useSnStore((state) => state.publicReuseFacilityUper)
|
||||
|
||||
//const { upHeadList, publicReuseFacilityUper, setHeadList, setTableData, decrementList, addRow } = useAStore.getState();
|
||||
const upHeadList = useSnStore(useAStore => useAStore.upHeadList);
|
||||
const publicReuseFacilityUper = useSnStore(useAStore => useAStore.publicReuseFacilityUper);
|
||||
const setHeadList = useSnStore(useAStore => useAStore.setHeadList);
|
||||
const setTableData = useSnStore(useAStore => useAStore.setTableData);
|
||||
const addRow = useSnStore(useAStore => useAStore.addRow);
|
||||
const decrementList = useSnStore(useAStore => useAStore.decrementList);
|
||||
|
||||
|
||||
//const { upHeadList, publicReuseFacilityUper} = useSnStore((state) => state)
|
||||
|
||||
//const setHeadList = useSnStore((state) => state.setHeadList)
|
||||
//const setTableData = useSnStore((state) => state.setTableData)
|
||||
//const decrementList = useSnStore((state) => state.decrementList)
|
||||
|
||||
const handleChangeHeader = () => {
|
||||
setHeadList(upHeadList)
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
console.log(11111)
|
||||
addRow({
|
||||
sido: "xxxxxxxxxx",
|
||||
sigungo: "bbbbbbbbb",
|
||||
facilityName: "newFacility",
|
||||
location: "S-T-S-000000",
|
||||
locationgubun: "000000",
|
||||
facilityCapacity: "오수관",
|
||||
});
|
||||
};
|
||||
|
||||
const searchDataList = () => {
|
||||
//if (newFacility.trim() === "") return;
|
||||
|
||||
setTableData([ {
|
||||
sido: "123456",
|
||||
sigungo: "관로1",
|
||||
facilityName: "newFacility",
|
||||
location: "S-T-S-000000",
|
||||
locationgubun: "000000",
|
||||
facilityCapacity: "오수관",
|
||||
}]);
|
||||
|
||||
|
||||
};
|
||||
|
||||
const total = 100;
|
||||
const headName= ()=>{
|
||||
return(
|
||||
<p className="pl-3 text-xl">현황정보</p>
|
||||
)
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="pl-3 text-2xl"> 현황정보 조회</p>
|
||||
<SearchDiv >
|
||||
<div className='grid grid-cols-12 gap-1 w-4/5'>
|
||||
<SelectBox label='시설물' selectArray={[{text:'관로', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
<SelectBox label='사용여부' selectArray={[{text:'사용', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
<SelectBox label='경과년도' selectArray={[{text:'10년', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
<SelectBox label='경사' selectArray={[{text:'', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
<SelectBox label='흐름방식' selectArray={[{text:'자연유하', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
<SelectBox label='용도' selectArray={[{text:'오수관', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
<SelectBox label='관종' selectArray={[{text:'PE관관', val:'1'}]} className='col-span-3' onSelectValue={(val: string)=>{}} selectClass='w-24' />
|
||||
{/* <SelectBox label='관경' selectArray={[{text:'50', val:'1'}]} className='col-span-3'/> */}
|
||||
</div>
|
||||
<div className='w-1/5 flex justify-end items-center'>
|
||||
<Button className='mr-10 rounded-lg'>조회</Button>
|
||||
</div>
|
||||
</SearchDiv>
|
||||
{/* </div> */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="col-span-2">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button onClick={searchDataList} className="mr-3 mt-4 bg-red-500 text-white px-4 py-2 rounded">
|
||||
조회
|
||||
</button>
|
||||
<button onClick={handleChangeHeader} className="mr-3 mt-4 bg-red-500 text-white px-4 py-2 rounded">
|
||||
헤더
|
||||
</button>
|
||||
<button onClick={decrementList} className="mr-3 mt-4 bg-red-500 text-white px-4 py-2 rounded">
|
||||
삭제
|
||||
</button>
|
||||
<button onClick={() => router.push("/oper/pplines")} className="mr-3 mt-4 bg-red-500 text-white px-4 py-2 rounded">
|
||||
이동
|
||||
</button>
|
||||
</div> );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
import Link from "next/link";
|
||||
import { Button } from "@common/components/ui";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<div><h3 className="text-2xl text-bold gap-4 mx-4">하수관망도</h3>
|
||||
<div className="flex flex-row gap-4 p-4">
|
||||
<Link href={"/map"}><Button>하수관망도</Button></Link>
|
||||
</div>
|
||||
</div>
|
||||
<div><h3 className="text-2xl text-bold gap-4 mx-4">운영정보 관리</h3>
|
||||
<div className="flex flex-row gap-4 p-4">
|
||||
<Link href={"/oper/floodPre"}><Button>도시침수 예방사업</Button></Link>
|
||||
<Link href={"/oper/oldPp"}><Button>노후 하수관로 정비사업</Button></Link>
|
||||
</div>
|
||||
</div>
|
||||
<div><h3 className="text-2xl text-bold gap-4 mx-4">I/I 분석(기초정보 설정)</h3>
|
||||
<div className="flex flex-row gap-4 p-4">
|
||||
<Link href={"/ii/irsetting"}><Button>침입수 분석</Button></Link>
|
||||
<Link href={"/ii/rdii"}><Button>RDII 분석</Button></Link>
|
||||
<Link href={"/ii/result"}><Button>분석결과</Button></Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './ppline'
|
||||
export * from './map'
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
/**
|
||||
* 관망도 API
|
||||
*/
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
|
||||
|
||||
export type LyrStngProps = {
|
||||
lyr_sn: string
|
||||
lyr_nm: string
|
||||
upper_lyr_sn: string
|
||||
drawOrdr: string
|
||||
flterSql: string
|
||||
gobjTy: string
|
||||
lyrTpNm: string
|
||||
useYn: string
|
||||
minViewSize: string
|
||||
iconNm: string
|
||||
lblAtrbNm: string
|
||||
fontNm: string
|
||||
fontIntnColor: string
|
||||
fontExtnColor: string
|
||||
fontExtnWdth: string
|
||||
lnWdth: string
|
||||
lnColor: string
|
||||
polgnColor: string
|
||||
initEprssYn: string
|
||||
clickPsblYn: string
|
||||
}
|
||||
|
||||
export const mapService = {
|
||||
//레이어 설정 조회
|
||||
getLyrStngs: () => {
|
||||
return axios.get(`${API_URL}/lyrStngs`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
/**
|
||||
* 하수관로 API
|
||||
*/
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/pplines`
|
||||
const PAGE_NO = 1
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
export type PplineProps = {
|
||||
ftreSmbol: string
|
||||
swageLnkgPplPk: string
|
||||
pbadmsEmd: string
|
||||
sttyEmd: string
|
||||
}
|
||||
|
||||
export const pplinesService = {
|
||||
//목록
|
||||
getPplines: (size: number = PAGE_SIZE, page: number = PAGE_NO) => {
|
||||
return axios.get(`${API_URL}`)
|
||||
},
|
||||
//상세
|
||||
getPpline: (swageLnkgPplPk: string) => {
|
||||
return axios.get(`${API_URL}/${swageLnkgPplPk}`)
|
||||
},
|
||||
//저장
|
||||
savePpline: (swageLnkgPplPk: string) => {
|
||||
return axios.post(`${API_URL}`)
|
||||
},
|
||||
//수정
|
||||
updatePpline: (swageLnkgPplPk: string) => {
|
||||
return axios.put(`${API_URL}/${swageLnkgPplPk}`)
|
||||
},
|
||||
//삭제
|
||||
removePpline: (swageLnkgPplPk: string) => {
|
||||
return axios.delete(`${API_URL}/${swageLnkgPplPk}`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
|
||||
import { type ReactNode, createContext, useContext, useRef } from 'react'
|
||||
import { useStore } from 'zustand'
|
||||
import { type SnStore, StoreZus } from './store'
|
||||
|
||||
|
||||
export type StoreApi = ReturnType<typeof StoreZus>
|
||||
|
||||
export const StoreContext = createContext<StoreApi | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
export interface StoreProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const StoreProvider = ({
|
||||
children,
|
||||
}: StoreProviderProps) => {
|
||||
const storeRef = useRef<StoreApi | null>(null)
|
||||
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = StoreZus()
|
||||
}
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</StoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSnStore = <T,>(
|
||||
//selector: (store: ReturnType<typeof StoreZus>) => T,
|
||||
selector: (store: SnStore) => T,
|
||||
): T => {
|
||||
const storeContext = useContext(StoreContext)
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error(`useStore must be used within StoreProvider`)
|
||||
}
|
||||
|
||||
return useStore(storeContext, selector)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
export type TableUpperProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
upName?: string;
|
||||
upSequnce?: number;
|
||||
}
|
||||
type PublicReuseFacility = {
|
||||
sido: string,
|
||||
sigungo: string,
|
||||
facilityName: string,
|
||||
location: string,
|
||||
locationgubun: string,
|
||||
facilityCapacity: string,
|
||||
yn?: string,
|
||||
authoDay?: string,
|
||||
area?: string,
|
||||
reuse?: string
|
||||
}
|
||||
const upHeadList:TableUpperProps[] = [
|
||||
{ id: "sido", title: "자산ID"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
];
|
||||
|
||||
const publicReuseFacilityUper:PublicReuseFacility[] = [
|
||||
{
|
||||
sido: "1048967",
|
||||
sigungo: "관로",
|
||||
facilityName: "양평군 양평읍 백안리 25-5전",
|
||||
location: "S-T-S-2018000675",
|
||||
locationgubun: "2018000675",
|
||||
facilityCapacity: "오수관",
|
||||
yn: "신설",
|
||||
authoDay: "양평하수처리구역",
|
||||
area: "30",
|
||||
reuse: "28,313,220"
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
|
||||
export type tableState = {
|
||||
isInit: boolean;
|
||||
upHeadList: TableUpperProps[];
|
||||
publicReuseFacilityUper: PublicReuseFacility[];
|
||||
}
|
||||
type tableActions = {
|
||||
decrementList: () => void
|
||||
}
|
||||
|
||||
export type tableType = tableState & tableActions;
|
||||
|
||||
export const tableStateExport:tableState= {
|
||||
isInit:false,
|
||||
upHeadList: upHeadList,
|
||||
publicReuseFacilityUper: publicReuseFacilityUper,
|
||||
}
|
||||
export const tableActionsExport:(set:any)=>tableActions=(set: any) => {
|
||||
return {
|
||||
decrementList:()=>set(
|
||||
(state:tableState) => {
|
||||
return (
|
||||
{
|
||||
upHeadList: state.upHeadList,
|
||||
publicReuseFacilityUper: state.publicReuseFacilityUper.slice(0, state.publicReuseFacilityUper.length - 1)
|
||||
})}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createStore } from 'zustand/vanilla';
|
||||
import { tableActionsExport, tableStateExport, type tableState, type tableType } from './table';
|
||||
|
||||
export type SnStore = tableType;
|
||||
//export type SnStore = ReturnType<typeof StoreZus>["getState"];
|
||||
export const StoreZus = (initState: tableState = tableStateExport) => {
|
||||
return createStore<SnStore>()((set) => {
|
||||
return {
|
||||
...initState,
|
||||
...tableActionsExport(set)
|
||||
}
|
||||
})
|
||||
}
|
||||
//store 등록하는 부분
|
||||
@@ -0,0 +1,116 @@
|
||||
export type TableUpperProps = {
|
||||
id: string,
|
||||
title: string,
|
||||
upSequnce?: number,
|
||||
upName?: string
|
||||
}
|
||||
type PublicReuseFacility = {
|
||||
sido: string,
|
||||
sigungo: string,
|
||||
facilityName: string,
|
||||
location: string,
|
||||
locationgubun: string,
|
||||
facilityCapacity: string,
|
||||
yn?: string,
|
||||
authoDay?: string,
|
||||
area?: string,
|
||||
reuse?: string
|
||||
}
|
||||
const upHeadList:TableUpperProps[] = [
|
||||
{ id: "sido", title: "자산ID"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
];
|
||||
|
||||
const publicReuseFacilityUper:PublicReuseFacility[] = [
|
||||
{
|
||||
sido: "1048967",
|
||||
sigungo: "관로",
|
||||
facilityName: "양평군 양평읍 백안리 25-5전",
|
||||
location: "S-T-S-2018000675",
|
||||
locationgubun: "2018000675",
|
||||
facilityCapacity: "오수관",
|
||||
yn: "신설",
|
||||
authoDay: "양평하수처리구역",
|
||||
area: "30",
|
||||
reuse: "28,313,220"
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
export type tableState = {
|
||||
isInit: boolean;
|
||||
upHeadList: TableUpperProps[];
|
||||
publicReuseFacilityUper: PublicReuseFacility[];
|
||||
}
|
||||
type tableActions = {
|
||||
setHeadList: (newHeadList: TableUpperProps[]) => void
|
||||
setTableData: (newTableDataList: PublicReuseFacility[]) => void
|
||||
decrementList: () => void
|
||||
addRow: (newTableData: PublicReuseFacility) => void
|
||||
}
|
||||
|
||||
export type tableType = tableState & tableActions;
|
||||
|
||||
export const tableStateExport:tableState= {
|
||||
isInit:false,
|
||||
upHeadList: [
|
||||
{ id: "sido", title: "자산ID"},
|
||||
{ id: "sigungo", title: "설비시설"},
|
||||
{ id: "facilityName", title: "주소"},
|
||||
{ id: "location", title: "자산코드"},
|
||||
{ id: "locationgubun", title: "자산명"},
|
||||
{ id: "facilityCapacity", title: "용도"},
|
||||
{ id: "yn", title: '신설/교체'},
|
||||
{ id: "authoDay", title: '처리구역'},
|
||||
{ id: "area", title: '내용연수' },
|
||||
{ id: "reuse", title: '취득금액' },
|
||||
],
|
||||
publicReuseFacilityUper: publicReuseFacilityUper,
|
||||
}
|
||||
export const tableActionsExport:(set:any)=>tableActions=(set: any) => {
|
||||
return {
|
||||
// 마지막 데이터 삭제
|
||||
decrementList: ()=>set(
|
||||
(state: tableState) => {
|
||||
return (
|
||||
{
|
||||
...state,
|
||||
upHeadList: state.upHeadList,
|
||||
publicReuseFacilityUper: state.publicReuseFacilityUper.slice(0, state.publicReuseFacilityUper.length - 1)
|
||||
})}
|
||||
),
|
||||
// 데이터 추가
|
||||
addRow: (newTableData: PublicReuseFacility)=>set(
|
||||
(state: tableState) => {
|
||||
console.log('asdfasdfadsf')
|
||||
return (
|
||||
{
|
||||
...state,
|
||||
upHeadList: state.upHeadList,
|
||||
publicReuseFacilityUper: [...state.publicReuseFacilityUper, newTableData]
|
||||
})}
|
||||
),
|
||||
// 테이블 헤더 변경
|
||||
setHeadList: (newHeadList: TableUpperProps[]) => set(
|
||||
(state: tableState) => ({
|
||||
...state,
|
||||
upHeadList: newHeadList
|
||||
})
|
||||
),
|
||||
// 시설 목록 데이터 변경
|
||||
setTableData: (newTableData: PublicReuseFacility[]) => set(
|
||||
(state: tableState) => ({
|
||||
...state,
|
||||
publicReuseFacilityUper: newTableData
|
||||
})
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@import "@common/assets/styles/base.css";
|
||||
@import "@common/assets/styles/diveicon.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: "Noto Sans KR", sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.card {
|
||||
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
|
||||
}
|
||||
.card-border {
|
||||
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
|
||||
}
|
||||
.card-header {
|
||||
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
|
||||
}
|
||||
.card-footer {
|
||||
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
|
||||
}
|
||||
|
||||
[data-theme="black"] {
|
||||
.card-border {
|
||||
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
|
||||
}
|
||||
.card-header {
|
||||
@apply border-b-[#3f4042];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_PORT: string;
|
||||
readonly VITE_PROXY_HOST: string;
|
||||
readonly VITE_SERVER_API_URL: string;
|
||||
readonly VITE_SITE_ID: string;
|
||||
readonly VITE_CLAIM_NAME: string;
|
||||
readonly VITE_ACCESS_TOKEN: string;
|
||||
readonly VITE_REFRESH_TOKEN: string;
|
||||
readonly VITE_AUTH_USER_ID: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { NextConfig } from "next";
|
||||
import webpack from "webpack";
|
||||
|
||||
const geoserverUrl = process.env.NEXT_PUBLIC_GEOSERVER_URL?.replace(/\/$/, '') ?? '';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
basePath: "/sn",
|
||||
typescript: {
|
||||
ignoreBuildErrors: true, // 이 줄 추가
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
"import.meta.env.VITE_PORT": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_PORT
|
||||
),
|
||||
"import.meta.env.VITE_PROXY_HOST": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_PROXY_HOST
|
||||
),
|
||||
"import.meta.env.VITE_SERVER_API_URL": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL
|
||||
),
|
||||
"import.meta.env.VITE_SITE_ID": JSON.stringify(process.env.SITE_ID),
|
||||
"import.meta.env.VITE_CLAIM_NAME": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_CLAIM_NAME
|
||||
),
|
||||
"import.meta.env.VITE_ACCESS_TOKEN": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_ACCESS_TOKEN
|
||||
),
|
||||
"import.meta.env.VITE_REFRESH_TOKEN": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_REFRESH_TOKEN
|
||||
),
|
||||
"import.meta.env.VITE_AUTH_USER_ID": JSON.stringify(
|
||||
process.env.NEXT_PUBLIC_AUTH_USER_ID
|
||||
),
|
||||
"process.env.SITE_ID": JSON.stringify(process.env.NEXT_PUBLIC_SITE_ID),
|
||||
})
|
||||
);
|
||||
|
||||
return config;
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/geoserver/:hasudo*',
|
||||
destination: `${geoserverUrl}/:hasudo*`,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "sn",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3002",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@common/assets": "workspace:*",
|
||||
"@common/business_components": "workspace:*",
|
||||
"@common/components": "workspace:*",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.67.1",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"axios": "^1.8.1",
|
||||
"chart.js": "^4.4.8",
|
||||
"dayjs": "^1.11.13",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "15.1.7",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.24.2",
|
||||
"zustand": "^5.0.3",
|
||||
"ol": "^7.3.0",
|
||||
"ol-ext": "^4.0.11",
|
||||
"proj4": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@faker-js/faker": "^9.5.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.7",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"steiger": "^0.5.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 961 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1016 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 468 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 757 B |
|
After Width: | Height: | Size: 785 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 791 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1009 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1009 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |