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]/initSettingsModal/page.jsx b/src/app/[locale]/initSettingsModal/page.jsx new file mode 100644 index 00000000..a081ef47 --- /dev/null +++ b/src/app/[locale]/initSettingsModal/page.jsx @@ -0,0 +1,16 @@ +import Hero from '@/components/Hero' +import InitSettingsModal from '@/components/InitSettingsModal' +import { initCheck } from '@/util/session-util' + +export default async function InitSettingsModalPage() { + await initCheck() + + return ( + <> + +
+ +
+ + ) +} 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 ( + <> +
+

물건정보

+
+
+ +
+ + ) +} diff --git a/src/app/[locale]/management/stuff/page.jsx b/src/app/[locale]/management/stuff/page.jsx index e2d3c1cd..7590a7cf 100644 --- a/src/app/[locale]/management/stuff/page.jsx +++ b/src/app/[locale]/management/stuff/page.jsx @@ -1,14 +1,19 @@ -import Hero from '@/components/Hero' +import StuffSearchCondition from '@/components/management/StuffSearchCondition' import Stuff from '@/components/management/Stuff' import { initCheck } from '@/util/session-util' - +import Hero from '@/components/Hero' export default async function ManagementStuffPage() { await initCheck() return ( <> - -
+ +
+
+ +
+
+
diff --git a/src/common/common.js b/src/common/common.js index 91a9583a..d4c22570 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -12,10 +12,37 @@ export const Mode = { CELL_POWERCON: 'cellPowercon', //파워콘 DRAW_HELP_LINE: 'drawHelpLine', // 보조선 그리기 모드 지붕 존재해야함 ADSORPTION_POINT: 'adsorptionPoint', //흡착점 모드 + OPENING: 'opening', //개구 모드 + SHADOW: 'shadow', //그림자 생성 모드 DEFAULT: 'default', } export const LineType = { EAVES: 'eaves', // 처마 RIDGE: 'ridge', // 용마루.... + YOSEMUNE: 'yosemune', //요세무네 + ONESIDE_FLOW_RIDGE: 'onesideFlowRidge', //한쪽흐름 용마루 + WALL_COLLECTION: 'wallCollection', //벽취합 + WALL_COLLECTION_TYPE: 'wallCollectionType', //벽취합(형) + WALL_COLLECTION_FLOW: 'wallCollectionFlow', //벽취합(흐름) + WALL_COLLECTION_FLOW_LEFT: 'wallCollectionFlowLeft', //벽취합(흐름 왼쪽) + WALL_COLLECTION_FLOW_RIGHT: 'wallCollectionFlowRight', //벽취합(흐름 오른쪽) + KERABA: 'keraba', //케라바 + KERABA_LEFT: 'kerabaLeft', //케라바 왼쪽 + KERABA_RIGHT: 'kerabaRight', //케라바 오른쪽 + VALLEY: 'valley', //골짜기 + L_ABANDON_VALLEY: 'lAbandonValley', //l의버림계곡 + MANSARD: 'mansard', //맨사드 + NO_SETTING: 'noSetting', //설정없음 +} + +// 오브젝트 배치 > 개구배치, 그림자배치 +export const BATCH_TYPE = { + OPENING: 'opening', + SHADOW: 'shadow', +} +// 오브젝트 배치 > 프리입력, 치수입력 +export const INPUT_TYPE = { + FREE: 'free', + DIMENSION: 'dimension', } diff --git a/src/components/Headers.jsx b/src/components/Headers.jsx index 331e899c..02859c96 100644 --- a/src/components/Headers.jsx +++ b/src/components/Headers.jsx @@ -10,6 +10,7 @@ export default function Headers() {
Intro Playground + Basic Settings Canvas Settings Roof Roof2 diff --git a/src/components/InitSettingsModal.jsx b/src/components/InitSettingsModal.jsx index a79c7a9e..5a6aad64 100644 --- a/src/components/InitSettingsModal.jsx +++ b/src/components/InitSettingsModal.jsx @@ -1,22 +1,32 @@ +'use client' + import { useEffect, useState, memo, useCallback } from 'react' import { Button, Checkbox, CheckboxGroup, RadioGroup, Radio, Input, Select, SelectItem } from '@nextui-org/react' import { useRecoilState, useRecoilValue } from 'recoil' import { modalContent, modalState } from '@/store/modalAtom' import { canvasSettingState } from '@/store/canvasAtom' import { useAxios } from '@/hooks/useAxios' +import { get, post } from '@/lib/Axios' export default function InitSettingsModal(props) { + const [objectNo, setObjectNo] = useState('test123240909002') // 후에 삭제 필요 + const [lastRoofSeq, setLastRoofSeq] = useState(0) // 마지막 roofSeq를 추적 const [open, setOpen] = useRecoilState(modalState) const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) const [roofMaterials, setRoofMaterials] = useState([]) const [basicSetting, setBasicSettings] = useState({ - type: '1', - inputType: '1', - angleType: 'slope', + roofDrawingSet: '1', + roofSizeSet: '1', + roofAngleSet: 'slope', roofs: [], }) - const { get, post } = useAxios() + const modelProps = { + open, + setOpen, + } + + //const { get, post } = useAxios() useEffect(() => { get({ url: '/api/roof-material/roof-material-infos' }).then((res) => { @@ -26,6 +36,49 @@ export default function InitSettingsModal(props) { setRoofMaterials(res) }) + get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${objectNo}` }).then((res) => { + if (res.length == 0) return + + // 'roofs' 배열을 생성하여 각 항목을 추가 + const roofsRow = res.map((item) => { + return { + roofDrawingSet: String(item.roofDrawingSet), + roofSizeSet: String(item.roofSizeSet), + roofAngleSet: item.roofAngleSet, + } + }) + + const roofsArray = res.some((item) => !item.roofSeq) + ? null //지붕재 추가 정보가 없다면 roofsArray를 null 처리하여 지붕재 추가 정보가 보이지 않게 한다. + : res.map((item) => ({ + roofSeq: String(item.roofSeq), + roofType: String(item.roofType), + roofWidth: String(item.roofWidth), + roofHeight: String(item.roofHeight), + roofGap: String(item.roofGap), + roofLayout: item.roofLayout, + })) + + // 나머지 데이터와 함께 'roofs' 배열을 patternData에 넣음 + const patternData = { + roofDrawingSet: roofsRow[0].roofDrawingSet, // 첫 번째 항목의 값을 사용 + roofSizeSet: roofsRow[0].roofSizeSet, // 첫 번째 항목의 값을 사용 + roofAngleSet: roofsRow[0].roofAngleSet, // 첫 번째 항목의 값을 사용 + roofs: roofsArray, // 만들어진 roofs 배열 + } + + // 데이터 설정 + setBasicSettings({ ...patternData }) + + // 초기 roofSeq 값을 업데이트 + if (roofsArray == null) { + //roofs(지붕재추가) 값이 없으면 lastRoofSeq는 1 설정 + setLastRoofSeq(1) + } else { + setLastRoofSeq(roofsArray.length + 1) + } + }) + if (!(Object.keys(canvasSetting).length === 0 && canvasSetting.constructor === Object)) { setBasicSettings({ ...canvasSetting }) } @@ -39,42 +92,99 @@ export default function InitSettingsModal(props) { //배열 추가 함수 const addRoofSetting = () => { - if (basicSetting.roofs.length === 4) { + if (basicSetting.roofs != null && basicSetting.roofs.length === 4) { alert('지붕재는 최대 4종까지 선택할 수 있습니다.') return } + //roofs가 null인 경우 배열 생성 + if (basicSetting.roofs == null) { + basicSetting.roofs = [] + } + //기본값 const newRoofSettings = { - id: basicSetting.roofs.length + 1, - roofId: '3', - width: '200', - height: '200', - gap: '0', - layout: 'parallel', + //roofSeq: basicSetting.roofs.length + 1, + roofSeq: lastRoofSeq, // 마지막 roofSeq를 1 증가 + roofType: '3', + roofWidth: '200', + roofHeight: '200', + roofGap: '0', + roofLayout: 'parallel', } setBasicSettings((prevState) => ({ ...prevState, roofs: [...prevState.roofs, newRoofSettings], })) + + setLastRoofSeq(newRoofSettings.roofSeq + 1) // roofSeq 값을 업데이트 } //배열 값 변경 함수 const handleRoofSettings = (id, event) => { - const roof = basicSetting.roofs.map((roof, i) => (id === roof.id ? { ...roof, [event.target.name]: event.target.value } : roof)) - setBasicSettings((prevState) => ({ - ...prevState, - roofs: [...roof], - })) + console.log(id) + + // 기본 세팅에서 roofs 배열을 복사 + const updatedRoofs = [...basicSetting.roofs] + + // roofSeq가 id와 일치하는 항목의 인덱스 찾기 + const index = updatedRoofs.findIndex((roof) => roof.roofSeq === id) + + if (index !== -1) { + // 해당 인덱스의 항목을 수정 + updatedRoofs[index] = { + ...updatedRoofs[index], + [event.target.name]: event.target.value, + } + + // 수정된 배열을 상태에 반영 + setBasicSettings((prevState) => ({ + ...prevState, + roofs: updatedRoofs, + })) + } + + // const roof = basicSetting.roofs.map((roof, i) => (id === roof.roofSeq ? { ...roof, [event.target.name]: event.target.value } : roof)) + + // setBasicSettings((prevState) => ({ + // ...prevState, + // roofs: [...roof], + // })) } - const submitCanvasConfig = () => { - post({ url: '/api/canvas-config', data: basicSetting }).then((res) => { - if (!res) { - setCanvasSetting({ ...basicSetting }) - } - }) + //저장 + const submitCanvasConfig = async () => { + if (!objectNo) { + alert('object_no를 입력하세요.') + return + } + + const patternData = { + objectNo, + roofDrawingSet: basicSetting.roofDrawingSet, + roofSizeSet: basicSetting.roofSizeSet, + roofAngleSet: basicSetting.roofAngleSet, + roofMaterialsAddList: basicSetting.roofs, + } + await post({ url: `/api/canvas-management/canvas-basic-settings`, data: patternData }) + + //Recoil 설정 + setCanvasSetting({ ...basicSetting }) + + // 저장 후 재조회 + //await handleSelect() + } + + // 삭제버튼 클릭시 해당 요소 id를 targetId로 전달받음 + const onRemove = async (targetId) => { + console.log(targetId) + + setBasicSettings((prevState) => ({ + ...prevState, + roofs: prevState.roofs.filter((roof) => roof.roofSeq !== targetId), + })) + // setBasicSettings({ ...newRoofSettings }) // 삭제한 데이터 배열을 setData()에 상태를 변화시킴 } return ( @@ -84,7 +194,13 @@ export default function InitSettingsModal(props) {
- + 치수 입력에 의한 물건작성
@@ -92,7 +208,13 @@ export default function InitSettingsModal(props) {
- + 복사도 입력 실측값 입력 육지붕 @@ -102,13 +224,20 @@ export default function InitSettingsModal(props) {
- + 경사 각도
+
지붕재 추가(단위 : mm)
+ {/* Roofs Array Rendering */} {basicSetting.roofs && basicSetting.roofs.map((roof, index) => { - return + return ( +
+ 타입 : + + 너비 : + handleRoofSettings(roof.roofSeq, e)} + /> + mm + 높이 : + handleRoofSettings(roof.roofSeq, e)} + /> + mm + 서까래 간격 : + handleRoofSettings(roof.roofSeq, e)} + /> + mm +
+ handleRoofSettings(roof.roofSeq, e)} + > + 병렬식 + 계단식 + +
+
+ +
+
+ ) })}
@@ -128,62 +330,9 @@ export default function InitSettingsModal(props) { + setObjectNo(e.target.value)} />
) } - -const RoofSelectBox = (props) => { - return ( -
- - props.onChange(props.roof.id, e)} - /> - props.onChange(props.roof.id, e)} - /> - mm - props.onChange(props.roof.id, e)} /> - mm -
- props.onChange(props.roof.id, e)} - > - 병렬식 - 계단식 - -
-
- ) -} diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 670f58c4..c8a48868 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -41,6 +41,7 @@ import GridSettingsModal from './GridSettingsModal' import { SurfaceShapeModal } from '@/components/ui/SurfaceShape' import { drawDirectionStringToArrow } from '@/util/qpolygon-utils' import ThumbnailList from '@/components/ui/ThumbnailLIst' +import ObjectPlacement from '@/components/ui/ObjectPlacement' export default function Roof2(props) { const { name, userId, email, isLoggedIn } = props @@ -764,6 +765,15 @@ export default function Roof2(props) { > 면형상 + {/**/} diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx index 2d6e5142..43288dbe 100644 --- a/src/components/Settings.jsx +++ b/src/components/Settings.jsx @@ -6,12 +6,57 @@ import { Button } from '@nextui-org/react' import { get, post } from '@/lib/Axios' import { useRecoilState } from 'recoil' import { customSettingsState } from '@/store/canvasAtom' +import { modalContent, modalState } from '@/store/modalAtom' + +import ColorPicker from './common/color-picker/ColorPicker' export default function Settings() { const [objectNo, setObjectNo] = useState('test123240829010') const [error, setError] = useState(null) const [customSettings, setCustomSettings] = useRecoilState(customSettingsState) + const [color, setColor] = useState('#ff0000') + + const [open, setOpen] = useRecoilState(modalState) + const [contents, setContent] = useRecoilState(modalContent) + + const handleSavePopup = () => { + console.log('color ', color) + } + + const handleClosePopup = () => { + setContent('') + setOpen(false) + console.log('colorSetting ', color) + } + + const colorSetting = ( + <> +
+

React ColorPicker

+ +
{color}
+
+

+ +

+ + ) + + const customStyles = { + overlay: { + backgroundColor: 'rgba(0,0,0,0.5)', + }, + content: { + width: '300px', + height: '400px', + margin: 'auto', + borderRadius: '4px', + boxShadow: '0 2px 4px rgba(0,0,0,0.2)', + padding: '20px', + }, + } + // 상태를 하나의 객체로 관리 const [settings, setSettings] = useState({ display1: Array(11).fill(false), // 화면 표시1 @@ -48,6 +93,21 @@ export default function Settings() { // 클릭 시 상태 변경 함수 const handleToggle = (type, index) => { + // '실선 그리드' 클릭 시 팝업 열기 + if (type === 'gridSettings' && gridItems.gridSettings[index] === '실선 그리드') { + //openGridPopup() + } + + // '그리드 색 설정' 클릭 시 팝업 열기 + if (type === 'gridSettings' && gridItems.gridSettings[index] === '그리드 색 설정') { + //setSelectedGridSetting(gridItems.gridSettings[index]) + //setIsPopupOpen(true) + //return prevSettings // 설정은 변경하지 않음 + + setOpen(true) + setContent({ ...colorSetting }) + } + setSettings((prevSettings) => { // prevSettings[type]이 배열인지 확인하고, 그렇지 않은 경우 빈 배열로 초기화 let updated = Array.isArray(prevSettings[type]) ? [...prevSettings[type]] : [] @@ -61,6 +121,24 @@ export default function Settings() { }) } + // '실선 그리드' 클릭 시 팝업을 열기 위한 함수 + const openGridPopup = () => { + const popupWidth = 500 + const popupHeight = 300 + + // 팝업 창 위치를 화면 중앙으로 조정하기 위해 계산 + const left = window.innerWidth / 2 - popupWidth / 2 + const top = window.innerHeight / 2 - popupHeight / 2 + + // 새 창 열기 + window + .open + //'./components/intro', // 팝업으로 띄울 페이지의 URL + //'_blank', // 새 창으로 열기 + //`width=${popupWidth},height=${popupHeight},top=${top},left=${left}`, // 크기와 위치 지정 + () + } + // Canvas Setting 조회 및 초기화 const handleSelect = async () => { try { @@ -221,6 +299,10 @@ export default function Settings() {
흡착점 ON

[그리드 설정]

+
+ +
{color}
+
{gridItems.gridSettings.map((item, index) => (
) } diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index b03a593d..3c8b2148 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -127,6 +127,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.on('modified', (e) => { this.addLengthText() + if (this.arrow) { + drawDirectionArrow(this) + } }) this.on('selected', () => { @@ -144,6 +147,17 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.canvas.remove(text) }) this.texts = null + + if (this.arrow) { + this.canvas.remove(this.arrow) + this.canvas + .getObjects() + .filter((obj) => obj.name === 'directionText' && obj.parent === this.arrow) + .forEach((text) => { + this.canvas.remove(text) + }) + this.arrow = null + } }) // polygon.fillCell({ width: 50, height: 30, padding: 10 }) 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) + }} + /> + +
+
+ 메모 +