Merge branch 'dev' into feature/dev-yj

This commit is contained in:
yjnoh 2025-03-21 11:24:08 +09:00
commit 12936ec1f9
13 changed files with 276 additions and 87 deletions

View File

@ -2,7 +2,7 @@ module.exports = {
apps: [
{
name: 'qcast-front-production',
script: 'npm run start',
script: 'npm run start:dev',
instance: 2,
exec_mode: 'cluster',
},

View File

@ -2,26 +2,111 @@
import { useState } from 'react'
import { useMessage } from '@/hooks/useMessage'
import { setSession, login } from '@/lib/authActions'
import { sessionStore } from '@/store/commonAtom'
import { useRecoilState } from 'recoil'
import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { useRouter } from 'next/navigation'
import GlobalSpinner from '@/components/common/spinner/GlobalSpinner'
export default function AutoLoginPage() {
const [isLoading, setIsLoading] = useState(true)
export default function AutoLoginPage({ autoLoginParam }) {
const router = useRouter()
const [isLoading, setIsLoading] = useState(autoLoginParam === 'Y' ? false : true)
const [globalLocaleState, setGlbalLocaleState] = useRecoilState(globalLocaleStore)
const { promisePost } = useAxios(globalLocaleState)
const { getMessage } = useMessage()
const [userId, setUserId] = useState('')
const [sessionState, setSessionState] = useRecoilState(sessionStore)
const [idFocus, setIdFocus] = useState(false)
const loginProcess = async () => {
setIsLoading(true)
await promisePost({ url: '/api/login/v1.0/user', data: { loginId: userId } }).then((response) => {
setIsLoading(false)
if (response.data) {
const res = response.data
const result = { ...res, storeLvl: res.groupId === '60000' ? '1' : '2', pwdInitYn: 'Y' }
setSession(result)
setSessionState(result)
login()
} else {
alert(getMessage('login.fail'))
router.push('/login?autoLoginParam1=Y')
}
})
}
return (
<>
{isLoading && <GlobalSpinner />}
<div className="login-input-frame">
<div className="login-frame-tit ">
<span>{getMessage('site.name')}</span>
{getMessage('site.sub_name')}
</div>
<div className="login-input-wrap">
<div className="login-area id" style={{ fontWeight: 'bolder' }}>
{getMessage('login.auto.page.text')}
{autoLoginParam !== 'Y' ? (
<>
<div className="login-input-frame">
<div className="login-frame-tit ">
<span>{getMessage('site.name')}</span>
{getMessage('site.sub_name')}
</div>
<div className="login-input-wrap">
<div className="login-area id" style={{ fontWeight: 'bolder' }}>
{getMessage('login.auto.page.text')}
</div>
</div>
</div>
</div>
</div>
</>
) : (
<>
<div className="login-input-frame">
<form
onSubmit={(e) => {
e.preventDefault()
loginProcess()
}}
className="space-y-6"
>
<div className="login-frame-tit">
<span>{getMessage('site.name')}</span>
{getMessage('site.sub_name')}
</div>
<div className="login-input-wrap">
<div className={`login-area id ${idFocus ? 'focus' : ''}`}>
<input
type="text"
className="login-input"
id="userId"
name="id"
required
value={userId}
placeholder={getMessage('login.id.placeholder')}
onChange={(e) => {
setUserId(e.target.value)
}}
onFocus={() => setIdFocus(true)}
onBlur={() => setIdFocus(false)}
/>
<button
type="button"
className="id-delete"
onClick={(e) => {
setUserId('')
}}
></button>
</div>
<div className="login-btn-box">
<button type="submit" className="login-btn">
{getMessage('login')}
</button>
</div>
</div>
</form>
</div>
</>
)}
</>
)
}

View File

@ -25,7 +25,9 @@ export default function Login() {
useEffect(() => {
if (autoLoginParam) {
autoLoginProcess(autoLoginParam)
if (autoLoginParam !== 'Y') {
autoLoginProcess(autoLoginParam)
}
}
// console.log('🚀 ~ checkSession ~ checkSession():', checkSession())
@ -334,7 +336,7 @@ export default function Login() {
</div>
</>
)}
{autoLoginParam && <AutoLogin />}
{autoLoginParam && <AutoLogin autoLoginParam={autoLoginParam} />}
</div>
<div className="login-copyright">COPYRIGHT©2024 Hanwha Japan All Rights Reserved.</div>
</div>

View File

@ -377,8 +377,8 @@ export default function Estimate({}) {
useEffect(() => {
if (estimateContextState.estimateType !== '') {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
saleStoreId: estimateContextState.sapSaleStoreId,
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
docTpCd: estimateContextState?.estimateType,
}
@ -387,6 +387,8 @@ export default function Estimate({}) {
if (isNotEmptyArray(res?.data)) {
setStorePriceList(res.data)
}
setItemChangeYn(true)
})
if (estimateContextState.estimateType === 'YJSS') {
@ -416,8 +418,6 @@ export default function Estimate({}) {
handlePricing('UNIT_PRICE')
}
}
setItemChangeYn(true)
}
}, [estimateContextState?.estimateType])
@ -481,8 +481,8 @@ export default function Estimate({}) {
//Pricing
const handlePricing = async (showPriceCd) => {
const param = {
saleStoreId: session.storeId,
sapSalesStoreCd: session.custCd,
saleStoreId: estimateContextState.sapSaleStoreId,
sapSalesStoreCd: estimateContextState.sapSalesStoreCd,
docTpCd: estimateContextState.estimateType,
priceCd: showPriceCd,
itemIdList: estimateContextState.itemList.filter((item) => item.delFlg === '0' && item.paDispOrder === null),
@ -506,7 +506,6 @@ export default function Estimate({}) {
})
}
}
setIsGlobalLoading(true)
await promisePost({ url: '/api/estimate/price/item-price-list', data: param }).then((res) => {
let updateList = []

View File

@ -119,10 +119,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.addLengthText()
this.on('moving', () => {
this.initLines()
this.addLengthText()
})
this.on('modified', (e) => {
this.initLines()
this.addLengthText()
})
@ -183,8 +185,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
this.lines = []
this.points.forEach((point, i) => {
const nextPoint = this.points[(i + 1) % this.points.length]
this.getCurrentPoints().forEach((point, i) => {
const nextPoint = this.getCurrentPoints()[(i + 1) % this.points.length]
const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], {
stroke: this.stroke,
strokeWidth: this.strokeWidth,

View File

@ -634,7 +634,7 @@ export default function CanvasMenu(props) {
onClick={() => setEstimatePopupOpen(true)}
>
<span className="ico ico01"></span>
<span className="name">{getMessage('plan.menu.estimate.docDown')}</span>
<span className="name">{getMessage('plan.menu.estimate.docDownload')}</span>
</button>
<button type="button" style={{ display: saveButtonStyle }} className="btn-frame gray ico-flx" onClick={handleEstimateSubmit}>
<span className="ico ico02"></span>

View File

@ -7,6 +7,7 @@ import { useMessage } from '@/hooks/useMessage'
import { usePopup } from '@/hooks/usePopup'
import { canvasState } from '@/store/canvasAtom'
import { usePolygon } from '@/hooks/usePolygon'
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
const FLOW_DIRECTION_TYPE = {
EIGHT_AZIMUTH: 'eightAzimuth',
@ -19,6 +20,8 @@ export default function FlowDirectionSetting(props) {
const canvas = useRecoilValue(canvasState)
const { getMessage } = useMessage()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
useEffect(() => {
return () => {
canvas?.discardActiveObject()
@ -53,6 +56,7 @@ export default function FlowDirectionSetting(props) {
})
drawDirectionArrow(roof)
canvas?.renderAll()
changeSurfaceLineType(roof)
closePopup(id)
}

View File

@ -261,6 +261,15 @@ export function useRoofAllocationSetting(id) {
* 지붕재 삭제
*/
const onDeleteRoofMaterial = (idx) => {
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
for (let i = 0; i < roofs.length; i++) {
if (roofs[i].roofMaterial.index === idx) {
swalFire({ type: 'alert', icon: 'error', text: getMessage('roof.material.can.not.delete') })
return
}
}
const isSelected = currentRoofList[idx].selected
const newRoofList = JSON.parse(JSON.stringify(currentRoofList)).filter((_, index) => index !== idx)
if (isSelected) {
@ -300,6 +309,7 @@ export function useRoofAllocationSetting(id) {
})
setRoofList(newRoofList)
setRoofMaterials(newRoofList)
const selectedRoofMaterial = newRoofList.find((roof) => roof.selected)
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
drawDirectionArrow(currentObject)
@ -308,6 +318,21 @@ export function useRoofAllocationSetting(id) {
basicSettingSave()
}
/**
* 기존 세팅된 지붕에 지붕재 내용을 바뀐 내용으로 수정
* @param newRoofMaterials
*/
const setRoofMaterials = (newRoofMaterials) => {
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
newRoofMaterials.forEach((roofMaterial) => {
const index = roofMaterial.index
const tempRoofs = roofs.filter((roof) => roof.roofMaterial?.index === index)
tempRoofs.forEach((roof) => {
setSurfaceShapePattern(roof, roofDisplay.column, false, roofMaterial)
})
})
}
/**
* 지붕면 할당
*/
@ -411,6 +436,8 @@ export function useRoofAllocationSetting(id) {
drawDirectionArrow(roof)
})
setRoofMaterials(newRoofList)
/** 외곽선 삭제 */
const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine')
removeTargets.forEach((obj) => {

View File

@ -32,6 +32,7 @@ import {
import { usePolygon } from '@/hooks/usePolygon'
import { POLYGON_TYPE } from '@/common/common'
import { usePopup } from '@/hooks/usePopup'
import { useSurfaceShapeBatch } from './useSurfaceShapeBatch'
import { roofDisplaySelector } from '@/store/settingAtom'
import { useRoofFn } from '@/hooks/common/useRoofFn'
@ -50,6 +51,8 @@ export function usePlacementShapeDrawing(id) {
const { addPolygonByLines, drawDirectionArrow } = usePolygon()
const { tempGridMode } = useTempGrid()
const { setSurfaceShapePattern } = useRoofFn()
const { changeSurfaceLineType } = useSurfaceShapeBatch({})
const canvasSetting = useRecoilValue(canvasSettingState)
const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
@ -253,11 +256,14 @@ export function usePlacementShapeDrawing(id) {
setPoints([])
canvas?.renderAll()
if (+canvasSetting?.roofSizeSet === 3) {
closePopup(id)
return
}
addPopup(id, 1, <PlacementSurfaceLineProperty id={id} roof={roof} />, false)
// if (+canvasSetting?.roofSizeSet === 3) {
// closePopup(id)
// return
// }
// addPopup(id, 1, <PlacementSurfaceLineProperty id={id} roof={roof} />, false)
changeSurfaceLineType(roof)
closePopup(id)
}
if (points.length < 3) {

View File

@ -3,7 +3,7 @@
import { useEffect } from 'react'
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
import { canvasSettingState, canvasState, currentCanvasPlanState, globalPitchState } from '@/store/canvasAtom'
import { MENU, POLYGON_TYPE } from '@/common/common'
import { MENU, POLYGON_TYPE, LINE_TYPE } from '@/common/common'
import { getIntersectionPoint, toFixedWithoutRounding } from '@/util/canvas-util'
import { degreesToRadians } from '@turf/turf'
import { QPolygon } from '@/components/fabric/QPolygon'
@ -111,7 +111,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
lockScalingX: true, // X 축 크기 조정 잠금
lockScalingY: true, // Y 축 크기 조정 잠금
name: MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP,
flipX: xInversion !== yInversion,
// flipX: xInversion !== yInversion,
// angle: xInversion && yInversion ? Math.abs((rotate + 180) % 360) : Math.abs(rotate),
// angle: rotate,
originX: 'center',
@ -120,6 +120,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
}
obj = new QPolygon(points, options)
let imageRotate = 0
if (xInversion && !yInversion) {
if (rotate % 180 === 0 || rotate < 0) {
@ -148,7 +149,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} else {
imageRotate = (rotate + 360) % 360
}
obj.set({ angle: imageRotate })
obj.set({ angle: imageRotate, flipX: xInversion !== yInversion })
obj.setCoords() //좌표 변경 적용
canvas?.add(obj)
@ -201,13 +202,15 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
setSurfaceShapePattern(batchSurface, roofDisplay.column)
drawDirectionArrow(batchSurface)
// if (setIsHidden) setIsHidden(false)
// closePopup(id)
initEvent()
if (+canvasSetting?.roofSizeSet === 3) return
const popupId = uuidv4()
addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />)
// if (+canvasSetting?.roofSizeSet === 3) return
// const popupId = uuidv4()
// addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />)
changeSurfaceLineType(batchSurface)
if (setIsHidden) setIsHidden(false)
})
} else {
if (setIsHidden) setIsHidden(false)
@ -1066,45 +1069,101 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
canvas?.renderAll()
}
const updateFlippedPoints = (polygon) => {
if (!(polygon instanceof fabric.Polygon)) {
console.error('The object is not a Polygon.')
return
}
/**
* 면형상 작도시 라인 속성 넣는 로직
* 폴리곤으로 보면 직선방향에 따라 아래쪽인지 윗쪽인지 판단이 가능하다고 생각하여
* south -> 밑면은 무조건 right direction이라 가정하고 작업함 좌우반전시 반대로 그려지는 경우도 생기지만 그럴땐 흐름방향에 따라 최대값(최소값) 찾아
* 해당 하는 흐름에 맞게 변경함
* @param { } polygon
*/
const { flipX, flipY, width, height, points, left, top, scaleX, scaleY } = polygon
// 현재 points의 사본 가져오기
const newPoints = points.map((point) => {
let x = point.x
let y = point.y
// flipX 적용
if (flipX) {
x = width - x
}
// flipY 적용
if (flipY) {
y = height - y
}
// 스케일 및 전역 좌표 고려
x = (x - width / 2) * scaleX + width / 2
y = (y - height / 2) * scaleY + height / 2
return { x, y }
const changeSurfaceLineType = (polygon) => {
polygon.lines.forEach((line) => {
line.attributes.type = LINE_TYPE.WALLLINE.GABLE
})
// flipX, flipY를 초기화
polygon.flipX = false
polygon.flipY = false
const directionConfig = {
south: { evaesDirection: 'right', ridgeDirection: 'left', coord1: 'y1', coord2: 'y2' },
north: { evaesDirection: 'left', ridgeDirection: 'right', coord1: 'y1', coord2: 'y2' },
east: { evaesDirection: 'top', ridgeDirection: 'bottom', coord1: 'x1', coord2: 'x2' },
west: { evaesDirection: 'bottom', ridgeDirection: 'top', coord1: 'x1', coord2: 'x2' },
}
// points 업데이트
polygon.set({ points: newPoints })
polygon.setCoords()
const { evaesDirection, ridgeDirection, coord1, coord2 } = directionConfig[polygon.direction] || directionConfig.west
return polygon
polygon.lines.forEach((line) => {
if (line[coord1] === line[coord2]) {
if (line.direction === evaesDirection) {
line.attributes.type = LINE_TYPE.WALLLINE.EAVES
line.stroke = 'rgb(47, 0, 255)'
} else if (line.direction === ridgeDirection) {
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
line.stroke = 'rgb(44, 255, 2)'
}
}
})
/**
* 진짜 처마 라인인지 확인하는 로직 -> 특정 모양에 따라 처마가 없는 경우가 있는데 위에 로직으로는
* 용마루도 처마로 만들어서 재보정
*/
//직선 찾는 로직
const maxLine = polygon.lines.filter((line) => line[coord1] === line[coord2])
if (maxLine.length > 0) {
const maxLineSorted = maxLine.reduce((a, b) => {
return (polygon.direction === 'south' || polygon.direction === 'east' ? b : a)[coord1] >
(polygon.direction === 'south' || polygon.direction === 'east' ? a : b)[coord1]
? b
: a
})
if (
(polygon.direction === 'south' && maxLineSorted.direction === 'left') ||
(polygon.direction === 'north' && maxLineSorted.direction === 'right') ||
(polygon.direction === 'east' && maxLineSorted.direction === 'bottom') ||
(polygon.direction === 'west' && maxLineSorted.direction === 'top')
) {
polygon.lines.forEach((line) => {
if (line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
} else if (line.attributes.type === LINE_TYPE.SUBLINE.RIDGE) {
line.attributes.type = LINE_TYPE.WALLLINE.EAVES
}
})
}
if (maxLine.length === 1) {
const maxLineCoord = polygon.lines.reduce((a, b) => {
return (polygon.direction === 'south' || polygon.direction === 'east' ? b : a)[coord1] >
(polygon.direction === 'south' || polygon.direction === 'east' ? a : b)[coord1]
? b
: a
})
const isRealEavesLine = polygon.lines.find((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
if (isRealEavesLine) {
if (polygon.direction === 'south' || polygon.direction === 'north') {
const targetCoord =
polygon.direction === 'south' ? Math.max(maxLineCoord.y1, maxLineCoord.y2) : Math.min(maxLineCoord.y1, maxLineCoord.y2)
const realLineCoord =
polygon.direction === 'south' ? Math.max(isRealEavesLine.y1, isRealEavesLine.y2) : Math.min(isRealEavesLine.y1, isRealEavesLine.y2)
if (targetCoord !== realLineCoord) {
isRealEavesLine.attributes.type = LINE_TYPE.SUBLINE.RIDGE
}
} else if (polygon.direction === 'east' || polygon.direction === 'west') {
const targetCoord = polygon.direction === 'east' ? Math.max(maxLineCoord.x1, maxLineCoord.x2) : Math.min(maxLineCoord.x1, maxLineCoord.x2)
const realLineCoord =
polygon.direction === 'east' ? Math.max(isRealEavesLine.x1, isRealEavesLine.x2) : Math.min(isRealEavesLine.x1, isRealEavesLine.x2)
if (targetCoord !== realLineCoord) {
isRealEavesLine.attributes.type = LINE_TYPE.SUBLINE.RIDGE
}
}
}
}
}
}
return {
@ -1115,5 +1174,6 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
changeSurfaceLinePropertyEvent,
changeSurfaceLineProperty,
changeSurfaceLinePropertyReset,
changeSurfaceLineType,
}
}

View File

@ -178,7 +178,7 @@
"modal.roof.alloc.select.parallel": "筋配置",
"modal.roof.alloc.select.stairs": "千鳥配置",
"modal.roof.alloc.apply": "選択した屋根材として割り当て",
"plan.menu.estimate.docDown": "各種資料ダウンロード",
"plan.menu.estimate.docDownload": "見積書出力",
"plan.menu.estimate.save": "保存",
"plan.menu.estimate.reset": "初期化",
"plan.menu.estimate.copy": "見積書のコピー",
@ -594,6 +594,7 @@
"myinfo.message.password.error": "パスワードが間違っています。",
"login": "ログイン",
"login.auto.page.text": "自動ログイン中です。",
"login.fail": "アカウントが存在しないか、パスワードが間違っています。",
"login.id.save": "ID保存",
"login.id.placeholder": "IDを入力してください。",
"login.password.placeholder": "パスワードを入力してください。",
@ -886,7 +887,7 @@
"estimate.detail.drawingEstimateCreateDate": "登録日",
"estimate.detail.lastEditDatetime": "変更日時",
"estimate.detail.saleStoreId": "一次販売店名",
"estimate.detail.estimateDate": "見積日",
"estimate.detail.estimateDate": "見積作成日",
"estimate.detail.otherSaleStoreId": "二次販売店名",
"estimate.detail.noOtherSaleStoreId": "二次店なし",
"estimate.detail.receiveUser": "担当者",
@ -1034,8 +1035,9 @@
"roof.exceed.count": "屋根材は4つまで選択可能です。",
"outerLine.property.fix": "外壁線の属性設定 を完了しますか?",
"outerLine.property.close": "外壁線の属性設定 を終了しますか?",
"want.to.complete.auxiliary.creation": "補助線の作成を完了しますか?",
"want.to.complete.auxiliary.creation": "補助線の作成を完了しますか?",
"modal.placement.initial.setting.plan.drawing.only.number": "(※数字は[半角]入力のみ可能です。)",
"wall.line.not.found": "外壁がありません",
"roof.line.not.found": "屋根形状がありません"
"roof.line.not.found": "屋根形状がありません",
"roof.material.can.not.delete" : "割り当てられた配置面があります。"
}

View File

@ -178,7 +178,7 @@
"modal.roof.alloc.select.parallel": "병렬식",
"modal.roof.alloc.select.stairs": "계단식",
"modal.roof.alloc.apply": "선택한 지붕재로 할당",
"plan.menu.estimate.docDown": "문서 다운로드",
"plan.menu.estimate.docDownload": "문서 다운로드",
"plan.menu.estimate.save": "저장",
"plan.menu.estimate.reset": "초기화",
"plan.menu.estimate.copy": "견적서 복사",
@ -594,6 +594,7 @@
"myinfo.message.password.error": "비밀번호가 틀렸습니다.",
"login": "로그인",
"login.auto.page.text": "자동로그인 중 입니다.",
"login.fail": "계정이 없거나 비밀번호가 잘못되었습니다.",
"login.id.save": "ID Save",
"login.id.placeholder": "아이디를 입력해주세요.",
"login.password.placeholder": "비밀번호를 입력해주세요.",
@ -886,7 +887,7 @@
"estimate.detail.drawingEstimateCreateDate": "등록일",
"estimate.detail.lastEditDatetime": "변경일시",
"estimate.detail.saleStoreId": "1차 판매점명",
"estimate.detail.estimateDate": "견적일",
"estimate.detail.estimateDate": "견적작성일",
"estimate.detail.otherSaleStoreId": "2차 판매점명",
"estimate.detail.noOtherSaleStoreId": "2차점 없음",
"estimate.detail.receiveUser": "담당자",
@ -1037,5 +1038,6 @@
"want.to.complete.auxiliary.creation": "보조선 작성을 완료하시겠습니까?",
"modal.placement.initial.setting.plan.drawing.only.number": "(※ 숫자는 [반각]입력만 가능합니다.)",
"wall.line.not.found": "외벽선이 없습니다.",
"roof.line.not.found": "지붕형상이 없습니다."
"roof.line.not.found": "지붕형상이 없습니다.",
"roof.material.can.not.delete" : "할당된 배치면이 있습니다."
}

View File

@ -348,15 +348,15 @@ export const calculateIntersection = (line1, line2) => {
}
// Determine the min and max for line1 and line2 for both x and y
const line1MinX = Math.min(line1.x1, line1.x2)
const line1MaxX = Math.max(line1.x1, line1.x2)
const line2MinX = Math.min(line2.x1, line2.x2)
const line2MaxX = Math.max(line2.x1, line2.x2)
const line1MinX = Math.min(line1.x1, line1.x2) - 5
const line1MaxX = Math.max(line1.x1, line1.x2) + 5
const line2MinX = Math.min(line2.x1, line2.x2) - 5
const line2MaxX = Math.max(line2.x1, line2.x2) + 5
const line1MinY = Math.min(line1.y1, line1.y2)
const line1MaxY = Math.max(line1.y1, line1.y2)
const line2MinY = Math.min(line2.y1, line2.y2)
const line2MaxY = Math.max(line2.y1, line2.y2)
const line1MinY = Math.min(line1.y1, line1.y2) - 5
const line1MaxY = Math.max(line1.y1, line1.y2) + 5
const line2MinY = Math.min(line2.y1, line2.y2) - 5
const line2MaxY = Math.max(line2.y1, line2.y2) + 5
// Check if the intersection X and Y are within the range of both lines
if (