Merge branch 'dev'
This commit is contained in:
commit
efbf268886
@ -1,3 +1,7 @@
|
|||||||
NEXT_PUBLIC_TEST="테스트변수입니다. development"
|
NEXT_PUBLIC_TEST="테스트변수입니다. development"
|
||||||
|
|
||||||
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"
|
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"
|
||||||
|
|
||||||
|
DATABASE_URL="sqlserver://mssql.devgrr.kr:1433;database=qcast;user=qcast;password=Qwertqaz12345;trustServerCertificate=true"
|
||||||
|
|
||||||
|
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
|
||||||
@ -1,3 +1,7 @@
|
|||||||
NEXT_PUBLIC_TEST="테스트변수입니다. production"
|
NEXT_PUBLIC_TEST="테스트변수입니다. production"
|
||||||
|
|
||||||
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"
|
NEXT_PUBLIC_API_SERVER_PATH="http://localhost:8080"
|
||||||
|
|
||||||
|
DATABASE_URL=""
|
||||||
|
|
||||||
|
SESSION_SECRET="i3iHH1yp2/2SpQSIySQ4bpyc4g0D+zCF9FAn5xUG0+Y="
|
||||||
4320
package-lock.json
generated
4320
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -10,22 +10,29 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nextui-org/react": "^2.4.2",
|
"@nextui-org/react": "^2.4.2",
|
||||||
"@prisma/client": "^5.17.0",
|
"@prisma/client": "^5.18.0",
|
||||||
|
"ag-grid-react": "^32.0.2",
|
||||||
"axios": "^1.7.3",
|
"axios": "^1.7.3",
|
||||||
"fabric": "^5.3.0",
|
"fabric": "^5.3.0",
|
||||||
"framer-motion": "^11.2.13",
|
"framer-motion": "^11.2.13",
|
||||||
|
"iron-session": "^8.0.2",
|
||||||
"mathjs": "^13.0.2",
|
"mathjs": "^13.0.2",
|
||||||
"mongodb": "^6.8.0",
|
"mssql": "^11.0.1",
|
||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
|
"next-international": "^1.2.4",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
"react-datepicker": "^7.3.0",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-responsive-modal": "^6.4.2",
|
||||||
|
"react-toastify": "^10.0.5",
|
||||||
"recoil": "^0.7.7",
|
"recoil": "^0.7.7",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@turf/turf": "^7.0.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prisma": "^5.17.0",
|
"prisma": "^5.18.0",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,26 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "mongodb"
|
provider = "sqlserver"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
model test {
|
model M_USER {
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
USER_ID String @id(map: "M_USER_pk") @db.VarChar(50)
|
||||||
content String
|
SALE_STORE_ID String @db.VarChar(10)
|
||||||
}
|
PASSWORD String @db.VarChar(10)
|
||||||
|
CATEGORY String? @db.NVarChar(20)
|
||||||
model canvas {
|
NAME String @db.NVarChar(20)
|
||||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
NAME_KANA String @db.NVarChar(20)
|
||||||
loginId String
|
TEL String? @db.VarChar(15)
|
||||||
canvas Json
|
FAX String? @db.VarChar(15)
|
||||||
|
MAIL String? @db.NVarChar(100)
|
||||||
|
GROUP_ID String @db.VarChar(5)
|
||||||
|
MODULE_SELECT_GROUP_ID String? @db.VarChar(5)
|
||||||
|
VERSION_MANAGEMENT_ID String? @db.VarChar(20)
|
||||||
|
DISP_COST_PRICE Boolean?
|
||||||
|
DISP_SELLING_PRICE Boolean?
|
||||||
|
REGIST_DATETIME DateTime? @db.DateTime
|
||||||
|
LAST_EDIT_DATETIME DateTime? @db.DateTime
|
||||||
|
LAST_EDIT_USER String? @db.VarChar(50)
|
||||||
}
|
}
|
||||||
11
src/app/[locale]/LocaleProvider.js
Normal file
11
src/app/[locale]/LocaleProvider.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { I18nProviderClient } from '@/locales/client'
|
||||||
|
|
||||||
|
export function LocaleProvider({ locale, children }) {
|
||||||
|
return (
|
||||||
|
<I18nProviderClient locale={locale} fallback={''}>
|
||||||
|
{children}
|
||||||
|
</I18nProviderClient>
|
||||||
|
)
|
||||||
|
}
|
||||||
83
src/app/[locale]/changelog/page.jsx
Normal file
83
src/app/[locale]/changelog/page.jsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
|
||||||
|
|
||||||
|
import { AxiosType, useAxios } from '@/hooks/useAxios'
|
||||||
|
// import { get } from '@/lib/Axios'
|
||||||
|
|
||||||
|
import QSelect from '@/components/ui/QSelect'
|
||||||
|
|
||||||
|
import styles from './changelog.module.css'
|
||||||
|
|
||||||
|
export default function changelogPage() {
|
||||||
|
const { get } = useAxios()
|
||||||
|
const testVar = process.env.NEXT_PUBLIC_TEST
|
||||||
|
|
||||||
|
const handleUsers = async () => {
|
||||||
|
// const users = await get('/api/user/find-all')
|
||||||
|
const params = {
|
||||||
|
type: AxiosType.INTERNAL,
|
||||||
|
url: '/api/user/find-all',
|
||||||
|
}
|
||||||
|
const users = await get(params)
|
||||||
|
console.log('users', users)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
author: 'SWYOO',
|
||||||
|
contents: '버튼 정리(템플릿 적용)',
|
||||||
|
date: '2024.07.16',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
author: 'SWYOO',
|
||||||
|
contents: 'README.md 파일 이미지 경로 수정',
|
||||||
|
date: '2024.07.17',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
author: 'SWYOO',
|
||||||
|
contents: '',
|
||||||
|
date: '',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container mx-auto p-4 m-4 border">
|
||||||
|
<div className={styles.test}>이 영역은 테스트입니다.</div>
|
||||||
|
<div>
|
||||||
|
<Table isStriped>
|
||||||
|
<TableHeader>
|
||||||
|
<TableColumn>DATE</TableColumn>
|
||||||
|
<TableColumn>NAME</TableColumn>
|
||||||
|
<TableColumn>CONTENTS</TableColumn>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((item) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
<TableCell>{item.date}</TableCell>
|
||||||
|
<TableCell>{item.author}</TableCell>
|
||||||
|
<TableCell>{item.contents}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<div className="m-2">
|
||||||
|
<QSelect />
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-orange-300 m-2">{testVar}</div>
|
||||||
|
<div>
|
||||||
|
<div className="m-2">
|
||||||
|
<Button onClick={handleUsers}>Button</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="test">
|
||||||
|
<p className="text-white">Sass 테스트입니다.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/error.jsx
Normal file
15
src/app/[locale]/error.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
export default function ServerError() {
|
||||||
|
return (
|
||||||
|
<section className="bg-white dark:bg-gray-900">
|
||||||
|
<div className="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
|
||||||
|
<div className="mx-auto max-w-screen-sm text-center">
|
||||||
|
<h1 className="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">500</h1>
|
||||||
|
<p className="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Internal Server Error.</p>
|
||||||
|
<p className="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">We are already working to solve the problem. </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
11
src/app/[locale]/intro/page.jsx
Normal file
11
src/app/[locale]/intro/page.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import Intro from '@/components/Intro'
|
||||||
|
|
||||||
|
export default function IntroPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container mx-auto p-4 m-4 border">
|
||||||
|
<Intro />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
15
src/app/[locale]/layout.js
Normal file
15
src/app/[locale]/layout.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useCurrentLocale } from '@/locales/client'
|
||||||
|
import { LocaleProvider } from './LocaleProvider'
|
||||||
|
|
||||||
|
export default function LocaleLayout({ children }) {
|
||||||
|
const locale = useCurrentLocale()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LocaleProvider locale={locale} fallback={''}>
|
||||||
|
{children}
|
||||||
|
</LocaleProvider>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/app/[locale]/login/page.jsx
Normal file
9
src/app/[locale]/login/page.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Login from '@/components/auth/Login'
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Login />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
src/app/[locale]/not-found.jsx
Normal file
25
src/app/[locale]/not-found.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<section className="bg-white dark:bg-gray-900">
|
||||||
|
<div className="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
|
||||||
|
<div className="mx-auto max-w-screen-sm text-center">
|
||||||
|
<h1 className="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">404</h1>
|
||||||
|
<p className="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>
|
||||||
|
<p className="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">
|
||||||
|
Sorry, we can't find that page. You'll find lots to explore on the home page.{' '}
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex text-white bg-primary-600 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-primary-900 my-4"
|
||||||
|
>
|
||||||
|
Back to Homepage
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
src/app/[locale]/page.js
Normal file
23
src/app/[locale]/page.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import MainPage from '@/components/Main'
|
||||||
|
import { getSession } from '@/lib/authActions'
|
||||||
|
import { getCurrentLocale } from '@/locales/server'
|
||||||
|
|
||||||
|
export default async function page() {
|
||||||
|
const session = await getSession()
|
||||||
|
|
||||||
|
const currentLocale = getCurrentLocale()
|
||||||
|
|
||||||
|
const mainPageProps = {
|
||||||
|
currentLocale,
|
||||||
|
isLoggedIn: session?.isLoggedIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="m-4">
|
||||||
|
<h3>Main</h3>
|
||||||
|
<MainPage {...mainPageProps} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,55 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
|
|
||||||
import Hero from '@/components/Hero'
|
|
||||||
import QSelect from '@/components/ui/QSelect'
|
|
||||||
import styles from './changelog.module.css'
|
|
||||||
import { get } from '@/lib/Axios'
|
|
||||||
|
|
||||||
export default function changelogPage() {
|
|
||||||
const testVar = process.env.NEXT_PUBLIC_TEST
|
|
||||||
|
|
||||||
const handleUsers = async () => {
|
|
||||||
const users = await get('/api/user/find-all')
|
|
||||||
console.log(users)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Hero title="Change log" />
|
|
||||||
<div className={styles.test}>이 영역은 테스트입니다.</div>
|
|
||||||
<div>
|
|
||||||
<Table isStriped>
|
|
||||||
<TableHeader>
|
|
||||||
<TableColumn>DATE</TableColumn>
|
|
||||||
<TableColumn>NAME</TableColumn>
|
|
||||||
<TableColumn>CONTENTS</TableColumn>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow key="2">
|
|
||||||
<TableCell>2024.07.17</TableCell>
|
|
||||||
<TableCell>SWYOO</TableCell>
|
|
||||||
<TableCell>` * README.md 파일 이미지 경로 수정 `</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
<TableRow key="1">
|
|
||||||
<TableCell>2024.07.16</TableCell>
|
|
||||||
<TableCell>SWYOO</TableCell>
|
|
||||||
<TableCell>` * 버튼 정리(템플릿 적용) `</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
<div className="m-2">
|
|
||||||
<QSelect />
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-orange-300 m-2">{testVar}</div>
|
|
||||||
<div>
|
|
||||||
<div className="m-2">
|
|
||||||
<Button onClick={handleUsers}>Button</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="test">
|
|
||||||
<p className="text-white">Sass 테스트입니다.</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import Hero from '@/components/Hero'
|
|
||||||
import Intro from '@/components/Intro'
|
|
||||||
|
|
||||||
export default function IntroPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Hero title="Drawing on canvas 2D Intro" />
|
|
||||||
<div className="container flex flex-wrap items-center justify-between mx-auto p-4 m-4 border">
|
|
||||||
<Intro />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,10 +1,15 @@
|
|||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
import './globals.css'
|
|
||||||
import '../styles/style.scss'
|
|
||||||
import Headers from '@/components/Headers'
|
|
||||||
import RecoilRootWrapper from './RecoilWrapper'
|
import RecoilRootWrapper from './RecoilWrapper'
|
||||||
import UIProvider from './UIProvider'
|
import UIProvider from './UIProvider'
|
||||||
import { headers } from 'next/headers'
|
import { headers } from 'next/headers'
|
||||||
|
import Headers from '@/components/Headers'
|
||||||
|
|
||||||
|
import { ToastContainer } from 'react-toastify'
|
||||||
|
import QModal from '@/components/common/modal/QModal'
|
||||||
|
|
||||||
|
import './globals.css'
|
||||||
|
import '../styles/style.scss'
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ['latin'] })
|
||||||
|
|
||||||
@ -24,6 +29,8 @@ export default function RootLayout({ children }) {
|
|||||||
{headerPathname !== '/login' && <Headers />}
|
{headerPathname !== '/login' && <Headers />}
|
||||||
<RecoilRootWrapper>
|
<RecoilRootWrapper>
|
||||||
<UIProvider>{children}</UIProvider>
|
<UIProvider>{children}</UIProvider>
|
||||||
|
<QModal />
|
||||||
|
<ToastContainer />
|
||||||
</RecoilRootWrapper>
|
</RecoilRootWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,91 +0,0 @@
|
|||||||
export default function page() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col align-center h-screen">
|
|
||||||
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
|
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
|
|
||||||
<img
|
|
||||||
alt="Your Company"
|
|
||||||
src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600"
|
|
||||||
className="mx-auto h-10 w-auto"
|
|
||||||
/>
|
|
||||||
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
|
|
||||||
Sign in to your account
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
|
||||||
<form action="#" method="POST" className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor="email"
|
|
||||||
className="block text-sm font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
Email address
|
|
||||||
</label>
|
|
||||||
<div className="mt-2">
|
|
||||||
<input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
required
|
|
||||||
autoComplete="email"
|
|
||||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<label
|
|
||||||
htmlFor="password"
|
|
||||||
className="block text-sm font-medium leading-6 text-gray-900"
|
|
||||||
>
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<div className="text-sm">
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="font-semibold text-indigo-600 hover:text-indigo-500"
|
|
||||||
>
|
|
||||||
Forgot password?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
autoComplete="current-password"
|
|
||||||
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p className="mt-10 text-center text-sm text-gray-500">
|
|
||||||
Not a member?{' '}
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500"
|
|
||||||
>
|
|
||||||
Start a 14 day free trial
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import Hero from '@/components/Hero'
|
import Main from '@/components/Main'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return <Hero title="Q.CAST III - Prototype" />
|
return <Main />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import Link from 'next/link'
|
|||||||
|
|
||||||
export default function Headers() {
|
export default function Headers() {
|
||||||
return (
|
return (
|
||||||
<div className="w-full absolute z-10">
|
<div className="w-full">
|
||||||
<nav className="container relative flex flex-wrap items-center justify-between mx-auto p-8">
|
<nav className="container relative flex flex-wrap items-center justify-between mx-auto p-8">
|
||||||
<Link href="/" className="font-bold text-3xl">
|
<Link href="/" className="font-bold text-3xl">
|
||||||
Home
|
Home
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export default function Hero(props) {
|
export default function Hero(props) {
|
||||||
return (
|
return (
|
||||||
<div className="pt-48 flex justify-center">
|
<div className="pt-48 flex justify-center">
|
||||||
<h1 className="text-6xl archivo-black-regular">{props.title}</h1>
|
<h1 className="text-4xl archivo-black-regular">{props.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,142 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { useRecoilState } from 'recoil'
|
||||||
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
|
|
||||||
|
import { AxiosType, useAxios } from '@/hooks/useAxios'
|
||||||
|
|
||||||
|
import { Button } from '@nextui-org/react'
|
||||||
|
|
||||||
|
import SingleDatePicker from './common/datepicker/SingleDatePicker'
|
||||||
|
import RangeDatePicker from './common/datepicker/RangeDatePicker'
|
||||||
|
import QGrid from './common/grid/QGrid'
|
||||||
|
import { QToast } from '@/hooks/useToast'
|
||||||
|
|
||||||
export default function Intro() {
|
export default function Intro() {
|
||||||
|
const { get } = useAxios()
|
||||||
|
|
||||||
|
// const [open, setOpen] = useState(false)
|
||||||
|
const [startDate, setStartDate] = useState(new Date())
|
||||||
|
const singleDatePickerProps = {
|
||||||
|
startDate,
|
||||||
|
setStartDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
const [dateRange, setDateRange] = useState([null, null])
|
||||||
|
const [startRangeDate, endRangeDate] = dateRange
|
||||||
|
const rangeDatePickerProps = {
|
||||||
|
startRangeDate,
|
||||||
|
endRangeDate,
|
||||||
|
setDateRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
// const gridProps = {
|
||||||
|
// isPageable: false,
|
||||||
|
// }
|
||||||
|
const [gridProps, setGridProps] = useState({
|
||||||
|
gridData: [],
|
||||||
|
isPageable: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
|
const [contents, setContent] = useRecoilState(modalContent)
|
||||||
|
|
||||||
|
const modelProps = {
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipsum = (
|
||||||
|
<>
|
||||||
|
<p className="text-2xl">title</p>
|
||||||
|
<p>
|
||||||
|
저작자·발명가·과학기술자와 예술가의 권리는 법률로써 보호한다. 이 헌법은 1988년 2월 25일부터 시행한다. 다만, 이 헌법을 시행하기 위하여 필요한
|
||||||
|
법률의 제정·개정과 이 헌법에 의한 대통령 및 국회의원의 선거 기타 이 헌법시행에 관한 준비는 이 헌법시행 전에 할 수 있다.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
국가는 주택개발정책등을 통하여 모든 국민이 쾌적한 주거생활을 할 수 있도록 노력하여야 한다. 통신·방송의 시설기준과 신문의 기능을 보장하기
|
||||||
|
위하여 필요한 사항은 법률로 정한다.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
국회에서 의결된 법률안은 정부에 이송되어 15일 이내에 대통령이 공포한다. 선거에 관한 경비는 법률이 정하는 경우를 제외하고는 정당 또는
|
||||||
|
후보자에게 부담시킬 수 없다.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchData() {
|
||||||
|
// const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
||||||
|
// const data = await response.json()
|
||||||
|
const data = await get({ type: AxiosType.EXTERNAL, url: 'https://www.ag-grid.com/example-assets/space-mission-data.json' })
|
||||||
|
setGridProps({ ...gridProps, gridData: data })
|
||||||
|
}
|
||||||
|
fetchData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Intro</h1>
|
<div className="text-2xl">
|
||||||
<p>Drawing on canvas 2D is a simple way to create graphics on the web.</p>
|
<Link href={'/login'}>
|
||||||
|
<Button color="primary">로그인 페이지로 이동</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="text-2xl">Single Date Picker</div>
|
||||||
|
<div>
|
||||||
|
<SingleDatePicker {...singleDatePickerProps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="text-2xl">Range Date Picker</div>
|
||||||
|
<div>
|
||||||
|
<RangeDatePicker {...rangeDatePickerProps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="text-2xl">QGrid</div>
|
||||||
|
<div>
|
||||||
|
<QGrid {...gridProps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="text-2xl">QModal</div>
|
||||||
|
<div>
|
||||||
|
{/* <Button color="primary" onClick={() => setOpen(true)}>
|
||||||
|
Open Modal
|
||||||
|
</Button>
|
||||||
|
<QModal {...modelProps}>{ipsum}</QModal> */}
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setContent(ipsum)
|
||||||
|
setOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open Modal
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="text-2xl">QToast</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
QToast({
|
||||||
|
message: 'This is a toast message',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open Toast
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/components/Main.jsx
Normal file
39
src/components/Main.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { logout } from '@/lib/authActions'
|
||||||
|
import { useChangeLocale, useI18n } from '@/locales/client'
|
||||||
|
import { Button, Chip } from '@nextui-org/react'
|
||||||
|
|
||||||
|
export default function MainPage(props) {
|
||||||
|
const { currentLocale, isLoggedIn } = props
|
||||||
|
const t = useI18n()
|
||||||
|
const changeLocale = useChangeLocale()
|
||||||
|
|
||||||
|
const handleChangeLocale = () => {
|
||||||
|
currentLocale === 'ja' ? changeLocale('ko') : changeLocale('ja')
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('MainPage', currentLocale)
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>{t('locale', { locale: <Chip color="primary">{currentLocale}</Chip> })}</h3>
|
||||||
|
<div>{t('hello')}</div>
|
||||||
|
<div>{t('welcome', { name: '효준' })}</div>
|
||||||
|
<div>
|
||||||
|
<Button onClick={handleChangeLocale}>Change Locale</Button>
|
||||||
|
</div>
|
||||||
|
{isLoggedIn && (
|
||||||
|
<div className="my-4">
|
||||||
|
<Button color="primary" onClick={handleLogout}>
|
||||||
|
로그아웃
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -2,15 +2,15 @@ import { useCanvas } from '@/hooks/useCanvas'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Mode, useMode } from '@/hooks/useMode'
|
import { Mode, useMode } from '@/hooks/useMode'
|
||||||
import { Button } from '@nextui-org/react'
|
import { Button } from '@nextui-org/react'
|
||||||
|
|
||||||
import RangeSlider from './ui/RangeSlider'
|
import RangeSlider from './ui/RangeSlider'
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray } from '@/store/canvasAtom'
|
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState, compassState } from '@/store/canvasAtom'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
||||||
import { calculateIntersection } from '@/util/canvas-util'
|
import { calculateIntersection } from '@/util/canvas-util'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
import offsetPolygon from '@/util/qpolygon-utils'
|
import * as turf from '@turf/turf'
|
||||||
|
import { toGeoJSON } from '@/util/qpolygon-utils'
|
||||||
|
|
||||||
export default function Roof2() {
|
export default function Roof2() {
|
||||||
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
||||||
@ -34,6 +34,10 @@ export default function Roof2() {
|
|||||||
//지붕재
|
//지붕재
|
||||||
const roofMaterial = useRecoilValue(roofMaterialState)
|
const roofMaterial = useRecoilValue(roofMaterialState)
|
||||||
|
|
||||||
|
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
||||||
|
|
||||||
|
const [compass, setCompass] = useRecoilState(compassState)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mode,
|
mode,
|
||||||
setMode,
|
setMode,
|
||||||
@ -49,6 +53,8 @@ export default function Roof2() {
|
|||||||
applyTemplateB,
|
applyTemplateB,
|
||||||
makeRoofPatternPolygon,
|
makeRoofPatternPolygon,
|
||||||
createRoofRack,
|
createRoofRack,
|
||||||
|
drawRoofPolygon,
|
||||||
|
drawCellInTrestle,
|
||||||
} = useMode()
|
} = useMode()
|
||||||
|
|
||||||
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
||||||
@ -202,14 +208,25 @@ export default function Roof2() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const eightPoint4 = [
|
const eightPoint4 = [
|
||||||
{ x: 228, y: 92 },
|
{ x: 200, y: 200 },
|
||||||
{ x: 228, y: 592 },
|
{ x: 200, y: 400 },
|
||||||
{ x: 478, y: 592 },
|
{ x: 500, y: 400 },
|
||||||
{ x: 478, y: 342 },
|
{ x: 500, y: 700 },
|
||||||
{ x: 728, y: 342 },
|
{ x: 800, y: 700 },
|
||||||
{ x: 728, y: 592 },
|
{ x: 800, y: 400 },
|
||||||
{ x: 1078, y: 592 },
|
{ x: 1100, y: 400 },
|
||||||
{ x: 1078, y: 92 },
|
{ x: 1100, y: 200 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const eightPoint5 = [
|
||||||
|
{ x: 140, y: 101 },
|
||||||
|
{ x: 140, y: 601 },
|
||||||
|
{ x: 440, y: 601 },
|
||||||
|
{ x: 440, y: 801 },
|
||||||
|
{ x: 840, y: 801 },
|
||||||
|
{ x: 840, y: 601 },
|
||||||
|
{ x: 1140, y: 601 },
|
||||||
|
{ x: 1140, y: 101 },
|
||||||
]
|
]
|
||||||
|
|
||||||
const twelvePoint = [
|
const twelvePoint = [
|
||||||
@ -227,6 +244,21 @@ export default function Roof2() {
|
|||||||
{ x: 995, y: 166 },
|
{ x: 995, y: 166 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const twelvePoint2 = [
|
||||||
|
{ x: 165, y: 81 },
|
||||||
|
{ x: 165, y: 1081 },
|
||||||
|
{ x: 465, y: 1081 },
|
||||||
|
{ x: 465, y: 781 },
|
||||||
|
{ x: 765, y: 781 },
|
||||||
|
{ x: 765, y: 1081 },
|
||||||
|
{ x: 1065, y: 1081 },
|
||||||
|
{ x: 1065, y: 581 },
|
||||||
|
{ x: 765, y: 581 },
|
||||||
|
{ x: 765, y: 281 },
|
||||||
|
{ x: 1065, y: 281 },
|
||||||
|
{ x: 1065, y: 81 },
|
||||||
|
]
|
||||||
|
|
||||||
const complicatedType = [
|
const complicatedType = [
|
||||||
{ x: 100, y: 100 },
|
{ x: 100, y: 100 },
|
||||||
{ x: 100, y: 1100 },
|
{ x: 100, y: 1100 },
|
||||||
@ -242,6 +274,29 @@ export default function Roof2() {
|
|||||||
{ x: 1000, y: 100 },
|
{ x: 1000, y: 100 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const testType = [
|
||||||
|
{ x: 500, y: 400 },
|
||||||
|
{ x: 650, y: 550 },
|
||||||
|
{ x: 575, y: 625 },
|
||||||
|
{ x: 325, y: 625 },
|
||||||
|
{ x: 100, y: 400 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const testType2 = [
|
||||||
|
{ x: 100, y: 400 },
|
||||||
|
{ x: 325, y: 625 },
|
||||||
|
{ x: 575, y: 625 },
|
||||||
|
{ x: 650, y: 550 },
|
||||||
|
{ x: 500, y: 400 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const triangleType = [
|
||||||
|
{ x: 100, y: 100 },
|
||||||
|
{ x: 100, y: 600 },
|
||||||
|
{ x: 600, y: 600 },
|
||||||
|
{ x: 600, y: 100 },
|
||||||
|
]
|
||||||
|
|
||||||
const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint]
|
const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint]
|
||||||
const newP = [
|
const newP = [
|
||||||
{ x: 450, y: 450 },
|
{ x: 450, y: 450 },
|
||||||
@ -249,9 +304,10 @@ export default function Roof2() {
|
|||||||
{ x: 675, y: 275 },
|
{ x: 675, y: 275 },
|
||||||
{ x: 450, y: 850 },
|
{ x: 450, y: 850 },
|
||||||
]
|
]
|
||||||
const polygon = new QPolygon(type1, {
|
|
||||||
|
const polygon = new QPolygon(twelvePoint, {
|
||||||
fill: 'transparent',
|
fill: 'transparent',
|
||||||
stroke: 'black',
|
stroke: 'green',
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
@ -259,9 +315,8 @@ export default function Roof2() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
canvas?.add(polygon)
|
canvas?.add(polygon)
|
||||||
handleOuterlinesTest2(polygon)
|
handleOuterlinesTest2(polygon, 50)
|
||||||
// const lines = togglePolygonLine(polygon)
|
setTemplateType(1)
|
||||||
// togglePolygonLine(lines[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rotateShape = () => {
|
const rotateShape = () => {
|
||||||
@ -433,6 +488,18 @@ export default function Roof2() {
|
|||||||
makeRoofPatternPolygon(roofStyle)
|
makeRoofPatternPolygon(roofStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteCell = () => {
|
||||||
|
const selectedCells = canvas?.getObjects().filter((obj) => obj.name === 'cell' && obj.selected)
|
||||||
|
|
||||||
|
selectedCells.forEach((cell) => {
|
||||||
|
canvas?.remove(cell)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCompassState = (degree) => {
|
||||||
|
setCompass(degree)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{canvas && (
|
{canvas && (
|
||||||
@ -462,27 +529,43 @@ export default function Roof2() {
|
|||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
||||||
지붕패턴2
|
지붕패턴2
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(90)}>
|
||||||
|
방위표◀
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(270)}>
|
||||||
|
방위표▶
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(180)}>
|
||||||
|
방위표▲
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(0)}>
|
||||||
|
방위표▼
|
||||||
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
|
||||||
지붕가대생성
|
지붕가대생성
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
|
||||||
태양광셀생성
|
태양광셀생성
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.CELL_POWERCON)}>
|
||||||
|
파워콘설치
|
||||||
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
||||||
텍스트박스 모드
|
텍스트박스 모드
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={handleUndo}>
|
{/*<Button className="m-1 p-2" onClick={handleUndo}>
|
||||||
Undo
|
Undo
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={handleRedo}>
|
<Button className="m-1 p-2" onClick={handleRedo}>
|
||||||
Redo
|
Redo
|
||||||
</Button>
|
</Button>*/}
|
||||||
<Button className="m-1 p-2" onClick={handleClear}>
|
<Button className="m-1 p-2" onClick={handleClear}>
|
||||||
clear
|
clear
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={zoomIn}>
|
{/*<Button className="m-1 p-2" onClick={zoomIn}>
|
||||||
확대
|
확대
|
||||||
</Button>
|
</Button>
|
||||||
|
*/}
|
||||||
<Button className="m-1 p-2" onClick={zoomOut}>
|
<Button className="m-1 p-2" onClick={zoomOut}>
|
||||||
축소
|
축소
|
||||||
</Button>
|
</Button>
|
||||||
@ -493,51 +576,59 @@ export default function Roof2() {
|
|||||||
<Button className="m-1 p-2" onClick={makePolygon}>
|
<Button className="m-1 p-2" onClick={makePolygon}>
|
||||||
다각형 추가
|
다각형 추가
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{templateType === 0 && (
|
||||||
className="m-1 p-2"
|
<>
|
||||||
onClick={() => {
|
<Button
|
||||||
setCanvasBackgroundWithDots(canvas, 10)
|
className="m-1 p-2"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
setCanvasBackgroundWithDots(canvas, 10)
|
||||||
점선 추가
|
}}
|
||||||
</Button>
|
>
|
||||||
<Button
|
점선 추가
|
||||||
className="m-1 p-2"
|
</Button>
|
||||||
onClick={() => {
|
<Button
|
||||||
setCanvasBackgroundWithDots(canvas, 20)
|
className="m-1 p-2"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
setCanvasBackgroundWithDots(canvas, 20)
|
||||||
점선 추가
|
}}
|
||||||
</Button>
|
>
|
||||||
|
점선 추가
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" onClick={makeQPolygon}>
|
||||||
|
QPolygon
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Button className="m-1 p-2" onClick={saveImage}>
|
<Button className="m-1 p-2" onClick={saveImage}>
|
||||||
저장
|
저장
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={makeQPolygon}>
|
{/*<Button className="m-1 p-2" onClick={rotateShape}>
|
||||||
QPolygon
|
|
||||||
</Button>
|
|
||||||
<Button className="m-1 p-2" onClick={rotateShape}>
|
|
||||||
회전
|
회전
|
||||||
</Button>
|
</Button>*/}
|
||||||
<Button className="m-1 p-2" onClick={makeQLine}>
|
{/*<Button className="m-1 p-2" onClick={makeQLine}>
|
||||||
QLine
|
QLine
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={PolygonToLine}>
|
<Button className="m-1 p-2" onClick={PolygonToLine}>
|
||||||
PolygonToLine
|
PolygonToLine
|
||||||
</Button>
|
</Button>
|
||||||
{/*<Button className="m-1 p-2" onClick={handleSaveCanvas}>
|
|
||||||
canvas 내용 저장하기
|
|
||||||
</Button>
|
|
||||||
<Button className="m-1 p-2" onClick={handleLoadCanvas}>
|
|
||||||
canvas 내용 불러오기
|
|
||||||
</Button>*/}
|
|
||||||
<Button className="m-1 p-2" onClick={addCanvas}>
|
<Button className="m-1 p-2" onClick={addCanvas}>
|
||||||
캔버스 추가
|
캔버스 추가
|
||||||
</Button>
|
</Button>*/}
|
||||||
<Button className="m-1 p-2" onClick={drawRoofMaterial}>
|
{templateType === 1 && (
|
||||||
지붕타입 지붕재
|
<>
|
||||||
</Button>
|
<Button className="m-1 p-2" onClick={drawRoofMaterial}>
|
||||||
<Button className="m-1 p-2" onClick={createRoofRack}>
|
지붕타입 지붕재
|
||||||
지붕가대
|
</Button>
|
||||||
|
<Button className="m-1 p-2" onClick={createRoofRack}>
|
||||||
|
지붕가대설치
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" onClick={drawCellInTrestle}>
|
||||||
|
선택한 가대 셀채우기
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Button className="m-1 p-2" onClick={deleteCell}>
|
||||||
|
선택 셀 지우기
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${showControl ? 'primary' : 'default'}`} onClick={handleShowController}>
|
<Button className="m-1 p-2" color={`${showControl ? 'primary' : 'default'}`} onClick={handleShowController}>
|
||||||
canvas 컨트롤러 {`${showControl ? '숨기기' : '보이기'}`}
|
canvas 컨트롤러 {`${showControl ? '숨기기' : '보이기'}`}
|
||||||
|
|||||||
75
src/components/auth/Login.jsx
Normal file
75
src/components/auth/Login.jsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { login } from '@/lib/authActions'
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col align-center h-screen">
|
||||||
|
<div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<img alt="Your Company" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" className="mx-auto h-10 w-auto" />
|
||||||
|
<h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">Sign in to your account</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
<form action={login} method="POST" className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="userId" className="block text-sm font-medium leading-6 text-gray-900">
|
||||||
|
User ID
|
||||||
|
</label>
|
||||||
|
<div className="mt-2">
|
||||||
|
<input
|
||||||
|
id="userId"
|
||||||
|
name="id"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
// autoComplete="email"
|
||||||
|
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<div className="text-sm">
|
||||||
|
<a href="#" className="font-semibold text-indigo-600 hover:text-indigo-500">
|
||||||
|
Forgot password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
autoComplete="current-password"
|
||||||
|
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p className="mt-10 text-center text-sm text-gray-500">
|
||||||
|
Not a member?{' '}
|
||||||
|
<a href="#" className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500">
|
||||||
|
Start a 14 day free trial
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
19
src/components/common/datepicker/RangeDatePicker.jsx
Normal file
19
src/components/common/datepicker/RangeDatePicker.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import DatePicker from 'react-datepicker'
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css'
|
||||||
|
|
||||||
|
export default function RangeDatePicker(props) {
|
||||||
|
const { startRangeDate, endRangeDate, setDateRange } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
dateFormat={`yyyy-MM-dd`}
|
||||||
|
selectsRange={true}
|
||||||
|
startDate={startRangeDate}
|
||||||
|
endDate={endRangeDate}
|
||||||
|
onChange={(update) => {
|
||||||
|
setDateRange(update)
|
||||||
|
}}
|
||||||
|
isClearable={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
8
src/components/common/datepicker/SingleDatePicker.jsx
Normal file
8
src/components/common/datepicker/SingleDatePicker.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import DatePicker from 'react-datepicker'
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css'
|
||||||
|
|
||||||
|
export default function SingleDatePicker(props) {
|
||||||
|
const { startDate, setStartDate } = props
|
||||||
|
|
||||||
|
return <DatePicker showIcon dateFormat={`yyyy-MM-dd`} selected={startDate} onChange={(date) => setStartDate(date)} />
|
||||||
|
}
|
||||||
89
src/components/common/grid/QGrid.jsx
Normal file
89
src/components/common/grid/QGrid.jsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
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 QGrid(props) {
|
||||||
|
console.log('QGrid props:', props)
|
||||||
|
const { gridData, gridColumns, isPageable = true } = props
|
||||||
|
const [count, setCount] = useState(0)
|
||||||
|
const [clickedCount, setClickedCount] = useState(0)
|
||||||
|
/**
|
||||||
|
* 행 데이터를 설정할 때 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(() => {
|
||||||
|
filter: true
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 단순 유형(string, boolean 및 number)의 속성은 렌더링 간에 값으로 비교되므로 후크를 사용할 필요가 없습니다.
|
||||||
|
*/
|
||||||
|
const rowBuffer = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 렌더링에서 그리드 상태를 재설정하지 않도록 useCallback을 사용하는 것이 좋습니다.
|
||||||
|
*/
|
||||||
|
const isRowSelectable = useCallback((node) => node.data.value > count, [count])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event Listeners의 경우 이벤트 처리기가 그리드 내에서 업데이트를 트리거하지 않으므로 useCallback을 사용할 필요가 없습니다.
|
||||||
|
* 그러나 콜백과 일관성을 유지하고 항상 useCallback을 사용하는 것이 더 쉬울 수 있습니다.
|
||||||
|
*/
|
||||||
|
const onFilterOpened = useCallback(() => {
|
||||||
|
console.log(`number of clicks is ${clickedCount}`)
|
||||||
|
}, [clickedCount])
|
||||||
|
|
||||||
|
// Fetch data & update rowData state
|
||||||
|
useEffect(() => {
|
||||||
|
// async function fetchData() {
|
||||||
|
// const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
||||||
|
// const data = await response.json()
|
||||||
|
// setRowData(data)
|
||||||
|
// }
|
||||||
|
|
||||||
|
console.log('use effect')
|
||||||
|
gridData ? setRowData(gridData) : ''
|
||||||
|
}, [gridData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="ag-theme-quartz" // applying the Data Grid theme
|
||||||
|
style={{ height: 500 }} // the Data Grid will fill the size of the parent container
|
||||||
|
>
|
||||||
|
<AgGridReact
|
||||||
|
rowBuffer={rowBuffer}
|
||||||
|
rowData={rowData}
|
||||||
|
columnDefs={colDefs}
|
||||||
|
defaultColDef={defaultColDef}
|
||||||
|
isRowSelectable={isRowSelectable}
|
||||||
|
onFilterOpened={onFilterOpened}
|
||||||
|
pagination={isPageable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
src/components/common/modal/QModal.jsx
Normal file
20
src/components/common/modal/QModal.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
|
|
||||||
|
import { Modal } from 'react-responsive-modal'
|
||||||
|
|
||||||
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
|
|
||||||
|
import 'react-responsive-modal/styles.css'
|
||||||
|
|
||||||
|
export default function QModal() {
|
||||||
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
|
const children = useRecoilValue(modalContent)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={() => setOpen(false)} center>
|
||||||
|
{children}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -29,6 +29,8 @@ export const QLine = fabric.util.createClass(fabric.Line, {
|
|||||||
this.setLength()
|
this.setLength()
|
||||||
|
|
||||||
this.direction = options.direction ?? getDirection({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
|
this.direction = options.direction ?? getDirection({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 })
|
||||||
|
this.startPoint = { x: this.x1, y: this.y1 }
|
||||||
|
this.endPoint = { x: this.x2, y: this.y2 }
|
||||||
|
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { fabric } from 'fabric'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
||||||
import { calculateAngle, dividePolygon, drawHelpLineInHexagon } from '@/util/qpolygon-utils'
|
import { calculateAngle, drawHippedRoof, inPolygon, lineIntersect, splitPolygonWithLines, toGeoJSON } from '@/util/qpolygon-utils'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||||
type: 'QPolygon',
|
type: 'QPolygon',
|
||||||
@ -14,6 +15,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
ridges: [],
|
ridges: [],
|
||||||
connectRidges: [],
|
connectRidges: [],
|
||||||
cells: [],
|
cells: [],
|
||||||
|
parentId: null,
|
||||||
|
innerLines: [],
|
||||||
initialize: function (points, options, canvas) {
|
initialize: function (points, options, canvas) {
|
||||||
// 소수점 전부 제거
|
// 소수점 전부 제거
|
||||||
points.forEach((point) => {
|
points.forEach((point) => {
|
||||||
@ -21,6 +24,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
point.y = Math.round(point.y)
|
point.y = Math.round(point.y)
|
||||||
})
|
})
|
||||||
options.sort = options.sort ?? true
|
options.sort = options.sort ?? true
|
||||||
|
options.parentId = options.parentId ?? null
|
||||||
|
|
||||||
if (!options.sort && points.length <= 8) {
|
if (!options.sort && points.length <= 8) {
|
||||||
points = sortedPointLessEightPoint(points)
|
points = sortedPointLessEightPoint(points)
|
||||||
} else {
|
} else {
|
||||||
@ -31,7 +36,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
}
|
}
|
||||||
const nextPoint = points[(i + 1) % points.length]
|
const nextPoint = points[(i + 1) % points.length]
|
||||||
const angle = calculateAngle(point, nextPoint)
|
const angle = calculateAngle(point, nextPoint)
|
||||||
if (!(Math.abs(angle) === 0 || Math.abs(angle) === 180)) {
|
if (!(Math.abs(angle) === 0 || Math.abs(angle) === 180 || Math.abs(angle) === 90)) {
|
||||||
isDiagonal = true
|
isDiagonal = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -146,11 +151,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
|
|
||||||
// 보조선 그리기
|
// 보조선 그리기
|
||||||
drawHelpLine(chon = 4) {
|
drawHelpLine(chon = 4) {
|
||||||
drawHelpLineInHexagon(this, chon)
|
// drawHelpLineInHexagon(this, chon)
|
||||||
|
drawHippedRoof(this, chon)
|
||||||
},
|
},
|
||||||
|
|
||||||
addLengthText() {
|
addLengthText() {
|
||||||
let points = this.points
|
let points = this.getCurrentPoints()
|
||||||
|
|
||||||
points.forEach((start, i) => {
|
points.forEach((start, i) => {
|
||||||
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i)
|
const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i)
|
||||||
@ -228,18 +234,17 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
|
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
for (let j = 0; j < rows; j++) {
|
for (let j = 0; j < rows; j++) {
|
||||||
const rectLeft = minX + i * (rectWidth + cell.padding) + tmpWidth
|
const rectLeft = minX + i * (rectWidth + cell.padding)
|
||||||
const rectTop = minY + j * (rectHeight + cell.padding) + tmpHeight
|
const rectTop = minY + j * (rectHeight + cell.padding)
|
||||||
|
|
||||||
const rectPoints = [
|
const rectPoints = [
|
||||||
{ x: rectLeft, y: rectTop },
|
{ x: rectLeft, y: rectTop },
|
||||||
{ x: rectLeft + rectWidth, y: rectTop },
|
|
||||||
{ x: rectLeft, y: rectTop + rectHeight },
|
{ x: rectLeft, y: rectTop + rectHeight },
|
||||||
{ x: rectLeft + rectWidth, y: rectTop + rectHeight },
|
{ x: rectLeft + rectWidth, y: rectTop + rectHeight },
|
||||||
|
{ x: rectLeft + rectWidth, y: rectTop },
|
||||||
]
|
]
|
||||||
const allPointsInside = rectPoints.every((point) => this.inPolygon(point))
|
|
||||||
|
|
||||||
if (allPointsInside) {
|
if (inPolygon(this.points, rectPoints)) {
|
||||||
const rect = new fabric.Rect({
|
const rect = new fabric.Rect({
|
||||||
left: rectLeft,
|
left: rectLeft,
|
||||||
top: rectTop,
|
top: rectTop,
|
||||||
@ -256,6 +261,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
name: 'cell',
|
name: 'cell',
|
||||||
idx: idx,
|
idx: idx,
|
||||||
|
parentId: this.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
idx++
|
idx++
|
||||||
@ -268,6 +274,277 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.cells = drawCellsArray
|
this.cells = drawCellsArray
|
||||||
return drawCellsArray
|
return drawCellsArray
|
||||||
},
|
},
|
||||||
|
fillCellABType(cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1 }) {
|
||||||
|
const points = this.points
|
||||||
|
|
||||||
|
const minX = Math.min(...points.map((p) => p.x)) //왼쪽
|
||||||
|
const maxX = Math.max(...points.map((p) => p.x)) //오른쪽
|
||||||
|
const minY = Math.min(...points.map((p) => p.y)) //위
|
||||||
|
const maxY = Math.max(...points.map((p) => p.y)) //아래
|
||||||
|
|
||||||
|
const boundingBoxWidth = maxX - minX
|
||||||
|
const boundingBoxHeight = maxY - minY
|
||||||
|
|
||||||
|
const rectWidth = cell.width
|
||||||
|
const rectHeight = cell.height
|
||||||
|
|
||||||
|
const cols = Math.floor((boundingBoxWidth + cell.padding) / (rectWidth + cell.padding))
|
||||||
|
const rows = Math.floor((boundingBoxHeight + cell.padding) / (rectHeight + cell.padding))
|
||||||
|
|
||||||
|
//전체 높이에서 패딩을 포함하고 rows를 곱해서 여백길이를 계산 후에 2로 나누면 반높이를 넣어서 중간으로 정렬
|
||||||
|
|
||||||
|
let centerHeight = rows > 1 ? (boundingBoxHeight - (rectHeight + cell.padding / 2) * rows) / 2 : (boundingBoxHeight - rectHeight * rows) / 2 //rows 1개 이상이면 cell 을 반 나눠서 중간을 맞춘다
|
||||||
|
let centerWidth = cols > 1 ? (boundingBoxWidth - (rectWidth + cell.padding / 2) * cols) / 2 : (boundingBoxWidth - rectWidth * cols) / 2
|
||||||
|
|
||||||
|
const drawCellsArray = [] //그려진 셀의 배열
|
||||||
|
|
||||||
|
let idx = 1
|
||||||
|
let startXPos, startYPos
|
||||||
|
|
||||||
|
for (let i = 0; i < cols; i++) {
|
||||||
|
for (let j = 0; j < rows; j++) {
|
||||||
|
const rectPoints = []
|
||||||
|
|
||||||
|
if (cell.referenceDirection !== 'none') {
|
||||||
|
//4각형은 기준점이 없다
|
||||||
|
|
||||||
|
if (cell.referenceDirection === 'top') {
|
||||||
|
//top, bottom은 A패턴만
|
||||||
|
if (cell.wallDirection === 'left') {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = minY + j * rectHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
startXPos = maxX - (1 + i) * rectWidth - 0.01
|
||||||
|
startYPos = minY + j * rectHeight + 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos + rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||||
|
)
|
||||||
|
} else if (cell.referenceDirection === 'bottom') {
|
||||||
|
if (cell.wallDirection === 'left') {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startXPos = maxX - i * rectWidth - 0.01
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
startXPos = startXPos - rectWidth //우 -> 좌 들어가야해서 마이너스 처리
|
||||||
|
}
|
||||||
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.referenceDirection === 'left') {
|
||||||
|
//여기서부턴 B패턴임
|
||||||
|
if (cell.wallDirection === 'top') {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = minY + j * rectHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding //밑으로
|
||||||
|
}
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos + j * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos + rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
|
||||||
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||||
|
}
|
||||||
|
} else if (cell.referenceDirection === 'right') {
|
||||||
|
if (cell.wallDirection === 'top') {
|
||||||
|
startXPos = maxX - i * rectWidth - 0.01
|
||||||
|
startYPos = minY + j * rectHeight + 0.01
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos + j * cell.padding //위에서 밑으로라 +
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos + rectHeight },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos + rectHeight },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startXPos = maxX - i * rectWidth - 0.01
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
|
startXPos = startXPos - rectWidth // 우측에서 -> 좌측으로 그려짐
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// centerWidth = 0 //나중에 중간 정렬 이면 어쩌구 함수 만들어서 넣음
|
||||||
|
if (['left', 'right'].includes(cell.wallDirection)) {
|
||||||
|
centerWidth = 0
|
||||||
|
} else if (['top', 'bottom'].includes(cell.wallDirection)) {
|
||||||
|
centerHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.wallDirection === 'left') {
|
||||||
|
startXPos = minX + i * rectWidth + centerWidth
|
||||||
|
startYPos = minY + j * rectHeight + centerHeight
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.wallDirection === 'right') {
|
||||||
|
startXPos = maxX - (1 + i) * rectWidth - 0.01 - centerWidth
|
||||||
|
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding
|
||||||
|
}
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.wallDirection === 'top') {
|
||||||
|
startXPos = minX + i * rectWidth - 0.01 + centerWidth
|
||||||
|
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.wallDirection === 'bottom') {
|
||||||
|
startXPos = minX + i * rectWidth + 0.01 + centerWidth
|
||||||
|
startYPos = maxY - (j + 1) * rectHeight - 0.01 - centerHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPointsInside = rectPoints.every((point) => this.inPolygonABType(point.x, point.y, points))
|
||||||
|
|
||||||
|
if (allPointsInside) {
|
||||||
|
//먼저 그룹화를 시켜놓고 뒤에서 글씨를 넣어서 변경한다
|
||||||
|
const text = new fabric.Text(``, {
|
||||||
|
fontFamily: 'serif',
|
||||||
|
fontSize: 30,
|
||||||
|
fill: 'black',
|
||||||
|
type: 'cellText',
|
||||||
|
})
|
||||||
|
|
||||||
|
const rect = new fabric.Rect({
|
||||||
|
// left: startXPos,
|
||||||
|
// top: startYPos,
|
||||||
|
width: rectWidth,
|
||||||
|
height: rectHeight,
|
||||||
|
fill: '#BFFD9F',
|
||||||
|
stroke: 'black',
|
||||||
|
selectable: true, // 선택 가능하게 설정
|
||||||
|
// lockMovementX: true, // X 축 이동 잠금
|
||||||
|
// lockMovementY: true, // Y 축 이동 잠금
|
||||||
|
// lockRotation: true, // 회전 잠금
|
||||||
|
// lockScalingX: true, // X 축 크기 조정 잠금
|
||||||
|
// lockScalingY: true, // Y 축 크기 조정 잠금
|
||||||
|
opacity: 0.8,
|
||||||
|
name: 'cell',
|
||||||
|
idx: idx,
|
||||||
|
type: 'cellRect',
|
||||||
|
})
|
||||||
|
|
||||||
|
var group = new fabric.Group([rect, text], {
|
||||||
|
left: startXPos,
|
||||||
|
top: startYPos,
|
||||||
|
})
|
||||||
|
|
||||||
|
idx++
|
||||||
|
drawCellsArray.push(group) //배열에 넣어서 반환한다
|
||||||
|
this.canvas.add(group)
|
||||||
|
this.canvas?.renderAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cells = drawCellsArray
|
||||||
|
return drawCellsArray
|
||||||
|
},
|
||||||
|
|
||||||
inPolygon(point) {
|
inPolygon(point) {
|
||||||
const vertices = this.points
|
const vertices = this.points
|
||||||
let intersects = 0
|
let intersects = 0
|
||||||
@ -291,7 +568,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let xInt = ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / (vertex2.y - vertex1.y) + vertex1.x
|
let xInt = ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / (vertex2.y - vertex1.y) + vertex1.x
|
||||||
if (xInt < point.x) {
|
if (xInt <= point.x) {
|
||||||
intersects++
|
intersects++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,6 +576,57 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
return intersects % 2 === 1
|
return intersects % 2 === 1
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inPolygonABType(x, y, polygon) {
|
||||||
|
let inside = false
|
||||||
|
let n = polygon.length
|
||||||
|
|
||||||
|
for (let i = 0, j = n - 1; i < n; j = i++) {
|
||||||
|
let xi = polygon[i].x
|
||||||
|
let yi = polygon[i].y
|
||||||
|
let xj = polygon[j].x
|
||||||
|
let yj = polygon[j].y
|
||||||
|
|
||||||
|
// console.log('xi : ', xi, 'yi : ', yi, 'xj : ', xj, 'yj : ', yj)
|
||||||
|
|
||||||
|
let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
|
||||||
|
if (intersect) inside = !inside
|
||||||
|
}
|
||||||
|
|
||||||
|
return inside
|
||||||
|
},
|
||||||
|
|
||||||
|
inPolygon2(rectPoints) {
|
||||||
|
const polygonCoords = toGeoJSON(this.points)
|
||||||
|
const rectCoords = toGeoJSON(rectPoints)
|
||||||
|
|
||||||
|
const outerPolygon = turf.polygon([polygonCoords])
|
||||||
|
const innerPolygon = turf.polygon([rectCoords])
|
||||||
|
// 각 점이 다각형 내부에 있는지 확인
|
||||||
|
const allPointsInside = rectCoords.every((coord) => {
|
||||||
|
const point = turf.point(coord)
|
||||||
|
return turf.booleanPointInPolygon(point, outerPolygon)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 사각형의 변 정의
|
||||||
|
const rectEdges = [
|
||||||
|
[rectCoords[0], rectCoords[1]],
|
||||||
|
[rectCoords[1], rectCoords[2]],
|
||||||
|
[rectCoords[2], rectCoords[3]],
|
||||||
|
[rectCoords[3], rectCoords[0]],
|
||||||
|
]
|
||||||
|
|
||||||
|
// 다각형의 변 정의
|
||||||
|
const outerEdges = turf.lineString(outerPolygon.geometry.coordinates[0])
|
||||||
|
|
||||||
|
// 사각형의 변들이 다각형의 변과 교차하는지 확인
|
||||||
|
const noEdgesIntersect = rectEdges.every((edge) => {
|
||||||
|
const line = turf.lineString(edge)
|
||||||
|
const intersects = turf.lineIntersect(line, outerEdges)
|
||||||
|
return intersects.features.length === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return allPointsInside && noEdgesIntersect
|
||||||
|
},
|
||||||
distanceFromEdge(point) {
|
distanceFromEdge(point) {
|
||||||
const vertices = this.getCurrentPoints()
|
const vertices = this.getCurrentPoints()
|
||||||
let minDistance = Infinity
|
let minDistance = Infinity
|
||||||
@ -330,24 +658,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
return minDistance
|
return minDistance
|
||||||
},
|
},
|
||||||
getCurrentPoints() {
|
getCurrentPoints() {
|
||||||
const scaleX = this.scaleX
|
const pathOffset = this.get('pathOffset')
|
||||||
const scaleY = this.scaleY
|
const matrix = this.calcTransformMatrix()
|
||||||
|
return this.get('points')
|
||||||
const left = this.left
|
.map(function (p) {
|
||||||
const top = this.top
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
||||||
|
})
|
||||||
// 시작점
|
.map(function (p) {
|
||||||
const point = this.points[0]
|
return fabric.util.transformPoint(p, matrix)
|
||||||
|
})
|
||||||
const movingX = left - point.x * scaleX
|
|
||||||
const movingY = top - point.y * scaleY
|
|
||||||
|
|
||||||
return this.points.map((point) => {
|
|
||||||
return {
|
|
||||||
x: point.x * scaleX + movingX,
|
|
||||||
y: point.y * scaleY + movingY,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
setWall: function (wall) {
|
setWall: function (wall) {
|
||||||
this.wall = wall
|
this.wall = wall
|
||||||
@ -358,6 +677,6 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
divideLine() {
|
divideLine() {
|
||||||
dividePolygon(this)
|
splitPolygonWithLines(this)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
67
src/hooks/useAxios.js
Normal file
67
src/hooks/useAxios.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export const AxiosType = {
|
||||||
|
INTERNAL: 'Internal',
|
||||||
|
EXTERNAL: 'External',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAxios() {
|
||||||
|
const getInstances = (type) => {
|
||||||
|
return axios.create({
|
||||||
|
baseURL: type === AxiosType.INTERNAL ? process.env.NEXT_PUBLIC_API_SERVER_PATH : '',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.interceptors.request.use((config) => {
|
||||||
|
// config['Authorization'] = localStorage.getItem('token')
|
||||||
|
//TODO: 인터셉터에서 추가 로직 구현
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
axios.interceptors.request.use(undefined, (error) => {
|
||||||
|
//TODO: 인터셉터에서 에러 처리 로직 구현
|
||||||
|
// if (error.isAxiosError && e.response?.status === 401) {
|
||||||
|
// localStorage.removeItem('token')
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
|
const get = async ({ type, url }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.get(url)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = async ({ type, url, data }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.post(url, data)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const put = async ({ type, url, data }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.put(url, data)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const patch = async ({ type, url, data }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.patch(url, data)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async ({ type, url }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.delete(url)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { get, post, put, patch, del }
|
||||||
|
}
|
||||||
@ -89,21 +89,37 @@ export function useCanvas(id) {
|
|||||||
canvas?.off('object:modified')
|
canvas?.off('object:modified')
|
||||||
canvas?.off('object:removed')
|
canvas?.off('object:removed')
|
||||||
canvas?.off('object:added')
|
canvas?.off('object:added')
|
||||||
canvas?.off('mouse:move', drawMouseLines)
|
canvas?.off('mouse:move')
|
||||||
canvas?.off('mouse:down', handleMouseDown)
|
canvas?.off('mouse:down')
|
||||||
}
|
}
|
||||||
|
|
||||||
const addEventOnObject = (e) => {
|
const addEventOnObject = (e) => {
|
||||||
const target = e.target
|
const target = e.target
|
||||||
if (target.name === 'cell') {
|
if (target.name === 'cell') {
|
||||||
target.on('mousedown', () => {
|
target.on('mousedown', () => {
|
||||||
target.set({ fill: 'red' })
|
if (target.get('selected')) {
|
||||||
|
target.set({ selected: false })
|
||||||
|
target.set({ fill: '#BFFD9F' })
|
||||||
|
} else {
|
||||||
|
target.set({ selected: true })
|
||||||
|
target.set({ fill: 'red' })
|
||||||
|
}
|
||||||
|
canvas?.renderAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.name === 'trestle') {
|
if (target.name === 'trestle') {
|
||||||
target.on('mousedown', () => {
|
target.on('mousedown', () => {
|
||||||
target.set({ strokeWidth: 5 })
|
if (target.get('selected')) {
|
||||||
|
target.set({ strokeWidth: 1 })
|
||||||
|
target.set({ strokeDashArray: [5, 5] })
|
||||||
|
target.set({ selected: false })
|
||||||
|
} else {
|
||||||
|
target.set({ strokeWidth: 5 })
|
||||||
|
target.set({ strokeDashArray: [0, 0] })
|
||||||
|
target.set({ selected: true })
|
||||||
|
}
|
||||||
|
canvas?.renderAll()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,8 +355,8 @@ export function useCanvas(id) {
|
|||||||
|
|
||||||
let top = targetObj.top + 10
|
let top = targetObj.top + 10
|
||||||
|
|
||||||
if (top > CANVAS.HEIGHT) {
|
if (top > canvasSize.vertical) {
|
||||||
top = CANVAS.HEIGHT
|
top = canvasSize.vertical
|
||||||
}
|
}
|
||||||
|
|
||||||
targetObj.set({ top: top })
|
targetObj.set({ top: top })
|
||||||
@ -387,8 +403,8 @@ export function useCanvas(id) {
|
|||||||
|
|
||||||
let left = targetObj.left + 10
|
let left = targetObj.left + 10
|
||||||
|
|
||||||
if (left > CANVAS.WIDTH) {
|
if (left > canvasSize.horizontal) {
|
||||||
left = CANVAS.WIDTH
|
left = canvasSize.horizontal
|
||||||
}
|
}
|
||||||
|
|
||||||
targetObj.set({ left: left })
|
targetObj.set({ left: left })
|
||||||
|
|||||||
1428
src/hooks/useMode.js
1428
src/hooks/useMode.js
File diff suppressed because it is too large
Load Diff
35
src/hooks/useToast.js
Normal file
35
src/hooks/useToast.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
const toastDefaultOptions = {
|
||||||
|
position: 'top-right',
|
||||||
|
autoClose: 3000,
|
||||||
|
draggable: false,
|
||||||
|
hideProgressBar: false,
|
||||||
|
rtl: false,
|
||||||
|
pauseOnFocusLoss: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
theme: 'light',
|
||||||
|
limit: 2,
|
||||||
|
closeOnClick: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const QToast = (props) => {
|
||||||
|
// type TypeOptions = 'info' | 'success' | 'warning' | 'error' | 'default'
|
||||||
|
const { message, type = 'info', options } = props
|
||||||
|
const customOptions = { ...toastDefaultOptions, ...options }
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'info':
|
||||||
|
return toast.info(message, customOptions)
|
||||||
|
case 'success':
|
||||||
|
return toast.success(message, customOptions)
|
||||||
|
case 'warning':
|
||||||
|
return toast.warn(message, customOptions)
|
||||||
|
case 'error':
|
||||||
|
return toast.error(message, customOptions)
|
||||||
|
default:
|
||||||
|
return toast(message, customOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { QToast }
|
||||||
61
src/lib/authActions.js
Normal file
61
src/lib/authActions.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { cookies } from 'next/headers'
|
||||||
|
import { redirect } from 'next/navigation'
|
||||||
|
|
||||||
|
import { getIronSession } from 'iron-session'
|
||||||
|
|
||||||
|
import { getUserByIdAndPassword } from './user'
|
||||||
|
import { defaultSession, sessionOptions } from './session'
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
const session = await getSession()
|
||||||
|
session.destroy()
|
||||||
|
redirect('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSession() {
|
||||||
|
let session
|
||||||
|
session = await getIronSession(cookies(), sessionOptions)
|
||||||
|
|
||||||
|
console.log('session:', session)
|
||||||
|
if (!session.isLoggedIn) {
|
||||||
|
session.isLoggedIn = defaultSession.isLoggedIn
|
||||||
|
}
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(formData) {
|
||||||
|
const session = await getSession()
|
||||||
|
|
||||||
|
const userId = formData.get('id')
|
||||||
|
const password = formData.get('password')
|
||||||
|
|
||||||
|
console.log('id:', userId)
|
||||||
|
console.log('password:', password)
|
||||||
|
|
||||||
|
// const user = {
|
||||||
|
// id: 1,
|
||||||
|
// name: 'jinsoo Kim',
|
||||||
|
// email: 'jinsoo.kim@example.com',
|
||||||
|
// }
|
||||||
|
const loginUser = await getUserByIdAndPassword({ userId, password })
|
||||||
|
console.log('loginUser:', loginUser)
|
||||||
|
|
||||||
|
if (!loginUser) {
|
||||||
|
throw Error('Wrong Credentials!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// session.id = user.id
|
||||||
|
// session.email = user.email
|
||||||
|
session.userId = loginUser.USER_ID
|
||||||
|
session.saleStoreId = loginUser.SALE_STORE_ID
|
||||||
|
session.name = loginUser.NAME
|
||||||
|
session.mail = loginUser.MAIL
|
||||||
|
session.isLoggedIn = true
|
||||||
|
console.log('session:', session)
|
||||||
|
|
||||||
|
await session.save()
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
16
src/lib/session.js
Normal file
16
src/lib/session.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export const defaultSession = {
|
||||||
|
userId: null,
|
||||||
|
saleStoreId: null,
|
||||||
|
name: null,
|
||||||
|
mail: null,
|
||||||
|
isLoggedIn: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sessionOptions = {
|
||||||
|
password: process.env.SESSION_SECRET,
|
||||||
|
cookieName: 'lama-session',
|
||||||
|
cookieOptions: {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
},
|
||||||
|
}
|
||||||
31
src/lib/user.js
Normal file
31
src/lib/user.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
const { PrismaClient } = require('@prisma/client')
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export async function getUserByIdAndPassword({ userId, password }) {
|
||||||
|
return prisma.m_USER.findFirst({
|
||||||
|
where: {
|
||||||
|
USER_ID: userId,
|
||||||
|
PASSWORD: password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUser(userId) {
|
||||||
|
return prisma.m_USER.findUnique({
|
||||||
|
where: {
|
||||||
|
user_id: userId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsers() {
|
||||||
|
return prisma.m_USER.findMany({
|
||||||
|
where: {
|
||||||
|
// USER_ID: 'daiwajoho01',
|
||||||
|
USER_ID: { in: ['daiwajoho01', 'daiwajoho', 'daiwabutsuryu'] },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
18
src/locales/client.js
Normal file
18
src/locales/client.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { createI18nClient } from 'next-international/client'
|
||||||
|
|
||||||
|
export const { useI18n, useScopedI18n, I18nProviderClient, useChangeLocale, defineLocale, useCurrentLocale } = createI18nClient(
|
||||||
|
{
|
||||||
|
ko: () => import('./ko'),
|
||||||
|
ja: () => import('./ja'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Uncomment to set base path
|
||||||
|
// basePath: '/base',
|
||||||
|
// Uncomment to use custom segment name
|
||||||
|
// segmentName: 'locale',
|
||||||
|
// Uncomment to set fallback locale
|
||||||
|
// fallbackLocale: en,
|
||||||
|
},
|
||||||
|
)
|
||||||
7
src/locales/ja.js
Normal file
7
src/locales/ja.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
console.log('Loaded JA')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hello: 'こんにちは',
|
||||||
|
welcome: 'こんにちは {name}!',
|
||||||
|
locale: '現在のロケールは {locale} です。',
|
||||||
|
}
|
||||||
7
src/locales/ko.js
Normal file
7
src/locales/ko.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
console.log('Loaded KO')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hello: '안녕',
|
||||||
|
welcome: '안녕 {name}!',
|
||||||
|
locale: '현재 로케일은 {locale}입니다.',
|
||||||
|
}
|
||||||
14
src/locales/server.js
Normal file
14
src/locales/server.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createI18nServer } from 'next-international/server'
|
||||||
|
|
||||||
|
export const { getI18n, getScopedI18n, getCurrentLocale, getStaticParams } = createI18nServer(
|
||||||
|
{
|
||||||
|
ko: () => import('./ko'),
|
||||||
|
ja: () => import('./ja'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Uncomment to use custom segment name
|
||||||
|
// segmentName: 'locale',
|
||||||
|
// Uncomment to set fallback locale
|
||||||
|
// fallbackLocale: en,
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -1,12 +1,27 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { createI18nMiddleware } from 'next-international/middleware'
|
||||||
|
|
||||||
|
const I18nMiddleware = createI18nMiddleware({
|
||||||
|
locales: ['ko', 'ja'],
|
||||||
|
defaultLocale: 'ko',
|
||||||
|
})
|
||||||
|
|
||||||
export function middleware(request) {
|
export function middleware(request) {
|
||||||
const requestHeaders = new Headers(request.headers)
|
return I18nMiddleware(request)
|
||||||
requestHeaders.set('x-pathname', request.nextUrl.pathname)
|
|
||||||
|
|
||||||
return NextResponse.next({
|
|
||||||
request: {
|
|
||||||
headers: requestHeaders,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)'],
|
||||||
|
}
|
||||||
|
|
||||||
|
// import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
// export function middleware(request) {
|
||||||
|
// const requestHeaders = new Headers(request.headers)
|
||||||
|
// requestHeaders.set('x-pathname', request.nextUrl.pathname)
|
||||||
|
|
||||||
|
// return NextResponse.next({
|
||||||
|
// request: {
|
||||||
|
// headers: requestHeaders,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export const roofPolygonArrayState = atom({
|
|||||||
|
|
||||||
export const templateTypeState = atom({
|
export const templateTypeState = atom({
|
||||||
key: 'templateType',
|
key: 'templateType',
|
||||||
default: 1, //1:모임지붕, 2:A타입, 3:B타입
|
default: 0, //1:모임지붕, 2:A타입, 3:B타입
|
||||||
dangerouslyAllowMutability: true,
|
dangerouslyAllowMutability: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -67,3 +67,9 @@ export const roofMaterialState = atom({
|
|||||||
default: { width: 20, height: 10, rafter: 0, roofStyle: 2 },
|
default: { width: 20, height: 10, rafter: 0, roofStyle: 2 },
|
||||||
dangerouslyAllowMutability: true,
|
dangerouslyAllowMutability: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const compassState = atom({
|
||||||
|
key: 'compass',
|
||||||
|
default: undefined,
|
||||||
|
dangerouslyAllowMutability: true,
|
||||||
|
})
|
||||||
|
|||||||
15
src/store/modalAtom.js
Normal file
15
src/store/modalAtom.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { atom } from 'recoil'
|
||||||
|
|
||||||
|
export const modalState = atom({
|
||||||
|
key: 'modalState',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const modalContent = atom({
|
||||||
|
key: 'modalContent',
|
||||||
|
default: (
|
||||||
|
<>
|
||||||
|
<div>test</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
})
|
||||||
@ -1 +1,2 @@
|
|||||||
@import '_test.scss';
|
@import '_test.scss';
|
||||||
|
@import 'react-toastify/dist/ReactToastify.css';
|
||||||
@ -38,7 +38,7 @@ export function actionHandler(eventData, transform, x, y) {
|
|||||||
|
|
||||||
// define a function that can keep the polygon in the same position when we change its width/height/top/left
|
// define a function that can keep the polygon in the same position when we change its width/height/top/left
|
||||||
export function anchorWrapper(anchorIndex, fn) {
|
export function anchorWrapper(anchorIndex, fn) {
|
||||||
return function (eventData, transform, x, y) {
|
return function(eventData, transform, x, y) {
|
||||||
let fabricObject = transform.target
|
let fabricObject = transform.target
|
||||||
let originX = fabricObject?.points[anchorIndex].x - fabricObject.pathOffset.x
|
let originX = fabricObject?.points[anchorIndex].x - fabricObject.pathOffset.x
|
||||||
let originY = fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
|
let originY = fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
|
||||||
@ -241,6 +241,14 @@ export const getRoofHypotenuse = (base) => {
|
|||||||
return Math.sqrt(base * base * 2)
|
return Math.sqrt(base * base * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 빗변의 길이를 입력받아 밑변의 길이를 반환
|
||||||
|
* @param base 빗변
|
||||||
|
*/
|
||||||
|
export const getAdjacent = (base) => {
|
||||||
|
return Math.round(Math.sqrt(Math.pow(base, 2) / 2))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 촌을 입력받아 각도를 반환
|
* 촌을 입력받아 각도를 반환
|
||||||
* @param chon
|
* @param chon
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user