diff --git a/.github/workflows/sn_build.yml b/.github/workflows/sn_build.yml new file mode 100644 index 000000000..51d9f5d98 --- /dev/null +++ b/.github/workflows/sn_build.yml @@ -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 \ No newline at end of file diff --git a/apps/sn/app/components/ui/contentLayout.tsx b/apps/sn/app/components/ui/contentLayout.tsx new file mode 100644 index 000000000..738e26b16 --- /dev/null +++ b/apps/sn/app/components/ui/contentLayout.tsx @@ -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 ( + + + <ContentsDiv> + {children} + </ContentsDiv> + </MainDiv> + ) +} + +export default ContentLayout \ No newline at end of file diff --git a/apps/sn/app/components/ui/fileUploadForm.tsx b/apps/sn/app/components/ui/fileUploadForm.tsx new file mode 100644 index 000000000..dd3575302 --- /dev/null +++ b/apps/sn/app/components/ui/fileUploadForm.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/components/ui/formSection.tsx b/apps/sn/app/components/ui/formSection.tsx new file mode 100644 index 000000000..b4e578aae --- /dev/null +++ b/apps/sn/app/components/ui/formSection.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/BaseLayout.tsx b/apps/sn/app/ii/BaseLayout.tsx new file mode 100644 index 000000000..2a346ed07 --- /dev/null +++ b/apps/sn/app/ii/BaseLayout.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/baseinfo/mock/page.tsx b/apps/sn/app/ii/baseinfo/mock/page.tsx new file mode 100644 index 000000000..0d9b4f46c --- /dev/null +++ b/apps/sn/app/ii/baseinfo/mock/page.tsx @@ -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 > + ) +} diff --git a/apps/sn/app/ii/baseinfo/observe/page.tsx b/apps/sn/app/ii/baseinfo/observe/page.tsx new file mode 100644 index 000000000..16bd24fb2 --- /dev/null +++ b/apps/sn/app/ii/baseinfo/observe/page.tsx @@ -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> + ) +} diff --git a/apps/sn/app/ii/baseinfo/polygon/page.tsx b/apps/sn/app/ii/baseinfo/polygon/page.tsx new file mode 100644 index 000000000..ca252fcc4 --- /dev/null +++ b/apps/sn/app/ii/baseinfo/polygon/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/baseinfo/regist/page.tsx b/apps/sn/app/ii/baseinfo/regist/page.tsx new file mode 100644 index 000000000..b8dbf6205 --- /dev/null +++ b/apps/sn/app/ii/baseinfo/regist/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/ir/irsetting/page.tsx b/apps/sn/app/ii/ir/irsetting/page.tsx new file mode 100644 index 000000000..95d99943c --- /dev/null +++ b/apps/sn/app/ii/ir/irsetting/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/ir/measureDataUpload/page.tsx b/apps/sn/app/ii/ir/measureDataUpload/page.tsx new file mode 100644 index 000000000..a7a461437 --- /dev/null +++ b/apps/sn/app/ii/ir/measureDataUpload/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/rdii/rain/page.tsx b/apps/sn/app/ii/rdii/rain/page.tsx new file mode 100644 index 000000000..e74560b66 --- /dev/null +++ b/apps/sn/app/ii/rdii/rain/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/ii/rdii/rdiisetting/page.tsx b/apps/sn/app/ii/rdii/rdiisetting/page.tsx new file mode 100644 index 000000000..dd4eb367c --- /dev/null +++ b/apps/sn/app/ii/rdii/rdiisetting/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/oper/floodPre/page.tsx b/apps/sn/app/oper/floodPre/page.tsx new file mode 100644 index 000000000..3c0303609 --- /dev/null +++ b/apps/sn/app/oper/floodPre/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/oper/oldPp/excelDown/excelDown.tsx b/apps/sn/app/oper/oldPp/excelDown/excelDown.tsx new file mode 100644 index 000000000..d1bd5cc70 --- /dev/null +++ b/apps/sn/app/oper/oldPp/excelDown/excelDown.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const ExcelDown = () => { + return ( + <div>excelDown</div> + ) +} + +export default ExcelDown \ No newline at end of file diff --git a/apps/sn/app/oper/oldPp/page.tsx b/apps/sn/app/oper/oldPp/page.tsx new file mode 100644 index 000000000..832942752 --- /dev/null +++ b/apps/sn/app/oper/oldPp/page.tsx @@ -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 \ No newline at end of file diff --git a/apps/sn/app/page.tsx b/apps/sn/app/page.tsx index dd54ce3f9..0ae1eb678 100644 --- a/apps/sn/app/page.tsx +++ b/apps/sn/app/page.tsx @@ -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> ); } diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json index ce78d2a6b..1f02a708e 100644 --- a/packages/common/business_components/package.json +++ b/packages/common/business_components/package.json @@ -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", diff --git a/packages/common/business_components/src/base.css b/packages/common/business_components/src/base.css index daaa92719..ab1424be8 100644 --- a/packages/common/business_components/src/base.css +++ b/packages/common/business_components/src/base.css @@ -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; } -} \ No newline at end of file +} diff --git a/packages/common/business_components/src/diveicon.css b/packages/common/business_components/src/diveicon.css deleted file mode 100644 index cce8cdaef..000000000 --- a/packages/common/business_components/src/diveicon.css +++ /dev/null @@ -1,1799 +0,0 @@ -@font-face { - font-family: "diveicon"; - src: url("../public/assets/fonts/diveicon.eot?stp8eo"); - src: - url("../public/assets/fonts/diveicon.eot?stp8eo#iefix") - format("embedded-opentype"), - url("../public/assets/fonts/diveicon.ttf?stp8eo") format("truetype"), - url("../public/assets/fonts/diveicon.woff?stp8eo") format("woff"), - url("../public/assets/fonts/diveicon.svg?stp8eo#diveicon") format("svg"); - font-weight: normal; - font-style: normal; - font-display: block; -} - -i.diveicon { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: "diveicon" !important; - speak: never; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.di-account-box:before { - content: "\e8f3"; -} -.di-account-circle:before { - content: "\e901"; -} -.di-add-image:before { - content: "\e902"; -} -.di-add-Note:before { - content: "\e903"; -} -.di-alarm-add:before { - content: "\e904"; -} -.di-alarm-clock:before { - content: "\e905"; -} -.di-alarm-off:before { - content: "\e906"; -} -.di-alarm-on:before { - content: "\e908"; -} -.di-alarm:before { - content: "\e909"; -} -.di-alternate:before { - content: "\e90a"; -} -.di-analytics:before { - content: "\e90b"; -} -.di-android:before { - content: "\e90c"; -} -.di-announcement:before { - content: "\e90d"; -} -.di-app-indicator:before { - content: "\e90e"; -} -.di-app-list:before { - content: "\e90f"; -} -.di-app-square:before { - content: "\e910"; -} -.di-apple:before { - content: "\e911"; -} -.di-apps-square:before { - content: "\e912"; -} -.di-apps:before { - content: "\e913"; -} -.di-archive-fill:before { - content: "\e914"; -} -.di-archive:before { - content: "\e915"; -} -.di-arrow-90left-down:before { - content: "\e916"; -} -.di-arrow-90left-up:before { - content: "\e917"; -} -.di-arrow-90left:before { - content: "\e918"; -} -.di-arrow-90right-down:before { - content: "\e919"; -} -.di-arrow-90right-up:before { - content: "\e91a"; -} -.di-arrow-90right:before { - content: "\e91b"; -} -.di-arrow-back-square-fill:before { - content: "\e91c"; -} -.di-arrow-back:before { - content: "\e91d"; -} -.di-arrow-down-square-fill:before { - content: "\e91e"; -} -.di-arrow-down-square:before { - content: "\e91f"; -} -.di-arrow-down:before { - content: "\e920"; -} -.di-arrow-forward-square-fill:before { - content: "\e921"; -} -.di-arrow-forward:before { - content: "\e922"; -} -.di-arrow-left-right:before { - content: "\e923"; -} -.di-arrow-left-square-fill:before { - content: "\e924"; -} -.di-arrow-left-square:before { - content: "\e925"; -} -.di-arrow-left:before { - content: "\e926"; -} -.di-arrow-lr-up-down:before { - content: "\e927"; -} -.di-arrow-reflash:before { - content: "\e928"; -} -.di-arrow-reset:before { - content: "\e929"; -} -.di-arrow-right-square-fill:before { - content: "\e92a"; -} -.di-arrow-right-square:before { - content: "\e92b"; -} -.di-arrow-right:before { - content: "\e92c"; -} -.di-arrow-up-down:before { - content: "\e92d"; -} -.di-arrow-up-square-fill:before { - content: "\e92e"; -} -.di-arrow-up-square:before { - content: "\e92f"; -} -.di-arrow-up:before { - content: "\e930"; -} -.di-arrows-angle-contract:before { - content: "\e931"; -} -.di-arrows-angle-expand:before { - content: "\e932"; -} -.di-arrows-fullscreen:before { - content: "\e933"; -} -.di-aspect-ratio-fill:before { - content: "\e934"; -} -.di-aspect-ratio:before { - content: "\e935"; -} -.di-assignment-ind:before { - content: "\e936"; -} -.di-assignment-turned-in:before { - content: "\e937"; -} -.di-assignment:before { - content: "\e938"; -} -.di-asterisk:before { - content: "\e939"; -} -.di-atm:before { - content: "\e93a"; -} -.di-badge-4k-fill:before { - content: "\e93b"; -} -.di-badge-4k:before { - content: "\e93c"; -} -.di-badge-8k-fill:before { - content: "\e93d"; -} -.di-badge-8k:before { - content: "\e93e"; -} -.di-badge-ad-fill:before { - content: "\e93f"; -} -.di-badge-ad:before { - content: "\e940"; -} -.di-badge-cc-fill:before { - content: "\e941"; -} -.di-badge-cc:before { - content: "\e942"; -} -.di-badge-hd-fill:before { - content: "\e943"; -} -.di-badge-hd:before { - content: "\e944"; -} -.di-badge-tm-fill:before { - content: "\e945"; -} -.di-badge-tm:before { - content: "\e946"; -} -.di-bag-check-fill:before { - content: "\e947"; -} -.di-bag-check:before { - content: "\e948"; -} -.di-bag-fill:before { - content: "\e949"; -} -.di-bag:before { - content: "\e94a"; -} -.di-bags:before { - content: "\e94b"; -} -.di-bar-chart-fill:before { - content: "\e94c"; -} -.di-bar-chart:before { - content: "\e94d"; -} -.di-battery-charging:before { - content: "\e94e"; -} -.di-battery-emty:before { - content: "\e94f"; -} -.di-battery-full:before { - content: "\e950"; -} -.di-battery-harf:before { - content: "\e951"; -} -.di-bell-alert-box-fill:before { - content: "\e952"; -} -.di-bell-alert-box:before { - content: "\e953"; -} -.di-bell-alert-fill:before { - content: "\e954"; -} -.di-bell-alert:before { - content: "\e955"; -} -.di-bell-notification-fill:before { - content: "\e956"; -} -.di-bell-notification-on-fill:before { - content: "\e957"; -} -.di-bell-notification-on:before { - content: "\e958"; -} -.di-bell-notification:before { - content: "\e959"; -} -.di-bike:before { - content: "\e95a"; -} -.di-bluetooth:before { - content: "\e95b"; -} -.di-boat:before { - content: "\e95c"; -} -.di-book:before { - content: "\e95d"; -} -.di-bookmark-check-fill:before { - content: "\e95e"; -} -.di-bookmark-check:before { - content: "\e95f"; -} -.di-bookmark-fill:before { - content: "\e960"; -} -.di-bookmark-new-fill:before { - content: "\e961"; -} -.di-bookmark-new:before { - content: "\e962"; -} -.di-bookmark-star-fill:before { - content: "\e963"; -} -.di-bookmark-star:before { - content: "\e964"; -} -.di-bookmark:before { - content: "\e965"; -} -.di-boostzone:before { - content: "\e966"; -} -.di-box-arrow-backup:before { - content: "\e967"; -} -.di-box-arrow-download:before { - content: "\e968"; -} -.di-box-arrow-link-blank:before { - content: "\e969"; -} -.di-box-arrow-upload:before { - content: "\e96a"; -} -.di-box-seam:before { - content: "\e96b"; -} -.di-box:before { - content: "\e96c"; -} -.di-braces:before { - content: "\e96d"; -} -.di-briefcase-fill:before { - content: "\e96e"; -} -.di-briefcase:before { - content: "\e96f"; -} -.di-brightness-fill:before { - content: "\e970"; -} -.di-brightness:before { - content: "\e971"; -} -.di-broadcast-pin:before { - content: "\e972"; -} -.di-broadcast:before { - content: "\e973"; -} -.di-bug-fill:before { - content: "\e974"; -} -.di-bug:before { - content: "\e975"; -} -.di-building:before { - content: "\e976"; -} -.di-bus:before { - content: "\e977"; -} -.di-calculator-fill:before { - content: "\e978"; -} -.di-calculator:before { - content: "\e979"; -} -.di-calendar-check-fill:before { - content: "\e97a"; -} -.di-calendar-check:before { - content: "\e97b"; -} -.di-calendar-day-fill:before { - content: "\e97c"; -} -.di-calendar-day:before { - content: "\e97d"; -} -.di-calendar-event-fill:before { - content: "\e97e"; -} -.di-calendar-event:before { - content: "\e97f"; -} -.di-calendar-fill:before { - content: "\e980"; -} -.di-calendar-week-fill:before { - content: "\e981"; -} -.di-calendar-week:before { - content: "\e982"; -} -.di-calendar:before { - content: "\e983"; -} -.di-call-end:before { - content: "\e984"; -} -.di-call:before { - content: "\e985"; -} -.di-camera-fill:before { - content: "\e986"; -} -.di-camera-video-fill:before { - content: "\e987"; -} -.di-camera-video:before { - content: "\e988"; -} -.di-camera:before { - content: "\e989"; -} -.di-car:before { - content: "\e98a"; -} -.di-card-fill:before { - content: "\e98b"; -} -.di-card:before { - content: "\e98c"; -} -.di-caret-down-fill:before { - content: "\e98d"; -} -.di-caret-down-square-fill:before { - content: "\e98e"; -} -.di-caret-down-square:before { - content: "\e98f"; -} -.di-caret-left-fill:before { - content: "\e990"; -} -.di-caret-left-square-fill:before { - content: "\e991"; -} -.di-caret-left-square:before { - content: "\e992"; -} -.di-caret-right-fill:before { - content: "\e993"; -} -.di-caret-right-square-fill:before { - content: "\e994"; -} -.di-caret-right-square:before { - content: "\e995"; -} -.di-caret-up-fill:before { - content: "\e996"; -} -.di-caret-up-square-fill:before { - content: "\e997"; -} -.di-caret-up-square:before { - content: "\e998"; -} -.di-cart-fill:before { - content: "\e999"; -} -.di-cart:before { - content: "\e99a"; -} -.di-cash-stack:before { - content: "\e99b"; -} -.di-cash:before { - content: "\e99c"; -} -.di-cast:before { - content: "\e99d"; -} -.di-cctv:before { - content: "\e99e"; -} -.di-chat-box-left-dots-fill:before { - content: "\e99f"; -} -.di-chat-box-left-dots:before { - content: "\e9a0"; -} -.di-chat-box-left-fill:before { - content: "\e9a1"; -} -.di-chat-box-left-text-fill:before { - content: "\e9a2"; -} -.di-chat-box-left-text:before { - content: "\e9a3"; -} -.di-chat-box-left:before { - content: "\e9a4"; -} -.di-chat-box-right-dots-fill:before { - content: "\e9a5"; -} -.di-chat-box-right-dots:before { - content: "\e9a6"; -} -.di-chat-box-right-fill:before { - content: "\e9a7"; -} -.di-chat-box-right-text-fill:before { - content: "\e9a8"; -} -.di-chat-box-right-text:before { - content: "\e9a9"; -} -.di-chat-box-right:before { - content: "\e9aa"; -} -.di-chat-circle-left-dot-fill:before { - content: "\e9ab"; -} -.di-chat-circle-left-dot:before { - content: "\e9ac"; -} -.di-chat-circle-left-fill:before { - content: "\e9ad"; -} -.di-chat-circle-left-text-fill:before { - content: "\e9ae"; -} -.di-chat-circle-left-text:before { - content: "\e9af"; -} -.di-chat-circle-left:before { - content: "\e9b0"; -} -.di-chat-circle-right-dot-fill:before { - content: "\e9b1"; -} -.di-chat-circle-right-dot:before { - content: "\e9b2"; -} -.di-chat-circle-right-fill:before { - content: "\e9b3"; -} -.di-chat-circle-right-text-fill:before { - content: "\e9b4"; -} -.di-chat-circle-right-text:before { - content: "\e9b5"; -} -.di-chat-circle-right:before { - content: "\e9b6"; -} -.di-check-box:before { - content: "\e9b7"; -} -.di-check-circle-fill:before { - content: "\e9b8"; -} -.di-check-circle-line:before { - content: "\e9b9"; -} -.di-check-circle:before { - content: "\e9ba"; -} -.di-check-square-fill:before { - content: "\e9bb"; -} -.di-check-square-line:before { - content: "\e9bc"; -} -.di-check-square:before { - content: "\e9bd"; -} -.di-check:before { - content: "\e9be"; -} -.di-chevron-bar-contract:before { - content: "\e9bf"; -} -.di-chevron-bar-down:before { - content: "\e9c0"; -} -.di-chevron-bar-expand:before { - content: "\e9c1"; -} -.di-chevron-bar-left:before { - content: "\e9c2"; -} -.di-chevron-bar-right:before { - content: "\e9c3"; -} -.di-chevron-bar-up:before { - content: "\e9c4"; -} -.di-chevron-contract:before { - content: "\e9c5"; -} -.di-chevron-double-down:before { - content: "\e9c6"; -} -.di-chevron-double-left:before { - content: "\e9c7"; -} -.di-chevron-double-right:before { - content: "\e9c8"; -} -.di-chevron-double-up:before { - content: "\e9c9"; -} -.di-chevron-down:before { - content: "\e9ca"; -} -.di-chevron-expand:before { - content: "\e9cb"; -} -.di-chevron-left:before { - content: "\e9cc"; -} -.di-chevron-right:before { - content: "\e9cd"; -} -.di-chevron-up:before { - content: "\e9ce"; -} -.di-clipboard-check:before { - content: "\e9cf"; -} -.di-clipboard-data:before { - content: "\e9d0"; -} -.di-clipboard-text:before { - content: "\e9d1"; -} -.di-clipboard:before { - content: "\e9d2"; -} -.di-clock-fill:before { - content: "\e9d3"; -} -.di-clock-history:before { - content: "\e9d4"; -} -.di-clock:before { - content: "\e9d5"; -} -.di-cloud-arrow-down-fill:before { - content: "\e9d6"; -} -.di-cloud-arrow-down:before { - content: "\e9d7"; -} -.di-cloud-arrow-up-fill:before { - content: "\e9d8"; -} -.di-cloud-arrow-up:before { - content: "\e9d9"; -} -.di-cloud-check-fill:before { - content: "\e9da"; -} -.di-cloud-check:before { - content: "\e9db"; -} -.di-cloud-fill:before { - content: "\e9dc"; -} -.di-cloud:before { - content: "\e9dd"; -} -.di-code-square:before { - content: "\e9de"; -} -.di-code:before { - content: "\e9df"; -} -.di-collection-fill:before { - content: "\e9e0"; -} -.di-collection:before { - content: "\e9e1"; -} -.di-compass-fill:before { - content: "\e9e2"; -} -.di-compass:before { - content: "\e9e3"; -} -.di-cone:before { - content: "\e9e4"; -} -.di-controller-fill:before { - content: "\e9e5"; -} -.di-controller:before { - content: "\e9e6"; -} -.di-copyright-sign:before { - content: "\e9e7"; -} -.di-copyright-symbol:before { - content: "\e9e8"; -} -.di-COVID-19-fill:before { - content: "\e9e9"; -} -.di-COVID-19:before { - content: "\e9ea"; -} -.di-cpu-fill:before { - content: "\e9eb"; -} -.di-cpu:before { - content: "\e9ec"; -} -.di-crop:before { - content: "\e9ed"; -} -.di-cup-fill:before { - content: "\e9ee"; -} -.di-cup-straw:before { - content: "\e9ef"; -} -.di-cup:before { - content: "\e9f0"; -} -.di-cursor-fill:before { - content: "\e9f1"; -} -.di-cursor-text:before { - content: "\e9f2"; -} -.di-cursor:before { - content: "\e9f3"; -} -.di-dash-circle-fill:before { - content: "\e9f4"; -} -.di-dash-circle:before { - content: "\e9f5"; -} -.di-dash-square-fill:before { - content: "\e9f6"; -} -.di-dash-square:before { - content: "\e9f7"; -} -.di-dash:before { - content: "\e9f8"; -} -.di-dashboard:before { - content: "\e9f9"; -} -.di-diagram:before { - content: "\e9fa"; -} -.di-diamond-fill:before { - content: "\e9fb"; -} -.di-diamond:before { - content: "\e9fc"; -} -.di-diamond2:before { - content: "\e9fd"; -} -.di-display-fill:before { - content: "\e9fe"; -} -.di-display:before { - content: "\e9ff"; -} -.di-dns:before { - content: "\ea00"; -} -.di-door-closed-fill:before { - content: "\ea01"; -} -.di-door-closed:before { - content: "\ea02"; -} -.di-download_app:before { - content: "\ea03"; -} -.di-download:before { - content: "\ea04"; -} -.di-email-fill-notification:before { - content: "\ea05"; -} -.di-email-fill-open:before { - content: "\ea06"; -} -.di-email-fill:before { - content: "\ea07"; -} -.di-email-notification:before { - content: "\ea08"; -} -.di-email-open:before { - content: "\ea09"; -} -.di-email:before { - content: "\ea0a"; -} -.di-emergenc_call:before { - content: "\ea0b"; -} -.di-emergenc:before { - content: "\ea0c"; -} -.di-exclamation-chat-fill:before { - content: "\ea0d"; -} -.di-exclamation-chat:before { - content: "\ea0e"; -} -.di-exclamation-circle-fill:before { - content: "\ea0f"; -} -.di-exclamation-circle:before { - content: "\ea10"; -} -.di-exclamation-diamond-fill:before { - content: "\ea11"; -} -.di-exclamation-diamond:before { - content: "\ea12"; -} -.di-exclamation-square-circle:before { - content: "\ea13"; -} -.di-exclamation-square-fill:before { - content: "\ea14"; -} -.di-exclamation-square:before { - content: "\ea15"; -} -.di-exclamation-triangle-fill:before { - content: "\ea16"; -} -.di-exclamation-triangle:before { - content: "\ea17"; -} -.di-exclamation:before { - content: "\ea18"; -} -.di-exclude:before { - content: "\ea19"; -} -.di-face-id:before { - content: "\ea1a"; -} -.di-facebook-circle:before { - content: "\ea1b"; -} -.di-facebook-square:before { - content: "\ea1c"; -} -.di-favorite-hart:before { - content: "\ea1d"; -} -.di-favorite-star:before { - content: "\ea1e"; -} -.di-file-copy:before { - content: "\ea1f"; -} -.di-file-excel:before { - content: "\ea20"; -} -.di-file-image:before { - content: "\ea21"; -} -.di-file-keynote:before { - content: "\ea22"; -} -.di-file-numbers:before { - content: "\ea23"; -} -.di-file-powerpoint:before { - content: "\ea24"; -} -.di-file-text:before { - content: "\ea25"; -} -.di-file-word:before { - content: "\ea26"; -} -.di-file:before { - content: "\ea27"; -} -.di-filter-circle-fill:before { - content: "\ea28"; -} -.di-filter-circle:before { - content: "\ea29"; -} -.di-filter-left:before { - content: "\ea2a"; -} -.di-filter-right:before { - content: "\ea2b"; -} -.di-filter-square-fill:before { - content: "\ea2c"; -} -.di-filter-square:before { - content: "\ea2d"; -} -.di-filter:before { - content: "\ea2e"; -} -.di-finder-folder:before { - content: "\ea2f"; -} -.di-finder:before { - content: "\ea30"; -} -.di-fingerprint:before { - content: "\ea31"; -} -.di-flag-fill:before { - content: "\ea32"; -} -.di-flag:before { - content: "\ea33"; -} -.di-flashlight:before { - content: "\ea34"; -} -.di-folder-check:before { - content: "\ea35"; -} -.di-folder-delet:before { - content: "\ea36"; -} -.di-folder-fill:before { - content: "\ea37"; -} -.di-folder-open-fill:before { - content: "\ea38"; -} -.di-folder-open:before { - content: "\ea39"; -} -.di-folder-plus:before { - content: "\ea3a"; -} -.di-folder-share:before { - content: "\ea3b"; -} -.di-folder-x:before { - content: "\ea3c"; -} -.di-folder:before { - content: "\ea3d"; -} -.di-font:before { - content: "\ea3e"; -} -.di-fullscreen-exit:before { - content: "\ea3f"; -} -.di-fullscreen:before { - content: "\ea40"; -} -.di-gear-fill:before { - content: "\ea41"; -} -.di-gear-square-fill:before { - content: "\ea42"; -} -.di-gear-system-update:before { - content: "\ea43"; -} -.di-gear:before { - content: "\ea44"; -} -.di-gem:before { - content: "\ea45"; -} -.di-geo-alt:before { - content: "\ea46"; -} -.di-geo-location:before { - content: "\ea47"; -} -.di-geo-my-location:before { - content: "\ea48"; -} -.di-gift-fill:before { - content: "\ea49"; -} -.di-gift:before { - content: "\ea4a"; -} -.di-graph-down:before { - content: "\ea4b"; -} -.di-graph-up:before { - content: "\ea4c"; -} -.di-grid-2-fill:before { - content: "\ea4d"; -} -.di-grid-3-fill:before { - content: "\ea4e"; -} -.di-grid:before { - content: "\ea4f"; -} -.di-grip-horizontal:before { - content: "\ea50"; -} -.di-grip-vertical:before { - content: "\ea51"; -} -.di-group:before { - content: "\ea52"; -} -.di-group2:before { - content: "\ea53"; -} -.di-hand-index:before { - content: "\ea54"; -} -.di-harf:before { - content: "\ea55"; -} -.di-hart-fill:before { - content: "\ea56"; -} -.di-hart-half:before { - content: "\ea57"; -} -.di-hash:before { - content: "\ea58"; -} -.di-hdd-network:before { - content: "\ea59"; -} -.di-hdd:before { - content: "\ea5a"; -} -.di-hexagon-fill:before { - content: "\ea5b"; -} -.di-hexagon-harf:before { - content: "\ea5c"; -} -.di-hexagon:before { - content: "\ea5d"; -} -.di-history:before { - content: "\ea5e"; -} -.di-home-fill:before { - content: "\ea5f"; -} -.di-home:before { - content: "\ea60"; -} -.di-hotel_bed:before { - content: "\ea61"; -} -.di-hotel:before { - content: "\ea62"; -} -.di-image-fill:before { - content: "\ea63"; -} -.di-image:before { - content: "\ea64"; -} -.di-images:before { - content: "\ea65"; -} -.di-info-box:before { - content: "\ea66"; -} -.di-info-circle:before { - content: "\ea67"; -} -.di-info-fill:before { - content: "\ea68"; -} -.di-info:before { - content: "\ea69"; -} -.di-instagram-circle:before { - content: "\ea6a"; -} -.di-instagram-fill:before { - content: "\ea6b"; -} -.di-instagram:before { - content: "\ea6c"; -} -.di-intersect:before { - content: "\ea6d"; -} -.di-justify-left:before { - content: "\ea6e"; -} -.di-justify-right:before { - content: "\ea6f"; -} -.di-justify:before { - content: "\ea70"; -} -.di-kakao-alim-talk:before { - content: "\ea71"; -} -.di-kakao-circle:before { - content: "\ea72"; -} -.di-kakao-firend-talk:before { - content: "\ea73"; -} -.di-kakao-story-circle:before { - content: "\ea74"; -} -.di-kakao-story:before { - content: "\ea75"; -} -.di-kakao-talk:before { - content: "\ea76"; -} -.di-key:before { - content: "\ea77"; -} -.di-keyboard:before { - content: "\ea78"; -} -.di-label:before { - content: "\ea79"; -} -.di-language:before { - content: "\ea7a"; -} -.di-laptop-fill:before { - content: "\ea7b"; -} -.di-laptop:before { - content: "\ea7c"; -} -.di-layer-back:before { - content: "\ea7d"; -} -.di-layer-front:before { - content: "\ea7e"; -} -.di-layer:before { - content: "\ea7f"; -} -.di-lightning-fill:before { - content: "\ea80"; -} -.di-lightning:before { - content: "\ea81"; -} -.di-list-check:before { - content: "\ea82"; -} -.di-list:before { - content: "\ea83"; -} -.di-local-airport-land:before { - content: "\ea84"; -} -.di-local-airport:before { - content: "\ea85"; -} -.di-local-atm:before { - content: "\ea86"; -} -.di-local-bar:before { - content: "\ea87"; -} -.di-local-cafe:before { - content: "\ea88"; -} -.di-local-dining:before { - content: "\ea89"; -} -.di-local-ev-station:before { - content: "\ea8a"; -} -.di-local-fastfood:before { - content: "\ea8b"; -} -.di-local-gas-station:before { - content: "\ea8c"; -} -.di-local-hospital:before { - content: "\ea8d"; -} -.di-local-laundry:before { - content: "\ea8e"; -} -.di-local-library:before { - content: "\ea8f"; -} -.di-local-museum:before { - content: "\ea90"; -} -.di-local-restaurant:before { - content: "\ea91"; -} -.di-local-store:before { - content: "\ea92"; -} -.di-lock-open:before { - content: "\ea93"; -} -.di-lock:before { - content: "\ea94"; -} -.di-map-fill:before { - content: "\ea95"; -} -.di-map:before { - content: "\ea96"; -} -.di-menu-close-fill:before { - content: "\ea97"; -} -.di-menu-close:before { - content: "\ea98"; -} -.di-menu-open-fill:before { - content: "\ea99"; -} -.di-menu-open:before { - content: "\ea9a"; -} -.di-mic-fill:before { - content: "\ea9b"; -} -.di-mic-mute-fill:before { - content: "\ea9c"; -} -.di-mic-mute:before { - content: "\ea9d"; -} -.di-mic:before { - content: "\ea9e"; -} -.di-minus:before { - content: "\ea9f"; -} -.di-mobile-android:before { - content: "\eaa0"; -} -.di-mobile-flip:before { - content: "\eaa1"; -} -.di-mobile-fold:before { - content: "\eaa2"; -} -.di-mobile-ios:before { - content: "\eaa3"; -} -.di-mobile-pad:before { - content: "\eaa4"; -} -.di-mobile-store:before { - content: "\eaa5"; -} -.di-mobile:before { - content: "\eaa6"; -} -.di-moon-crescent:before { - content: "\eaa7"; -} -.di-moon-half:before { - content: "\eaa8"; -} -.di-moon:before { - content: "\eaa9"; -} -.di-more-horiz:before { - content: "\eaaa"; -} -.di-more-vert:before { - content: "\eaab"; -} -.di-movie-fill:before { - content: "\eaac"; -} -.di-movie:before { - content: "\eaad"; -} -.di-music-backward-fill:before { - content: "\eaae"; -} -.di-music-backward:before { - content: "\eaaf"; -} -.di-music-forward-fill:before { - content: "\eab0"; -} -.di-music-forward:before { - content: "\eab1"; -} -.di-music-list:before { - content: "\eab2"; -} -.di-music-pause-fill:before { - content: "\eab3"; -} -.di-music-pause:before { - content: "\eab4"; -} -.di-music-play-fill:before { - content: "\eab5"; -} -.di-music-play:before { - content: "\eab6"; -} -.di-music-stop-fill:before { - content: "\eab7"; -} -.di-music-stop:before { - content: "\eab8"; -} -.di-music:before { - content: "\eab9"; -} -.di-naver-blog:before { - content: "\eaba"; -} -.di-naver-circle:before { - content: "\eabb"; -} -.di-naver:before { - content: "\eabc"; -} -.di-news:before { - content: "\eabd"; -} -.di-option:before { - content: "\eabe"; -} -.di-paperclip:before { - content: "\eabf"; -} -.di-payment:before { - content: "\eac0"; -} -.di-pen-fill:before { - content: "\eac1"; -} -.di-pen:before { - content: "\eac2"; -} -.di-pencil-fill:before { - content: "\eac3"; -} -.di-pencil-square:before { - content: "\eac4"; -} -.di-pencil:before { - content: "\eac5"; -} -.di-person-fill:before { - content: "\eac6"; -} -.di-person:before { - content: "\eac7"; -} -.di-personal-information:before { - content: "\eac8"; -} -.di-pet:before { - content: "\eac9"; -} -.di-power:before { - content: "\eaca"; -} -.di-print:before { - content: "\eacb"; -} -.di-problem:before { - content: "\eacc"; -} -.di-profile-smile:before { - content: "\eacd"; -} -.di-profile:before { - content: "\eace"; -} -.di-push-assemble:before { - content: "\eacf"; -} -.di-push-fill:before { - content: "\ead0"; -} -.di-push-notification:before { - content: "\ead1"; -} -.di-push:before { - content: "\ead2"; -} -.di-question-answer:before { - content: "\ead3"; -} -.di-question-circle-fill:before { - content: "\ead4"; -} -.di-question-circle:before { - content: "\ead5"; -} -.di-question-diamond-fill:before { - content: "\ead6"; -} -.di-question-diamond:before { - content: "\ead7"; -} -.di-question-square-fill:before { - content: "\ead8"; -} -.di-question-square:before { - content: "\ead9"; -} -.di-question:before { - content: "\eada"; -} -.di-radio:before { - content: "\eadb"; -} -.di-receipt:before { - content: "\eadc"; -} -.di-refresh-fill:before { - content: "\eadd"; -} -.di-refresh:before { - content: "\eade"; -} -.di-reset-fill:before { - content: "\eadf"; -} -.di-reset:before { - content: "\eae0"; -} -.di-rotation-lock:before { - content: "\eae1"; -} -.di-rotation:before { - content: "\eae2"; -} -.di-rwis:before { - content: "\eae3"; -} -.di-search:before { - content: "\eae4"; -} -.di-server:before { - content: "\eae5"; -} -.di-share:before { - content: "\eae6"; -} -.di-shield:before { - content: "\eae7"; -} -.di-shift-fill:before { - content: "\eae8"; -} -.di-shift:before { - content: "\eae9"; -} -.di-shuffle:before { - content: "\eaea"; -} -.di-silent:before { - content: "\eaeb"; -} -.di-sim:before { - content: "\eaec"; -} -.di-slash:before { - content: "\eaed"; -} -.di-sliders:before { - content: "\eaee"; -} -.di-smart-watch:before { - content: "\eaef"; -} -.di-sort-alpha-down-alt:before { - content: "\eaf0"; -} -.di-sort-alpha-down:before { - content: "\eaf1"; -} -.di-sort-alpha-up-alt:before { - content: "\eaf2"; -} -.di-sort-alpha-up:before { - content: "\eaf3"; -} -.di-sort-down-alt:before { - content: "\eaf4"; -} -.di-sort-down:before { - content: "\eaf5"; -} -.di-sort-numeric-down-alt:before { - content: "\eaf6"; -} -.di-sort-numeric-down:before { - content: "\eaf7"; -} -.di-sort-numeric-up-alt:before { - content: "\eaf8"; -} -.di-sort-numeric-up:before { - content: "\eaf9"; -} -.di-sort-up-alt:before { - content: "\eafa"; -} -.di-sort-up:before { - content: "\eafb"; -} -.di-sound:before { - content: "\eafc"; -} -.di-soundwave:before { - content: "\eafd"; -} -.di-square-fill:before { - content: "\eafe"; -} -.di-square:before { - content: "\eaff"; -} -.di-star-circle:before { - content: "\eb00"; -} -.di-star-fill:before { - content: "\eb01"; -} -.di-star:before { - content: "\eb02"; -} -.di-statistics-circle:before { - content: "\eb03"; -} -.di-statistics:before { - content: "\eb04"; -} -.di-sticky-fill:before { - content: "\eb05"; -} -.di-sticky:before { - content: "\eb06"; -} -.di-stoplights-fill:before { - content: "\eb07"; -} -.di-stoplights:before { - content: "\eb08"; -} -.di-sunny:before { - content: "\eb09"; -} -.di-tab:before { - content: "\eb0a"; -} -.di-table:before { - content: "\eb0b"; -} -.di-terminal-fill:before { - content: "\eb0c"; -} -.di-terminal:before { - content: "\eb0d"; -} -.di-ternnel:before { - content: "\eb0e"; -} -.di-textarea-t:before { - content: "\eb0f"; -} -.di-textarea:before { - content: "\eb10"; -} -.di-thermometer-full:before { - content: "\eb11"; -} -.di-thermometer-half:before { - content: "\eb12"; -} -.di-thermometer:before { - content: "\eb13"; -} -.di-time-end:before { - content: "\eb14"; -} -.di-time-full:before { - content: "\eb15"; -} -.di-time-start:before { - content: "\eb16"; -} -.di-toggle-off:before { - content: "\eb17"; -} -.di-toggle-on:before { - content: "\eb18"; -} -.di-toggle:before { - content: "\eb19"; -} -.di-train:before { - content: "\eb1a"; -} -.di-trash-fill:before { - content: "\eb1b"; -} -.di-trash:before { - content: "\eb1c"; -} -.di-truck-fill:before { - content: "\eb1d"; -} -.di-truck:before { - content: "\eb1e"; -} -.di-ui-checks-grid:before { - content: "\eb1f"; -} -.di-ui-radios-grid:before { - content: "\eb20"; -} -.di-upload:before { - content: "\eb21"; -} -.di-vibration:before { - content: "\eb22"; -} -.di-vms:before { - content: "\eb23"; -} -.di-volume-down-fill:before { - content: "\eb24"; -} -.di-volume-down:before { - content: "\eb25"; -} -.di-volume-mute-fill:before { - content: "\eb26"; -} -.di-volume-mute:before { - content: "\eb27"; -} -.di-volume-off-fill:before { - content: "\eb28"; -} -.di-volume-off:before { - content: "\eb29"; -} -.di-volume-up-fill:before { - content: "\eb2a"; -} -.di-volume-up:before { - content: "\eb2b"; -} -.di-weather-cloud-fill:before { - content: "\eb2c"; -} -.di-weather-cloud:before { - content: "\eb2d"; -} -.di-weather-cloudy-gusts:before { - content: "\eb2e"; -} -.di-weather-cloudy-windy:before { - content: "\eb2f"; -} -.di-weather-cloudy:before { - content: "\eb30"; -} -.di-weather-fahrenheit:before { - content: "\eb31"; -} -.di-weather-fog:before { - content: "\eb32"; -} -.di-weather-hail:before { - content: "\eb33"; -} -.di-weather-humidity:before { - content: "\eb34"; -} -.di-weather-hurricane:before { - content: "\eb35"; -} -.di-weather-lightning:before { - content: "\eb36"; -} -.di-weather-rain:before { - content: "\eb37"; -} -.di-weather-rainny:before { - content: "\eb38"; -} -.di-weather-snow:before { - content: "\eb39"; -} -.di-weather-snowflake:before { - content: "\eb3a"; -} -.di-weather-sunny-overcast:before { - content: "\eb3b"; -} -.di-weather-sunny:before { - content: "\eb3c"; -} -.di-weather-thunderstorm:before { - content: "\eb3d"; -} -.di-weather-umbrella:before { - content: "\eb3e"; -} -.di-weather-windy:before { - content: "\eb3f"; -} -.di-whater:before { - content: "\eb40"; -} -.di-whather-celsius:before { - content: "\eb41"; -} -.di-wifi-1:before { - content: "\eb42"; -} -.di-wifi-2:before { - content: "\eb43"; -} -.di-wifi-off:before { - content: "\eb44"; -} -.di-wifi:before { - content: "\eb45"; -} -.di-window:before { - content: "\eb46"; -} -.di-x-circle-fill:before { - content: "\eb47"; -} -.di-x-circle:before { - content: "\eb48"; -} -.di-x-square-fill:before { - content: "\eb49"; -} -.di-x-square:before { - content: "\eb4a"; -} -.di-x:before { - content: "\eb4b"; -} -.di-zoom-in:before { - content: "\eb4c"; -} -.di-zoom-out:before { - content: "\eb4d"; -} -.di-server-line:before { - content: "\e907"; -} diff --git a/packages/common/business_components/src/index.css b/packages/common/business_components/src/index.css deleted file mode 100644 index 46896e552..000000000 --- a/packages/common/business_components/src/index.css +++ /dev/null @@ -1,11 +0,0 @@ - -@layer base {} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/date_picker/index.tsx b/packages/common/business_components/src/ui/date_picker/index.tsx index 16aa2c075..d3a6a05c1 100644 --- a/packages/common/business_components/src/ui/date_picker/index.tsx +++ b/packages/common/business_components/src/ui/date_picker/index.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/dialog_ui/index.tsx b/packages/common/business_components/src/ui/dialog_ui/index.tsx index 27c67458d..b9e341279 100644 --- a/packages/common/business_components/src/ui/dialog_ui/index.tsx +++ b/packages/common/business_components/src/ui/dialog_ui/index.tsx @@ -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> - )} </> ); } diff --git a/packages/common/business_components/src/ui/excel_download/index.tsx b/packages/common/business_components/src/ui/excel_download/index.tsx new file mode 100644 index 000000000..d1370b95c --- /dev/null +++ b/packages/common/business_components/src/ui/excel_download/index.tsx @@ -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'); + }; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index c3022740d..e2273e8e5 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -16,4 +16,5 @@ export * from "./search_form_right"; export * from "./table_div"; export * from "./list_div"; export * from "./tab"; -export * from "./dialog_ui" \ No newline at end of file +export * from "./dialog_ui"; +export * from "./excel_download"; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/list_div/index.tsx b/packages/common/business_components/src/ui/list_div/index.tsx index a096a4c04..abe480c7a 100644 --- a/packages/common/business_components/src/ui/list_div/index.tsx +++ b/packages/common/business_components/src/ui/list_div/index.tsx @@ -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> ) diff --git a/packages/common/business_components/src/ui/search_form/index.tsx b/packages/common/business_components/src/ui/search_form/index.tsx index 34a6d41e6..860c99c69 100644 --- a/packages/common/business_components/src/ui/search_form/index.tsx +++ b/packages/common/business_components/src/ui/search_form/index.tsx @@ -1,7 +1,7 @@ function SearchDiv ({children}:{ children: React.ReactNode; }) { return ( <div className="flex card card-border !mt-6"> - {children} + {children} </div> ) } diff --git a/packages/common/business_components/src/ui/search_form_left/index.tsx b/packages/common/business_components/src/ui/search_form_left/index.tsx index 950306231..248e8f625 100644 --- a/packages/common/business_components/src/ui/search_form_left/index.tsx +++ b/packages/common/business_components/src/ui/search_form_left/index.tsx @@ -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> + ) ) } diff --git a/packages/common/business_components/src/ui/search_form_left/searchFormLeft.md b/packages/common/business_components/src/ui/search_form_left/searchFormLeft.md new file mode 100644 index 000000000..04531c27f --- /dev/null +++ b/packages/common/business_components/src/ui/search_form_left/searchFormLeft.md @@ -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} +> + +``` +  ㆍisShowPart : 검색단에서 구분(행정별,유역별,환경청별) 세팅 여부(보일 경우 : Y, 아닐경우 : N 혹은 미첨부) + +  ㆍselectSidoData : 전체 시도 데이터 + +  ㆍselectSigunData : 전체 시군구구 데이터 + +  ㆍselectRegionData : 전체 유역역 데이터 + +  ㆍselectUnitWaterShedData : 전체 단위유역 데이터 + +  ㆍselectMiddleRegionData : 전체 중권역 데이터 + +  ㆍselectEnvPartData : 환경청 전체 데이터 + +  ㆍsetRtnPartValue : 선택한 구분 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnSidoValue : 선택한 시도 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnSigunValue : 선택한 시군구 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnRegionValue : 선택한 유역 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnUnitWaterShedValue : 선택한 단위유역 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnMiddleRegionValue : 선택한 중유역 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnEnvPartValue : 선택한 환경청 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnEnvPartSidoValue : 선택한 환경청의 시도 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnEnvPartSigunValue : 선택한 환경청 시도의 시군구 set함수(조회시 페이지에서 사용) + +  ㆍ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> +``` + + diff --git a/packages/common/business_components/src/ui/search_form_right/index.tsx b/packages/common/business_components/src/ui/search_form_right/index.tsx index 676b90849..fda7eefd6 100644 --- a/packages/common/business_components/src/ui/search_form_right/index.tsx +++ b/packages/common/business_components/src/ui/search_form_right/index.tsx @@ -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 }; \ No newline at end of file +export { SearchFormRight }; diff --git a/packages/common/business_components/src/ui/search_input/index.tsx b/packages/common/business_components/src/ui/search_input/index.tsx index 0da6cffdc..06ffce4ff 100644 --- a/packages/common/business_components/src/ui/search_input/index.tsx +++ b/packages/common/business_components/src/ui/search_input/index.tsx @@ -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 }; \ No newline at end of file +export { SearchInput }; diff --git a/packages/common/business_components/src/ui/search_input_select/index.tsx b/packages/common/business_components/src/ui/search_input_select/index.tsx index 5a386b77b..bf8787356 100644 --- a/packages/common/business_components/src/ui/search_input_select/index.tsx +++ b/packages/common/business_components/src/ui/search_input_select/index.tsx @@ -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 }; \ No newline at end of file +export { SearchInputSelect }; diff --git a/packages/common/business_components/src/ui/select/index.tsx b/packages/common/business_components/src/ui/select/index.tsx index b03932256..ee6296677 100644 --- a/packages/common/business_components/src/ui/select/index.tsx +++ b/packages/common/business_components/src/ui/select/index.tsx @@ -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 }; \ No newline at end of file +export { SelectBox }; diff --git a/packages/common/business_components/src/ui/select/lib/selectBox.tsx b/packages/common/business_components/src/ui/select/lib/selectBox.tsx index 08e3abed3..cc4bac4d1 100644 --- a/packages/common/business_components/src/ui/select/lib/selectBox.tsx +++ b/packages/common/business_components/src/ui/select/lib/selectBox.tsx @@ -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 } + diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index b8c2706bc..994b9a827 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -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> </> ); -} +} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/table/uiPageNations.tsx b/packages/common/business_components/src/ui/table/uiPageNations.tsx index a7b788d71..891fc4c81 100644 --- a/packages/common/business_components/src/ui/table/uiPageNations.tsx +++ b/packages/common/business_components/src/ui/table/uiPageNations.tsx @@ -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> + ); } \ No newline at end of file diff --git a/packages/common/business_components/tailwind.config.js b/packages/common/business_components/tailwind.config.js index f30c9f77a..9ce3117a0 100644 --- a/packages/common/business_components/tailwind.config.js +++ b/packages/common/business_components/tailwind.config.js @@ -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")], }; diff --git a/packages/common/components/src/ui/table/index.tsx b/packages/common/components/src/ui/table/index.tsx index 797f48dfd..082b2e33b 100644 --- a/packages/common/components/src/ui/table/index.tsx +++ b/packages/common/components/src/ui/table/index.tsx @@ -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"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82f59a6cd..9d4ad4920 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'}