Merge remote-tracking branch 'origin/iipbs/sn/dev' into iipbs/sn/map

This commit is contained in:
yschoi
2025-04-24 10:32:15 +09:00
39 changed files with 2355 additions and 2088 deletions
+19
View File
@@ -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,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,58 @@
"use client"
import { FileUpload, SelectBox } from '@common/business_components/ui';
import { Button } 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="flex items-center space-x-4">
<div className="w-[400px]">
<FileUpload
files={files}
setFiles={setFiles}
fileTypes=".xlsx,.xls"
/>
</div>
<SelectBox
className="w-50"
selectClass="w-50 rounded-lg bg-white"
label="관리년도"
selectArray={yearOptions}
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
/>
<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>
)
}
export default FileUploadForm
+24
View File
@@ -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-[120px_1fr] items-center">
<div className="text-xl font-medium">{titleNm}</div>
<FormField
control={form.control}
name={name}
render={({ field }) => children}
/>
</div>
)
}
export default FormSection
+91
View File
@@ -0,0 +1,91 @@
'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 TableInfoData = {
headList: { id: string; title: string; align?: string; }[]
tableData: { [key: string]: string | number | boolean | object }[];
cellClickFunc: Function
onPageChangeFunc: Function
}
type BaseLayout = {
children: ReactNode
titleName: TitleName
form: any
onSubmitEvent: Function
graphBtn: boolean
tableInfoData: TableInfoData
}
// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ?
/**
*
* @param children
* @param titleName : 메인 타이틀, 첫번째 요소 타이틀, 두번째 요소 타이틀
* @param form : zod 스키마
* @param onSubmitEvent : 폼 이벤트 처리
* @param graphBtn : 하단에 그래프 버튼 유무 처리
* @param tableInfoData : 하단 테이블 관련 props 항목
* @returns
*/
const BaseLayout = ({ children, titleName, form, onSubmitEvent, graphBtn, tableInfoData }: BaseLayout) => {
return (
<ContentLayout title={titleName.mainTitle}>
<SearchDiv>
<SearchFormLeft>
<SelectBox className="col-span-3" label='시설검색' selectArray={[{ text: '한강 유역청', val: '1' }]} onSelectValue={(val) => { }} selectClass={undefined} />
<SelectBox label='' selectArray={[{ text: '경기도', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
<SelectBox label='' selectArray={[{ text: '양평군', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
<SelectBox label='' selectArray={[{ text: '양평하수처리장', val: '1' }]} className="col-span-3" onSelectValue={() => { }} selectClass='' />
</SearchFormLeft>
<SearchFormRight>
<Button>조회</Button>
</SearchFormRight>
</SearchDiv>
<ListDiv>
<div className="card-header">
<h3 className="text-2xl font-medium">{titleName.firstCardTitle}</h3>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmitEvent)}>
<div className="card !m-[1.2rem]">
{/* 폼 정보 입력 칸 */}
{children}
<div className="flex justify-end mt-1">
<Button type="submit">자료업로드</Button>
</div>
</div>
</form>
</Form>
</ListDiv>
<ListDiv>
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">{titleName.secondCardTitle}</h3>
<div>
<Button className="!mr-[1.2rem]">삭제</Button>
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="!mr-[1.2rem]">그래프</Button> : null}
</div>
</div>
<UiTable
headName=''
headlist={tableInfoData.headList}
tableData={tableInfoData.tableData}
total={0}
pageSize={10}
children={undefined}
cellClick={(e) => { tableInfoData.cellClickFunc(e) }}
onPageChange={(e) => { tableInfoData.onPageChangeFunc(e) }}
/>
</ListDiv>
</ContentLayout>
)
}
export default BaseLayout
+175
View File
@@ -0,0 +1,175 @@
"use client"
import { z } from "zod"
import { useForm, useFieldArray } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormUi, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox } from "@common/business_components/ui"
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, Button } from "@common/components/ui"
import { UiTable } from "@common/business_components/ui"
import React from "react"
import ContentLayout from "@/components/ui/contentLayout"
import ListDiv from "@common/business_components/ui/list_div"
import BaseLayout from "@/ii/BaseLayout"
// 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(),
}))
})
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 rtxNmHeadlist = [
{ id: 'no', title: '번호' },
{ id: 'hasu', title: '하수처리장' },
{ id: 'guyeok', title: '처리구역' },
{ id: 'cnfcj', title: '출처' },
{ id: 'time', title: '입력시간' }
]
// 테이블 페이지 변경 핸들러
const handlePageChange = (page: number) => {
console.log('Page changed:', page)
}
return (
<BaseLayout titleName={{ mainTitle: '모의정보 설정', firstCardTitle: '모의정보 설정', secondCardTitle: 'RTX계수 입력현황' }} form={form} onSubmitEvent={() => { }} graphBtn={false} tableInfoData={{ headList: rtxNmHeadlist, tableData: [{}], cellClickFunc: () => { }, onPageChangeFunc: () => { } }}>
<div className="space-y-[1.2rem]">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 처리구역</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<SelectBox label="" selectArray={[{ text: '양평', val: 1 }]} onSelectValue={() => { }} selectClass="" className="w-[150px]" />
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ RDII 기여면적(ha)</div>
<FormField
control={form.control}
name="rdiiArea"
render={({ field }) => (
<Input
className="h-10 text-center text-xl w-[150px]"
placeholder=""
type=""
{...field}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ RTK계수 출처</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<Input
className="h-10 text-center text-xl w-[250px]"
placeholder=""
{...field}
/>
)}
/>
</div>
</div>
{/* 테이블 컨테이너 */}
<div className="mt-[1.2rem]">
<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>
</BaseLayout >
)
}
+101
View File
@@ -0,0 +1,101 @@
"use client"
import { z } from "zod"
import { useForm, useFieldArray } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormField, SelectBox } from "@common/business_components/ui"
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, DatePicker } from "@common/components/ui"
import React, { useState } from "react"
import BaseLayout from "@/ii/BaseLayout"
// zod 스키마 정의
const formSchema = z.object({
region: z.string(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
})
const region:{text:string,val:number}[] = [{text:'양평', val:1},{text:'강남',val:2},{text:'test',val:3}]
export default function RTKForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
region: "",
inputdate: new Date(),
rtkSource: "",
}
})
const [selectedDate, setSelectedDate] = useState(new Date())
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'no', title: '번호' },
{ id: 'hasu', title: '하수처리장' },
{ id: 'guyeok', title: '처리구역' },
{ id: 'fileNm', title: '파일명' },
{ id: 'time', title: '입력시간' }
]
const rtxNmDataList = [
{ "no": '1', 'hasu': '하수처리장', 'guyeok': '처리구역', 'fileNm': '파일', 'time': '2025-04-18' }
]
const handlePageChange = (page: number) => {
console.log('Page changed:', page)
}
return (
<BaseLayout titleName={{mainTitle: "관망도 등록",firstCardTitle: "SWMM5 파일 업로드",secondCardTitle: "SWMM 파일 업로드현황"}} form={form} onSubmitEvent={()=>{}} graphBtn={false} tableInfoData={{headList: rtxNmHeadlist, tableData: rtxNmDataList, cellClickFunc: ()=>{}, onPageChangeFunc: ()=>{}}} >
{/* 기본 정보 입력 폼 */}
<div className="space-y-[1.2rem]">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 처리구역</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<SelectBox label='' selectArray={region} onSelectValue={()=>{}} selectClass="" className="w-[150px]"/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 입력일</div>
<FormField
control={form.control}
name="inputdate"
render={({ field }) => (
<DatePicker
selected={selectedDate}
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }}
mode={"single"}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 파일위치</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file"/>
</div>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 비고</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<Input
className="h-17 text-center text-xl w-[200px]"
placeholder=""
{...field}
/>
)}
/>
</div>
</div>
</BaseLayout>
)
}
+96
View File
@@ -0,0 +1,96 @@
'use client'
import React, { useState } from 'react'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { FormField, SelectBox } from '@common/business_components/ui'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DatePicker, Input } from '@common/components/ui'
import BaseLayout from '@/ii/BaseLayout'
// zod 스키마 정의
const formSchema = z.object({
region: z.string(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
})
const region:{text:string,val:number}[] = [{text:'양평', val:1},{text:'강남',val:2},{text:'test',val:3}]
const Page = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
region: "",
inputdate: new Date(),
rtkSource: "",
}
})
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'no', title: '번호' },
{ id: 'hasu', title: '하수처리장' },
{ id: 'guyeok', title: '처리구역' },
{ id: 'fileNm', title: '파일명' },
{ id: 'time', title: '입력시간' }
]
const rtxNmDataList = [
{ "no": '1', 'hasu': '하수처리장', 'guyeok': '처리구역', 'fileNm': '파일', 'time': '2025-04-18' }
]
const [selectedDate, setSelectedDate] = useState(new Date())
return (
<BaseLayout titleName={{ mainTitle: '티센 폴리곤 등록', firstCardTitle: '맨홀별 티센 폴리곤(shape파일) 업로드', secondCardTitle: '수용가-맨홀 매칭 파일 업로드 현황' }} form={form} onSubmitEvent={() => { }} graphBtn={false} tableInfoData={{ headList:rtxNmHeadlist, tableData:rtxNmDataList, cellClickFunc: () => { }, onPageChangeFunc: () => { } }}>
<div className="space-y-[1.2rem]">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 처리구역</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<SelectBox label='' selectArray={region} onSelectValue={() => { } } selectClass={undefined} className='w-[150px]'/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 입력일</div>
<FormField
control={form.control}
name="inputdate"
render={({ field }) => (
<DatePicker
selected={selectedDate}
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }}
mode={"single"}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 파일위치</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 비고</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<Input
className="h-17 text-center text-xl w-[200px]"
placeholder=""
{...field}
/>
)}
/>
</div>
</div>
</BaseLayout>
)
}
export default Page
+99
View File
@@ -0,0 +1,99 @@
"use client"
import { z } from "zod"
import { FormField} from '@common/business_components/ui'
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DatePicker, Input } from '@common/components/ui'
import React, { useState } from 'react'
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import BaseLayout from "@/ii/BaseLayout"
// zod 스키마 정의
const formSchema = z.object({
region: z.string(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
})
function RegistForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
region: "",
inputdate: new Date(),
rtkSource: "",
}
})
const [selectedDate, setSelectedDate] = useState(new Date())
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log("✅ 제출 데이터:", data)
}
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'no', title: '번호' },
{ id: 'hasu', title: '하수처리장' },
{ id: 'guyeok', title: '처리구역' },
{ id: 'fileNm', title: '파일명' },
{ id: 'time', title: '입력시간' }
]
const rtxNmDataList = [
{ "no": '1', 'hasu': '하수처리장', 'guyeok': '처리구역', 'fileNm': '파일', 'time': '2025-04-18' }
]
return (
<BaseLayout titleName={{ mainTitle: "수용가-맨홀 등록", firstCardTitle: "수용가-맨홀 매칭 Shape파일 업로드", secondCardTitle: "수용가-맨홀 매칭 Shape파일 업로드 현황" }} form={form} onSubmitEvent={onSubmit} graphBtn={false} tableInfoData={{
headList: rtxNmHeadlist, tableData: rtxNmDataList, cellClickFunc: () => { }, onPageChangeFunc: () => { }
}}>
{/* 기본 정보 입력 폼 */}
<div className="space-y-[1.2rem]">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 처리구역</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="양평">양평</SelectItem>
<SelectItem value="강남">강남</SelectItem>
<SelectItem value="마포">마포</SelectItem>
</SelectContent>
</Select>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 입력일</div>
<FormField
control={form.control}
name="inputdate"
render={({ field }) => (
<DatePicker
selected={selectedDate}
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }}
mode={"single"}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 파일위치</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
</div>
</BaseLayout>
)
}
export default RegistForm
+114
View File
@@ -0,0 +1,114 @@
'use client'
import React, { useState } from 'react'
import BaseLayout from '../../BaseLayout'
import { FormField, SelectBox } from '@common/business_components/ui'
import { 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'
const formSchema = z.object({
region: z.string(),
suyeonga: z.number(),
inputdate: z.coerce.date(),
rtkSource: 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: {
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
}
})
console.log('test')
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'nm', title: '분석번호' },
{ id: 'hasu', title: '하수처리장' },
{ id: 'region', title: '처리구역' },
{ id: 'sangsu', title: '상수사용량' },
{ id: 'wjsghks', title: '오수 전환율' },
{ id: 'tnsdhtn', title: '순오수량' },
{ id: 'rkddn', title: '강우 유출량' },
{ id: 'rdii', title: 'RDII' }
]
const rtxNmDataList = [
{ "nm": '1', 'hasu': '', 'region': '처리', 'sangsu': '파일', 'wjsghks': '', 'tnsdhtn': '', 'rkddn': '', 'rdii': '' }
]
const [selectedDate, setSelectedDate] = useState(new Date())
return (
<BaseLayout titleName={{ mainTitle: '침입수 산정', firstCardTitle: '침입수 산정 기초자료 설정', secondCardTitle: '침입수 산정결과 현황' }} form={form} onSubmitEvent={() => { }} graphBtn={true} tableInfoData={{ headList: rtxNmHeadlist, tableData: rtxNmDataList, cellClickFunc: () => { }, onPageChangeFunc: () => { } }}>
<div className="grid grid-cols-2 gap-4">
<FormSection name='region' form={form} titleNm='○ 처리구역'><SelectBox label='' selectArray={region} onSelectValue={() => { }} selectClass={undefined} className='w-[150px]' /></FormSection>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 오수 전환율</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<Input
className="h-[3.2rem] text-center text-xl w-[150px]"
placeholder=""
{...field}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 수용가 수</div>
<FormField
control={form.control}
name="suyeonga"
render={({ field }) => (
<SelectBox label='' selectArray={[]} onSelectValue={() => { }} selectClass={undefined} className='w-[150px]' />
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 수용가-맨홀 매칭</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 상수사용량 정보</div>
<FormField
control={form.control}
name="inputdate"
render={({ field }) => (
<DatePicker
selected={selectedDate}
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }}
mode={"single"} className='w-[150px]'
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 관망도 자료</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
</div>
</BaseLayout>
)
}
export default IrSetting
@@ -0,0 +1,98 @@
'use client'
import React, { useState } from 'react'
import BaseLayout from '../../BaseLayout'
import { FormField, SelectBox } from '@common/business_components/ui'
import { DatePicker, Input } from '@common/components/ui'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
const formSchema = z.object({
region: z.string(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
})
const region: { text: string, val: number }[] = [{ text: '양평', val: 1 }, { text: '강남', val: 2 }, { text: 'test', val: 3 }]
const MeasureDataUpload = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
region: "",
inputdate: new Date(),
rtkSource: "",
}
})
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'fileNm', title: '파일명' },
{ id: 'status', title: '상태' },
{ id: 'code', title: '지정코드' },
{ id: 'cjfl', title: '처리' },
{ id: 'add', title: '추가' },
{ id: 'update', title: '변경' },
{ id: 'error', title: '오류' },
{ id: 'skip', title: 'Skip' }
]
const rtxNmDataList = [
{ "status": '상태', 'code': '123824', 'cjfl': '처리', 'fileNm': '파일', 'add': '', 'update': '', 'error': '', 'skip': '' }
]
const [selectedDate, setSelectedDate] = useState(new Date())
return (
<BaseLayout titleName={{ mainTitle: '측정자료 등록', firstCardTitle: '하수측정자료 업로드', secondCardTitle: '하수측정자료 파일 업로드 현황' }} form={form} onSubmitEvent={() => { }} graphBtn={true} tableInfoData={{ headList: rtxNmHeadlist, tableData: rtxNmDataList, cellClickFunc: () => { }, onPageChangeFunc: () => { } }}>
<div className="space-y-[1.2rem]">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 처리구역</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<SelectBox label='' selectArray={region} onSelectValue={() => { }} selectClass={undefined} className='w-[150px]' />
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 입력일</div>
<FormField
control={form.control}
name="inputdate"
render={({ field }) => (
<DatePicker
selected={selectedDate}
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }}
mode={"single"} className='w-[150px]'
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 관로 관리번호</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<Input
className="h-[3.2rem] text-center text-xl w-[150px]"
placeholder=""
{...field}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 파일위치</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
</div>
</BaseLayout>
)
}
export default MeasureDataUpload
+54
View File
@@ -0,0 +1,54 @@
'use client'
import React, { useState } from 'react'
import BaseLayout from '../../BaseLayout'
import { FormField, SelectBox } from '@common/business_components/ui'
import { DatePicker, Input } from '@common/components/ui'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
const formSchema = z.object({
region: z.string(),
})
const region: { text: string, val: number }[] = [{ text: '양평', val: 1 }, { text: '강남', val: 2 }, { text: 'test', val: 3 }]
const RainDataUpload = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
region: "",
}
})
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'fileNm', title: '파일명' },
{ id: 'status', title: '상태' },
{ id: 'code', title: '지정코드' },
{ id: 'cjfl', title: '처리' },
{ id: 'add', title: '추가' },
{ id: 'update', title: '변경' },
{ id: 'error', title: '오류' },
{ id: 'skip', title: 'Skip' }
]
const rtxNmDataList = [
{ "status": '상태', 'code': '123824', 'cjfl': '처리', 'fileNm': '파일', 'add': '', 'update': '', 'error': '', 'skip': '' }
]
return (
<BaseLayout titleName={{ mainTitle: '강우자료 등록', firstCardTitle: '강우자료 업로드', secondCardTitle: '강우자료 파일 업로드 현황' }} form={form} onSubmitEvent={() => { }} graphBtn={true} tableInfoData={{ headList: rtxNmHeadlist, tableData: rtxNmDataList, cellClickFunc: () => { }, onPageChangeFunc: () => { } }}>
<div className="space-y-[1.2rem]">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 파일위치</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
</div>
</BaseLayout>
)
}
export default RainDataUpload
+149
View File
@@ -0,0 +1,149 @@
'use client'
import BaseLayout from '@/ii/BaseLayout'
import { FormField, SelectBox } from '@common/business_components/ui'
import { DatePicker, Input } from '@common/components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
const formSchema = z.object({
region: z.string(),
suyeonga:z.number(),
meter:z.string(),
inputdate: z.coerce.date(),
inpSource:z.string(),
rtkSource: z.string(),
})
const region: { text: string, val: number }[] = [{ text: '양평', val: 1 }, { text: '강남', val: 2 }, { text: 'test', val: 3 }]
const RdiiSetting = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
region: "",
meter:"",
suyeonga:0,
inputdate: new Date(),
inpSource:"",
rtkSource: "",
}
})
// TODO: 임시 데이터
const rtxNmHeadlist = [
{ id: 'nm', title: '분석번호' },
{ id: 'hasu', title: '하수처리장' },
{ id: 'region', title: '처리구역' },
{ id: 'sangsu', title: '상수사용량' },
{ id: 'wjsghks', title: '오수 전환율' },
{ id: 'tnsdhtn', title: '순오수량' },
{ id: 'rkddn', title: '강우 유출량' },
{ id: 'rdii', title: 'RDII' }
]
const rtxNmDataList = [
{ "nm": '1', 'hasu': '', 'region': '처리', 'sangsu': '파일', 'wjsghks': '', 'tnsdhtn': '', 'rkddn': '', 'rdii': '' }
]
const [selectedDate, setSelectedDate] = useState(new Date())
return (
<BaseLayout titleName={{ mainTitle: 'RDII 산정', firstCardTitle: 'RDII산정 기초자료 설정', secondCardTitle: 'RDII 산정결과 현황' }} form={form} onSubmitEvent={() => { }} graphBtn={true} tableInfoData={{ headList: rtxNmHeadlist, tableData: rtxNmDataList, cellClickFunc: () => { }, onPageChangeFunc: () => { } }}>
<div className="grid grid-cols-3 gap-4">
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 처리구역</div>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<SelectBox label='' selectArray={region} onSelectValue={() => { }} selectClass={undefined} className='w-[150px]' />
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 상수사용량 정보</div>
<FormField
control={form.control}
name="inputdate"
render={({ field }) => (
<DatePicker
selected={selectedDate}
onSelect={(date: Date | undefined) => { if (date) setSelectedDate(date) }}
mode={"single"} className='w-[150px]'
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 관망도자료</div>
<FormField
control={form.control}
name="rtkSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ RDII 기여면적(ha)</div>
<FormField
control={form.control}
name="meter"
render={({ field }) => (
<Input
className="h-[3.2rem] text-center text-xl w-[150px]"
placeholder=""
{...field}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 오수 전환율</div>
<FormField
control={form.control}
name="inpSource"
render={({ field }) => (
<Input
className="h-[3.2rem] text-center text-xl w-[150px]"
placeholder=""
{...field}
/>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 강우자료</div>
<FormField
control={form.control}
name="inpSource"
render={({ field }) => (
<div className="w-[150px]">
<Input id="picture" type="file" />
</div>
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 수용가 수</div>
<FormField
control={form.control}
name="suyeonga"
render={({ field }) => (
<SelectBox label='' selectArray={[]} onSelectValue={() => { }} selectClass={undefined} className='w-[150px] h-[3.2rem]' />
)}
/>
</div>
<div className="grid grid-cols-[120px_1fr] items-center">
<div className="text-xl font-medium">○ 수용가-맨홀 매칭</div>
<FormField
control={form.control}
name="inpSource"
render={({ field }) => (
<SelectBox label='' selectArray={[{'text':'매칭(양평).shp','val':'1'}]} onSelectValue={() => { }} selectClass={undefined} className='h-[3.2rem] w-[150px]' />
)}
/>
</div>
</div>
</BaseLayout>
)
}
export default RdiiSetting
+81
View File
@@ -0,0 +1,81 @@
'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'
const tableHeadlist = [
{ id: 'no', title: 'NO' },
{ id: 'fileNm', title: '파일명' },
{ id: 'manageYear', title: '기준년도' },
{ id: 'registDt', title: '등록 일자' },
{ id: 'registUsr', title: '등록자' }
]
const tableData = [
{ 'no': '1', 'fileNm': '파일명', 'manageYear': '2023', 'registDt': '2025-04-16', 'registUsr': 'me' },
{ 'no': '2', 'fileNm': '파일명', 'manageYear': '2024', 'registDt': '2025-04-16', 'registUsr': 'me2' },
{ 'no': '3', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3' }
]
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={20} 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
+64
View File
@@ -0,0 +1,64 @@
'use client'
import { ContentsDiv, MainDiv, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiTable } from '@common/business_components/ui'
import { Button, DatePicker, 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
const tableHeadlist = [
{ id: 'no', title: 'NO' },
{ id: 'fileNm', title: '파일명' },
{ id: 'manageYear', title: '관리년도' },
{ id: 'registDt', title: '등록 일자' },
{ id: 'registUsr', title: '등록자' }
]
const tableData = [
{ 'no': '1', 'fileNm': '파일명', 'manageYear': '2023', 'registDt': '2025-04-16', 'registUsr': 'me' },
{ 'no': '2', 'fileNm': '파일명', 'manageYear': '2024', 'registDt': '2025-04-16', 'registUsr': 'me2' },
{ 'no': '3', 'fileNm': '파일명', 'manageYear': '2025', 'registDt': '2025-04-16', 'registUsr': 'me3' }
]
const Page = () => {
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
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>
</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={20} 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
+30 -4
View File
@@ -4,10 +4,36 @@ import { Button } from "@common/components/ui";
export default function Home() {
return (
<div className="grid grid-cols-12 gap-2">
<div className="col-span-2">
<Link href={"/oper/pplines"}><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/pplines"}><Button>관로시설</Button></Link>
<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/baseinfo/mock"}><Button>모의정보 설정</Button></Link>
<Link href={"/ii/baseinfo/observe"}><Button>관망도 등록</Button></Link>
<Link href={"/ii/baseinfo/polygon"}><Button>티센 폴리곤 등록</Button></Link>
<Link href={"/ii/baseinfo/regist"}><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/ir/measureDataUpload"}><Button>하수측정자료 업로드</Button></Link>
<Link href={"/ii/ir/irsetting"}><Button>침입수 산정</Button></Link>
</div>
</div>
<div>
<h3 className="text-2xl text-bold gap-4 mx-4">I/I 분석(RDII 분석)</h3>
<div className="flex flex-row gap-4 p-4">
<Link href={"/ii/rdii/rain"}><Button>강우자료 등록</Button></Link>
<Link href={"/ii/rdii/rdiisetting"}><Button>rdii 산정</Button></Link>
</div>
</div>
</div>
);
}
@@ -42,6 +42,7 @@
"recharts": "^2.15.1",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.1",
"xlsx": "^0.18.5",
"zod": "^3.24.2"
},
"devDependencies": {
@@ -63,7 +64,7 @@
"eslint-plugin-react-refresh": "^0.4.16",
"eslint-plugin-storybook": "^0.11.2",
"globals": "^15.14.0",
"postcss": "^8.5.1",
"postcss": "^8.5.3",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"storybook": "^8.5.3",
@@ -1,69 +1,10 @@
@import "@common/assets/styles/base.css";
@import "@common/assets/styles/diveicon.css";
@import "@common/assets/styles/components.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: #ffffff;
--foreground: #0a0a0a;
--card: #ffffff;
--card-foreground: #0a0a0a;
--popover: #ffffff;
--popover-foreground: #0a0a0a;
--primary: #2094fa;
--primary-foreground: #fafafa;
--secondary: #f5f5f5;
--secondary-foreground: #0f1e33;
--muted: #f5f5f5;
--muted-foreground: #737373;
--accent: #f5f5f5;
--accent-foreground: #0f1e33;
--destructive: #ff4141;
--destructive-foreground: #fafafa;
--border: #e5e5e5;
--input: #e5e5e5;
--label: #666666;
--ring: #0a0a0a;
--chart-1: #d95b43;
--chart-2: #299392;
--chart-3: #263e4d;
--chart-4: #dfb938;
--chart-5: #f08634;
--radius: 1rem;
}
[data-mode="dark"] {
--background: #0a0a0a;
--foreground: #fafafa;
--card: #0a0a0a;
--card-foreground: #fafafa;
--popover: #0a0a0a;
--popover-foreground: #fafafa;
--primary: #2094fa;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a2a2a2;
--accent: #262626;
--accent-foreground: #fafafa;
--destructive: #b22222;
--destructive-foreground: #fafafa;
--border: #262626;
--input: #262626;
--label: #f1f1f1;
--ring: #d4d4d4;
--chart-1: #3399ff;
--chart-2: #339966;
--chart-3: #ff9933;
--chart-4: #cc66ff;
--chart-5: #ff3399;
}
}
@layer base {
* {
@apply border-border;
@@ -74,4 +15,4 @@
body {
@apply bg-background text-foreground;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,11 +0,0 @@
@layer base {}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
@@ -9,12 +9,23 @@ import type { DayPickerSingleProps } from "react-day-picker";
import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from "@common/components/ui";
import { cn } from "../../lib/utils";
import { FormControl } from "../form";
import { ko } from "date-fns/locale";
import { useState } from "react";
type DatePickerProps = DayPickerSingleProps & { field: { value: Date; onChange: (date: Date | undefined) => void } ,selectClass:string|undefined };
export function DatePicker({ field, selectClass }: DatePickerProps) {
const [open, setOpen] = useState(false);
const handleSelect = (date: Date | undefined) => {
field.onChange(date); // React Hook Form 업데이트
setOpen(false);
};
return (
<Popover>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<FormControl>
<Button
@@ -27,7 +38,12 @@ export function DatePicker({ field, selectClass }: DatePickerProps) {
):selectClass}
>
{field?.value ? (
format(field?.value, "PPP")
format(
field.value instanceof Date && !isNaN(field.value.getTime())
? field.value
: new Date()
, "PPP", { locale: ko })
) : (
<span>Pick a date</span>
)}
@@ -39,8 +55,9 @@ export function DatePicker({ field, selectClass }: DatePickerProps) {
<Calendar
mode="single"
selected={field?.value}
onSelect={field?.onChange}
onSelect={handleSelect}
initialFocus
locale={ko}
/>
</PopoverContent>
</Popover>
@@ -1,52 +1,46 @@
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@common/components";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from "@common/components";
type ModalType = {
bodyClass: string;
butNm: string;
title: string;
body: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
noHeaderYn?: 'N' | 'Y';
noFooterYn?: 'N' | 'Y';
headerTitle?: string;
footerContent?: React.ReactNode;
}
export function UiModal({ bodyClass, butNm, title, body, open, onOpenChange }: ModalType) {
const dialogProps = open !== undefined && onOpenChange !== undefined
? { open, onOpenChange }
: {};
export function UiModal({
bodyClass,
body,
open,
onOpenChange,
noHeaderYn = 'N',
noFooterYn = 'N',
headerTitle,
footerContent
}: ModalType) {
return (
<>
{!open && (
<Dialog>
<DialogTrigger asChild>
<Button className="mr-4 rounded-lg">{butNm}</Button>
</DialogTrigger>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={bodyClass}>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
{body}
<DialogFooter className="sm:justify-start">
</DialogFooter>
{noHeaderYn === 'N' && (
<DialogHeader>
<DialogDescription>
{headerTitle}
</DialogDescription>
</DialogHeader>
)}
{(open && body)}
{noFooterYn === 'N' && (
<DialogFooter className="sm:justify-start">
{footerContent}
</DialogFooter>
)}
</DialogContent>
</Dialog>
)}
{open && (
<Dialog {...dialogProps}>
<DialogContent className={bodyClass}>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>
</DialogDescription>
</DialogHeader>
{body}
<DialogFooter className="sm:justify-start">
</DialogFooter>
</DialogContent>
</Dialog>
)}
</>
);
}
@@ -0,0 +1,16 @@
import * as XLSX from 'xlsx';
type ExcelDownloadProps = {
data : JSON | JSON[];
fileName : string;
sheetNameArray : string[];
}
export const downloadExcel = ({ data, fileName = new Date().getTime().toString()+'.xlsx', sheetNameArray = ["Sheet1"] }: ExcelDownloadProps) => {
const worksheet = XLSX.utils.json_to_sheet(Array.isArray(data) ? data : [data]);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameArray[0]);
//let buffer = XLSX.write(workbook, { bookType: "xlsx", type: "buffer" });
//XLSX.write(workbook, { bookType: "xlsx", type: "binary" });
XLSX.writeFile(workbook, fileName+'.xlsx');
};
@@ -16,4 +16,5 @@ export * from "./search_form_right";
export * from "./table_div";
export * from "./list_div";
export * from "./tab";
export * from "./dialog_ui"
export * from "./dialog_ui";
export * from "./excel_download";
@@ -1,8 +1,8 @@
import React from 'react'
function ListDiv(props: {children: React.ReactNode}) {
function ListDiv(props: {children: React.ReactNode, className?: string|undefined}) {
return (
<div className="card card-border">
<div className={`card card-border ${props.className}`}>
{props.children}
</div>
)
@@ -1,7 +1,7 @@
function SearchDiv ({children}:{ children: React.ReactNode; }) {
return (
<div className="flex card card-border !mt-6">
{children}
{children}
</div>
)
}
@@ -1,10 +1,317 @@
import React from 'react'
import React, { useEffect, useState } from 'react';
import type { SelectDataType } from '../search_input_select';
import { SelectBox } from '../select';
/**
* 검색 폼 왼쪽 컴포넌트 타입
*
* @param isShowPart 구분 조회 여부
* @param children 자식 컴포넌트
* @param selectSidoData 시도 데이터
* @param selectSigunData 시군구 데이터
* @param selectRegionData 유역 데이터
* @param selectUnitWaterShedData 단위유역 데이터
* @param selectMiddleRegionData 중권역 데이터
* @param selectEnvPartData 환경청 데이터
* @param selectEnvPartSidoData 환경청 시도 데이터
* @param selectEnvPartSigunData 환경청 시군구 데이터
* @param setRtnPartValue 부분 값 변경 핸들러
*/
type SearchFormLeftProps = {
isShowPart?: boolean | undefined;
children: React.ReactNode;
selectSidoData?: SelectDataType[] | [];
selectSigunData?: SelectDataType[] | [];
selectRegionData?: SelectDataType[] | [];
selectUnitWaterShedData?: SelectDataType[] | [];
selectMiddleRegionData?: SelectDataType[] | [];
selectEnvPartData?: EnvPartSelectDataType[] | [];
selectEnvPartSidoData?: SelectDataType[] | [];
selectEnvPartSigunData?: SelectDataType[] | [];
setRtnPartValue?: (value: string) => void;
setRtnSidoValue?: (value: string) => void;
setRtnSigunValue?: (value: string) => void;
setRtnRegionValue?: (value: string) => void;
setRtnUnitWaterShedValue?: (value: string) => void;
setRtnMiddleRegionValue?: (value: string) => void;
setRtnEnvPartValue?: (value: string) => void;
setRtnEnvPartSidoValue?: (value: string) => void;
setRtnEnvPartSigunValue?: (value: string) => void;
resetTrigger?: boolean;
}
type EnvPartSelectDataType = SelectDataType & {
adstDliStrCd: string,
}
const selectPartData:SelectDataType[] = [
{ text: "행정별", val: '0' },
{ text: "유역별", val: '01' },
{ text: "환경청별", val: '02' },
];
function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSigunValue, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false }: SearchFormLeftProps) {
const [partValue, setPartValue] = useState<string>('0');
const [showPart, setShowPart] = useState<string>('');
const [sidoValue, setSidoValue] = useState<string>('0');
const [sigunValue, setSigunValue] = useState<string>('0');
const [regionValue, setRegionValue] = useState<string>('0');
const [unitWaterShedValue, setUnitWaterShedValue] = useState<string>('0');
const [middleRegionValue, setMiddleRegionValue] = useState<string>('0');
const [envPartValue, setEnvPartValue] = useState<string>('0');
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>('0');
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>('0');
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredUnitWaterShedData, setFilteredUnitWaterShedData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredMiddleRegionData, setFilteredMiddleRegionData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredEnvPartData, setFilteredEnvPartData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd:'' }]);
const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd:'' }]);
const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState<SelectDataType[]>([]);
// 시군구 세팅
const initializeSigunData = (sidoCd: string) => {
if (sidoCd === '0') {
setSigunValue('0');
setFilteredSigunData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectSigunData.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd)
];
setSigunValue('0');
setFilteredSigunData(newFilteredData);
}
}
// 단위유역 세팅
const initializeUnitWaterShedData = (regionCd: string) => {
if(regionCd === '0') {
setUnitWaterShedValue('0');
setFilteredUnitWaterShedData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectUnitWaterShedData.filter((item: SelectDataType) => item.val.substring(0, 1) === regionCd)
];
setUnitWaterShedValue('0');
setFilteredUnitWaterShedData(newFilteredData);
}
}
// 중권역 세팅
const initializeMiddleRegionData = (unitWaterShedCd: string) => {
if(unitWaterShedCd === '0') {
setMiddleRegionValue('0');
setFilteredMiddleRegionData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectMiddleRegionData.filter((item: SelectDataType) => item.val.substring(0, 2) === unitWaterShedCd)
];
setMiddleRegionValue('0');
setFilteredMiddleRegionData(newFilteredData);
}
}
// 환경청 세팅 - 시도
const initializeCityDataByEnvPartSido = (envPartCd: string) => {
const foundEnvPart = selectEnvPartData.find((item: EnvPartSelectDataType) => item.val === envPartCd);
const cityArray = foundEnvPart?.adstDliStrCd.toString().split('|').join('').split(',');
// 시군구 데이터
const envSigunData = selectSigunData.filter((item: SelectDataType) => cityArray?.includes(item.val));
// 환경청 시도 데이터
const sidoArray = [...new Set(envSigunData.map((item: SelectDataType) => item.val.substring(0, 2)))];
const envSidoData = selectSidoData.filter((item: SelectDataType) => sidoArray?.includes(item.val));
setFilteredEnvPartSigunDataSelected(envSigunData);
setFilteredEnvPartSidoData([
{ text: "전체", val: '0', adstDliStrCd:'' },
...envSidoData.map((item: SelectDataType) => ({ text: item.text, val: item.val, adstDliStrCd:'' }))
]);
}
// 환경청 세팅 - 시군구
const initializeCityDataByEnvPartSigun = (sidoCd: string) => {
const envSigunData = filteredEnvPartSigunDataSelected.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd);
setFilteredEnvPartSigunData([
{ text: "전체", val: '0'},
...envSigunData.map((item: SelectDataType) => ({ text: item.text, val: item.val }))
]);
}
const onSelectValue = (val: string, type: string) => {
switch(type) {
case 'part':
setPartValue(val);
if (setRtnPartValue) {
setRtnPartValue(val);
}
// 구분
if(val == '01') {
setShowPart('01');
initializeMiddleRegionData('0');
initializeUnitWaterShedData('0');
} else if(val == '02') {
setShowPart('02');
setFilteredEnvPartData([
{ text: "전체", val: '0', adstDliStrCd:'' },
...selectEnvPartData.filter((item: EnvPartSelectDataType) => item.val !== null && item.adstDliStrCd !== '')
]);
setEnvPartValue('0');
initializeCityDataByEnvPartSido('0');
} else {
setShowPart('0');
initializeSigunData('0');
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
}
// 다른 selectbox 전체로 초기화
onSelectValue(selectSidoData[0]?.val || '0', 'sido');
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
break;
case 'sido':
setSidoValue(val);
if (setRtnSidoValue) {
setRtnSidoValue(val);
}
initializeSigunData(val);
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
break;
case 'sigun':
if (setRtnSigunValue) {
setRtnSigunValue(val);
}
setSigunValue(val);
break;
// 유역 세팅
case 'region' :
setRegionValue(val);
if (setRtnRegionValue) {
setRtnRegionValue(val);
}
initializeUnitWaterShedData(val);
onSelectValue(selectUnitWaterShedData[0]?.val || '0', 'unitWaterShed');
break;
case 'unitWaterShed' :
setUnitWaterShedValue(val);
if (setRtnUnitWaterShedValue) {
setRtnUnitWaterShedValue(val);
}
initializeMiddleRegionData(val);
onSelectValue(selectMiddleRegionData[0]?.val || '0', 'middleRegion');
break;
case 'middleRegion' :
setMiddleRegionValue(val);
if (setRtnMiddleRegionValue) {
setRtnMiddleRegionValue(val);
}
break;
// 환경청 세팅
case 'envPart' :
setEnvPartValue(val);
if (setRtnEnvPartValue) {
setRtnEnvPartValue(val);
}
initializeCityDataByEnvPartSido(val);
onSelectValue(filteredEnvPartSidoData[0]?.val || '0', 'envPartSido');
break;
case 'envPartSido' :
setEnvPartSidoValue(val);
if (setRtnEnvPartSidoValue) {
setRtnEnvPartSidoValue(val);
}
initializeCityDataByEnvPartSigun(val);
onSelectValue(filteredEnvPartSigunData[0]?.val || '0', 'envPartSigun');
break;
case 'envPartSigun' :
setEnvPartSigunValue(val);
if (setRtnEnvPartSigunValue) {
setRtnEnvPartSigunValue(val);
}
break;
}
}
// 리셋 함수
const resetAllValues = () => {
setPartValue('0');
setShowPart('');
setSidoValue('0');
setSigunValue('0');
setRegionValue('0');
setUnitWaterShedValue('0');
setMiddleRegionValue('0');
setEnvPartValue('0');
setEnvPartSidoValue('0');
setEnvPartSigunValue('0');
setFilteredSigunData([{ text: "전체", val: '0' }]);
setFilteredUnitWaterShedData([{ text: "전체", val: '0' }]);
setFilteredMiddleRegionData([{ text: "전체", val: '0' }]);
setFilteredEnvPartData([{ text: "전체", val: '0', adstDliStrCd: '' }]);
setFilteredEnvPartSidoData([{ text: "전체", val: '0', adstDliStrCd: '' }]);
setFilteredEnvPartSigunData([{ text: "전체", val: '0' }]);
if (setRtnPartValue) setRtnPartValue('0');
if (setRtnSidoValue) setRtnSidoValue('0');
if (setRtnSigunValue) setRtnSigunValue('0');
if (setRtnRegionValue) setRtnRegionValue('0');
if (setRtnUnitWaterShedValue) setRtnUnitWaterShedValue('0');
if (setRtnMiddleRegionValue) setRtnMiddleRegionValue('0');
if (setRtnEnvPartValue) setRtnEnvPartValue('0');
if (setRtnEnvPartSidoValue) setRtnEnvPartSidoValue('0');
if (setRtnEnvPartSigunValue) setRtnEnvPartSigunValue('0');
};
// 리셋 트리거 변경 감지
useEffect(() => {
if (resetTrigger) {
resetAllValues();
// 리셋 후 트리거를 false로 설정
if (setRtnPartValue) {
setRtnPartValue('0');
}
}
}, [resetTrigger]);
function SearchFormLeft(props: {children: React.ReactNode}) {
return (
<div className="grid grid-cols-12 gap-4 w-4/5 mx-4">
{props.children}
isShowPart ? (
<div className="grid grid-cols-12 gap-5 w-11/12 m-4">
<SelectBox className="col-span-3" label={'구분'} selectArray={selectPartData} onSelectValue={(val) => onSelectValue(val.toString(), 'part')} value={partValue} selectClass={undefined}/>
{showPart === '01' ? (
<>
<SelectBox className="col-span-3" label={'유역'} selectArray={selectRegionData} onSelectValue={(val) => onSelectValue(val.toString(), 'region')} value={regionValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'단위유역'} selectArray={filteredUnitWaterShedData} onSelectValue={(val) => onSelectValue(val.toString(), 'unitWaterShed')} value={unitWaterShedValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'중권역'} selectArray={filteredMiddleRegionData} onSelectValue={(val) => onSelectValue(val.toString(), 'middleRegion')} value={middleRegionValue} selectClass={undefined}/>
</>
) : showPart === '02' ? (
<>
<SelectBox className="col-span-3" label={'환경청'} selectArray={filteredEnvPartData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPart')} value={envPartValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'시도'} selectArray={filteredEnvPartSidoData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSido')} value={envPartSidoValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'시군구'} selectArray={filteredEnvPartSigunData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSigun')} value={envPartSigunValue} selectClass={undefined}/>
</>
) : (
<>
<SelectBox className="col-span-3" label={'시도'} selectArray={selectSidoData} onSelectValue={(val) => onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'시군구'} selectArray={filteredSigunData} onSelectValue={(val) => onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={undefined}/>
</>
)}
{children}
</div>
) : (
<div className="grid grid-cols-12 gap-5 w-11/12 m-4">
{children}
</div>
)
)
}
@@ -0,0 +1,375 @@
## SearchFormLeft 모듈
#### 검색영역 좌측 모듈로 행정별, 유역별, 환경청을 선택했을 때 검색 영역을 세팅해주고 childnode를 붙여서 출력해주는 모듈
#### 1. 컴포넌트 호출방법
```js
import { SearchFormLeft } from "@common/business_components";
.....
// state 지정
const [partValue, setPartValue] = useState<string>("0");
const [sidoValue, setSidoValue] = useState<string>("0");
const [sigunValue, setSigunValue] = useState<string>("0");
const [regionValue, setRegionValue] = useState<string>("0");
const [untitWaterShedValue, setUntitWaterShedValue] = useState<string>("0");
const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
const [envPartValue, setEnvPartValue] = useState<string>("0");
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>("0");
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>("0");
const [resetTrigger, setResetTrigger] = useState<boolean>(false);
.....
<SearchFormLeft isShowPart={true}
selectSidoData={selectSidoData}
selectSigunData={selectSigunData}
selectRegionData={selectRegionData}
selectUnitWaterShedData={selectUnitWaterShedData}
selectMiddleRegionData={selectMiddleRegionData}
selectEnvPartData={selectEnvPartData}
setRtnPartValue={setPartValue}
setRtnSidoValue={setSidoValue}
setRtnSigunValue={setSigunValue}
setRtnRegionValue={setRegionValue}
setRtnUnitWaterShedValue={setUntitWaterShedValue}
setRtnMiddleRegionValue={setMiddleRegionValue}
setRtnEnvPartValue={setEnvPartValue}
setRtnEnvPartSidoValue={setEnvPartSidoValue}
setRtnEnvPartSigunValue={setEnvPartSigunValue}
resetTrigger={resetTrigger}
>
```
&emsp; ㆍisShowPart : 검색단에서 구분(행정별,유역별,환경청별) 세팅 여부(보일 경우 : Y, 아닐경우 : N 혹은 미첨부)
&emsp; ㆍselectSidoData : 전체 시도 데이터
&emsp; ㆍselectSigunData : 전체 시군구구 데이터
&emsp; ㆍselectRegionData : 전체 유역역 데이터
&emsp; ㆍselectUnitWaterShedData : 전체 단위유역 데이터
&emsp; ㆍselectMiddleRegionData : 전체 중권역 데이터
&emsp; ㆍselectEnvPartData : 환경청 전체 데이터
&emsp; ㆍsetRtnPartValue : 선택한 구분 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnSidoValue : 선택한 시도 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnSigunValue : 선택한 시군구 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnRegionValue : 선택한 유역 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnUnitWaterShedValue : 선택한 단위유역 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnMiddleRegionValue : 선택한 중유역 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnEnvPartValue : 선택한 환경청 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnEnvPartSidoValue : 선택한 환경청의 시도 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnEnvPartSigunValue : 선택한 환경청 시도의 시군구 set함수(조회시 페이지에서 사용)
&emsp; ㆍresetTrigger : 초기화 trigger 변수
#### 2. 세팅방법
```js
/**
* store - index.js
* ※ 업무파트별로 common/폴더에 스토어를 만들어놓는걸 추천!
* ※ fetch 구문은 각자가 개발 후 return 된 데이터 타입만 맞추면 된다.
*/
// 시도 API 호출 함수 추가
const fetchSidoData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/city', null);
const data = res.data;
selectSidoData = [
{ text: "전체", val: '0'},
...data.map((item: sidoDataType) => ({
text: item.sido || '',
val: String(item.adstDstrCd) || ''
}))
];
return selectSidoData;
} catch (error) {
console.error('시도 데이터를 가져오는데 실패했습니다:', error);
return selectSidoData;
}
};
// 시군구 API 호출 함수 추가
const fetchSigunData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/sgg', null);
const data = res.data;
selectSigunData = [
{ text: "전체", val: '0' },
...data
.map((item: sigunDataType) => ({
text: item.gugun || '',
val: String(item.adstDstrCd) || ''
}))
];
return selectSigunData;
} catch (error) {
console.error('시군구 데이터를 가져오는데 실패했습니다:', error);
return selectSigunData;
}
};
// 권역 API 호출 함수 추가
const fetchRegionData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/sarea', null);
const data = res.data;
return [
{ text: "전체", val: '0'},
...data.map((item: regionDataType) => ({
text: item.rvbsn || '',
val: String(item.rvbsnCd) || ''
}))
];
} catch (error) {
console.error('권역 데이터를 가져오는데 실패했습니다:', error);
return selectRegionData;
}
};
// 중권역 API 호출 함수 추가
const fetchMiddleRegionData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/midArea', null);
const data = res.data;
return [
{ text: "전체", val: '0' },
...data
.map((item: middleRegionDataType) => ({
text: item.mdmbsn || '',
val: String(item.rvbsnCd) || ''
}))
];
} catch (error) {
console.error('중권역 데이터를 가져오는데 실패했습니다:', error);
return selectMiddleRegionData;
}
};
// 단위유역 API 호출 함수 추가
const fetchUnitWaterShedData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/largeArea', null);
const data = res.data;
return [
{ text: "전체", val: '0' },
...data
.map((item: unitWaterShedDataType) => ({
text: item.lgbsn || '',
val: String(item.rvbsnCd) || ''
}))
];
} catch (error) {
console.error('단위유역 데이터를 가져오는데 실패했습니다:', error);
return selectUnitWaterShedData;
}
};
// 환경청 데이터
const fetchEnvPartData = async (): Promise<envPartSelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/govArea', null);
const data = res.data;
selectEnvPartData = [
{ text: "전체", val: '0', adstDliStrCd: '' },
...data
.map((item: envPartDataType) => ({
text: item.lclgvrnmntNm || '',
val: String(item.lclgvrnmntCd) || '',
adstDliStrCd: item.adstDliStrCd || ''
}))
];
return selectEnvPartData;
} catch (error) {
console.error('환경청 데이터를 가져오는데 실패했습니다:', error);
return selectEnvPartData;
}
};
.......
// 타입 및 state 반환
export type CommonType = {
Common: {
isInit: boolean;
selectSidoData: SelectDataType[];
selectSigunData: SelectDataType[];
selectPartData: SelectDataType[];
selectUpdownData: SelectDataType[];
selectOperationData: SelectDataType[];
selectFacilityPartData: SelectDataType[];
selectSearchYear: SelectDataType[];
selectUnitWaterShedData: SelectDataType[];
selectRegionData: SelectDataType[];
selectMiddleRegionData: SelectDataType[];
selectEnvPartSidoData: SelectDataType[];
selectEnvPartSigunData: SelectDataType[];
selectFlowTerm: SelectDataType[];
selectFlowPanel: SelectDataType[];
selectEnvPartData: envPartSelectDataType[];
}
}
export const commonState:CommonType = {
Common: {
isInit: false,
selectSidoData: selectSidoData,
selectSigunData: selectSigunData,
selectPartData: selectPartData,
selectUpdownData: selectUpdownData,
selectOperationData: selectOperationData,
selectFacilityPartData: selectFacilityPartData,
selectSearchYear: selectSearchYear,
selectRegionData: selectRegionData,
selectUnitWaterShedData: selectUnitWaterShedData,
selectMiddleRegionData: selectMiddleRegionData,
selectEnvPartSidoData : selectEnvPartSidoData,
selectEnvPartSigunData : selectEnvPartSigunData,
selectFlowTerm: selectFlowTerm,
selectFlowPanel: selectFlowPanel,
selectEnvPartData : selectEnvPartData,
}
}
.....
// action export
export type CommonAction = {
initializeCommonData: () => Promise<void>;
}
.......
// reducer 세팅
export const CommonReducer:(set:any)=>CommonAction=(set: any) => {
return {
// 시도 변경 이벤트
initializeCommonData: async () => {
const promiseSet = await Promise.all([fetchSidoData(), fetchSigunData(), fetchRegionData(), fetchUnitWaterShedData(), fetchMiddleRegionData(), fetchEnvPartData()]);
const [sidoData, sggData, regionData, unitData, midData, envData] = promiseSet;
set((state: CommonType) => ({
Common: {
...state.Common,
selectSidoData: sidoData,
selectSigunData: sggData,
selectRegionData: regionData,
selectMiddleRegionData: midData,
selectUnitWaterShedData: unitData,
selectEnvPartData: envData
}
})
)},
}
}
```
```js
/**
* page.tsx
*/
.......
// store에서 액션 호출 (각 페이지 유형별로 세팅)
const {
....
,initializeCommonData
....
} = useFoStore((state) => state);
.......
// 데이터 반환
const {
isInit,
selectPartData,
selectUpdownData,
selectOperationData,
selectFacilityPartData,
selectSidoData,
selectSigunData,
selectRegionData,
selectUnitWaterShedData,
selectMiddleRegionData,
selectEnvPartData,
} = Common;
.......
// 페이지 내에서 사용할 수 있게 state 설정
const [partValue, setPartValue] = useState<string>("0");
const [sidoValue, setSidoValue] = useState<string>("0");
const [sigunValue, setSigunValue] = useState<string>("0");
const [regionValue, setRegionValue] = useState<string>("0");
const [untitWaterShedValue, setUntitWaterShedValue] = useState<string>("0");
const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
const [envPartValue, setEnvPartValue] = useState<string>("0");
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>("0");
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>("0");
const [resetTrigger, setResetTrigger] = useState<boolean>(false);
<!-- 초기화 기능 -->
const resetVal = () => {
console.log('=== 초기화 ===');
console.log(selectSidoData);
// SelectBox 초기화 - 각 selectArray의 첫 번째 옵션 값 사용
const initialPartValue = selectPartData[0]?.val || "0";
const initialSidoValue = selectSidoData[0]?.val || "0";
const initialSigunValue = selectSigunData[0]?.val || "0";
const initialRegionValue = selectRegionData[0]?.val || "0";
const initialUnitWaterShedValue = selectUnitWaterShedData[0]?.val || "0";
const initialMiddleRegionValue = selectMiddleRegionData[0]?.val || "0";
const initialEnvPartValue = selectEnvPartData[0]?.val || "0";
const initialEnvPartSidoValue = selectEnvPartData[0]?.val || "0";
const initialEnvPartSigunValue = selectEnvPartData[0]?.val || "0";
// 상태값 설정
setPartValue(initialPartValue);
setSidoValue(initialSidoValue);
setSigunValue(initialSigunValue);
setRegionValue(initialRegionValue);
setUntitWaterShedValue(initialUnitWaterShedValue);
setMiddleRegionValue(initialMiddleRegionValue);
setEnvPartValue(initialEnvPartValue);
setEnvPartSidoValue(initialEnvPartSidoValue);
setEnvPartSigunValue(initialEnvPartSigunValue);
// 리셋 후 트리거를 false로 설정
setTimeout(() => {
setResetTrigger(false);
}, 0);
}
// 조회 함수
const chkVal = useCallback(() => {
setResetPage(true);
// 조회 funciton
searchFcltyQltyList(1);
}, [searchFcltyQltyList]);
// 그리드 page 넘어갈 때 필요한 함수
const handlePageChange = useCallback((page: number) => {
setResetPage(false);
searchFcltyQltyList(page);
}, [searchFcltyQltyList]);
// 테이블 호출 컴포넌트
<UiTable cellClick={(index: number | undefined) => index !== undefined && cellClick(index)} headName={"시설별 수질현황"} tableData={mainList} headlist={upHeadList2} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange}>
<HeadMakeColSpan upHeadList={upHeadList2} layoutNum={layoutNum}/>
</UiTable>
```
@@ -1,11 +1,11 @@
import React from 'react'
import React from 'react';
function SearchFormRight(props: {children: React.ReactNode}) {
return (
<div className="w-1/5 mx-5 flex items-center justify-end space-x-2">
<div className="w-1/12 mx-5 flex flex-col items-center justify-center space-x-2">
{props.children}
</div>
)
}
export { SearchFormRight };
export { SearchFormRight };
@@ -3,14 +3,27 @@ import React from "react";
const SearchInput = React.forwardRef<HTMLInputElement, {className:string, label:string, textValue:string, setText:(val:string)=>void}>(({className, label, textValue, setText}, ref) => {
const changeText=(e:React.ChangeEvent<HTMLInputElement>)=>{
if(e?.target?.value) setText(e.target.value)
}
if(e?.target?.value){
setText(e.target.value)
}
}
const keyUp=(e:any)=>{
if(e.keyCode===8 && e.target.value.length==1){
setText('')
}
}
return (
<div className={className+" flex items-center"} >
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
<Input className="flex-1 ml-8 w-3/4 h-10 border rounded" value={textValue} onChange={changeText}></Input>
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
<Input className="!text-lg flex-1 w-3/4 h-14 border rounded" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
</div>
)
});
export { SearchInput };
export { SearchInput };
@@ -24,12 +24,12 @@ const SearchInputSelect = React.forwardRef<HTMLInputElement, {className:string,
return (
<div className={className+" flex items-center"} >
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
<div className="flex w-full items-center">
<Input className="w-2/3 text-lg h-10 p-2 border rounded" value={textValue} onChange={changeText} />
<Input className="w-2/3 text-xl h-14 p-2 border rounded" value={textValue} onChange={changeText} />
<Select onValueChange={onSelectValue} value={selectValue}>
<SelectTrigger className="ml-2 w-1/3 text-lg" >
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
<SelectTrigger className="ml-2 w-1/3" >
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
</SelectTrigger>
{/* <SelectContent ref={refs ? (refs as unknown as RefType)?.selectValue : null}> */}
<SelectContent >
@@ -42,4 +42,4 @@ const SearchInputSelect = React.forwardRef<HTMLInputElement, {className:string,
</div>
)
});
export { SearchInputSelect };
export { SearchInputSelect };
@@ -23,7 +23,7 @@ const SelectBox = React.forwardRef<
return (
<div className={className+" flex items-center"} ref={ref}>
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
<Select onValueChange={onSelectValue} value={value !== undefined ? String(value) : undefined}>
<SelectTrigger className={selectClass?selectClass:"flex-1 p-2 border rounded"}>
<SelectValue placeholder={placeHolder} />
@@ -38,4 +38,4 @@ const SelectBox = React.forwardRef<
)
})
export { SelectBox };
export { SelectBox };
@@ -1,6 +1,6 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import * as React from "react"
import { cn } from "../../../lib/utils"
@@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-13 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-xl ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
@@ -103,7 +103,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
className={cn("py-1.5 pl-8 pr-2 text-lg font-semibold", className)}
{...props}
/>
))
@@ -116,7 +116,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-lg outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
@@ -145,14 +145,6 @@ const SelectSeparator = React.forwardRef<
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue
}
@@ -1,32 +1,57 @@
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
import { useEffect, useState } from "react";
import UiPageNations from "./uiPageNations";
type UiTableProps = {
headName: React.ReactNode | undefined;
tableData: { [key: string]: string | number | boolean }[];
tableData: { [key: string]: string | number | boolean | object}[];
headlist: UiHeadProps[];
pageSize: number;
total: number;
children: React.ReactNode | undefined;
cellClick : ((index:number|undefined)=>void) | undefined;
cellClick : ((index:number|undefined, headId:string)=>void )| undefined;
onPageChange: (page: number) => void;
className?: string;
resetPageNo?: boolean;
}
type UiHeadProps = {
id: string;
title: string;
align?: string;
}
export function UiTable({ tableData, headlist, pageSize, total, children, headName, cellClick }: UiTableProps) {
const cur = 1;
export function UiTable({ tableData, headlist, pageSize, total, children, headName, cellClick, onPageChange, className = '', resetPageNo = false }: UiTableProps) {
const [cur, setCur] = useState<number>(1);
const [idx, setIdx] = useState<number>(-1);
useEffect(() => {
if (resetPageNo) {
setCur(1);
onPageChange(1);
}
}, [onPageChange, resetPageNo]);
const handlePageChange = (page: number) => {
setCur(page);
onPageChange(page);
}
const handleCellClick = (index: number,headId:string )=> {
if (cellClick) {
cellClick(index,headId);
setIdx(index);
}
};
return (
<>
{headName ? <div className="card-header">
<h3 className="text-2xl font-medium text-label">{headName}</h3>
</div> : <></>}
<div className="card !m-[1.2rem] max-h-[40rem] overflow-auto !p-0r">
<Table>
<TableHeader>
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
<Table variant="secondary">
<TableHeader className="font-bold">
{children ? <>{children}</> : <TableRow>
{headlist?.map((head) => (
<TableHead key={head.id}>{head.title}</TableHead>
@@ -34,17 +59,26 @@ export function UiTable({ tableData, headlist, pageSize, total, children, headNa
</TableRow>}
</TableHeader>
<TableBody className='text-center'>
{tableData?.map((el: { [key: string]: string | number | boolean }, index) => (
<TableRow key={index + "row"} >
{headlist.map((head) => (
<TableCell className="cursor-pointer" onClick={()=>cellClick && cellClick(index)} key={head.id + index}>{el[head.id]}</TableCell>
{tableData?.map((el: { [key: string]: string | number | boolean | object}, index) => (
<TableRow key={index + "row"}>
{headlist?.map((head) => (
<TableCell
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${
cellClick && idx === index ? '!font-[1000] cursor-pointer' :
cellClick ? 'cursor-pointer' : ''
}`}
onClick={() => handleCellClick(index, head.id)}
key={head.id + index}
>
{typeof el[head.id] === 'number' ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : String(el[head.id] ?? '')}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
{total <= 0 ? <></> : <UiPageNations total={total} cur={cur} pageSize={pageSize} />}
{total <= 0 ? <></> : <UiPageNations total={total} cur={cur} pageSize={pageSize} onPageChange={handlePageChange} />}
</div>
</>
);
}
}
@@ -1,46 +1,77 @@
'use client'
'use client';
import { useEffect, useState } from "react";
import { Pagination, PaginationContent, PaginationItem, PaginationPrevious, PaginationLink, PaginationNext } from "./lib/pagination";
import { PaginationNext } from "./lib/pagination";
import { PaginationLink } from "./lib/pagination";
import { PaginationPrevious } from "./lib/pagination";
import { PaginationItem } from "./lib/pagination";
import { PaginationContent } from "./lib/pagination";
import { Pagination } from "./lib/pagination";
interface CommonPageNationsProps {
total: number;
cur: number;
pageSize: number;
total: number;
cur: number;
pageSize: number;
onPageChange: (page: number) => void;
}
export default function UiPageNations(commonPageNationsProps: CommonPageNationsProps) {
const [pageProps, setPageProps] = useState(commonPageNationsProps);
const [pageProps, setPageProps] = useState(commonPageNationsProps);
useEffect(() => {
setPageProps(commonPageNationsProps);
}, [commonPageNationsProps]);
useEffect(() => {
setPageProps(commonPageNationsProps);
} ,[commonPageNationsProps]);
const totalPages = Math.ceil(pageProps.total / pageProps.pageSize);
const pagesPerGroup = 10;
const currentGroup = Math.floor((pageProps.cur - 1) / pagesPerGroup);
const startPage = currentGroup * pagesPerGroup + 1;
const endPage = Math.min(startPage + pagesPerGroup - 1, totalPages);
const handlePageChange = (page: number) => {
pageProps.onPageChange(page);
};
return(
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
return (
<Pagination>
<PaginationContent>
{/* 이전 그룹으로 이동 */}
<PaginationItem>
<PaginationPrevious
href="#"
onClick={() => {
if (startPage > 1) handlePageChange(startPage - 1);
}}
/>
</PaginationItem>
{/* 현재 그룹 페이지들 표시 */}
{
Array.from({length:Math.ceil(commonPageNationsProps.total/commonPageNationsProps.pageSize)},(_,i)=>i+1).map((el,index)=>(
<PaginationItem key={index}>
<PaginationLink href= '#'>{el}</PaginationLink>
</PaginationItem>
))
}
<PaginationItem>
<PaginationNext href="#" />
Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i).map((pageNum) => (
<PaginationItem key={pageNum}>
<PaginationLink
href="#"
isActive={pageNum === pageProps.cur}
onClick={() => handlePageChange(pageNum)}
>
{pageNum}
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
))
}
{/* 다음 그룹으로 이동 */}
<PaginationItem>
<PaginationNext
href="#"
onClick={() => {
if (endPage < totalPages) handlePageChange(endPage + 1);
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
@@ -1,93 +1,8 @@
import presetConfig from "../../../tailwind.config.preset";
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class", "[data-mode='dark']"],
content: ["./src/**/*.{ts,tsx,js,jsx}", "./.storybook/**/*.{ts,tsx,js,jsx}"],
theme: {
extend: {
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
card: {
DEFAULT: "var(--card)",
foreground: "var(--card-foreground)",
},
popover: {
DEFAULT: "var(--popover)",
foreground: "var(--popover-foreground)",
},
primary: {
DEFAULT: "var(--primary)",
foreground: "var(--primary-foreground)",
},
secondary: {
DEFAULT: "var(--secondary)",
foreground: "var(--secondary-foreground)",
},
muted: {
DEFAULT: "var(--muted)",
foreground: "var(--muted-foreground)",
},
accent: {
DEFAULT: "var(--accent)",
foreground: "var(--accent-foreground)",
},
destructive: {
DEFAULT: "var(--destructive)",
foreground: "var(--destructive-foreground)",
},
border: "var(--border)",
input: "var(--input)",
label: "var(--label)",
ring: "var(--ring)",
chart: {
1: "var(--chart-1)",
2: "var(--chart-2)",
3: "var(--chart-3)",
4: "var(--chart-4)",
5: "var(--chart-5)",
},
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"spin-parent": {
from: { transform: "translate(-50%,-50%) rotate(0deg)" },
to: { transform: "translate(-50%,-50%) rotate(360deg)" },
},
"spin-child": {
from: { transform: "translate(0,0) rotate(0deg)" },
to: { transform: "translate(0,0) rotate(360deg)" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"spin-parent": "spin-parent 6s linear infinite",
"spin-child": "spin-child 1s ease-in-out infinite",
},
backgroundImage: {
"gradient-blue": "linear-gradient(.25turn, #2094fa, #00e5dd)",
"gradient-blue-hover":
"linear-gradient(.25turn, #2298ff, #01dcda, #00e8b4)",
"gradient-green": "linear-gradient(.25turn, #00bad0, #00eb6f)",
"gradient-green-hover": "linear-gradient(.25turn, #00bad0, #12fe81)",
"gradient-red": "linear-gradient(.25turn, #ff487c, #ffc425)",
"gradient-red-hover": "linear-gradient(.25turn, #ff487c, #ffea3a)",
"gradient-purple": "linear-gradient(.25turn, #be30d7, #ff9264)",
"gradient-purple-hover": "linear-gradient(.25turn, #be30d7, #ffaf5a)",
},
},
},
presets: [presetConfig],
content: ["./src/**/*.{ts,tsx,js,jsx}"],
plugins: [require("tailwindcss-animate")],
};
@@ -29,7 +29,7 @@ const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
<thead ref={ref} className={cn("sticky top-0 bg-secondary [&_tr]:border-b", className)} {...props} />
));
TableHeader.displayName = "TableHeader";
+166 -2
View File
@@ -140,6 +140,106 @@ importers:
specifier: ^5
version: 5.6.3
apps/sn:
dependencies:
'@common/assets':
specifier: workspace:*
version: link:../../packages/common/assets
'@common/business_components':
specifier: workspace:*
version: link:../../packages/common/business_components
'@common/components':
specifier: workspace:*
version: link:../../packages/common/components
'@frontend-opensource/use-react-hooks':
specifier: ^1.1.4
version: 1.1.4
'@hookform/resolvers':
specifier: ^4.1.3
version: 4.1.3(react-hook-form@7.54.2)
'@radix-ui/react-label':
specifier: ^2.1.2
version: 2.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-slot':
specifier: ^1.1.2
version: 1.1.2(@types/react@18.3.18)(react@18.3.1)
'@tanstack/react-query':
specifier: ^5.67.1
version: 5.67.1(react@18.3.1)
'@tanstack/react-table':
specifier: ^8.21.2
version: 8.21.2(react-dom@18.3.1)(react@18.3.1)
axios:
specifier: ^1.8.1
version: 1.8.1
chart.js:
specifier: ^4.4.8
version: 4.4.8
dayjs:
specifier: ^1.11.13
version: 1.11.13
js-cookie:
specifier: ^3.0.5
version: 3.0.5
next:
specifier: 15.1.7
version: 15.1.7(react-dom@18.3.1)(react@18.3.1)
react-chartjs-2:
specifier: ^5.3.0
version: 5.3.0(chart.js@4.4.8)(react@18.3.1)
react-hook-form:
specifier: ^7.54.2
version: 7.54.2(react@18.3.1)
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.17)
zod:
specifier: ^3.24.2
version: 3.24.2
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@18.3.18)(react@18.3.1)
devDependencies:
'@eslint/eslintrc':
specifier: ^3
version: 3.3.0
'@faker-js/faker':
specifier: ^9.5.0
version: 9.5.0
'@types/js-cookie':
specifier: ^3.0.6
version: 3.0.6
'@types/node':
specifier: ^22.13.1
version: 22.13.9
'@types/webpack':
specifier: ^5.28.5
version: 5.28.5
eslint:
specifier: ^9
version: 9.21.0
eslint-config-next:
specifier: 15.1.7
version: 15.1.7(eslint@9.21.0)(typescript@5.6.3)
postcss:
specifier: ^8
version: 8.5.3
prettier:
specifier: ^3.4.2
version: 3.5.2
prettier-plugin-tailwindcss:
specifier: ^0.6.11
version: 0.6.11(prettier@3.5.2)
steiger:
specifier: ^0.5.5
version: 0.5.5(typescript@5.6.3)
tailwindcss:
specifier: ^3.4.1
version: 3.4.17
typescript:
specifier: ^5
version: 5.6.3
apps/wr:
dependencies:
'@common/business_components':
@@ -266,6 +366,9 @@ importers:
tailwind-merge:
specifier: ^3.0.1
version: 3.0.2
xlsx:
specifier: ^0.18.5
version: 0.18.5
zod:
specifier: ^3.24.2
version: 3.24.2
@@ -325,7 +428,7 @@ importers:
specifier: ^15.14.0
version: 15.15.0
postcss:
specifier: ^8.5.1
specifier: ^8.5.3
version: 8.5.3
prettier:
specifier: ^3.4.2
@@ -1320,6 +1423,7 @@ packages:
/@faker-js/faker@9.5.0:
resolution: {integrity: sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw==}
engines: {node: '>=18.0.0', npm: '>=9.0.0'}
deprecated: Please update to a newer version
dev: true
/@feature-sliced/filesystem@2.4.0:
@@ -3853,6 +3957,11 @@ packages:
hasBin: true
dev: true
/adler-32@1.3.1:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
dev: false
/ajv-formats@2.1.1(ajv@8.17.1):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
@@ -4236,6 +4345,14 @@ packages:
/caniuse-lite@1.0.30001701:
resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==}
/cfb@1.2.2:
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
engines: {node: '>=0.8'}
dependencies:
adler-32: 1.3.1
crc-32: 1.2.2
dev: false
/chai@5.2.0:
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
engines: {node: '>=12'}
@@ -4325,6 +4442,11 @@ packages:
engines: {node: '>=6'}
dev: false
/codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
dev: false
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -4402,6 +4524,12 @@ packages:
typescript: 5.6.3
dev: true
/crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
hasBin: true
dev: false
/cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -5516,6 +5644,11 @@ packages:
mime-types: 2.1.35
dev: false
/frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'}
dev: false
/fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: true
@@ -6083,7 +6216,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 22.13.5
'@types/node': 22.13.9
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
@@ -7571,6 +7704,13 @@ packages:
whatwg-url: 7.1.0
dev: true
/ssf@0.11.2:
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
engines: {node: '>=0.8'}
dependencies:
frac: 1.1.2
dev: false
/stable-hash@0.0.4:
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
dev: true
@@ -8535,11 +8675,21 @@ packages:
dependencies:
isexe: 2.0.0
/wmf@1.0.2:
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
engines: {node: '>=0.8'}
dev: false
/word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
dev: true
/word@0.3.0:
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
engines: {node: '>=0.8'}
dev: false
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -8569,6 +8719,20 @@ packages:
optional: true
dev: true
/xlsx@0.18.5:
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
engines: {node: '>=0.8'}
hasBin: true
dependencies:
adler-32: 1.3.1
cfb: 1.2.2
codepage: 1.15.0
crc-32: 1.2.2
ssf: 0.11.2
wmf: 1.0.2
word: 0.3.0
dev: false
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}