diff --git a/package.json b/package.json
index 9f060d6d..d01bc912 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"react-colorful": "^5.6.1",
"react-datepicker": "^7.3.0",
"react-dom": "^18",
+ "react-hook-form": "^7.53.0",
"react-icons": "^5.3.0",
"react-responsive-modal": "^6.4.2",
"react-toastify": "^10.0.5",
diff --git a/src/app/[locale]/management/stuff/detail/page.jsx b/src/app/[locale]/management/stuff/detail/page.jsx
new file mode 100644
index 00000000..8b84287a
--- /dev/null
+++ b/src/app/[locale]/management/stuff/detail/page.jsx
@@ -0,0 +1,15 @@
+import React from 'react'
+import Hero from '@/components/Hero'
+import StuffDetail from '@/components/management/StuffDetail'
+export default function ManagementStuffDetailPage() {
+ return (
+ <>
+
@@ -223,79 +336,3 @@ export default function InitSettingsModal(props) {
>
)
}
-
-const RoofSelectBox = (props) => {
- return (
-
-
타입 :
-
-
너비 :
-
props.onChange(props.roof.roofSeq, e)}
- />
- mm
-
높이 :
-
props.onChange(props.roof.roofSeq, e)}
- />
- mm
-
서까래 간격 :
-
props.onChange(props.roof.roofSeq, e)}
- />
- mm
-
- props.onChange(props.roof.roofSeq, e)}
- >
- 병렬식
- 계단식
-
-
-
-
-
-
- )
-}
diff --git a/src/components/common/datepicker/RangeDatePicker.jsx b/src/components/common/datepicker/RangeDatePicker.jsx
index ad802f49..de80221f 100644
--- a/src/components/common/datepicker/RangeDatePicker.jsx
+++ b/src/components/common/datepicker/RangeDatePicker.jsx
@@ -14,6 +14,7 @@ export default function RangeDatePicker(props) {
setDateRange(update)
}}
isClearable={true}
+ // showMonthYearPicker={true}
/>
)
}
diff --git a/src/components/management/Stuff.jsx b/src/components/management/Stuff.jsx
index da7b834d..e26b11a3 100644
--- a/src/components/management/Stuff.jsx
+++ b/src/components/management/Stuff.jsx
@@ -1,7 +1,342 @@
+'use client'
+
+import React, { useEffect, useState, useRef } from 'react'
+import { useRouter, usePathname } from 'next/navigation'
+import { Button } from '@nextui-org/react'
+import { useAxios } from '@/hooks/useAxios'
+import StuffQGrid from './StuffQGrid'
+import { useI18n } from '@/locales/client'
+import { useRecoilValue } from 'recoil'
+import { stuffSearchState } from '@/store/stuffAtom'
+import { queryStringFormatter } from '@/util/common-utils'
+import dayjs from 'dayjs'
+import isLeapYear from 'dayjs/plugin/isLeapYear' // 윤년 판단 플러그인
+dayjs.extend(isLeapYear)
+
export default function Stuff() {
+ const stuffSearchParams = useRecoilValue(stuffSearchState)
+
+ const { get, del } = useAxios()
+ const gridRef = useRef()
+ const lang = useI18n()
+
+ const [gridCount, setGridCount] = useState(0)
+ const [selectedRowData, setSelectedRowData] = useState([])
+ const [selectedRowDataCount, setSelectedRowDataCount] = useState(0)
+
+ const router = useRouter()
+ const pathname = usePathname()
+
+ //그리드 내부 복사버튼
+ const copyNo = async (value) => {
+ try {
+ await navigator.clipboard.writeText(value)
+ alert('물건번호가 복사되었습니다.')
+ } catch (error) {
+ alert('물건번호 복사에 실패했습니다.')
+ }
+ }
+
+ const [gridProps, setGridProps] = useState({
+ gridData: [],
+ isPageable: false,
+ // sets 10 rows per page (default is 100)
+ paginationPageSize: 100,
+ // allows the user to select the page size from a predefined list of page sizes
+ paginationPageSizeSelector: [100, 200, 300, 400],
+ gridColumns: [
+ {
+ field: 'lastEditDatetime',
+ headerName: lang('stuff.gridHeader.lastEditDatetime'),
+ headerCheckboxSelection: true,
+ headerCheckboxSelectionCurrentPageOnly: true, //페이징시 현재 페이지만 체크되도록
+ checkboxSelection: true,
+ showDisabledCheckboxes: true,
+ // headerClass: 'centered', //_test.scss에 추가 테스트
+ // .centered {
+ // .ag-header-cell-label {
+ // justify-content: center !important;
+ // }
+ // }
+ cellStyle: { textAlign: 'center' },
+ //suppressMovable: true, //헤더 못움직이게
+ // width : 100
+ // minWidth : 100
+ // maxWidth : 100
+ valueFormatter: function (params) {
+ if (params.value) {
+ return dayjs(params?.value).format('YYYY.MM.DD HH:mm:ss')
+ } else {
+ return null
+ }
+ },
+ },
+ {
+ field: 'objectNo',
+ headerName: lang('stuff.gridHeader.objectNo'),
+ // headerClass: 'centered', //_test.scss에 추가 테스트
+ cellRenderer: function (params) {
+ if (params.data.objectNo) {
+ return (
+
+
+ {params.value}
+
+ )
+ }
+ },
+ cellRendererParams: {
+ onPress: copyNo,
+ },
+ },
+ {
+ field: 'planTotCnt',
+ headerName: lang('stuff.gridHeader.planTotCnt'),
+ cellStyle: { textAlign: 'right' },
+ },
+ { field: 'objectName', headerName: lang('stuff.gridHeader.objectName'), cellStyle: { textAlign: 'left' } },
+ {
+ field: 'saleStoreId',
+ headerName: lang('stuff.gridHeader.saleStoreId'),
+ cellStyle: { textAlign: 'left' },
+ },
+ { field: 'saleStoreName', headerName: lang('stuff.gridHeader.saleStoreName'), cellStyle: { textAlign: 'left' } },
+ { field: 'address', headerName: lang('stuff.gridHeader.address'), cellStyle: { textAlign: 'left' } },
+ { field: 'dispCompanyName', headerName: lang('stuff.gridHeader.dispCompanyName'), cellStyle: { textAlign: 'left' } },
+ { field: 'receiveUser', headerName: lang('stuff.gridHeader.receiveUser'), cellStyle: { textAlign: 'left' } },
+ {
+ field: 'specDate',
+ headerName: lang('stuff.gridHeader.specDate'),
+ valueFormatter: function (params) {
+ if (params.value) {
+ return dayjs(params?.value).format('YYYY.MM.DD')
+ } else {
+ return null
+ }
+ },
+ cellStyle: { textAlign: 'center' },
+ },
+ {
+ field: 'createDatetime',
+ headerName: lang('stuff.gridHeader.createDatetime'),
+ valueFormatter: function (params) {
+ if (params.value) {
+ return dayjs(params?.value).format('YYYY.MM.DD')
+ } else {
+ return null
+ }
+ },
+ cellStyle: { textAlign: 'center' },
+ },
+ ],
+ gridCount: 0,
+ })
+
+ //그리드 더블클릭
+ const getCellDoubleClicked = (event) => {
+ if (event.column.colId === 'objectNo') {
+ return
+ } else {
+ console.log(' 상세이동::::::::', event.data)
+ if (event.data.objectNo) {
+ router.push(`${pathname}/detail?objectNo=${event.data.objectNo.toString()}`)
+ }
+ }
+ }
+
+ //그리드 체크박스 선택시
+ const getSelectedRowdata = (data) => {
+ setSelectedRowData(data)
+ setSelectedRowDataCount(data.length)
+ }
+
+ //물건삭제
+ const fnDeleteRowData = (data) => {
+ console.log('물건삭제:::::::::::')
+ if (data.length === 0) {
+ return alert('삭제할 데이터를 선택하세요')
+ }
+ let errCount = 0
+ data.forEach((cell) => {
+ if (!cell.objectNo) {
+ if (errCount === 0) {
+ alert('물건정보가 있는 행만 삭제 됩니다')
+ }
+ errCount++
+ }
+ })
+
+ async function fetchDelete(data) {
+ console.log('물건삭제API호출!!!!!!!!!', data)
+ //행추가말고 api데이터만 보냄
+ // let newData = data.filter((item) => item.company != null)
+ // console.log('삭제에 전송되는 데이타::', newData)
+ // await del({ url: '', data:newData })
+ await get({ url: 'https://www.ag-grid.com/example-assets/space-mission-data.json' })
+ // try {
+ // const res = await del({url:'', data:newData})
+
+ // if(!res || res.length === 0) {
+
+ // } else {
+ fetchData()
+ // }
+ // } catch (error) {
+ // console.error('Data Delete error:', error);
+ // }
+ }
+
+ // 삭제API 완료 후 fetchData Api호출
+ async function fetchData() {
+ console.log('물건삭제후 조회API호출!!!!!!!!!!!!!', stuffSearchParams)
+ const data = await get({ url: 'https://www.ag-grid.com/example-assets/space-mission-data.json' })
+ setGridProps({ ...gridProps, gridData: data, count: data.length })
+ setGridCount(data.length)
+ //data.length = 10
+ //setGridProps({ ...gridProps, gridData: data, count: data.length-1})
+ //setGridCount(data.length - 1 )
+ }
+
+ if (errCount === 0) {
+ // console.log('errCount::::::::', errCount)
+ fetchDelete(data)
+ // fetchData()
+ } else {
+ alert('물건정보가 있는 행만 선택해주세요')
+ }
+ }
+
+ //행추가
+ let newCount = 0
+ const addRowItems = () => {
+ // console.log('girdRef::::::', gridRef.current.api)
+ const newItems = [
+ {
+ mission: newCount + 1,
+ successful: true,
+ },
+ ]
+ gridRef.current.api.applyTransaction({
+ add: newItems,
+ addIndex: newCount,
+ })
+ newCount++
+ }
+
+ //행삭제
+ const removeRowItems = () => {
+ // console.log('selectedRowData::', selectedRowData)
+ let errCount = 0
+ selectedRowData.forEach((cell) => {
+ if (!cell.company) {
+ let newSelectedRowData = selectedRowData.filter((item) => item.company == null)
+ gridRef.current.api.applyTransaction({ remove: newSelectedRowData })
+ } else {
+ if (errCount === 0) {
+ alert('행추가로 추가 한 행만 삭제됩니다.')
+ }
+ errCount++
+ }
+ })
+ }
+
+ // 진입시 그리드 데이터 조회
+ useEffect(() => {
+ if (stuffSearchParams?.code === 'S') {
+ const params = {
+ schObjectNo: '',
+ schSaleStoreId: '',
+ schAddress: '',
+ schObjectName: '',
+ schSaleStoreName: '',
+ schSpecDateYn: '',
+ schReceiveUser: '',
+ schDispCompanyName: '',
+ schDateType: 'U',
+ schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'),
+ schToDt: dayjs(new Date()).format('YYYY-MM-DD'),
+ }
+
+ async function fetchData() {
+ console.log('화면진입:::::::::::::', params)
+ const apiUrl = `/api/object/v1.0/object?saleStoreId=201TES01&${queryStringFormatter(params)}`
+ // console.log('apiUrl::', apiUrl)
+
+ await get({
+ url: apiUrl,
+ }).then((res) => {
+ if (res.length > 0) {
+ console.log('API결과:::::::', res)
+ setGridProps({ ...gridProps, gridData: res, count: res.length })
+ setGridCount(res.length)
+ }
+ })
+ }
+ fetchData()
+ }
+ }, [])
+
+ useEffect(() => {
+ if (stuffSearchParams?.code === 'E') {
+ console.log('조회 눌럿을때 ::::::::::::::', stuffSearchParams)
+ async function fetchData() {
+ const apiUrl = `/api/object/v1.0/object?saleStoreId=201TES01&${queryStringFormatter(stuffSearchParams)}`
+ await get({ url: apiUrl }).then((res) => {
+ console.log('API결과:::::::', res)
+ setGridProps({ ...gridProps, gridData: res, count: res.length })
+ setGridCount(res.length)
+ })
+ }
+ fetchData()
+ }
+ }, [stuffSearchParams])
+
return (
<>
-
Management Stuff
+
+ 물건목록
+
+ 전체 : {gridCount} // 선택 : {selectedRowDataCount}
+
+
+ {/* */}
+ {/*
+ */}
+
+
+
+
+
>
)
}
diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx
new file mode 100644
index 00000000..2f39b066
--- /dev/null
+++ b/src/components/management/StuffDetail.jsx
@@ -0,0 +1,388 @@
+'use client'
+
+import React, { useState, useEffect } from 'react'
+import { useRouter, useSearchParams } from 'next/navigation'
+import { Input, RadioGroup, Radio, Button, Autocomplete, AutocompleteItem, Select, SelectItem, Checkbox, Textarea } from '@nextui-org/react'
+import Link from 'next/link'
+import { get } from '@/lib/Axios'
+import { queryStringFormatter } from '@/util/common-utils'
+import dayjs from 'dayjs'
+export default function StuffDetail() {
+ const router = useRouter()
+ const searchParams = useSearchParams()
+ const [receiveUser, setReceiveUser] = useState('') //담당자
+ const [name2, setName2] = useState('') //물건명
+ const [name3, setName3] = useState('') //물건명후리가나
+ const [zipCode, setZipCode] = useState('') //우편번호
+ const [name5, setName5] = useState('') //수직적설량
+ const [gubun, setGubun] = useState('NEW') //신축 기축
+ const [sel, setSel] = useState('') //경칭선택
+ const [sel2, setSel2] = useState('') //발전량시뮬레이션지역
+ const [sel3, setSel3] = useState('') //기준풍속
+ const [sel4, setSel4] = useState('') //설치높이
+
+ const [errors, setErrors] = useState({})
+ const [isFormValid, setIsFormValid] = useState(false) //임시저장, 진짜저장 버튼 컨트롤
+ const [testSelOption, setTestSelOption] = useState([]) // 테스트용
+ const [autoSelectValue, setAutoSelectValue] = useState('') //판매점명 자동완성
+ const [buttonValid, setButtonValid] = useState(true) //주소검색 활성화 컨트롤
+ const [isSelected, setIsSelected] = useState(false) //한랭지대첵 체크박스
+ const [isSelected2, setIsSelected2] = useState(false) //염해지역용아이템사용 체크박스
+ const [gubun2, setGubun2] = useState('1') //면조도구분 라디오
+ const [gubun3, setGubun3] = useState('A') //계약조건 라디오
+ const [memo, setMemo] = useState('') //메모
+ const objectNo = searchParams.get('objectNo') //url에서 물건번호 꺼내서 바로 set
+
+ const [address1, setAddress1] = useState('') //우편API리턴 도도부현명
+ const [address2, setAddress2] = useState('') //우편API리턴 시구정촌명
+ const [address3, setAddress3] = useState('') //우편API리턴 마을 지역명
+ const [prefcode, setPrefCode] = useState(1) //우편API prefcode
+
+ const [editMode, setEditMode] = useState('NEW')
+ const [detailData, setDetailData] = useState({})
+
+ useEffect(() => {
+ // console.log('상세화면진입:::::::::', searchParams.get('objectNo'))
+ // console.log('물건번호::::', objectNo)
+
+ if (objectNo) {
+ console.log('상세::')
+ setEditMode('EDIT')
+ //http://localhost:8080/api/object/v1.0/object/R201TES01240906007/1
+ //일단 플랜번호 무조건 1로
+ //API 호출
+ get({ url: `/api/object/v1.0/object/${objectNo}/1` }).then((res) => {
+ if (res != null) {
+ // console.log('res:::::::', res)
+ setDetailData(res)
+ //setTestSelOption(res)
+ }
+ })
+ } else {
+ console.log('신규:::')
+ }
+ }, [objectNo])
+
+ useEffect(() => {
+ validateForm()
+ }, [receiveUser, name2, name3, gubun, sel, autoSelectValue, zipCode, sel2, sel3, name5, sel4])
+
+ // 우편번호 숫자만 체크
+ const textTypeHandler = (e) => {
+ //\D 숫자가 아닌것(특수문자포함)과 매치, [^0-9]와 동일
+ if (!e.target.value.match(/\D/g)) {
+ setZipCode(e.target.value)
+ }
+ }
+
+ // 수직적설량 숫자만
+ const textTypeHandler2 = (e) => {
+ if (!e.target.value.match(/[^0-9]/g)) {
+ setName5(e.target.value)
+ }
+ }
+ const validateForm = () => {
+ let errors = {}
+
+ if (!receiveUser || receiveUser.trim().length === 0) {
+ errors.receiveUser = '담당자 is required.'
+ }
+
+ if (!name2 || name2.trim().length === 0) {
+ errors.name2 = '물건명 is required.'
+ }
+
+ if (!name3 || name3.trim().length === 0) {
+ errors.name3 = '물건명후리가나 is required.'
+ }
+
+ if (!sel) {
+ errors.sel = '경칭선택 is required'
+ }
+
+ if (!sel2) {
+ errors.sel2 = '발전량시뮬레이션지역 is required'
+ }
+
+ if (!sel3) {
+ errors.sel3 = '기준풍속 is required'
+ }
+
+ if (!sel4) {
+ errors.sel4 = '설치높이 is required'
+ }
+
+ if (!autoSelectValue) {
+ errors.autoSelectValue = '판매점ID자동완성 is required'
+ }
+
+ if (!zipCode || zipCode.length != 7) {
+ errors.zipCode = '우편번호 is required.'
+ setButtonValid(true)
+ } else {
+ setButtonValid(false)
+ }
+
+ if (!name5) {
+ errors.name5 = '수직적설량 is required.'
+ }
+
+ // console.log('errors::', errors)
+ setErrors(errors)
+ setIsFormValid(Object.keys(errors).length === 0)
+ }
+
+ // 우편번호 API
+ const onSearchPostNumber = () => {
+ if (!zipCode) {
+ return alert('우편번호 입력해')
+ }
+ const params = {
+ zipcode: zipCode,
+ }
+
+ get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => {
+ console.log('우편API RES::::::::', res)
+ if (res.status === 200) {
+ if (res.results.length > 0) {
+ setAddress1(res.results[0].address1)
+ setAddress2(res.results[0].address2)
+ setAddress3(res.results[0].address3)
+ setPrefCode(res.results[0].prefcode)
+ } else {
+ alert('등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.')
+ }
+ } else {
+ alert(res.message)
+ }
+ })
+ }
+
+ const onTempSave = () => {
+ console.log('임시저장::', isFormValid)
+ }
+
+ const onSave = () => {
+ console.log('진짜저장isFormValid:::', isFormValid)
+ }
+
+ const moveList = () => {
+ router.push('/management/stuff')
+ }
+
+ const changeAddress2 = (e) => {
+ console.log('e:::::::', e.target.value)
+ }
+
+ return (
+ <>
+ {(editMode === 'NEW' &&
신규:::::::::::
) ||
상세:::::::::::
}
+
+
+ 물건번호
+ {objectNo}
+
+
+ 사양확정일
+ {detailData?.specDate ? dayjs(detailData.specDate).format('YYYY.MM.DD') : null}
+
+
+ 갱신일시
+
+ {detailData?.lastEditDatetime
+ ? dayjs(detailData.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')
+ : detailData?.createDatetime
+ ? dayjs(detailData.createDatetime).format('YYYY.MM.DD HH:mm:ss')
+ : null}
+
+
+
+ 등록일
+
+
+
+
(*필수 입력항목)
+
+ 담당자*
+ setReceiveUser(e.target.value)} />
+
+
+
물건구분/물건명 *
+
{
+ setGubun(e.target.value)
+ }}
+ />
+
+
{
+ setGubun(e.target.value)
+ }}
+ />
+
+
+ setName2(e.target.value)} />
+
+
+
+
+
+ 물건명 후리가나
+ setName3(e.target.value)} />
+
+
+
+
판매점명 /ID *
+
+
+ {(option) => {option.name}}
+
+
+
+
+ 우편번호*
+
+
+ *우편번호 7자리를 입력한 후, 주소검색 버튼을 클릭해 주십시오
+
+
+ 도도부현 / 주소*
+ {/* */}
+
+
+
+ 발전량시뮬레이션지역*
+
+
+
+ 기준풍속*
+
+ m/s이하
+
+
+ 수직적설량*
+ cm
+
+ 한랭지대책시행
+
+
+
+ 면조도구분*
+ {
+ setGubun2(e.target.value)
+ }}
+ />
+
+ {
+ setGubun2(e.target.value)
+ }}
+ />
+
+
+ 염해지역용아이템사용
+
+
+
+ 설치높이*
+
+ m
+
+
+ 계약조건
+ {
+ setGubun3(e.target.value)
+ }}
+ />
+
+ {
+ setGubun3(e.target.value)
+ }}
+ />
+
+
+
+ 메모
+
+
+ {!isFormValid ? (
+ <>
+
+ >
+ ) : (
+ <>
+
+ >
+ )}
+
+
+
+ {/*
*/}
+ >
+ )
+}
diff --git a/src/components/management/StuffQGrid.jsx b/src/components/management/StuffQGrid.jsx
new file mode 100644
index 00000000..8139991b
--- /dev/null
+++ b/src/components/management/StuffQGrid.jsx
@@ -0,0 +1,116 @@
+import React from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { AgGridReact } from 'ag-grid-react'
+
+import 'ag-grid-community/styles/ag-grid.css'
+import 'ag-grid-community/styles/ag-theme-quartz.css'
+
+export default function StuffQGrid(props) {
+ const { gridData, gridColumns, isPageable = true, count, gridRef } = props
+ /**
+ * 행 데이터를 설정할 때 useState을 사용하여 렌더링 전반에 걸쳐 일관된 배열 참조를 유지하는 것이 좋습니다
+ */
+ const [rowData, setRowData] = useState(null)
+
+ /**
+ * Column Definitions를 설정할 때는 useMemo 또는 useState를 사용하여 렌더 간에 일관된 참조를 유지하십시오.
+ * 응용 프로그램이 Column Definitions를 동적으로 변경하는 경우에도 렌더링 간에 일관된 참조를 유지하려면 useMemo 또는 useState를 사용하십시오.
+ */
+ const [colDefs, setColDefs] = useState(
+ gridColumns ?? [
+ { field: 'mission', filter: true },
+ { field: 'company' },
+ { field: 'location' },
+ { field: 'date' },
+ { field: 'price', valueFormatter: (params) => `₩ ${params.value.toLocaleString()}` },
+ { field: 'successful' },
+ { field: 'rocket' },
+ ],
+ )
+
+ /**
+ * defaultColDef 속성을 제공할 때 이 인라인 또는 구성 요소의 단순 개체로 정의하지 마십시오. 이렇게 하면 모든 렌더링에서 새 인스턴스가 생성됩니다.
+ * 대신 or useState 를 사용하여 useMemo 렌더 간에 일관된 참조가 유지되도록 합니다.
+ */
+ const defaultColDef = useMemo(() => {
+ return {
+ filter: false,
+ flex: 1,
+ sortable: false,
+ suppressMovable: true,
+ resizable: false,
+ suppressSizeToFit: false,
+ headerClass: 'centered', //_test.scss에 추가 테스트
+ }
+ }, [])
+
+ /**
+ * 단순 유형(string, boolean 및 number)의 속성은 렌더링 간에 값으로 비교되므로 후크를 사용할 필요가 없습니다.
+ */
+ const rowBuffer = 100
+
+ /**
+ * 모든 렌더링에서 그리드 상태를 재설정하지 않도록 useCallback을 사용하는 것이 좋습니다.
+ * api데이타 해당 컬럼에 따라 로우 체크박스 체크 가능여부 등 컨트롤
+ */
+ const isRowSelectable = useCallback(
+ (params) => {
+ return !!params.data
+ },
+ [count],
+ )
+
+ // 체크박스 체크시
+ const onSelectionChanged = useCallback((event) => {
+ props.getSelectedRowdata(event.api.getSelectedRows())
+ }, [])
+
+ //더블클릭
+ const onCellDoubleClicked = useCallback((event) => {
+ // if (event.column.colId === 'company') {
+ // return
+ // } else {
+ props.getCellDoubleClicked(event)
+ // }
+ }, [])
+
+ //컨텐츠에 따라 컬럼넓이 자동조절
+ const autoSizeStrategy = useMemo(() => {
+ return {
+ type: 'fitCellContents',
+ }
+ }, [])
+
+ const onGridReady = useCallback((event) => {
+ // 헤더 사이즈 조정 컬럼에 width값으로 계산
+ event.api.sizeColumnsToFit()
+ }, [])
+
+ // Fetch data & update rowData state
+ useEffect(() => {
+ gridData ? setRowData(gridData) : ''
+ }, [gridData])
+
+ return (
+
+ )
+}
diff --git a/src/components/management/StuffSearchCondition.jsx b/src/components/management/StuffSearchCondition.jsx
new file mode 100644
index 00000000..f0d665d0
--- /dev/null
+++ b/src/components/management/StuffSearchCondition.jsx
@@ -0,0 +1,264 @@
+'use client'
+
+import React, { useEffect } from 'react'
+import { useState } from 'react'
+import { Input, RadioGroup, Radio, Button } from '@nextui-org/react'
+import RangeDatePicker from '@/components/common/datepicker/RangeDatePicker'
+import { useRecoilState, useResetRecoilState } from 'recoil'
+import { stuffSearchState } from '@/store/stuffAtom'
+import dayjs from 'dayjs'
+import isLeapYear from 'dayjs/plugin/isLeapYear' // 윤년 판단 플러그인
+dayjs.extend(isLeapYear)
+import Link from 'next/link'
+export default function StuffSearchCondition() {
+ //달력 props 관련 날짜 셋팅
+ const [dateRange, setDateRange] = useState([dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')])
+ const [startRangeDate, endRangeDate] = dateRange
+
+ const rangeDatePickerProps = {
+ startRangeDate, //시작일
+ endRangeDate, //종료일
+ setDateRange,
+ }
+
+ //여기서 선택한 검색조건들을 recoil로 관리
+ const resetStuffRecoil = useResetRecoilState(stuffSearchState)
+ const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState)
+ const [objectNo, setObjectNo] = useState('') //물건번호
+ const [saleStoreId, setSaleStoreId] = useState('') //판매대리점ID
+ const [address, setAddress] = useState('') //물건주소
+ const [objectName, setobjectName] = useState('') //물건명
+ const [saleStoreName, setSaleStoreName] = useState('') //판매대리점명
+ const [specDateYn, setSpecDateYn] = useState('') //사양 확인('', 'Y', 'N')
+ const [receiveUser, setReceiveUser] = useState('') //담당자
+ const [dispCompanyName, setDispCompanyName] = useState('') //견적처
+ const [dateType, setDateType] = useState('U') //갱신일(U)/등록일(R)
+
+ // 조회
+ const onSubmit = () => {
+ let diff = dayjs(endRangeDate).diff(startRangeDate, 'day')
+ if (diff > 366) {
+ return alert('최대1년 조회 가능합니다.')
+ }
+ setStuffSearch({
+ schObjectNo: stuffSearch?.schObjectNo ? stuffSearch.schObjectNo : objectNo,
+ schSaleStoreId: stuffSearch?.schSaleStoreId ? stuffSearch.schSaleStoreId : saleStoreId,
+ schAddress: stuffSearch?.schAddress ? stuffSearch.schAddress : address,
+ schObjectName: stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName,
+ schSaleStoreName: stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName,
+ schSpecDateYn: stuffSearch?.schSpecDateYn ? stuffSearch.schSpecDateYn : specDateYn,
+ schReceiveUser: stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser,
+ schDispCompanyName: stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName,
+ schDateType: stuffSearch?.schDateType ? stuffSearch.schDateType : dateType,
+ schFromDt: dayjs(startRangeDate).format('YYYY-MM-DD'),
+ schToDt: dayjs(endRangeDate).format('YYYY-MM-DD'),
+ code: 'E',
+ })
+ }
+
+ //초기화
+ const resetRecoil = () => {
+ setObjectNo('')
+ setSaleStoreId('')
+ setAddress('')
+ setobjectName('')
+ setSaleStoreName('')
+ setSpecDateYn('')
+ setReceiveUser('')
+ setDispCompanyName('')
+ setDateType('U')
+ setDateRange([dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')])
+ resetStuffRecoil()
+ }
+
+ //x로 날짜 비웠을때 기본값으로 셋팅
+ useEffect(() => {
+ if (!startRangeDate && !endRangeDate) {
+ setDateRange([dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'), dayjs(new Date()).format('YYYY-MM-DD')])
+ }
+ }, [startRangeDate, endRangeDate])
+
+ useEffect(() => {
+ setDateRange([
+ stuffSearch?.schFromDt ? stuffSearch.schFromDt : dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'),
+ stuffSearch?.schToDt ? stuffSearch.schToDt : dayjs(new Date()).format('YYYY-MM-DD'),
+ ])
+ }, [stuffSearch])
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {Array(4)
+ .fill()
+ .map((_, i) => {
+ if (i === 0) {
+ return (
+
+ {
+ setObjectNo(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schObjectNo: e.target.value })
+ }}
+ />
+ {
+ setSaleStoreId(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreId: e.target.value })
+ }}
+ />
+ {
+ setAddress(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', address: e.target.value })
+ }}
+ />
+
+ )
+ } else if (i === 1) {
+ return (
+
+
{
+ setobjectName(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schObjectName: e.target.value })
+ }}
+ />
+
{
+ setSaleStoreName(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreName: e.target.value })
+ }}
+ />
+
+ {
+ setSpecDateYn(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
+ }}
+ />
+
+
+
+ {
+ setSpecDateYn(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
+ }}
+ />
+
+
+
+ {
+ setSpecDateYn(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
+ }}
+ />
+
+
+
+ )
+ } else if (i === 2) {
+ return (
+
+ {
+ setReceiveUser(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schReceiveUser: e.target.value })
+ }}
+ />
+ {
+ setDispCompanyName(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schDispCompanyName: e.target.value })
+ }}
+ />
+
+ )
+ } else {
+ return (
+
+
+ {
+ setDateType(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
+ }}
+ />
+
+
+
+ {
+ setDateType(e.target.value)
+ setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
+ }}
+ />
+
+
+
+
+ )
+ }
+ })}
+
+ >
+ )
+}
diff --git a/src/components/ui/ObjectPlacement.jsx b/src/components/ui/ObjectPlacement.jsx
index 7bb10f14..f7a73a5c 100644
--- a/src/components/ui/ObjectPlacement.jsx
+++ b/src/components/ui/ObjectPlacement.jsx
@@ -5,16 +5,7 @@ import { modalState } from '@/store/modalAtom'
import { fabric } from 'fabric'
import { QPolygon } from '@/components/fabric/QPolygon'
import { modeState, objectPlacementModeState } from '@/store/canvasAtom'
-
-const BATCH_TYPE = {
- OPENING: 'opening',
- SHADOW: 'shadow',
-}
-
-const INPUT_TYPE = {
- FREE: 'free',
- DIMENSION: 'dimension',
-}
+import { BATCH_TYPE, INPUT_TYPE } from '@/common/common'
const ObjectPlacement = ({ canvas }) => {
const [open, setOpen] = useRecoilState(modalState)
diff --git a/src/components/ui/SurfaceShape.jsx b/src/components/ui/SurfaceShape.jsx
index ac30138f..706729a3 100644
--- a/src/components/ui/SurfaceShape.jsx
+++ b/src/components/ui/SurfaceShape.jsx
@@ -13,7 +13,7 @@ import { getIntersectionPoint } from '@/util/canvas-util'
* @constructor
*/
export const SurfaceShapeModal = ({ canvas }) => {
- const [type, setType] = useState(0)
+ const [type, setType] = useState(1)
const setOpen = useSetRecoilState(modalState)
const fontSize = useRecoilValue(fontSizeState)
//지붕재
diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js
index 2d99be3b..e6ae4dbf 100644
--- a/src/hooks/useCanvas.js
+++ b/src/hooks/useCanvas.js
@@ -138,12 +138,24 @@ export function useCanvas(id) {
if (canvas) {
if (canvas?._objects.length > 0) {
const poppedObject = canvas?._objects.pop()
+ const group = []
+ group.push(poppedObject)
+
+ if (poppedObject.parent || poppedObject.parentId) {
+ canvas
+ ?.getObjects()
+ .filter((obj) => obj.parent === poppedObject.parent || obj.parentId === poppedObject.parentId || obj === poppedObject.parent)
+ .forEach((obj) => {
+ group.push(obj)
+ canvas?.remove(obj)
+ })
+ }
setHistory((prev) => {
if (prev === undefined) {
- return poppedObject ? [poppedObject] : []
+ return poppedObject ? [group] : []
}
- return poppedObject ? [...prev, poppedObject] : prev
+ return poppedObject ? [...prev, group] : prev
})
canvas?.renderAll()
}
@@ -154,7 +166,13 @@ export function useCanvas(id) {
if (canvas && history) {
if (history.length > 0) {
setIsLocked(true)
- canvas?.add(history[history.length - 1])
+ if (Array.isArray(history[history.length - 1])) {
+ history[history.length - 1].forEach((obj) => {
+ canvas?.add(obj)
+ })
+ } else {
+ canvas?.add(history[history.length - 1])
+ }
const newHistory = history.slice(0, -1)
setHistory(newHistory)
}
diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js
index a19d2944..dc3312d2 100644
--- a/src/hooks/useMode.js
+++ b/src/hooks/useMode.js
@@ -35,7 +35,7 @@ import { QPolygon } from '@/components/fabric/QPolygon'
import offsetPolygon, { inPolygon } from '@/util/qpolygon-utils'
import { isObjectNotEmpty } from '@/util/common-utils'
import * as turf from '@turf/turf'
-import { Mode } from '@/common/common'
+import { INPUT_TYPE, Mode } from '@/common/common'
export function useMode() {
const [mode, setMode] = useRecoilState(modeState)
@@ -1078,24 +1078,38 @@ export function useMode() {
alert('지붕 내부에만 생성 가능합니다.')
return
}
- mouseEvent.openingMode.isDown = true
-
mouseEvent.openingMode.origX = pointer.x
mouseEvent.openingMode.origY = pointer.y
- mouseEvent.openingMode.rect = new fabric.Rect({
- fill: 'white',
- stroke: 'black',
- strokeWidth: 1,
- left: mouseEvent.openingMode.origX,
- top: mouseEvent.openingMode.origY,
- originX: 'left',
- originY: 'top',
- width: pointer.x - mouseEvent.openingMode.origX,
- height: pointer.y - mouseEvent.openingMode.origY,
- angle: 0,
- transparentCorners: false,
- })
- canvas.add(mouseEvent.openingMode.rect)
+ if (objectPlacementMode.inputType === INPUT_TYPE.FREE) {
+ mouseEvent.openingMode.isDown = true
+
+ mouseEvent.openingMode.rect = new fabric.Rect({
+ fill: 'white',
+ stroke: 'black',
+ strokeWidth: 1,
+ left: mouseEvent.openingMode.origX,
+ top: mouseEvent.openingMode.origY,
+ originX: 'left',
+ originY: 'top',
+ width: pointer.x - mouseEvent.openingMode.origX,
+ height: pointer.y - mouseEvent.openingMode.origY,
+ })
+ canvas.add(mouseEvent.openingMode.rect)
+ } else if (objectPlacementMode.inputType === INPUT_TYPE.DIMENSION) {
+ mouseEvent.openingMode.rect = new fabric.Rect({
+ fill: 'white',
+ stroke: 'black',
+ strokeWidth: 1,
+ left: mouseEvent.openingMode.origX,
+ top: mouseEvent.openingMode.origY,
+ originX: 'left',
+ originY: 'top',
+ width: Number(objectPlacementMode.width),
+ height: Number(objectPlacementMode.height),
+ })
+ canvas.add(mouseEvent.openingMode.rect)
+ canvas.off('mouse:move')
+ }
},
move: (e) => {
if (!mouseEvent.openingMode.isDown) return
@@ -1130,6 +1144,7 @@ export function useMode() {
}
mouseEvent.openingMode.rect.set({ name: 'opening' })
+ setMode(Mode.DEFAULT)
},
},
}
@@ -1147,7 +1162,17 @@ export function useMode() {
{ x: rect.left + rect.width, y: rect.top },
]
+ const rect1Corners = {
+ minX: Math.min(...rectPoints.map((point) => point.x)),
+ maxX: Math.max(...rectPoints.map((point) => point.x)),
+ minY: Math.min(...rectPoints.map((point) => point.y)),
+ maxY: Math.max(...rectPoints.map((point) => point.y)),
+ }
+ let isCross = true
for (let i = 0; i < openings.length; i++) {
+ if (i !== 0 && isCross) {
+ break
+ }
const rect2 = openings[i]
const rect2Points = [
{ x: rect2.left, y: rect2.top },
@@ -1155,9 +1180,30 @@ export function useMode() {
{ x: rect2.left + rect2.width, y: rect2.top + rect2.height },
{ x: rect2.left + rect2.width, y: rect2.top },
]
+
+ const rect2Corners = {
+ minX: Math.min(...rect2Points.map((point) => point.x)),
+ maxX: Math.max(...rect2Points.map((point) => point.x)),
+ minY: Math.min(...rect2Points.map((point) => point.y)),
+ maxY: Math.max(...rect2Points.map((point) => point.y)),
+ }
+
+ // Check if one rectangle is to the left of the other
+ if (
+ rect1Corners.maxX < rect2Corners.minX ||
+ rect2Corners.maxX < rect1Corners.minX ||
+ rect1Corners.maxY < rect2Corners.minY ||
+ rect2Corners.maxY < rect1Corners.minY
+ ) {
+ isCross = false
+ continue
+ } else {
+ isCross = true
+ break
+ }
}
- return false
+ return isCross
}
const checkInsideRoof = (rect) => {
diff --git a/src/locales/ja.js b/src/locales/ja.js
index 2b7df6bd..5ae1b171 100644
--- a/src/locales/ja.js
+++ b/src/locales/ja.js
@@ -4,4 +4,87 @@ export default {
hello: 'こんにちは',
welcome: 'こんにちは {name}!',
locale: '現在のロケールは {locale} です。',
+ common: {
+ require: '필수',
+ },
+ site: {
+ name: 'Q.CAST III',
+ sub_name: '태양광 발전 시스템 도면관리 사이트',
+ },
+ login: {
+ login: 'Login',
+ init_password: {
+ btn: '비밀번호 초기화',
+ title: '비밀번호 초기화',
+ sub_title: '비밀번호를 초기화할 아이디와 이메일 주소를 입력해 주세요.',
+ },
+ },
+ join: {
+ title: 'Q.CAST3 로그인ID 발행 신청',
+ sub1: {
+ title: '판매대리점 정보',
+ comment: '※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)',
+ storeQcastNm: '판매대리점명',
+ storeQcastNm_placeholder: '株式会社エネルギア・ソリューション・アンド・サービス(2次店:山口住機販売有限会社)',
+ storeQcastNmKana: '판매대리점명 후리가나',
+ storeQcastNmKana_placeholder: 'カブシキガイシャエネルギア・ソリューション・アン',
+ postCd: '우편번호',
+ postCd_placeholder: '숫자 7자리',
+ addr: '주소',
+ addr_placeholder: '전각50자이내',
+ telNo: '전화번호',
+ telNo_placeholder: '00-0000-0000',
+ fax: 'FAX 번호',
+ fax_placeholder: '00-0000-0000',
+ },
+ sub2: {
+ title: '담당자 정보',
+ userNm: '담당자명',
+ userNmKana: '담당자명 후리가나',
+ userId: '신청 ID',
+ email: '이메일 주소',
+ telNo: '전화번호',
+ telNo_placeholder: '00-0000-0000',
+ fax: 'FAX 번호',
+ fax_placeholder: '00-0000-0000',
+ category: '부서명',
+ },
+ sub3: {
+ title: '견적서 제출용 회사정보',
+ qtCompNm: '회사명',
+ qtPostCd: '우편번호',
+ qtPostCd_placeholder: '숫자 7자리',
+ qtAddr: '주소',
+ qtAddr_placeholder: '전각50자이내',
+ qtEmail: '이메일 주소',
+ qtTelNo: '전화번호',
+ qtTelNo_placeholder: '00-0000-0000',
+ qtFax: 'FAX 번호',
+ qtFax_placeholder: '00-0000-0000',
+ },
+ btn: {
+ approval_request: 'ID 승인요청',
+ },
+ complete: {
+ title: 'Q.CAST3 로그인ID 발행신청 완료',
+ contents: '※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.',
+ email_comment: '담당자 이메일 주소',
+ email: 'test@naver.com',
+ },
+ },
+ stuff: {
+ gridHeader: {
+ lastEditDatetime: '갱신일시',
+ objectNo: '물건번호',
+ planTotCnt: '플랜 수',
+ objectName: '물건명',
+ saleStoreId: '대리점ID',
+ saleStoreName: '대리점명',
+ address: '물건주소',
+ dispCompanyName: '견적처',
+ receiveUser: '담당자',
+ specDate: '사양확인',
+ createDatetime: '등록일',
+ },
+ },
}
diff --git a/src/locales/ko.js b/src/locales/ko.js
index 40e17ba2..c01b913c 100644
--- a/src/locales/ko.js
+++ b/src/locales/ko.js
@@ -4,4 +4,88 @@ export default {
hello: '안녕',
welcome: '안녕 {name}!',
locale: '현재 로케일은 {locale}입니다.',
+ common: {
+ require: '필수',
+ },
+ site: {
+ name: 'Q.CAST III',
+ sub_name: '태양광 발전 시스템 도면관리 사이트',
+ },
+ login: {
+ login: '로그인',
+ init_password: {
+ btn: '비밀번호 초기화',
+ title: '비밀번호 초기화',
+ sub_title: '비밀번호를 초기화할 아이디와 이메일 주소를 입력해 주세요.',
+ complete_message: '비밀번호가 초기화 되었습니다. 초기화된 비밀번호는 아이디와 같습니다.',
+ },
+ },
+ join: {
+ title: 'Q.CAST3 로그인ID 발행 신청',
+ sub1: {
+ title: '판매대리점 정보',
+ comment: '※ 등록되는 리셀러의 회사 이름을 입력하십시오. (2차점은 「○○판매주식회사(2차점:××설비주식회사)」로 기입해 주세요.)',
+ storeQcastNm: '판매대리점명',
+ storeQcastNm_placeholder: '주식회사 에너지 기어 솔루션 앤 서비스 (2차점: 야마구치 주기 판매 유한회사)',
+ storeQcastNmKana: '판매대리점명 후리가나',
+ storeQcastNmKana_placeholder: '주식회사 에너지 기어 솔루션',
+ postCd: '우편번호',
+ postCd_placeholder: '숫자 7자리',
+ addr: '주소',
+ addr_placeholder: '전각50자이내',
+ telNo: '전화번호',
+ telNo_placeholder: '00-0000-0000',
+ fax: 'FAX 번호',
+ fax_placeholder: '00-0000-0000',
+ },
+ sub2: {
+ title: '담당자 정보',
+ userNm: '담당자명',
+ userNmKana: '담당자명 후리가나',
+ userId: '신청 ID',
+ email: '이메일 주소',
+ telNo: '전화번호',
+ telNo_placeholder: '00-0000-0000',
+ fax: 'FAX 번호',
+ fax_placeholder: '00-0000-0000',
+ category: '부서명',
+ },
+ sub3: {
+ title: '견적서 제출용 회사정보',
+ qtCompNm: '회사명',
+ qtPostCd: '우편번호',
+ qtPostCd_placeholder: '숫자 7자리',
+ qtAddr: '주소',
+ qtAddr_placeholder: '전각50자이내',
+ qtEmail: '이메일 주소',
+ qtTelNo: '전화번호',
+ qtTelNo_placeholder: '00-0000-0000',
+ qtFax: 'FAX 번호',
+ qtFax_placeholder: '00-0000-0000',
+ },
+ btn: {
+ approval_request: 'ID 승인요청',
+ },
+ complete: {
+ title: 'Q.CAST3 로그인ID 발행신청 완료',
+ contents: '※ 신청한 ID가 승인되면, 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.',
+ email_comment: '담당자 이메일 주소',
+ email: 'test@naver.com',
+ },
+ },
+ stuff: {
+ gridHeader: {
+ lastEditDatetime: '갱신일시',
+ objectNo: '물건번호',
+ planTotCnt: '플랜 수',
+ objectName: '물건명',
+ saleStoreId: '대리점ID',
+ saleStoreName: '대리점명',
+ address: '물건주소',
+ dispCompanyName: '견적처',
+ receiveUser: '담당자',
+ specDate: '사양확인',
+ createDatetime: '등록일',
+ },
+ },
}
diff --git a/src/store/stuffAtom.js b/src/store/stuffAtom.js
new file mode 100644
index 00000000..a122543c
--- /dev/null
+++ b/src/store/stuffAtom.js
@@ -0,0 +1,22 @@
+import { atom } from 'recoil'
+import dayjs from 'dayjs'
+import isLeapYear from 'dayjs/plugin/isLeapYear' // 윤년 판단 플러그인
+dayjs.extend(isLeapYear)
+export const stuffSearchState = atom({
+ key: 'stuffSearchState',
+ default: {
+ schObjectNo: '', //물건번호
+ schSaleStoreId: '', //판매대리점ID
+ schAddress: '', //물건주소
+ schObjectName: '', //물건명
+ schSaleStoreName: '', //판매대리점명
+ schSpecDateYn: '', //사양타입 ('', 'Y', 'N')
+ schReceiveUser: '', //담당자
+ schDispCompanyName: '', //견적처
+ schDateType: 'U', //갱신일(U)/등록일(R)
+ schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'), //시작일
+ schToDt: dayjs(new Date()).format('YYYY-MM-DD'), //종료일
+ code: 'S',
+ },
+ dangerouslyAllowMutability: true,
+})
diff --git a/src/styles/_test.scss b/src/styles/_test.scss
index 55440362..9e542bbd 100644
--- a/src/styles/_test.scss
+++ b/src/styles/_test.scss
@@ -41,7 +41,7 @@
.grid-item {
width: 100%;
height: 100%;
- border: 1px solid black; /* 그리드 외각선 */
+ border: 1px solid black; /* 그리드 외각선 */
text-align: center; /* 그리드 내 가운데 정렬 */
}
@@ -79,3 +79,9 @@
background-color: white;
color: black;
}
+
+.centered {
+ .ag-header-cell-label {
+ justify-content: center !important;
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 6bd3608f..ed3490ac 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5515,6 +5515,11 @@ react-dom@^18:
loose-envify "^1.1.0"
scheduler "^0.23.2"
+react-hook-form@^7.53.0:
+ version "7.53.0"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab"
+ integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==
+
react-icons@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c"