diff --git a/web-app/app/app.css b/web-app/app/app.css index 1b651e1..5ba9722 100644 --- a/web-app/app/app.css +++ b/web-app/app/app.css @@ -1,9 +1,62 @@ @import "tailwindcss"; -@import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css"; + +@font-face { + font-family: "Pretendard"; + font-weight: 400; + font-style: normal; + src: url("~/shared/assets/fonts/Pretendard-Regular.subset.woff") format("woff"); + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + font-weight: 500; + font-style: normal; + src: url("~/shared/assets/fonts/Pretendard-Medium.subset.woff") format("woff"); + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + font-weight: 600; + font-style: normal; + src: url("~/shared/assets/fonts/Pretendard-SemiBold.subset.woff") format("woff"); + font-display: swap; +} + +@font-face { + font-family: "Pretendard"; + font-weight: 700; + font-style: normal; + src: url("~/shared/assets/fonts/Pretendard-Bold.subset.woff") format("woff"); + font-display: swap; +} @theme { - --font-sans: "Pretendard Variable", ui-sans-serif, system-ui, sans-serif, + --font-sans: "Pretendard", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + + /* Primary (Navy) */ + --color-primary: var(--color-dabeeo-navy-main); + --color-primary-secondary: var(--color-dabeeo-navy-secondary); + --color-primary-tertiary: var(--color-dabeeo-navy-tertiary); + --color-primary-tertiary01: var(--color-dabeeo-navy-tertiary01); + --color-primary-tertiary02: var(--color-dabeeo-navy-tertiary02); + + /* Navy */ + --color-dabeeo-navy-main: #00387d; + --color-dabeeo-navy-secondary: #032651; + --color-dabeeo-navy-tertiary: #5c84b4; + --color-dabeeo-navy-tertiary01: #d4dde9; + --color-dabeeo-navy-tertiary02: #f0f3f7; + + /* Gray */ + --color-dabeeo-gray-44: #444444; + --color-dabeeo-gray-99: #999999; + --color-dabeeo-gray-be: #bebebe; + --color-dabeeo-gray-da: #dadada; + --color-dabeeo-gray-eb: #ebebeb; + --color-dabeeo-gray-f9: #f9f9f9; } html, diff --git a/web-app/app/routes.ts b/web-app/app/routes.ts index c059ada..8e23db0 100644 --- a/web-app/app/routes.ts +++ b/web-app/app/routes.ts @@ -7,6 +7,7 @@ export default [ route('login', './routes/login/page.tsx'), ]), layout('./routes/layout.tsx', [ + index('./routes/page.tsx'), ...prefix('imagery', [ ...prefix('aerial', [ index('./routes/imagery/aerial/page.tsx'), diff --git a/web-app/app/routes/code/page.tsx b/web-app/app/routes/code/page.tsx index e69de29..8c2de45 100644 --- a/web-app/app/routes/code/page.tsx +++ b/web-app/app/routes/code/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
공통코드 관리
; +} diff --git a/web-app/app/routes/hyper-parameter/page.tsx b/web-app/app/routes/hyper-parameter/page.tsx index e69de29..b660a34 100644 --- a/web-app/app/routes/hyper-parameter/page.tsx +++ b/web-app/app/routes/hyper-parameter/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
하이퍼파라미터 설정
; +} diff --git a/web-app/app/routes/inference/[id]/page.tsx b/web-app/app/routes/inference/[id]/page.tsx index e69de29..c3ca336 100644 --- a/web-app/app/routes/inference/[id]/page.tsx +++ b/web-app/app/routes/inference/[id]/page.tsx @@ -0,0 +1,5 @@ +import type { Route } from './+types/page'; + +export default function Page({ params }: Route.ComponentProps) { + return
추론 상세 id: {params.inferenceId}
; +} diff --git a/web-app/app/routes/labeling/label/page.tsx b/web-app/app/routes/labeling/label/page.tsx index e69de29..7b62d00 100644 --- a/web-app/app/routes/labeling/label/page.tsx +++ b/web-app/app/routes/labeling/label/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
라벨링 작업
; +} diff --git a/web-app/app/routes/labeling/review/page.tsx b/web-app/app/routes/labeling/review/page.tsx index e69de29..7f54ca4 100644 --- a/web-app/app/routes/labeling/review/page.tsx +++ b/web-app/app/routes/labeling/review/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
라벨링 검수
; +} diff --git a/web-app/app/routes/layout.tsx b/web-app/app/routes/layout.tsx index 7b69e96..fa62dde 100644 --- a/web-app/app/routes/layout.tsx +++ b/web-app/app/routes/layout.tsx @@ -1,10 +1,31 @@ import { Outlet } from 'react-router'; +import { LayoutMenu } from '~/shared/components/menu/LayoutMenu'; +import { MENU_ITEMS } from '~/shared/constants/menu'; + export default function Layout() { return ( -
- 기본 레이아웃 - +
+ +
+
+
+
+ +
+
+
); } diff --git a/web-app/app/routes/log/audit/page.tsx b/web-app/app/routes/log/audit/page.tsx index e69de29..d0d2b32 100644 --- a/web-app/app/routes/log/audit/page.tsx +++ b/web-app/app/routes/log/audit/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
감사 로그
; +} diff --git a/web-app/app/routes/log/system/page.tsx b/web-app/app/routes/log/system/page.tsx index e69de29..0e57abc 100644 --- a/web-app/app/routes/log/system/page.tsx +++ b/web-app/app/routes/log/system/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
시스템 로그
; +} diff --git a/web-app/app/routes/model/[id]/page.tsx b/web-app/app/routes/model/[id]/page.tsx index e69de29..b436d5f 100644 --- a/web-app/app/routes/model/[id]/page.tsx +++ b/web-app/app/routes/model/[id]/page.tsx @@ -0,0 +1,5 @@ +import type { Route } from './+types/page'; + +export default function Page({ params }: Route.ComponentProps) { + return
모델 상세 id: {params.modelId}
; +} diff --git a/web-app/app/routes/model/page.tsx b/web-app/app/routes/model/page.tsx index e69de29..0806a74 100644 --- a/web-app/app/routes/model/page.tsx +++ b/web-app/app/routes/model/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
모델 목록
; +} diff --git a/web-app/app/routes/page.tsx b/web-app/app/routes/page.tsx new file mode 100644 index 0000000..94417df --- /dev/null +++ b/web-app/app/routes/page.tsx @@ -0,0 +1,5 @@ +import { Navigate } from 'react-router'; + +export default function Page() { + return ; +} diff --git a/web-app/app/routes/schedule/page.tsx b/web-app/app/routes/schedule/page.tsx index e69de29..1f7ed6d 100644 --- a/web-app/app/routes/schedule/page.tsx +++ b/web-app/app/routes/schedule/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
스케줄 목록
; +} diff --git a/web-app/app/shared/assets/fonts/Pretendard-Bold.subset.woff b/web-app/app/shared/assets/fonts/Pretendard-Bold.subset.woff new file mode 100644 index 0000000..06ba102 Binary files /dev/null and b/web-app/app/shared/assets/fonts/Pretendard-Bold.subset.woff differ diff --git a/web-app/app/shared/assets/fonts/Pretendard-Medium.subset.woff b/web-app/app/shared/assets/fonts/Pretendard-Medium.subset.woff new file mode 100644 index 0000000..f97a78f Binary files /dev/null and b/web-app/app/shared/assets/fonts/Pretendard-Medium.subset.woff differ diff --git a/web-app/app/shared/assets/fonts/Pretendard-Regular.subset.woff b/web-app/app/shared/assets/fonts/Pretendard-Regular.subset.woff new file mode 100644 index 0000000..174736a Binary files /dev/null and b/web-app/app/shared/assets/fonts/Pretendard-Regular.subset.woff differ diff --git a/web-app/app/shared/assets/fonts/Pretendard-SemiBold.subset.woff b/web-app/app/shared/assets/fonts/Pretendard-SemiBold.subset.woff new file mode 100644 index 0000000..ee2fa3d Binary files /dev/null and b/web-app/app/shared/assets/fonts/Pretendard-SemiBold.subset.woff differ diff --git a/web-app/app/shared/components/menu/LayoutMenu.tsx b/web-app/app/shared/components/menu/LayoutMenu.tsx new file mode 100644 index 0000000..9b886a8 --- /dev/null +++ b/web-app/app/shared/components/menu/LayoutMenu.tsx @@ -0,0 +1,22 @@ +import { useCallback } from 'react'; +import { useLocation, useNavigate } from 'react-router'; + +import { Menu, type MenuItemChildrenType, type MenuItemType } from './Menu'; + +interface Props { + items: MenuItemType[]; +} + +export const LayoutMenu = ({ items }: Props) => { + const { pathname } = useLocation(); + const navigate = useNavigate(); + + const onSelectionChange = useCallback( + (menu: MenuItemChildrenType) => { + navigate(menu.menuUrl); + }, + [navigate], + ); + + return ; +}; diff --git a/web-app/app/shared/components/menu/Menu.tsx b/web-app/app/shared/components/menu/Menu.tsx new file mode 100644 index 0000000..1576e3f --- /dev/null +++ b/web-app/app/shared/components/menu/Menu.tsx @@ -0,0 +1,124 @@ +import { useCallback, useEffect, useId, useState } from 'react'; + +export type MenuItemChildrenType = { + id: string; + name: string; + menuUrl: string; +}; +export type MenuItemType = { + id: string; + name: string; + menuUrl?: null; + children: MenuItemChildrenType[]; +}; +export interface MenuProps { + items: MenuItemType[]; + onSelectionChange: (menu: MenuItemChildrenType) => void; + currentPath: string; +} +export const Menu = (props: MenuProps) => { + const { items, currentPath, onSelectionChange } = props; + const id = useId(); + + const getCurrentPathItem = useCallback(() => { + return items.find((i) => i.children.some((c) => currentPath.includes(c.menuUrl))); + }, [items, currentPath]); + + const [expandedItemKeys, setExpandedItemKeys] = useState(() => { + const item = getCurrentPathItem(); + return item ? [item.id] : []; + }); + + useEffect(() => { + const item = getCurrentPathItem(); + setExpandedItemKeys((prev) => { + if (item && prev.length === 1 && prev[0] === item.id) { + return prev; + } + return item ? [item.id] : []; + }); + }, [getCurrentPathItem]); + + return ( + + ); +}; diff --git a/web-app/app/shared/components/menu/index.ts b/web-app/app/shared/components/menu/index.ts new file mode 100644 index 0000000..2ffc9a2 --- /dev/null +++ b/web-app/app/shared/components/menu/index.ts @@ -0,0 +1,2 @@ +export { Menu } from './Menu'; +export type { MenuProps, MenuItemType, MenuItemChildrenType } from './Menu'; diff --git a/web-app/app/shared/constants/menu.ts b/web-app/app/shared/constants/menu.ts new file mode 100644 index 0000000..77ddbac --- /dev/null +++ b/web-app/app/shared/constants/menu.ts @@ -0,0 +1,53 @@ +import type { MenuItemType } from '~/shared/components/menu'; + +export const MENU_ITEMS: MenuItemType[] = [ + { + id: 'imagery', + name: '영상데이터관리', + children: [ + { id: 'aerial', name: '항공영상관리', menuUrl: '/imagery/aerial' }, + { id: 'satellite', name: '위성영상관리', menuUrl: '/imagery/satellite' }, + { id: 'drone', name: '드론영상관리', menuUrl: '/imagery/drone' }, + ], + }, + { + id: 'inference', + name: '추론관리', + children: [{ id: 'inference-list', name: '추론 목록', menuUrl: '/inference' }], + }, + { + id: 'model', + name: '모델관리', + children: [{ id: 'model-list', name: '모델 목록', menuUrl: '/model' }], + }, + { + id: 'labeling', + name: '라벨링', + children: [ + { id: 'label', name: '라벨링 작업', menuUrl: '/labeling/label' }, + { id: 'review', name: '라벨링 검수', menuUrl: '/labeling/review' }, + ], + }, + { + id: 'log', + name: '로그', + children: [ + { id: 'audit', name: '감사 로그', menuUrl: '/log/audit' }, + { id: 'system', name: '시스템 로그', menuUrl: '/log/system' }, + ], + }, + { + id: 'schedule', + name: '스케줄관리', + children: [{ id: 'schedule-list', name: '스케줄 목록', menuUrl: '/schedule' }], + }, + { + id: 'system', + name: '시스템관리', + children: [ + { id: 'code', name: '공통코드 관리', menuUrl: '/code' }, + { id: 'hyper-parameter', name: '하이퍼파라미터 설정', menuUrl: '/hyper-parameter' }, + { id: 'user', name: '사용자 관리', menuUrl: '/user' }, + ], + }, +]; diff --git a/web-app/eslint.config.js b/web-app/eslint.config.js index 069448c..dd9ac71 100644 --- a/web-app/eslint.config.js +++ b/web-app/eslint.config.js @@ -21,6 +21,8 @@ export default tseslint.config( { rules: { '@stylistic/jsx-one-expression-per-line': 'off', + '@stylistic/multiline-ternary': 'off', + 'react-hooks/set-state-in-effect': 'off', }, }, );