diff --git a/apps/sn/.gitignore b/apps/sn/.gitignore new file mode 100644 index 000000000..5ef6a5207 --- /dev/null +++ b/apps/sn/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/sn/.prettierrc b/apps/sn/.prettierrc new file mode 100644 index 000000000..b4bfed357 --- /dev/null +++ b/apps/sn/.prettierrc @@ -0,0 +1,3 @@ +{ + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/apps/sn/app/layout.tsx b/apps/sn/app/layout.tsx new file mode 100644 index 000000000..aa2049dcc --- /dev/null +++ b/apps/sn/app/layout.tsx @@ -0,0 +1,8 @@ +import type { Metadata } from "next"; +import { App } from "@/app"; + +export const metadata: Metadata = { + title: "유역하수도 통합 정보플랫폼", +}; + +export default App; diff --git a/apps/sn/app/site/page.tsx b/apps/sn/app/site/page.tsx new file mode 100644 index 000000000..0cf120630 --- /dev/null +++ b/apps/sn/app/site/page.tsx @@ -0,0 +1,2 @@ +import { SitePage } from "@/pages/site"; +export default SitePage; diff --git a/apps/sn/app/site/register/page.tsx b/apps/sn/app/site/register/page.tsx new file mode 100644 index 000000000..a11d0b60c --- /dev/null +++ b/apps/sn/app/site/register/page.tsx @@ -0,0 +1,2 @@ +import { RegisterPage } from "@/pages/site"; +export default RegisterPage; diff --git a/apps/sn/eslint.config.mjs b/apps/sn/eslint.config.mjs new file mode 100644 index 000000000..c85fb67c4 --- /dev/null +++ b/apps/sn/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/apps/sn/next.config.ts b/apps/sn/next.config.ts new file mode 100644 index 000000000..cb651cdc0 --- /dev/null +++ b/apps/sn/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = {}; + +export default nextConfig; diff --git a/apps/sn/package.json b/apps/sn/package.json new file mode 100644 index 000000000..0c125f73e --- /dev/null +++ b/apps/sn/package.json @@ -0,0 +1,27 @@ +{ + "name": "sn", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "NODE_OPTIONS='--inspect' next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@common/business_components": "workspace:*", + "@common/components": "workspace:*", + "next": "15.1.7", + "zod": "^3.24.2", + "@hookform/resolvers": "^4.1.3", + "react-hook-form": "^7.54.2" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "eslint": "^9", + "eslint-config-next": "15.1.7", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +} diff --git a/apps/sn/pages/.gitkeep b/apps/sn/pages/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/sn/postcss.config.mjs b/apps/sn/postcss.config.mjs new file mode 100644 index 000000000..1a69fd2a4 --- /dev/null +++ b/apps/sn/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/apps/sn/src/app/index.tsx b/apps/sn/src/app/index.tsx new file mode 100644 index 000000000..9223b0790 --- /dev/null +++ b/apps/sn/src/app/index.tsx @@ -0,0 +1,12 @@ +import "./styles/base.css"; +import "./styles/components.css"; + +const App = ({ children }: Readonly<{ children: React.ReactNode }>) => { + return ( + +
{children} + + ); +}; + +export { App }; diff --git a/apps/sn/src/app/styles/base.css b/apps/sn/src/app/styles/base.css new file mode 100644 index 000000000..e24111afe --- /dev/null +++ b/apps/sn/src/app/styles/base.css @@ -0,0 +1,11 @@ +@import "@common/assets/styles/base.css"; +@import "@common/assets/styles/diveicon.css"; + +@tailwind base; +@tailwind utilities; + +html, +body { + font-family: "Noto Sans KR", sans-serif; + font-size: 10px; +} diff --git a/apps/sn/src/app/styles/components.css b/apps/sn/src/app/styles/components.css new file mode 100644 index 000000000..4d5caaf96 --- /dev/null +++ b/apps/sn/src/app/styles/components.css @@ -0,0 +1,16 @@ +@tailwind components; + +@layer components { + .card { + @apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666]; + } + .card-border { + @apply shadow-blur border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)]; + } + .card-header { + @apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4; + } + .card-footer { + @apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4; + } +} diff --git a/apps/sn/src/pages/site/index.ts b/apps/sn/src/pages/site/index.ts new file mode 100644 index 000000000..22c7fef7b --- /dev/null +++ b/apps/sn/src/pages/site/index.ts @@ -0,0 +1,2 @@ +export { RegisterPage } from "./ui/register-page"; +export { SitePage } from "./ui/site-page"; diff --git a/apps/sn/src/pages/site/model/__mocks__.tsx b/apps/sn/src/pages/site/model/__mocks__.tsx new file mode 100644 index 000000000..1d11afa9a --- /dev/null +++ b/apps/sn/src/pages/site/model/__mocks__.tsx @@ -0,0 +1,59 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { fakerKO as faker } from "@faker-js/faker"; + +type Site = { + id: number; + 사이트코드: string; + 사이트명: string; + 대표_URL: string; + 기본_사이트_여부: boolean; + 사용_여부: boolean; + 등록자: string; + 등록일: string; +}; + +faker.seed(123); + +const data: Site[] = Array.from({ length: 100 }, (_, i) => ({ + id: i + 1, + 사이트코드: faker.string.alpha(2).toUpperCase(), + 사이트명: faker.lorem.word(), + 대표_URL: faker.internet.url(), + 기본_사이트_여부: faker.datatype.boolean(), + 사용_여부: faker.datatype.boolean(), + 등록자: faker.person.fullName(), + 등록일: faker.date.recent().toISOString().split("T")[0] as string, +})); + +const columnHelper = createColumnHelper