Merge branch 'dev' of https://git.jetbrains.space/nalpari/q-cast-iii/qcast-front into dev
This commit is contained in:
commit
ffabdf1b19
@ -25,6 +25,7 @@
|
|||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-datepicker": "^7.3.0",
|
"react-datepicker": "^7.3.0",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-hook-form": "^7.53.0",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-responsive-modal": "^6.4.2",
|
"react-responsive-modal": "^6.4.2",
|
||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
|
|||||||
16
src/app/[locale]/initSettingsModal/page.jsx
Normal file
16
src/app/[locale]/initSettingsModal/page.jsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<Hero title="Basic Settings" />
|
||||||
|
<div className="flex flex-col justify-center my-8">
|
||||||
|
<InitSettingsModal />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/management/stuff/detail/page.jsx
Normal file
15
src/app/[locale]/management/stuff/detail/page.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Hero from '@/components/Hero'
|
||||||
|
import StuffDetail from '@/components/management/StuffDetail'
|
||||||
|
export default function ManagementStuffDetailPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="pt-48 flex justify-left">
|
||||||
|
<h1 className="text-4xl archivo-black-regular">물건정보</h1>
|
||||||
|
</div>
|
||||||
|
<div className="m2">
|
||||||
|
<StuffDetail />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,14 +1,19 @@
|
|||||||
import Hero from '@/components/Hero'
|
import StuffSearchCondition from '@/components/management/StuffSearchCondition'
|
||||||
import Stuff from '@/components/management/Stuff'
|
import Stuff from '@/components/management/Stuff'
|
||||||
import { initCheck } from '@/util/session-util'
|
import { initCheck } from '@/util/session-util'
|
||||||
|
import Hero from '@/components/Hero'
|
||||||
export default async function ManagementStuffPage() {
|
export default async function ManagementStuffPage() {
|
||||||
await initCheck()
|
await initCheck()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero title="물건관리" />
|
<Hero title="물건현황" />
|
||||||
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
|
<div>
|
||||||
|
<div className="m2">
|
||||||
|
<StuffSearchCondition />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-center my-8 pt-20">
|
||||||
<Stuff />
|
<Stuff />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -12,10 +12,37 @@ export const Mode = {
|
|||||||
CELL_POWERCON: 'cellPowercon', //파워콘
|
CELL_POWERCON: 'cellPowercon', //파워콘
|
||||||
DRAW_HELP_LINE: 'drawHelpLine', // 보조선 그리기 모드 지붕 존재해야함
|
DRAW_HELP_LINE: 'drawHelpLine', // 보조선 그리기 모드 지붕 존재해야함
|
||||||
ADSORPTION_POINT: 'adsorptionPoint', //흡착점 모드
|
ADSORPTION_POINT: 'adsorptionPoint', //흡착점 모드
|
||||||
|
OPENING: 'opening', //개구 모드
|
||||||
|
SHADOW: 'shadow', //그림자 생성 모드
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LineType = {
|
export const LineType = {
|
||||||
EAVES: 'eaves', // 처마
|
EAVES: 'eaves', // 처마
|
||||||
RIDGE: 'ridge', // 용마루....
|
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',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export default function Headers() {
|
|||||||
<div className="space-x-4 text-xl">
|
<div className="space-x-4 text-xl">
|
||||||
<Link href="/intro">Intro</Link>
|
<Link href="/intro">Intro</Link>
|
||||||
<Link href="/playground">Playground</Link>
|
<Link href="/playground">Playground</Link>
|
||||||
|
<Link href="/initSettingsModal">Basic Settings</Link>
|
||||||
<Link href="/settings">Canvas Settings</Link>
|
<Link href="/settings">Canvas Settings</Link>
|
||||||
<Link href="/roof">Roof</Link>
|
<Link href="/roof">Roof</Link>
|
||||||
<Link href="/roof2">Roof2</Link>
|
<Link href="/roof2">Roof2</Link>
|
||||||
|
|||||||
@ -1,22 +1,32 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState, memo, useCallback } from 'react'
|
import { useEffect, useState, memo, useCallback } from 'react'
|
||||||
import { Button, Checkbox, CheckboxGroup, RadioGroup, Radio, Input, Select, SelectItem } from '@nextui-org/react'
|
import { Button, Checkbox, CheckboxGroup, RadioGroup, Radio, Input, Select, SelectItem } from '@nextui-org/react'
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
import { modalContent, modalState } from '@/store/modalAtom'
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
import { canvasSettingState } from '@/store/canvasAtom'
|
import { canvasSettingState } from '@/store/canvasAtom'
|
||||||
import { useAxios } from '@/hooks/useAxios'
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
|
import { get, post } from '@/lib/Axios'
|
||||||
|
|
||||||
export default function InitSettingsModal(props) {
|
export default function InitSettingsModal(props) {
|
||||||
|
const [objectNo, setObjectNo] = useState('test123240909002') // 후에 삭제 필요
|
||||||
|
const [lastRoofSeq, setLastRoofSeq] = useState(0) // 마지막 roofSeq를 추적
|
||||||
const [open, setOpen] = useRecoilState(modalState)
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
|
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
|
||||||
const [roofMaterials, setRoofMaterials] = useState([])
|
const [roofMaterials, setRoofMaterials] = useState([])
|
||||||
const [basicSetting, setBasicSettings] = useState({
|
const [basicSetting, setBasicSettings] = useState({
|
||||||
type: '1',
|
roofDrawingSet: '1',
|
||||||
inputType: '1',
|
roofSizeSet: '1',
|
||||||
angleType: 'slope',
|
roofAngleSet: 'slope',
|
||||||
roofs: [],
|
roofs: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const { get, post } = useAxios()
|
const modelProps = {
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
//const { get, post } = useAxios()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
get({ url: '/api/roof-material/roof-material-infos' }).then((res) => {
|
get({ url: '/api/roof-material/roof-material-infos' }).then((res) => {
|
||||||
@ -26,6 +36,49 @@ export default function InitSettingsModal(props) {
|
|||||||
setRoofMaterials(res)
|
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)) {
|
if (!(Object.keys(canvasSetting).length === 0 && canvasSetting.constructor === Object)) {
|
||||||
setBasicSettings({ ...canvasSetting })
|
setBasicSettings({ ...canvasSetting })
|
||||||
}
|
}
|
||||||
@ -39,42 +92,99 @@ export default function InitSettingsModal(props) {
|
|||||||
|
|
||||||
//배열 추가 함수
|
//배열 추가 함수
|
||||||
const addRoofSetting = () => {
|
const addRoofSetting = () => {
|
||||||
if (basicSetting.roofs.length === 4) {
|
if (basicSetting.roofs != null && basicSetting.roofs.length === 4) {
|
||||||
alert('지붕재는 최대 4종까지 선택할 수 있습니다.')
|
alert('지붕재는 최대 4종까지 선택할 수 있습니다.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//roofs가 null인 경우 배열 생성
|
||||||
|
if (basicSetting.roofs == null) {
|
||||||
|
basicSetting.roofs = []
|
||||||
|
}
|
||||||
|
|
||||||
//기본값
|
//기본값
|
||||||
const newRoofSettings = {
|
const newRoofSettings = {
|
||||||
id: basicSetting.roofs.length + 1,
|
//roofSeq: basicSetting.roofs.length + 1,
|
||||||
roofId: '3',
|
roofSeq: lastRoofSeq, // 마지막 roofSeq를 1 증가
|
||||||
width: '200',
|
roofType: '3',
|
||||||
height: '200',
|
roofWidth: '200',
|
||||||
gap: '0',
|
roofHeight: '200',
|
||||||
layout: 'parallel',
|
roofGap: '0',
|
||||||
|
roofLayout: 'parallel',
|
||||||
}
|
}
|
||||||
|
|
||||||
setBasicSettings((prevState) => ({
|
setBasicSettings((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
roofs: [...prevState.roofs, newRoofSettings],
|
roofs: [...prevState.roofs, newRoofSettings],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
setLastRoofSeq(newRoofSettings.roofSeq + 1) // roofSeq 값을 업데이트
|
||||||
}
|
}
|
||||||
|
|
||||||
//배열 값 변경 함수
|
//배열 값 변경 함수
|
||||||
const handleRoofSettings = (id, event) => {
|
const handleRoofSettings = (id, event) => {
|
||||||
const roof = basicSetting.roofs.map((roof, i) => (id === roof.id ? { ...roof, [event.target.name]: event.target.value } : roof))
|
console.log(id)
|
||||||
setBasicSettings((prevState) => ({
|
|
||||||
...prevState,
|
// 기본 세팅에서 roofs 배열을 복사
|
||||||
roofs: [...roof],
|
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) => {
|
const submitCanvasConfig = async () => {
|
||||||
if (!res) {
|
if (!objectNo) {
|
||||||
setCanvasSetting({ ...basicSetting })
|
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 (
|
return (
|
||||||
@ -84,7 +194,13 @@ export default function InitSettingsModal(props) {
|
|||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<RadioGroup label="도면 작성방법" name="type" orientation="horizontal" value={basicSetting.type} onChange={handleBasicSetting}>
|
<RadioGroup
|
||||||
|
label="도면 작성방법"
|
||||||
|
name="roofDrawingSet"
|
||||||
|
orientation="horizontal"
|
||||||
|
value={basicSetting.roofDrawingSet}
|
||||||
|
onChange={handleBasicSetting}
|
||||||
|
>
|
||||||
<Radio value="1">치수 입력에 의한 물건작성</Radio>
|
<Radio value="1">치수 입력에 의한 물건작성</Radio>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
@ -92,7 +208,13 @@ export default function InitSettingsModal(props) {
|
|||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<RadioGroup label="치수 입력방법" name="inputType" orientation="horizontal" value={basicSetting.inputType} onChange={handleBasicSetting}>
|
<RadioGroup
|
||||||
|
label="치수 입력방법"
|
||||||
|
name="roofSizeSet"
|
||||||
|
orientation="horizontal"
|
||||||
|
value={basicSetting.roofSizeSet}
|
||||||
|
onChange={handleBasicSetting}
|
||||||
|
>
|
||||||
<Radio value="1">복사도 입력</Radio>
|
<Radio value="1">복사도 입력</Radio>
|
||||||
<Radio value="2">실측값 입력</Radio>
|
<Radio value="2">실측값 입력</Radio>
|
||||||
<Radio value="3">육지붕</Radio>
|
<Radio value="3">육지붕</Radio>
|
||||||
@ -102,13 +224,20 @@ export default function InitSettingsModal(props) {
|
|||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<RadioGroup label="지붕각도 설정" name="angleType" orientation="horizontal" value={basicSetting.angleType} onChange={handleBasicSetting}>
|
<RadioGroup
|
||||||
|
label="지붕각도 설정"
|
||||||
|
name="roofAngleSet"
|
||||||
|
orientation="horizontal"
|
||||||
|
value={basicSetting.roofAngleSet}
|
||||||
|
onChange={handleBasicSetting}
|
||||||
|
>
|
||||||
<Radio value="slope">경사</Radio>
|
<Radio value="slope">경사</Radio>
|
||||||
<Radio value="angle">각도</Radio>
|
<Radio value="angle">각도</Radio>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex space-x-4">지붕재 추가(단위 : mm)</div>
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<button className="px-3 py-1 bg-blue-500 text-white rounded mr-3" onClick={addRoofSetting}>
|
<button className="px-3 py-1 bg-blue-500 text-white rounded mr-3" onClick={addRoofSetting}>
|
||||||
Add
|
Add
|
||||||
@ -116,9 +245,82 @@ export default function InitSettingsModal(props) {
|
|||||||
<span className="text-sm text-gray-500">※ 지붕재는 최대 4종까지 선택할 수 있습니다.</span>
|
<span className="text-sm text-gray-500">※ 지붕재는 최대 4종까지 선택할 수 있습니다.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Roofs Array Rendering */}
|
||||||
{basicSetting.roofs &&
|
{basicSetting.roofs &&
|
||||||
basicSetting.roofs.map((roof, index) => {
|
basicSetting.roofs.map((roof, index) => {
|
||||||
return <RoofSelectBox roofMaterials={roofMaterials} roof={roof} key={index} onChange={handleRoofSettings} />
|
return (
|
||||||
|
<div key={index} className="mb-4 flex flex-wrap items-center space-x-4" style={{ border: '1px solid black' }}>
|
||||||
|
<span> 타입 : </span>
|
||||||
|
<Select
|
||||||
|
aria-label="roofMaterial"
|
||||||
|
className={'w-52'}
|
||||||
|
name="roofType"
|
||||||
|
onChange={(e) => handleRoofSettings(roof.roofSeq, e)}
|
||||||
|
items={roofMaterials}
|
||||||
|
defaultSelectedKeys={roof.roofType ? roof.roofType : ''}
|
||||||
|
selectedKeys={roof.roofType}
|
||||||
|
value={roof.roofType}
|
||||||
|
>
|
||||||
|
{(roofMaterial) => (
|
||||||
|
<SelectItem key={roofMaterial.id} value={roofMaterial.id}>
|
||||||
|
{roofMaterial.name}
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
<span> 너비 : </span>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
name="roofWidth"
|
||||||
|
placeholder="너비"
|
||||||
|
value={roof.roofWidth}
|
||||||
|
className="w-24"
|
||||||
|
onChange={(e) => handleRoofSettings(roof.roofSeq, e)}
|
||||||
|
/>
|
||||||
|
mm
|
||||||
|
<span> 높이 : </span>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
name="roofHeight"
|
||||||
|
placeholder="높이"
|
||||||
|
value={roof.roofHeight}
|
||||||
|
className="w-24"
|
||||||
|
onChange={(e) => handleRoofSettings(roof.roofSeq, e)}
|
||||||
|
/>
|
||||||
|
mm
|
||||||
|
<span> 서까래 간격 : </span>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
name="roofGap"
|
||||||
|
placeholder="간격"
|
||||||
|
value={roof.roofGap}
|
||||||
|
className="w-24"
|
||||||
|
onChange={(e) => handleRoofSettings(roof.roofSeq, e)}
|
||||||
|
/>
|
||||||
|
mm
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<RadioGroup
|
||||||
|
orientation="horizontal"
|
||||||
|
name="roofLayout"
|
||||||
|
value={roof.roofLayout}
|
||||||
|
defaultValue="parallel"
|
||||||
|
onChange={(e) => handleRoofSettings(roof.roofSeq, e)}
|
||||||
|
>
|
||||||
|
<Radio value="parallel">병렬식</Radio>
|
||||||
|
<Radio value="cascade">계단식</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
onRemove(roof.roofSeq)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div className="flex gap-4 items-right">
|
<div className="flex gap-4 items-right">
|
||||||
@ -128,62 +330,9 @@ export default function InitSettingsModal(props) {
|
|||||||
<Button size="sm" onClick={() => setOpen(!open)}>
|
<Button size="sm" onClick={() => setOpen(!open)}>
|
||||||
취소
|
취소
|
||||||
</Button>
|
</Button>
|
||||||
|
<input type="text" placeholder="Object No 입력" value={objectNo} onChange={(e) => setObjectNo(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoofSelectBox = (props) => {
|
|
||||||
return (
|
|
||||||
<div className="mb-4 flex flex-wrap items-center space-x-4" style={{ border: '1px solid black' }}>
|
|
||||||
<Select
|
|
||||||
aria-label="roofMaterial"
|
|
||||||
className={'w-52'}
|
|
||||||
name="roofId"
|
|
||||||
onChange={(e) => props.onChange(props.roof.id, e)}
|
|
||||||
items={props.roofMaterials}
|
|
||||||
defaultSelectedKeys={props.roof.roofId ? props.roof.roofId : ''}
|
|
||||||
selectedKeys={props.roof.roofId}
|
|
||||||
value={props.roof.roofId}
|
|
||||||
>
|
|
||||||
{(roofMaterial) => (
|
|
||||||
<SelectItem key={roofMaterial.id} value={roofMaterial.id}>
|
|
||||||
{roofMaterial.name}
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
name="width"
|
|
||||||
placeholder="너비"
|
|
||||||
value={props.roof.width}
|
|
||||||
className="w-24"
|
|
||||||
onChange={(e) => props.onChange(props.roof.id, e)}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
name="height"
|
|
||||||
placeholder="높이"
|
|
||||||
value={props.roof.height}
|
|
||||||
className="w-24"
|
|
||||||
onChange={(e) => props.onChange(props.roof.id, e)}
|
|
||||||
/>
|
|
||||||
mm
|
|
||||||
<Input type="text" name="gap" placeholder="간격" value={props.roof.gap} className="w-24" onChange={(e) => props.onChange(props.roof.id, e)} />
|
|
||||||
mm
|
|
||||||
<div className="flex space-x-4">
|
|
||||||
<RadioGroup
|
|
||||||
orientation="horizontal"
|
|
||||||
name="layout"
|
|
||||||
value={props.roof.layout}
|
|
||||||
defaultValue="parallel"
|
|
||||||
onChange={(e) => props.onChange(props.roof.id, e)}
|
|
||||||
>
|
|
||||||
<Radio value="parallel">병렬식</Radio>
|
|
||||||
<Radio value="cascade">계단식</Radio>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import GridSettingsModal from './GridSettingsModal'
|
|||||||
import { SurfaceShapeModal } from '@/components/ui/SurfaceShape'
|
import { SurfaceShapeModal } from '@/components/ui/SurfaceShape'
|
||||||
import { drawDirectionStringToArrow } from '@/util/qpolygon-utils'
|
import { drawDirectionStringToArrow } from '@/util/qpolygon-utils'
|
||||||
import ThumbnailList from '@/components/ui/ThumbnailLIst'
|
import ThumbnailList from '@/components/ui/ThumbnailLIst'
|
||||||
|
import ObjectPlacement from '@/components/ui/ObjectPlacement'
|
||||||
|
|
||||||
export default function Roof2(props) {
|
export default function Roof2(props) {
|
||||||
const { name, userId, email, isLoggedIn } = props
|
const { name, userId, email, isLoggedIn } = props
|
||||||
@ -764,6 +765,15 @@ export default function Roof2(props) {
|
|||||||
>
|
>
|
||||||
면형상
|
면형상
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="m-1 p-2"
|
||||||
|
onClick={() => {
|
||||||
|
setContent(<ObjectPlacement canvas={canvas} />)
|
||||||
|
setOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
오브젝트 배치
|
||||||
|
</Button>
|
||||||
{/*<Button className="m-1 p-2" onClick={rotateShape}>
|
{/*<Button className="m-1 p-2" onClick={rotateShape}>
|
||||||
회전
|
회전
|
||||||
</Button>*/}
|
</Button>*/}
|
||||||
|
|||||||
@ -6,12 +6,57 @@ import { Button } from '@nextui-org/react'
|
|||||||
import { get, post } from '@/lib/Axios'
|
import { get, post } from '@/lib/Axios'
|
||||||
import { useRecoilState } from 'recoil'
|
import { useRecoilState } from 'recoil'
|
||||||
import { customSettingsState } from '@/store/canvasAtom'
|
import { customSettingsState } from '@/store/canvasAtom'
|
||||||
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
|
|
||||||
|
import ColorPicker from './common/color-picker/ColorPicker'
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const [objectNo, setObjectNo] = useState('test123240829010')
|
const [objectNo, setObjectNo] = useState('test123240829010')
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [customSettings, setCustomSettings] = useRecoilState(customSettingsState)
|
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 = (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<h1>React ColorPicker</h1>
|
||||||
|
<ColorPicker color={color} setColor={setColor} />
|
||||||
|
<div className="p-4">{color}</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={handleSavePopup}>저장</button> <p />
|
||||||
|
<button onClick={handleClosePopup}>취소</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
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({
|
const [settings, setSettings] = useState({
|
||||||
display1: Array(11).fill(false), // 화면 표시1
|
display1: Array(11).fill(false), // 화면 표시1
|
||||||
@ -48,6 +93,21 @@ export default function Settings() {
|
|||||||
|
|
||||||
// 클릭 시 상태 변경 함수
|
// 클릭 시 상태 변경 함수
|
||||||
const handleToggle = (type, index) => {
|
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) => {
|
setSettings((prevSettings) => {
|
||||||
// prevSettings[type]이 배열인지 확인하고, 그렇지 않은 경우 빈 배열로 초기화
|
// prevSettings[type]이 배열인지 확인하고, 그렇지 않은 경우 빈 배열로 초기화
|
||||||
let updated = Array.isArray(prevSettings[type]) ? [...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 조회 및 초기화
|
// Canvas Setting 조회 및 초기화
|
||||||
const handleSelect = async () => {
|
const handleSelect = async () => {
|
||||||
try {
|
try {
|
||||||
@ -221,6 +299,10 @@ export default function Settings() {
|
|||||||
<div className="grid-item">흡착점 ON</div>
|
<div className="grid-item">흡착점 ON</div>
|
||||||
</div>
|
</div>
|
||||||
<h1>[그리드 설정]</h1>
|
<h1>[그리드 설정]</h1>
|
||||||
|
<div>
|
||||||
|
<ColorPicker color={color} setColor={setColor} />
|
||||||
|
<div className="p-4">{color}</div>
|
||||||
|
</div>
|
||||||
<div className="grid-container2">
|
<div className="grid-container2">
|
||||||
{gridItems.gridSettings.map((item, index) => (
|
{gridItems.gridSettings.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export default function RangeDatePicker(props) {
|
|||||||
setDateRange(update)
|
setDateRange(update)
|
||||||
}}
|
}}
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
|
// showMonthYearPicker={true}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,6 +127,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
|
|
||||||
this.on('modified', (e) => {
|
this.on('modified', (e) => {
|
||||||
this.addLengthText()
|
this.addLengthText()
|
||||||
|
if (this.arrow) {
|
||||||
|
drawDirectionArrow(this)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.on('selected', () => {
|
this.on('selected', () => {
|
||||||
@ -144,6 +147,17 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.canvas.remove(text)
|
this.canvas.remove(text)
|
||||||
})
|
})
|
||||||
this.texts = null
|
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 })
|
// polygon.fillCell({ width: 50, height: 30, padding: 10 })
|
||||||
|
|||||||
@ -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() {
|
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 (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
// isDisabled={params.data.successful ? false : true}
|
||||||
|
color="default"
|
||||||
|
onPress={() => {
|
||||||
|
copyNo(params.value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
복사
|
||||||
|
</Button>
|
||||||
|
<span>{params.value}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Management Stuff</h1>
|
<div className="text-2xl">
|
||||||
|
물건목록
|
||||||
|
<span>
|
||||||
|
전체 : {gridCount} // 선택 : {selectedRowDataCount}
|
||||||
|
</span>
|
||||||
|
<div align="right">
|
||||||
|
{/* <Button
|
||||||
|
color="primary"
|
||||||
|
onPress={() => {
|
||||||
|
fnDeleteRowData(selectedRowData)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
물건삭제
|
||||||
|
</Button> */}
|
||||||
|
{/* <Button
|
||||||
|
color="primary"
|
||||||
|
onPress={() => {
|
||||||
|
addRowItems()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
행추가
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onPress={() => {
|
||||||
|
removeRowItems()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
행삭제
|
||||||
|
</Button> */}
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '100%', height: '100%' }}>
|
||||||
|
<StuffQGrid {...gridProps} getSelectedRowdata={getSelectedRowdata} getCellDoubleClicked={getCellDoubleClicked} gridRef={gridRef} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
388
src/components/management/StuffDetail.jsx
Normal file
388
src/components/management/StuffDetail.jsx
Normal file
@ -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' && <div>신규:::::::::::</div>) || <div>상세:::::::::::</div>}
|
||||||
|
<div className="flex w-full flex-wrap md:flex-nowrap gap-4">
|
||||||
|
<div>
|
||||||
|
<span>물건번호</span>
|
||||||
|
<span>{objectNo}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>사양확정일</span>
|
||||||
|
<span>{detailData?.specDate ? dayjs(detailData.specDate).format('YYYY.MM.DD') : null}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>갱신일시</span>
|
||||||
|
<span>
|
||||||
|
{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}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>등록일</span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>(*필수 입력항목)</div>
|
||||||
|
<div>
|
||||||
|
<span>담당자*</span>
|
||||||
|
<input type="text" placeholder="Kim Ji Young" defaultValue={detailData?.receiveUser} onChange={(e) => setReceiveUser(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>물건구분/물건명 *</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_gubun"
|
||||||
|
id="radio_new"
|
||||||
|
value={'NEW'}
|
||||||
|
checked={gubun === 'NEW' ? true : false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGubun(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_new">신축</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_gubun"
|
||||||
|
id="radio_old"
|
||||||
|
value={'OLD'}
|
||||||
|
checked={gubun === 'OLD' ? true : false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGubun(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_old">기축</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" placeholder="물건명" value={name2} onChange={(e) => setName2(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full max-w-xs flex-col gap-2">
|
||||||
|
<Select label="경칭선택" className="max-w-xs" onChange={(e) => setSel(e.target.value)}>
|
||||||
|
<SelectItem key="1">111</SelectItem>
|
||||||
|
<SelectItem key="2">222</SelectItem>
|
||||||
|
<SelectItem key="3">333</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>물건명 후리가나</span>
|
||||||
|
<input type="text" placeholder="물건명 후리가나" value={name3} onChange={(e) => setName3(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>판매점명 /ID *</span>
|
||||||
|
<div className="flex w-full max-w-xs flex-col gap-2">
|
||||||
|
<Autocomplete
|
||||||
|
className="max-w-xs"
|
||||||
|
defaultItems={testSelOption}
|
||||||
|
label="판매점ID자동완성으로 바꾸기"
|
||||||
|
selectedKey={autoSelectValue}
|
||||||
|
onSelectionChange={setAutoSelectValue}
|
||||||
|
>
|
||||||
|
{(option) => <AutocompleteItem key={option.id}>{option.name}</AutocompleteItem>}
|
||||||
|
</Autocomplete>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>우편번호*</span>
|
||||||
|
<input type="text" placeholder="숫자7자리 입력여부 체크" value={zipCode} maxLength="7" onChange={textTypeHandler} />
|
||||||
|
<Button onClick={onSearchPostNumber} isDisabled={buttonValid}>
|
||||||
|
주소검색
|
||||||
|
</Button>
|
||||||
|
*우편번호 7자리를 입력한 후, 주소검색 버튼을 클릭해 주십시오
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>도도부현 / 주소*</span>
|
||||||
|
{/* <input type="text" placeholder="주소검색 결과 select로 변경하기 공통코드 api리턴값 selected" disabled /> */}
|
||||||
|
<input type="text" placeholder="주소검색 결과 주소 셋팅 칸" value={address2 + address3} onChange={changeAddress2} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>발전량시뮬레이션지역*</span>
|
||||||
|
<Select label="발전량시뮬레이션지역" className="max-w-xs" onChange={(e) => setSel2(e.target.value)}>
|
||||||
|
<SelectItem key="1">111</SelectItem>
|
||||||
|
<SelectItem key="2">222</SelectItem>
|
||||||
|
<SelectItem key="3">333</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>기준풍속*</span>
|
||||||
|
<Select label="기준풍속" className="max-w-xs" onChange={(e) => setSel3(e.target.value)}>
|
||||||
|
<SelectItem key="1">111</SelectItem>
|
||||||
|
<SelectItem key="2">222</SelectItem>
|
||||||
|
<SelectItem key="3">333</SelectItem>
|
||||||
|
</Select>
|
||||||
|
m/s이하
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>수직적설량*</span>
|
||||||
|
<input type="text" placeholder="수직적설량" value={name5} maxLength="3" onChange={textTypeHandler2} /> cm
|
||||||
|
<Checkbox isSelected={isSelected} onValueChange={setIsSelected}>
|
||||||
|
한랭지대책시행
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>면조도구분*</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_gubun2"
|
||||||
|
id="radio_1"
|
||||||
|
value={'1'}
|
||||||
|
checked={gubun2 === '1' ? true : false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGubun2(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_1">III·IV</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_gubun2"
|
||||||
|
id="radio_2"
|
||||||
|
value={'2'}
|
||||||
|
checked={gubun2 === '2' ? true : false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGubun2(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_2">II</label>
|
||||||
|
<Checkbox isSelected={isSelected2} onValueChange={setIsSelected2}>
|
||||||
|
염해지역용아이템사용
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>설치높이*</span>
|
||||||
|
<Select label="설치높이" className="max-w-xs" onChange={(e) => setSel4(e.target.value)}>
|
||||||
|
<SelectItem key="1">111</SelectItem>
|
||||||
|
<SelectItem key="2">222</SelectItem>
|
||||||
|
<SelectItem key="3">333</SelectItem>
|
||||||
|
</Select>
|
||||||
|
m
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>계약조건</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_gubun3"
|
||||||
|
id="radio_a"
|
||||||
|
value={'A'}
|
||||||
|
checked={gubun3 === 'A' ? true : false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGubun3(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_a">잉여</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_gubun3"
|
||||||
|
id="radio_b"
|
||||||
|
value={'B'}
|
||||||
|
checked={gubun3 === 'B' ? true : false}
|
||||||
|
onChange={(e) => {
|
||||||
|
setGubun3(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_2">전량</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>메모</span>
|
||||||
|
<Textarea
|
||||||
|
value={memo}
|
||||||
|
onValueChange={setMemo}
|
||||||
|
disableAutosize
|
||||||
|
classNames={{
|
||||||
|
base: 'max-w-xs',
|
||||||
|
input: 'resize-y min-h-[40px]',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!isFormValid ? (
|
||||||
|
<>
|
||||||
|
<Button onClick={onTempSave}>임시저장</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button onClick={onSave}>진짜저장</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Link href="/management/stuff">
|
||||||
|
<button type="button">물건목록</button>
|
||||||
|
</Link>
|
||||||
|
{/* <Button onPress={moveList}>물건목록2</Button> */}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
116
src/components/management/StuffQGrid.jsx
Normal file
116
src/components/management/StuffQGrid.jsx
Normal file
@ -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 (
|
||||||
|
<div className="ag-theme-quartz" style={{ height: 500 }}>
|
||||||
|
<AgGridReact
|
||||||
|
ref={gridRef}
|
||||||
|
onGridReady={onGridReady}
|
||||||
|
rowBuffer={rowBuffer}
|
||||||
|
rowData={rowData}
|
||||||
|
columnDefs={colDefs}
|
||||||
|
defaultColDef={defaultColDef}
|
||||||
|
isRowSelectable={isRowSelectable}
|
||||||
|
rowSelection={'multiple'}
|
||||||
|
suppressRowClickSelection={true}
|
||||||
|
onSelectionChanged={onSelectionChanged}
|
||||||
|
onCellDoubleClicked={onCellDoubleClicked}
|
||||||
|
pagination={isPageable}
|
||||||
|
//paginationPageSize={paginationPageSize}
|
||||||
|
//paginationPageSizeSelector={paginationPageSizeSelector}
|
||||||
|
autoSizeStrategy={autoSizeStrategy}
|
||||||
|
overlayNoRowsTemplate={'<span className="ag-overlay-loading-center">물건 목록이 없습니다.</span>'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
264
src/components/management/StuffSearchCondition.jsx
Normal file
264
src/components/management/StuffSearchCondition.jsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<div align="right">
|
||||||
|
<Link href="/management/stuff/detail">
|
||||||
|
<button type="button">물건신규등록</button>
|
||||||
|
</Link>
|
||||||
|
<Button size="sm" onClick={onSubmit}>
|
||||||
|
조회
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" onClick={resetRecoil}>
|
||||||
|
초기화
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex flex-col gap-4">
|
||||||
|
{Array(4)
|
||||||
|
.fill()
|
||||||
|
.map((_, i) => {
|
||||||
|
if (i === 0) {
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="물건번호 입력"
|
||||||
|
value={stuffSearch?.code === 'E' ? stuffSearch.schObjectNo : objectNo}
|
||||||
|
onChange={(e) => {
|
||||||
|
setObjectNo(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schObjectNo: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="판매대리점ID 입력"
|
||||||
|
value={stuffSearch?.schSaleStoreId ? stuffSearch.schSaleStoreId : saleStoreId}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSaleStoreId(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreId: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="물건주소 입력"
|
||||||
|
value={stuffSearch?.address ? stuffSearch.address : address}
|
||||||
|
onChange={(e) => {
|
||||||
|
setAddress(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', address: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else if (i === 1) {
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="물건명 입력"
|
||||||
|
value={stuffSearch?.schObjectName ? stuffSearch.schObjectName : objectName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setobjectName(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schObjectName: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="물건명 입력"
|
||||||
|
value={stuffSearch?.schSaleStoreName ? stuffSearch.schSaleStoreName : saleStoreName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSaleStoreName(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schSaleStoreName: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_sayang"
|
||||||
|
id="radio_all"
|
||||||
|
checked={stuffSearch?.schSpecDateYn === '' ? true : false}
|
||||||
|
value={''}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSpecDateYn(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_all">ALL</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_sayang"
|
||||||
|
id="radio_y"
|
||||||
|
checked={stuffSearch?.schSpecDateYn === 'Y' ? true : false}
|
||||||
|
value={'Y'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSpecDateYn(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_y">사양 확인</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio"
|
||||||
|
id="radio_n"
|
||||||
|
checked={stuffSearch?.schSpecDateYn === 'N' ? true : false}
|
||||||
|
value={'N'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSpecDateYn(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schSpecDateYn: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_n">사양 미확인</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else if (i === 2) {
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="담당자 입력"
|
||||||
|
value={stuffSearch?.schReceiveUser ? stuffSearch.schReceiveUser : receiveUser}
|
||||||
|
onChange={(e) => {
|
||||||
|
setReceiveUser(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schReceiveUser: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="견적처 입력"
|
||||||
|
value={stuffSearch?.schDispCompanyName ? stuffSearch.schDispCompanyName : dispCompanyName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDispCompanyName(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schDispCompanyName: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex w-full flex-wrap md:flex-nowrap mb-6 md:mb-0 gap-4">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_ptype"
|
||||||
|
id="radio_u"
|
||||||
|
checked={stuffSearch?.schDateType === 'U' ? true : false}
|
||||||
|
value={'U'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDateType(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_u">갱신일</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio_ptype"
|
||||||
|
id="radio_r"
|
||||||
|
checked={stuffSearch?.schDateType === 'R' ? true : false}
|
||||||
|
value={'R'}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDateType(e.target.value)
|
||||||
|
setStuffSearch({ ...stuffSearch, code: 'S', schDateType: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="radio_r">등록일</label>
|
||||||
|
</div>
|
||||||
|
<RangeDatePicker {...rangeDatePickerProps} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
146
src/components/ui/ObjectPlacement.jsx
Normal file
146
src/components/ui/ObjectPlacement.jsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { Button, Input } from '@nextui-org/react'
|
||||||
|
import { useRecoilState, useSetRecoilState } from 'recoil'
|
||||||
|
import { modalState } from '@/store/modalAtom'
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
import { modeState, objectPlacementModeState } from '@/store/canvasAtom'
|
||||||
|
import { BATCH_TYPE, INPUT_TYPE } from '@/common/common'
|
||||||
|
|
||||||
|
const ObjectPlacement = ({ canvas }) => {
|
||||||
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
|
const [mode, setMode] = useRecoilState(modeState)
|
||||||
|
const [objectPlacementMode, setObjectPlacementModeState] = useRecoilState(objectPlacementModeState)
|
||||||
|
const [width, setWidth] = useState(0)
|
||||||
|
const [height, setHeight] = useState(0)
|
||||||
|
const [areaBoundary, setAreaBoundary] = useState(true)
|
||||||
|
|
||||||
|
// opening or shadow 개구 / 그림자
|
||||||
|
const [batchType, setBatchType] = useState(BATCH_TYPE.OPENING)
|
||||||
|
|
||||||
|
// free or dimension 프리 / 치수
|
||||||
|
const [inputType, setInputType] = useState(INPUT_TYPE.FREE)
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
setMode(batchType)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 w-full max-w-xs border border-gray-300">
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-gray-700 text-sm font-bold mb-2">오브젝트 배치</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-b mb-4">
|
||||||
|
<h2 className="font-semibold">개구 · 그림자 배치</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex">
|
||||||
|
<Button
|
||||||
|
className={`w-1/2 py-2 ${objectPlacementMode.batchType === BATCH_TYPE.OPENING ? 'bg-blue-500 text-white rounded-l' : 'bg-gray-200 text-gray-700 rounded-r'}`}
|
||||||
|
onClick={() => {
|
||||||
|
setBatchType(BATCH_TYPE.OPENING)
|
||||||
|
setObjectPlacementModeState({ ...objectPlacementMode, batchType: BATCH_TYPE.OPENING })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
개구 배치
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={`w-1/2 py-2 ${objectPlacementMode.batchType === BATCH_TYPE.SHADOW ? 'bg-blue-500 text-white rounded-l' : 'bg-gray-200 text-gray-700 rounded-r'}`}
|
||||||
|
onClick={() => {
|
||||||
|
setBatchType(BATCH_TYPE.SHADOW)
|
||||||
|
setObjectPlacementModeState({ ...objectPlacementMode, batchType: BATCH_TYPE.SHADOW })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
그림자 배치
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="mb-2 text-gray-700 font-semibold">설정</div>
|
||||||
|
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="inline-flex items-center">
|
||||||
|
<Input
|
||||||
|
type="radio"
|
||||||
|
name="inputType"
|
||||||
|
checked={objectPlacementMode.inputType === INPUT_TYPE.FREE}
|
||||||
|
onClick={() => {
|
||||||
|
setObjectPlacementModeState({ ...objectPlacementMode, inputType: INPUT_TYPE.FREE })
|
||||||
|
}}
|
||||||
|
className="form-radio text-blue-500"
|
||||||
|
/>
|
||||||
|
<span className="ml-2">프리입력</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-2">
|
||||||
|
<label className="inline-flex items-center">
|
||||||
|
<Input
|
||||||
|
type="radio"
|
||||||
|
name="inputType"
|
||||||
|
checked={objectPlacementMode.inputType === INPUT_TYPE.DIMENSION}
|
||||||
|
onClick={() => {
|
||||||
|
setObjectPlacementModeState({ ...objectPlacementMode, inputType: INPUT_TYPE.DIMENSION })
|
||||||
|
}}
|
||||||
|
className="form-radio text-blue-500"
|
||||||
|
/>
|
||||||
|
<span className="ml-2">치수입력</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex mb-2">
|
||||||
|
<div className="mr-2">
|
||||||
|
<label className="block text-gray-700 text-sm mb-1">가로길이</label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
placeholder="mm"
|
||||||
|
value={objectPlacementMode.width}
|
||||||
|
onChange={(e) => {
|
||||||
|
setObjectPlacementModeState({ ...objectPlacementMode, width: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-700 text-sm mb-1">세로길이</label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
placeholder="mm"
|
||||||
|
value={objectPlacementMode.height}
|
||||||
|
onChange={(e) => {
|
||||||
|
setObjectPlacementModeState({ ...objectPlacementMode, height: e.target.value })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="inline-flex items-center">
|
||||||
|
<Input
|
||||||
|
type="checkbox"
|
||||||
|
name={`areaBoundary`}
|
||||||
|
checked={objectPlacementMode.areaBoundary}
|
||||||
|
onClick={() => setObjectPlacementModeState({ ...objectPlacementMode, areaBoundary: !objectPlacementMode.areaBoundary })}
|
||||||
|
className="form-checkbox text-blue-500"
|
||||||
|
/>
|
||||||
|
<span className="ml-2">영역교차</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Button onClick={handleSave} className="bg-gray-500 text-white py-2 px-4 rounded">
|
||||||
|
저장
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObjectPlacement
|
||||||
@ -13,7 +13,7 @@ import { getIntersectionPoint } from '@/util/canvas-util'
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export const SurfaceShapeModal = ({ canvas }) => {
|
export const SurfaceShapeModal = ({ canvas }) => {
|
||||||
const [type, setType] = useState(0)
|
const [type, setType] = useState(1)
|
||||||
const setOpen = useSetRecoilState(modalState)
|
const setOpen = useSetRecoilState(modalState)
|
||||||
const fontSize = useRecoilValue(fontSizeState)
|
const fontSize = useRecoilValue(fontSizeState)
|
||||||
//지붕재
|
//지붕재
|
||||||
|
|||||||
@ -138,12 +138,24 @@ export function useCanvas(id) {
|
|||||||
if (canvas) {
|
if (canvas) {
|
||||||
if (canvas?._objects.length > 0) {
|
if (canvas?._objects.length > 0) {
|
||||||
const poppedObject = canvas?._objects.pop()
|
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) => {
|
setHistory((prev) => {
|
||||||
if (prev === undefined) {
|
if (prev === undefined) {
|
||||||
return poppedObject ? [poppedObject] : []
|
return poppedObject ? [group] : []
|
||||||
}
|
}
|
||||||
return poppedObject ? [...prev, poppedObject] : prev
|
return poppedObject ? [...prev, group] : prev
|
||||||
})
|
})
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
}
|
}
|
||||||
@ -154,7 +166,13 @@ export function useCanvas(id) {
|
|||||||
if (canvas && history) {
|
if (canvas && history) {
|
||||||
if (history.length > 0) {
|
if (history.length > 0) {
|
||||||
setIsLocked(true)
|
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)
|
const newHistory = history.slice(0, -1)
|
||||||
setHistory(newHistory)
|
setHistory(newHistory)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,14 +27,15 @@ import {
|
|||||||
guideLineState,
|
guideLineState,
|
||||||
horiGuideLinesState,
|
horiGuideLinesState,
|
||||||
vertGuideLinesState,
|
vertGuideLinesState,
|
||||||
|
objectPlacementModeState,
|
||||||
} from '@/store/canvasAtom'
|
} from '@/store/canvasAtom'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
import offsetPolygon from '@/util/qpolygon-utils'
|
import offsetPolygon, { inPolygon } from '@/util/qpolygon-utils'
|
||||||
import { isObjectNotEmpty } from '@/util/common-utils'
|
import { isObjectNotEmpty } from '@/util/common-utils'
|
||||||
import * as turf from '@turf/turf'
|
import * as turf from '@turf/turf'
|
||||||
import { Mode } from '@/common/common'
|
import { INPUT_TYPE, Mode } from '@/common/common'
|
||||||
|
|
||||||
export function useMode() {
|
export function useMode() {
|
||||||
const [mode, setMode] = useRecoilState(modeState)
|
const [mode, setMode] = useRecoilState(modeState)
|
||||||
@ -74,6 +75,8 @@ export function useMode() {
|
|||||||
const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState)
|
const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState)
|
||||||
const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState)
|
const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState)
|
||||||
|
|
||||||
|
const [objectPlacementMode, setObjectPlacementModeState] = useRecoilState(objectPlacementModeState)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if (!canvas) {
|
// if (!canvas) {
|
||||||
// canvas?.setZoom(0.8)
|
// canvas?.setZoom(0.8)
|
||||||
@ -104,11 +107,7 @@ export function useMode() {
|
|||||||
}, [endPoint])
|
}, [endPoint])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
canvas?.off('mouse:out', removeMouseLines)
|
|
||||||
canvas?.on('mouse:out', removeMouseLines)
|
|
||||||
changeMode(canvas, mode)
|
changeMode(canvas, mode)
|
||||||
canvas?.off('mouse:move')
|
|
||||||
canvas?.on('mouse:move', drawMouseLines)
|
|
||||||
}, [mode, horiGuideLines, vertGuideLines])
|
}, [mode, horiGuideLines, vertGuideLines])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -423,6 +422,17 @@ export function useMode() {
|
|||||||
break
|
break
|
||||||
case 'adsorptionPoint':
|
case 'adsorptionPoint':
|
||||||
canvas?.on('mouse:down', mouseEvent.adsorptionPoint)
|
canvas?.on('mouse:down', mouseEvent.adsorptionPoint)
|
||||||
|
break
|
||||||
|
case 'shadow':
|
||||||
|
canvas?.on('mouse:down', mouseEvent.shadowMode.down)
|
||||||
|
canvas?.on('mouse:move', mouseEvent.shadowMode.move)
|
||||||
|
canvas?.on('mouse:up', mouseEvent.shadowMode.up)
|
||||||
|
break
|
||||||
|
case 'opening':
|
||||||
|
canvas?.on('mouse:down', mouseEvent.openingMode.down)
|
||||||
|
canvas?.on('mouse:move', mouseEvent.openingMode.move)
|
||||||
|
canvas?.on('mouse:up', mouseEvent.openingMode.up)
|
||||||
|
|
||||||
break
|
break
|
||||||
case 'default':
|
case 'default':
|
||||||
canvas?.off('mouse:down')
|
canvas?.off('mouse:down')
|
||||||
@ -587,10 +597,9 @@ export function useMode() {
|
|||||||
|
|
||||||
const mouseAndkeyboardEventClear = () => {
|
const mouseAndkeyboardEventClear = () => {
|
||||||
canvas?.off('mouse:down')
|
canvas?.off('mouse:down')
|
||||||
Object.keys(mouseEvent).forEach((key) => {
|
canvas?.off('mouse:move')
|
||||||
canvas?.off('mouse:down', mouseEvent[key])
|
canvas?.off('mouse:up')
|
||||||
document.removeEventListener('contextmenu', mouseEvent[key])
|
canvas?.off('mouse:out')
|
||||||
})
|
|
||||||
|
|
||||||
Object.keys(keyboardEvent).forEach((key) => {
|
Object.keys(keyboardEvent).forEach((key) => {
|
||||||
document.removeEventListener('keydown', keyboardEvent[key])
|
document.removeEventListener('keydown', keyboardEvent[key])
|
||||||
@ -674,6 +683,7 @@ export function useMode() {
|
|||||||
|
|
||||||
const changeMode = (canvas, mode) => {
|
const changeMode = (canvas, mode) => {
|
||||||
mouseAndkeyboardEventClear()
|
mouseAndkeyboardEventClear()
|
||||||
|
addCommonMouseEvent()
|
||||||
setMode(mode)
|
setMode(mode)
|
||||||
setCanvas(canvas)
|
setCanvas(canvas)
|
||||||
|
|
||||||
@ -713,6 +723,12 @@ export function useMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 모든 모드에서 사용되는 공통 이벤트 추가
|
||||||
|
const addCommonMouseEvent = () => {
|
||||||
|
canvas?.on('mouse:move', drawMouseLines)
|
||||||
|
canvas?.on('mouse:out', removeMouseLines)
|
||||||
|
}
|
||||||
|
|
||||||
const changeKeyboardEvent = (mode) => {
|
const changeKeyboardEvent = (mode) => {
|
||||||
if (mode === Mode.EDIT) {
|
if (mode === Mode.EDIT) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
@ -992,6 +1008,214 @@ export function useMode() {
|
|||||||
canvas.add(circle)
|
canvas.add(circle)
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
},
|
},
|
||||||
|
//면 형상 배치 모드
|
||||||
|
surfaceShapeMode: (o) => {},
|
||||||
|
// 그림자 모드
|
||||||
|
shadowMode: {
|
||||||
|
rect: null,
|
||||||
|
isDown: false,
|
||||||
|
origX: 0,
|
||||||
|
origY: 0,
|
||||||
|
down: (o) => {
|
||||||
|
if (mode !== Mode.SHADOW) return
|
||||||
|
mouseEvent.shadowMode.isDown = true
|
||||||
|
const pointer = canvas.getPointer(o.e)
|
||||||
|
mouseEvent.shadowMode.origX = pointer.x
|
||||||
|
mouseEvent.shadowMode.origY = pointer.y
|
||||||
|
mouseEvent.shadowMode.rect = new fabric.Rect({
|
||||||
|
fill: 'grey',
|
||||||
|
left: mouseEvent.shadowMode.origX,
|
||||||
|
top: mouseEvent.shadowMode.origY,
|
||||||
|
originX: 'left',
|
||||||
|
originY: 'top',
|
||||||
|
opacity: 0.3,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
angle: 0,
|
||||||
|
transparentCorners: false,
|
||||||
|
})
|
||||||
|
canvas.add(mouseEvent.shadowMode.rect)
|
||||||
|
},
|
||||||
|
move: (e) => {
|
||||||
|
if (!mouseEvent.shadowMode.isDown) return
|
||||||
|
const pointer = canvas.getPointer(e.e)
|
||||||
|
if (mouseEvent.shadowMode.origX > pointer.x) {
|
||||||
|
mouseEvent.shadowMode.rect.set({ left: Math.abs(pointer.x) })
|
||||||
|
}
|
||||||
|
if (mouseEvent.shadowMode.origY > pointer.y) {
|
||||||
|
mouseEvent.shadowMode.rect.set({ top: Math.abs(pointer.y) })
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEvent.shadowMode.rect.set({ width: Math.abs(mouseEvent.shadowMode.origX - pointer.x) })
|
||||||
|
mouseEvent.shadowMode.rect.set({ height: Math.abs(mouseEvent.shadowMode.origY - pointer.y) })
|
||||||
|
},
|
||||||
|
up: (o) => {
|
||||||
|
mouseEvent.shadowMode.isDown = false
|
||||||
|
setMode(Mode.DEFAULT)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openingMode: {
|
||||||
|
rect: null,
|
||||||
|
isDown: false,
|
||||||
|
origX: 0,
|
||||||
|
origY: 0,
|
||||||
|
down: (o) => {
|
||||||
|
if (mode !== Mode.OPENING) return
|
||||||
|
const roofs = canvas?._objects.filter((obj) => obj.name === 'roof')
|
||||||
|
if (roofs.length === 0) {
|
||||||
|
alert('지붕을 먼저 그려주세요')
|
||||||
|
setMode(Mode.DEFAULT)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pointer = canvas.getPointer(o.e)
|
||||||
|
let selectRoof = null
|
||||||
|
roofs.forEach((roof) => {
|
||||||
|
if (roof.inPolygon({ x: pointer.x, y: pointer.y })) {
|
||||||
|
selectRoof = roof
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!selectRoof) {
|
||||||
|
alert('지붕 내부에만 생성 가능합니다.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mouseEvent.openingMode.origX = pointer.x
|
||||||
|
mouseEvent.openingMode.origY = pointer.y
|
||||||
|
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
|
||||||
|
const pointer = canvas.getPointer(e.e)
|
||||||
|
if (mouseEvent.openingMode.origX > pointer.x) {
|
||||||
|
mouseEvent.openingMode.rect.set({ left: Math.abs(pointer.x) })
|
||||||
|
}
|
||||||
|
if (mouseEvent.openingMode.origY > pointer.y) {
|
||||||
|
mouseEvent.openingMode.rect.set({ top: Math.abs(pointer.y) })
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEvent.openingMode.rect.set({ width: Math.abs(mouseEvent.openingMode.origX - pointer.x) })
|
||||||
|
mouseEvent.openingMode.rect.set({ height: Math.abs(mouseEvent.openingMode.origY - pointer.y) })
|
||||||
|
},
|
||||||
|
up: (o) => {
|
||||||
|
mouseEvent.openingMode.isDown = false
|
||||||
|
|
||||||
|
const { areaBoundary } = objectPlacementMode
|
||||||
|
|
||||||
|
//roof의 내부에 있는지 확인
|
||||||
|
if (!checkInsideRoof(mouseEvent.openingMode.rect)) {
|
||||||
|
setMode(Mode.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 영역 교차인지 확인
|
||||||
|
if (!areaBoundary) {
|
||||||
|
const isCross = checkCrossAreaBoundary(mouseEvent.openingMode.rect)
|
||||||
|
if (isCross) {
|
||||||
|
alert('영역이 교차되었습니다.')
|
||||||
|
canvas.remove(mouseEvent.openingMode.rect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mouseEvent.openingMode.rect.set({ name: 'opening' })
|
||||||
|
setMode(Mode.DEFAULT)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkCrossAreaBoundary = (rect) => {
|
||||||
|
const openings = canvas?._objects.filter((obj) => obj.name === 'opening')
|
||||||
|
if (openings.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const rectPoints = [
|
||||||
|
{ x: rect.left, y: rect.top },
|
||||||
|
{ x: rect.left, y: rect.top + rect.height },
|
||||||
|
{ x: rect.left + rect.width, y: rect.top + rect.height },
|
||||||
|
{ 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 },
|
||||||
|
{ x: rect2.left, y: rect2.top + rect2.height },
|
||||||
|
{ 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 isCross
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkInsideRoof = (rect) => {
|
||||||
|
let result = true
|
||||||
|
const roofs = canvas?._objects.filter((obj) => obj.name === 'roof')
|
||||||
|
if (roofs.length === 0) {
|
||||||
|
alert('지붕을 먼저 그려주세요')
|
||||||
|
canvas?.remove(rect)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInterSectPointByMouseLine = () => {
|
const getInterSectPointByMouseLine = () => {
|
||||||
|
|||||||
@ -4,4 +4,87 @@ export default {
|
|||||||
hello: 'こんにちは',
|
hello: 'こんにちは',
|
||||||
welcome: 'こんにちは {name}!',
|
welcome: 'こんにちは {name}!',
|
||||||
locale: '現在のロケールは {locale} です。',
|
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: '등록일',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,90 @@
|
|||||||
{
|
{
|
||||||
"hi": "こんにちは"
|
"hi": "こんにちは",
|
||||||
|
"common.message.no.data": "No data",
|
||||||
|
"common.message.no.dataDown": "ダウンロードするデータがありません",
|
||||||
|
"common.message.noData": "表示するデータがありません",
|
||||||
|
"common.message.search": "search success",
|
||||||
|
"common.message.insert": "insert success",
|
||||||
|
"common.message.update": "update success",
|
||||||
|
"common.message.delete": "削除",
|
||||||
|
"common.message.restoration": "復元",
|
||||||
|
"common.message.cancel": "キャンセル",
|
||||||
|
"common.message.send": "メールを送信しました.",
|
||||||
|
"common.message.no.delete": "削除するデータがありません",
|
||||||
|
"common.message.save": "保存",
|
||||||
|
"common.message.transfer": "転送",
|
||||||
|
"common.message.batch.exec": "batch success",
|
||||||
|
"common.message.not.mov": "移動できません.",
|
||||||
|
"common.message.required.data": "{0} は入力必須項目となります。",
|
||||||
|
"common.message.save.error": "データの保存中にエラーが発生しました。 サイト管理者にお問い合わせください。",
|
||||||
|
"common.message.transfer.error": "データの転送中にエラーが発生しました。 サイト管理者にお問い合わせください。",
|
||||||
|
"common.message.delete.error": "データの削除中にエラーが発生しました。 サイト管理者にお問い合わせください。",
|
||||||
|
"common.message.batch.error": "バッチの実行中にエラーが発生しました。 サイト管理者に連絡してください。",
|
||||||
|
"common.message.send.error": "データの送信中にエラーが発生しました。サイト管理者にお問い合わせください",
|
||||||
|
"common.message.communication.error": "ネットワークエラーが発生しました。サイト管理者に連絡してください。",
|
||||||
|
"common.message.data.error": "{0} はデータ形式が無効です。",
|
||||||
|
"common.message.data.setting.error": "{0} は削除されたか、すでに構成されているデータです。",
|
||||||
|
"common.message.parameter.error": "パラメータエラー",
|
||||||
|
"common.message.product.parameter.error": "存在しない製品があります。",
|
||||||
|
"common.message.customer.parameter.error": "存在しない顧客があります。",
|
||||||
|
"common.message.file.exists.error": "ファイルが正常にアップロードされないためにエラーが発生しました",
|
||||||
|
"common.message.file.download.exists": "ファイルが存在しません。",
|
||||||
|
"common.message.file.download.error": "ァイルのダウンロードエラー",
|
||||||
|
"common.message.file.template.validation01": "フォルダをアップロードできません",
|
||||||
|
"common.message.file.template.validation02": "アップロードできるのはExcelファイルのみです。",
|
||||||
|
"common.message.file.template.validation03": "登録できない拡張子です",
|
||||||
|
"common.message.file.template.validation04": "容量を超えています アップロード可能な容量:{0} MB",
|
||||||
|
"common.message.file.template.validation05": "アップロードファイルを選択して下さい",
|
||||||
|
"common.message.multi.insert": "合計 {0} 件数 ({1}成功、 {2} 失敗 {3})",
|
||||||
|
"common.message.error": "エラーが発生しました。サイト管理者に連絡してください。",
|
||||||
|
"common.message.data.save": "保存しますか?",
|
||||||
|
"common.message.data.delete": " 削除しますか?",
|
||||||
|
"common.message.data.exists": "{0} はすでに存在するデータです。",
|
||||||
|
"common.message.data.no.exists": "{0} は存在しないデータです。",
|
||||||
|
"common.message.all": "All",
|
||||||
|
"common.message.tab.close.all": "すべてのタブを閉じますか?",
|
||||||
|
"common.message.transfer.save": "{0}件転送しますか?",
|
||||||
|
"common.message.confirm.save": "保存しますか?",
|
||||||
|
"common.message.confirm.confirm": "承認しますか?",
|
||||||
|
"common.message.confirm.request": "承認リクエストしますか?",
|
||||||
|
"common.message.confirm.delete": "削除しますか?",
|
||||||
|
"common.message.confirm.close": "閉じますか?",
|
||||||
|
"common.message.confirm.unclose": "クローズ中止しますか?",
|
||||||
|
"common.message.confirm.cancel": "キャンセルしますか?",
|
||||||
|
"common.message.confirm.uncancel": "キャンセル中止しますか?",
|
||||||
|
"common.message.confirm.copy": "コピーしますか?",
|
||||||
|
"common.message.confirm.createSo": "S/O作成しますか?",
|
||||||
|
"common.message.confirm.mark": "保存完了",
|
||||||
|
"common.message.confirm.mail": "メールを送信しますか?",
|
||||||
|
"common.message.confirm.printPriceItem": "価格を印刷しますか?",
|
||||||
|
"common.message.confirm.allAppr ": "Do you want to Batch approve the selected data?",
|
||||||
|
"common.message.confirm.deliveryFee": "送料を登録しますか?",
|
||||||
|
"common.message.success.delete": "削除完了",
|
||||||
|
"common.message.success.close": "閉じる",
|
||||||
|
"common.message.success.unclose": "キャンセルしました",
|
||||||
|
"common.message.validation.date": "終了日を開始日より前にすることはできません。 もう一度入力してください。",
|
||||||
|
"common.message.no.editfield": "フィールドを編集できません",
|
||||||
|
"common.message.success.rmmail": "リスク管理チームにメールを送信しました。",
|
||||||
|
"common.message.password.validation01": "パスワードの変更が一致しません。",
|
||||||
|
"common.message.password.validation02": "英語、数字、特殊文字を組み合わせた8桁以上を入力してください。",
|
||||||
|
"common.message.password.validation03": "パスワードをIDと同じにすることはできません。",
|
||||||
|
"common.message.menu.validation01": "注文を保存するメニューはありません.",
|
||||||
|
"common.message.menu.validation02": "The same sort order exists.",
|
||||||
|
"common.message.menuCode.check01": "登録可能",
|
||||||
|
"common.message.menuCode.check02": "登録できません",
|
||||||
|
"common.message.pleaseSelect": "{0}を選択してください",
|
||||||
|
"common.message.pleaseInput": "{0}を入力してください。",
|
||||||
|
"common.message.pleaseInputOr": "{0}または{1}を入力してください。",
|
||||||
|
"common.message.approved ": "承認済み",
|
||||||
|
"common.message.errorFieldExist": "エラー項目が存在します",
|
||||||
|
"common.message.storeIdExist ": "既に利用されている販売店IDです",
|
||||||
|
"common.message.userIdExist ": "すでに使用しているユーザーID。",
|
||||||
|
"common.message.noExists ": "削除された掲示物です",
|
||||||
|
"common.message.emailReqTo": "メール宛先が必要です",
|
||||||
|
"common.message.downloadPeriod": "ダウンロード検索期間を{0}日以内に選択してください。",
|
||||||
|
"common.message.backToSubmit": "販売店ブロック解除実行しますか?",
|
||||||
|
"common.message.backToG3": "Back to G3処理実行しますか?",
|
||||||
|
"common.message.writeToConfirm": "作成解除を実行しますか?",
|
||||||
|
"common.message.password.init.success": "パスワード [{0}] に初期化されました。",
|
||||||
|
"common.message.no.edit.save": "この文書は変更できません。"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,4 +4,88 @@ export default {
|
|||||||
hello: '안녕',
|
hello: '안녕',
|
||||||
welcome: '안녕 {name}!',
|
welcome: '안녕 {name}!',
|
||||||
locale: '현재 로케일은 {locale}입니다.',
|
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: '등록일',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,90 @@
|
|||||||
{
|
{
|
||||||
"hi": "안녕하세요"
|
"hi": "안녕하세요",
|
||||||
|
"common.message.no.data": "No data",
|
||||||
|
"common.message.no.dataDown": "No data to download",
|
||||||
|
"common.message.noData": "No data to display",
|
||||||
|
"common.message.search": "search success",
|
||||||
|
"common.message.insert": "insert success",
|
||||||
|
"common.message.update": "update success",
|
||||||
|
"common.message.delete": "Deleted",
|
||||||
|
"common.message.restoration": "Restored",
|
||||||
|
"common.message.cancel": "Canceled",
|
||||||
|
"common.message.send": "The mail has been sent.",
|
||||||
|
"common.message.no.delete": "There is no data to delete.",
|
||||||
|
"common.message.save": "Saved.",
|
||||||
|
"common.message.transfer": "Transfered",
|
||||||
|
"common.message.batch.exec": "batch success",
|
||||||
|
"common.message.not.mov": "Its impossible to move.",
|
||||||
|
"common.message.required.data": "{0} is required input value.",
|
||||||
|
"common.message.save.error": "An error occurred while saving the data. Please contact site administrator.",
|
||||||
|
"common.message.transfer.error": "An error occurred while transfer the data. Please contact site administrator.",
|
||||||
|
"common.message.delete.error": "An error occurred while deleting data. Please contact site administrator.",
|
||||||
|
"common.message.batch.error": "An error occurred while executing the batch. Please contact site administrator.",
|
||||||
|
"common.message.send.error": "Error sending data, please contact your administrator.",
|
||||||
|
"common.message.communication.error": "Network error occurred. \n Please contact site administrator.",
|
||||||
|
"common.message.data.error": "{0} The data format is not valid.",
|
||||||
|
"common.message.data.setting.error": "{0} is data that has been deleted or already configured.",
|
||||||
|
"common.message.parameter.error": "Parameter Error",
|
||||||
|
"common.message.product.parameter.error": "존재하지 않는 제품이 있습니다.",
|
||||||
|
"common.message.customer.parameter.error": "존재하지 않는 고객이 있습니다.",
|
||||||
|
"common.message.file.exists.error": "Error due to file not uploading normally",
|
||||||
|
"common.message.file.download.exists": "File does not exist.",
|
||||||
|
"common.message.file.download.error": "File download error",
|
||||||
|
"common.message.file.template.validation01": "Unable to upload folder",
|
||||||
|
"common.message.file.template.validation02": "Only Excel files can be uploaded.",
|
||||||
|
"common.message.file.template.validation03": "Non-registerable extension",
|
||||||
|
"common.message.file.template.validation04": "Exceed capacity \n Uploadable capacity : {0} MB",
|
||||||
|
"common.message.file.template.validation05": "업로드 파일을 선택해주세요.",
|
||||||
|
"common.message.multi.insert": "Total {0} cases ({1} successes, {2} failures {3})",
|
||||||
|
"common.message.error": "Error occurred, please contact site administrator.",
|
||||||
|
"common.message.data.save": "Do you want to save it?",
|
||||||
|
"common.message.data.delete": "Do you want to delete it?",
|
||||||
|
"common.message.data.exists": "{0} is data that already exists.",
|
||||||
|
"common.message.data.no.exists": "{0} is data that does not exist.",
|
||||||
|
"common.message.all": "All",
|
||||||
|
"common.message.tab.close.all": "Close all tabs?",
|
||||||
|
"common.message.transfer.save": "Want to {0} transfer it?",
|
||||||
|
"common.message.confirm.save": "Want to save it?",
|
||||||
|
"common.message.confirm.confirm": "Want to approve?",
|
||||||
|
"common.message.confirm.request": "Would you like to request a Approval?",
|
||||||
|
"common.message.confirm.delete": "Do you want to delete it?",
|
||||||
|
"common.message.confirm.close": "Want to close?",
|
||||||
|
"common.message.confirm.unclose": "Do you want to cancel the close?",
|
||||||
|
"common.message.confirm.cancel": "Want to cancellation?",
|
||||||
|
"common.message.confirm.uncancel": "Do you want to cancel the cancellation?",
|
||||||
|
"common.message.confirm.copy": "Do you want to copy?",
|
||||||
|
"common.message.confirm.createSo": "Create Sales Order?",
|
||||||
|
"common.message.confirm.mark": "Saved.",
|
||||||
|
"common.message.confirm.mail": "Do you want to send mail?",
|
||||||
|
"common.message.confirm.printPriceItem": "Would you like to print item price?",
|
||||||
|
"common.message.confirm.allAppr ": "Do you want to Batch approve the selected data?",
|
||||||
|
"common.message.confirm.deliveryFee": "Do you want to register shipping fee?",
|
||||||
|
"common.message.success.delete": "Deleted.",
|
||||||
|
"common.message.success.close": "Closed.",
|
||||||
|
"common.message.success.unclose": "Cancel Closed.",
|
||||||
|
"common.message.validation.date": "The end date cannot be earlier than the start date. Please enter it again.",
|
||||||
|
"common.message.no.editfield": "Can not edit field",
|
||||||
|
"common.message.success.rmmail": "You have successfully sent mail to the Risk Management team.",
|
||||||
|
"common.message.password.validation01": "Change passwords do not match.",
|
||||||
|
"common.message.password.validation02": "Please enter at least 8 digits combining English, numbers, and special characters.",
|
||||||
|
"common.message.password.validation03": "Password cannot be the same as ID.",
|
||||||
|
"common.message.menu.validation01": "There is no menu to save the order.",
|
||||||
|
"common.message.menu.validation02": "The same sort order exists.",
|
||||||
|
"common.message.menuCode.check01": "Registerable",
|
||||||
|
"common.message.menuCode.check02": "Unable to register",
|
||||||
|
"common.message.pleaseSelect": "Please Select {0}",
|
||||||
|
"common.message.pleaseInput": "Please Input a {0}.",
|
||||||
|
"common.message.pleaseInputOr": "Please Input a {0} or {1}.",
|
||||||
|
"common.message.approved ": "Approved.",
|
||||||
|
"common.message.errorFieldExist": "Error Field Exist",
|
||||||
|
"common.message.storeIdExist ": "이미 사용하고 있는 판매점 ID 입니다.",
|
||||||
|
"common.message.userIdExist ": "이미 사용하고 있는 사용자 ID 입니다.",
|
||||||
|
"common.message.noExists ": "삭제된 게시물 입니다.",
|
||||||
|
"common.message.emailReqTo": "Email To is required",
|
||||||
|
"common.message.downloadPeriod": "Please select the download search period within {0} days.",
|
||||||
|
"common.message.backToSubmit": "판매점 블록 해제를 실행하시겠습니까?",
|
||||||
|
"common.message.backToG3": "Back to G3 처리를 실행하시겠습니까?",
|
||||||
|
"common.message.writeToConfirm": "작성 해제를 실행하시겠습니까?",
|
||||||
|
"common.message.password.init.success": "비밀번호 [{0}]로 초기화 되었습니다.",
|
||||||
|
"common.message.no.edit.save": "This document cannot be changed."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,3 +161,15 @@ export const globalCompassState = atom({
|
|||||||
default: 0,
|
default: 0,
|
||||||
dangerouslyAllowMutability: true,
|
dangerouslyAllowMutability: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 면형상 배치 모드
|
||||||
|
export const surfacePlacementModeState = atom({
|
||||||
|
key: 'surfacePlacementMode',
|
||||||
|
default: { width: 0, height: 0, areaBoundary: true, inputType: 'free' },
|
||||||
|
})
|
||||||
|
|
||||||
|
// 오브젝트 배치 모드
|
||||||
|
export const objectPlacementModeState = atom({
|
||||||
|
key: 'objectPlacementMode',
|
||||||
|
default: { width: 0, height: 0, areaBoundary: false, inputType: 'free', batchType: 'opening' },
|
||||||
|
})
|
||||||
|
|||||||
22
src/store/stuffAtom.js
Normal file
22
src/store/stuffAtom.js
Normal file
@ -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,
|
||||||
|
})
|
||||||
@ -41,7 +41,7 @@
|
|||||||
.grid-item {
|
.grid-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 1px solid black; /* 그리드 외각선 */
|
border: 1px solid black; /* 그리드 외각선 */
|
||||||
text-align: center; /* 그리드 내 가운데 정렬 */
|
text-align: center; /* 그리드 내 가운데 정렬 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,3 +79,9 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
.ag-header-cell-label {
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2714,6 +2714,12 @@ export const drawDirectionArrow = (polygon) => {
|
|||||||
if (!direction) {
|
if (!direction) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
polygon.canvas
|
||||||
|
.getObjects()
|
||||||
|
.filter((obj) => obj.name === 'directionText' && obj.parent === polygon.arrow)
|
||||||
|
.forEach((obj) => polygon.canvas.remove(obj))
|
||||||
|
|
||||||
let arrow = null
|
let arrow = null
|
||||||
let points = []
|
let points = []
|
||||||
|
|
||||||
@ -2721,13 +2727,13 @@ export const drawDirectionArrow = (polygon) => {
|
|||||||
polygon.canvas.remove(polygon.arrow)
|
polygon.canvas.remove(polygon.arrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
let centerPoint = polygon.getCenterPoint()
|
let centerPoint = { x: polygon.width / 2 + polygon.left, y: polygon.height / 2 + polygon.top }
|
||||||
let stickeyPoint
|
let stickeyPoint
|
||||||
|
|
||||||
const polygonMaxX = Math.max(...polygon.points.map((point) => point.x))
|
const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x))
|
||||||
const polygonMinX = Math.min(...polygon.points.map((point) => point.x))
|
const polygonMinX = Math.min(...polygon.getCurrentPoints().map((point) => point.x))
|
||||||
const polygonMaxY = Math.max(...polygon.points.map((point) => point.y))
|
const polygonMaxY = Math.max(...polygon.getCurrentPoints().map((point) => point.y))
|
||||||
const polygonMinY = Math.min(...polygon.points.map((point) => point.y))
|
const polygonMinY = Math.min(...polygon.getCurrentPoints().map((point) => point.y))
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case 'north':
|
case 'north':
|
||||||
@ -2800,6 +2806,7 @@ export const drawDirectionArrow = (polygon) => {
|
|||||||
polygon.arrow = arrow
|
polygon.arrow = arrow
|
||||||
polygon.canvas.add(arrow)
|
polygon.canvas.add(arrow)
|
||||||
polygon.canvas.renderAll()
|
polygon.canvas.renderAll()
|
||||||
|
drawDirectionStringToArrow(polygon.canvas, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2807,7 +2814,7 @@ export const drawDirectionArrow = (polygon) => {
|
|||||||
* @param canvas
|
* @param canvas
|
||||||
* @param compass
|
* @param compass
|
||||||
*/
|
*/
|
||||||
export const drawDirectionStringToArrow = (canvas, compass, fontSize) => {
|
export const drawDirectionStringToArrow = (canvas, compass = 0) => {
|
||||||
const arrows = canvas?.getObjects().filter((obj) => obj.name === 'arrow')
|
const arrows = canvas?.getObjects().filter((obj) => obj.name === 'arrow')
|
||||||
|
|
||||||
if (arrows.length === 0) {
|
if (arrows.length === 0) {
|
||||||
@ -3000,8 +3007,10 @@ const addTextByArrows = (arrows, txt, canvas) => {
|
|||||||
originX: 'center',
|
originX: 'center',
|
||||||
originY: 'center',
|
originY: 'center',
|
||||||
name: 'directionText',
|
name: 'directionText',
|
||||||
|
selectable: false,
|
||||||
left: arrow.stickeyPoint.x,
|
left: arrow.stickeyPoint.x,
|
||||||
top: arrow.stickeyPoint.y,
|
top: arrow.stickeyPoint.y,
|
||||||
|
parent: arrow,
|
||||||
})
|
})
|
||||||
canvas.add(text)
|
canvas.add(text)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5515,6 +5515,11 @@ react-dom@^18:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.2"
|
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:
|
react-icons@^5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c"
|
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user