feat: GUARDiA Solutions Portal - React 19 + 15 solutions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.claude/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
42
CLAUDE.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# GUARDiA Solution Portal — Claude Code 가이드
|
||||||
|
|
||||||
|
> React 19 + Vite + TypeScript | 15개 솔루션 상세 페이지 독립 프로젝트
|
||||||
|
> 디자인 전면 재구성 (zioinfo-web 솔루션 메뉴 분리·리디자인)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 하네스: solution-build
|
||||||
|
|
||||||
|
**목표:** zioinfo-web 솔루션 섹션을 독립 프로젝트로 분리하고 디자인을 전면 재구성한다.
|
||||||
|
15개 GUARDiA 솔루션 상세 페이지(ITSM·ERP·CRM·OCR·BI·PMS·RPA·Groupware·Portal·Mall·CMS·MES·ESN·HRM·FA)를 포함한다.
|
||||||
|
|
||||||
|
**트리거:** 솔루션 페이지 개발, 컴포넌트 추가·수정, 디자인 변경, 신규 솔루션 등록, 빌드·배포, 다시 실행, 업데이트 요청 시 `solution-build` 스킬을 사용하라. 단순 질문은 직접 응답 가능.
|
||||||
|
|
||||||
|
**변경 이력:**
|
||||||
|
| 날짜 | 변경 내용 | 대상 | 사유 |
|
||||||
|
|------|----------|------|------|
|
||||||
|
| 2026-06-17 | 초기 하네스 구성 | 전체 | zioinfo-web 솔루션 분리 + 디자인 재구성 |
|
||||||
|
| 2026-06-17 | CI/CD 파이프라인 추가 | Jenkinsfile, devops.md, deploy.md | 구현→테스트→배포(자동)→테스트 파이프라인 구성 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 하네스: 솔루션 포털 통합
|
||||||
|
|
||||||
|
**목표:** 15개 솔루션 ID/PW·단계별 시연 가이드·모바일 앱 화면이 포함된 솔루션 포털 완성 + CMDB 자산 등록 + CI/CD + 매뉴얼 생성.
|
||||||
|
|
||||||
|
**트리거:** 솔루션 포털 구현, 시연 가이드, ID/PW 포함 가이드, CMDB 자산 등록, 지오정보기술 서버 등록, solution.zioinfo.co.kr 배포, SVG 아이콘 제작, 솔루션 매뉴얼, 다시 실행, 업데이트 요청 시 `solution-portal-orchestrator` 스킬을 사용하라.
|
||||||
|
|
||||||
|
**에이전트:**
|
||||||
|
- `solution-portal-dev` — React 19 포털 구현 (선 SVG 아이콘 직접 제작 포함)
|
||||||
|
- `solution-devops-dev` — Gitea + deploy_server + Nginx CI/CD
|
||||||
|
- `cmdb-registrar` — ITSM CMDB 기관/담당자/서버 자산 등록
|
||||||
|
- `solution-doc-writer` — guardia-docs 솔루션별 시연 매뉴얼
|
||||||
|
|
||||||
|
**아이콘 원칙:** 모든 아이콘은 SVG stroke 스타일로 직접 그린다. 외부 라이브러리 금지.
|
||||||
|
아이콘 SVG 코드: `.claude/skills/solution-portal-orchestrator/references/icon-guide.md`
|
||||||
|
솔루션 데이터: `.claude/skills/solution-portal-orchestrator/references/solutions-data.md`
|
||||||
|
|
||||||
|
**변경 이력:**
|
||||||
|
| 날짜 | 변경 내용 | 대상 | 사유 |
|
||||||
|
|------|----------|------|------|
|
||||||
|
| 2026-06-17 | solution-portal-orchestrator 하네스 구성 | agents 4개 + 스킬 + references | 솔루션 포털 + CMDB + CI/CD + 매뉴얼 통합 구축 |
|
||||||
57
Jenkinsfile
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
DEPLOY_PATH = '/opt/solution/public'
|
||||||
|
BACKUP_PATH = '/opt/solution/public_backup'
|
||||||
|
HEALTH_URL = 'http://solution.zioinfo.co.kr'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh 'npm ci'
|
||||||
|
sh 'npm run build'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
sh 'npm test -- --watchAll=false --passWithNoTests || true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deploy') {
|
||||||
|
when { branch 'main' }
|
||||||
|
steps {
|
||||||
|
sh "cp -r ${DEPLOY_PATH} ${BACKUP_PATH} 2>/dev/null || true"
|
||||||
|
sh "rsync -a --delete dist/ ${DEPLOY_PATH}/"
|
||||||
|
sh 'systemctl reload nginx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Smoke Test') {
|
||||||
|
when { branch 'main' }
|
||||||
|
steps {
|
||||||
|
sh "curl -sf ${HEALTH_URL} | grep -q 'GUARDiA' && echo '[PASS] 배포 성공' || (echo '[FAIL] smoke test 실패'; exit 1)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
failure {
|
||||||
|
echo '배포 실패 — 백업으로 롤백'
|
||||||
|
sh "cp -r ${BACKUP_PATH}/ ${DEPLOY_PATH}/ 2>/dev/null || true"
|
||||||
|
sh 'systemctl reload nginx 2>/dev/null || true'
|
||||||
|
}
|
||||||
|
success {
|
||||||
|
echo '✓ solution.zioinfo.co.kr 배포 완료'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>GUARDiA Solution Portal — 지오정보기술</title>
|
||||||
|
<meta name="description" content="GUARDiA AI 기반 엔터프라이즈 솔루션 포털. ITSM·ERP·CRM·OCR·BI·PMS·RPA·Groupware·Portal·Mall·CMS·MES·ESN·HRM 15개 솔루션 소개 및 운영 가이드." />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1888
package-lock.json
generated
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "guardia-solution-portal",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test": "echo 'No tests' && exit 0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-router-dom": "^7.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.6.0",
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/App.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||||
|
import { Routes, Route } from 'react-router-dom';
|
||||||
|
import { Navbar } from './components/Navbar';
|
||||||
|
import { HomePage } from './pages/HomePage';
|
||||||
|
import { CatalogPage } from './pages/CatalogPage';
|
||||||
|
import { SolutionDetailPage } from './pages/SolutionDetailPage';
|
||||||
|
const App = () => {
|
||||||
|
return (_jsxs(_Fragment, { children: [_jsx(Navbar, {}), _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(HomePage, {}) }), _jsx(Route, { path: "/catalog", element: _jsx(CatalogPage, {}) }), _jsx(Route, { path: "/solution/:slug", element: _jsx(SolutionDetailPage, {}) })] })] }));
|
||||||
|
};
|
||||||
|
export default App;
|
||||||
21
src/App.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Routes, Route } from 'react-router-dom';
|
||||||
|
import { Navbar } from './components/Navbar';
|
||||||
|
import { HomePage } from './pages/HomePage';
|
||||||
|
import { CatalogPage } from './pages/CatalogPage';
|
||||||
|
import { SolutionDetailPage } from './pages/SolutionDetailPage';
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="/catalog" element={<CatalogPage />} />
|
||||||
|
<Route path="/solution/:slug" element={<SolutionDetailPage />} />
|
||||||
|
</Routes>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
7
src/assets/icons/bi.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="18" y1="20" x2="18" y2="10"/>
|
||||||
|
<line x1="12" y1="20" x2="12" y2="4"/>
|
||||||
|
<line x1="6" y1="20" x2="6" y2="14"/>
|
||||||
|
<line x1="2" y1="20" x2="22" y2="20"/>
|
||||||
|
<polyline points="2,10 7,7 12,9 17,5 22,8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 379 B |
6
src/assets/icons/cms.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
||||||
|
<line x1="3" y1="9" x2="21" y2="9"/>
|
||||||
|
<line x1="3" y1="15" x2="21" y2="15"/>
|
||||||
|
<line x1="9" y1="9" x2="9" y2="21"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 339 B |
6
src/assets/icons/crm.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="9" cy="7" r="3"/>
|
||||||
|
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"/>
|
||||||
|
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
||||||
|
<path d="M21 21v-2a4 4 0 0 0-3-3.85"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 336 B |
7
src/assets/icons/erp.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M3 21h18"/>
|
||||||
|
<path d="M5 21V7l7-4 7 4v14"/>
|
||||||
|
<path d="M9 21v-4h6v4"/>
|
||||||
|
<circle cx="16.5" cy="10.5" r="1.5"/>
|
||||||
|
<path d="M14.5 10.5c0-.83.34-1.58.88-2.12"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 338 B |
6
src/assets/icons/esn.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
|
||||||
|
<line x1="7" y1="7" x2="7.01" y2="7"/>
|
||||||
|
<path d="M15 5c1 1.5 1.5 3 1.5 5s-.5 3.5-1.5 5"/>
|
||||||
|
<path d="M18 3c1.5 2 2.5 4.5 2.5 7s-1 5-2.5 7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |
8
src/assets/icons/fa.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="2" y="14" width="4" height="6" rx="1"/>
|
||||||
|
<path d="M6 17h3l3-6h5"/>
|
||||||
|
<path d="M17 11V7a2 2 0 0 0-2-2h-2"/>
|
||||||
|
<circle cx="13" cy="4" r="2"/>
|
||||||
|
<path d="M20 9l2 2-2 2"/>
|
||||||
|
<line x1="17" y1="11" x2="22" y2="11"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
10
src/assets/icons/groupware.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="5" r="2"/>
|
||||||
|
<circle cx="19" cy="12" r="2"/>
|
||||||
|
<circle cx="12" cy="19" r="2"/>
|
||||||
|
<circle cx="5" cy="12" r="2"/>
|
||||||
|
<line x1="12" y1="7" x2="17.5" y2="10.5"/>
|
||||||
|
<line x1="17.5" y1="13.5" x2="12" y2="17"/>
|
||||||
|
<line x1="12" y1="17" x2="6.5" y2="13.5"/>
|
||||||
|
<line x1="6.5" y1="10.5" x2="12" y2="7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 482 B |
6
src/assets/icons/hrm.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="9" cy="7" r="4"/>
|
||||||
|
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"/>
|
||||||
|
<polyline points="16,8 19,5 22,8"/>
|
||||||
|
<line x1="19" y1="5" x2="19" y2="14"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 334 B |
5
src/assets/icons/itsm.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="8" y="2" width="8" height="4" rx="1"/>
|
||||||
|
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
|
||||||
|
<path d="m9 12 2 2 4-4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
6
src/assets/icons/mall.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/>
|
||||||
|
<line x1="3" y1="6" x2="21" y2="6"/>
|
||||||
|
<path d="M12 10c-1 0-2 .5-2 2s1 2 2 2 2-.5 2-2-1-2-2-2z"/>
|
||||||
|
<path d="M12 8v1M10.3 9.3l.7.7M14 10l-.7.7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 381 B |
8
src/assets/icons/mes.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M3 21h18"/>
|
||||||
|
<path d="M3 21V11l5-3v3l5-3v3l5-3v10"/>
|
||||||
|
<rect x="7" y="14" width="3" height="7"/>
|
||||||
|
<rect x="14" y="14" width="3" height="7"/>
|
||||||
|
<line x1="18" y1="5" x2="18" y2="8"/>
|
||||||
|
<path d="M16 5h4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 384 B |
6
src/assets/icons/ocr.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||||
|
<polyline points="14,2 14,8 20,8"/>
|
||||||
|
<line x1="4" y1="13" x2="20" y2="13"/>
|
||||||
|
<line x1="8" y1="10" x2="8" y2="16"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 360 B |
9
src/assets/icons/pms.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="2" y="3" width="20" height="18" rx="2"/>
|
||||||
|
<line x1="8" y1="3" x2="8" y2="21"/>
|
||||||
|
<line x1="16" y1="3" x2="16" y2="21"/>
|
||||||
|
<rect x="3" y="6" width="4" height="3" rx="1"/>
|
||||||
|
<rect x="3" y="12" width="4" height="3" rx="1"/>
|
||||||
|
<rect x="9" y="6" width="6" height="3" rx="1"/>
|
||||||
|
<rect x="17" y="6" width="4" height="3" rx="1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 502 B |
7
src/assets/icons/portal.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="7" y="2" width="10" height="20" rx="2"/>
|
||||||
|
<path d="M12 10v4"/>
|
||||||
|
<circle cx="12" cy="12" r="1"/>
|
||||||
|
<path d="M4 6C2.5 7.5 2 9.5 2 12s.5 4.5 2 6"/>
|
||||||
|
<path d="M20 6c1.5 1.5 2 3.5 2 6s-.5 4.5-2 6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 376 B |
10
src/assets/icons/rpa.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="4" y="6" width="16" height="12" rx="3"/>
|
||||||
|
<circle cx="9" cy="11" r="1.5"/>
|
||||||
|
<circle cx="15" cy="11" r="1.5"/>
|
||||||
|
<path d="M9 15h6"/>
|
||||||
|
<line x1="12" y1="3" x2="12" y2="6"/>
|
||||||
|
<circle cx="12" cy="2.5" r="0.5"/>
|
||||||
|
<line x1="4" y1="11" x2="2" y2="11"/>
|
||||||
|
<line x1="20" y1="11" x2="22" y2="11"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 472 B |
3
src/assets/icons/ui/check.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="20,6 9,17 4,12"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 206 B |
3
src/assets/icons/ui/chevron-right.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="9,18 15,12 9,6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 206 B |
4
src/assets/icons/ui/copy.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
||||||
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 290 B |
5
src/assets/icons/ui/external-link.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||||
|
<polyline points="15,3 21,3 21,9"/>
|
||||||
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 318 B |
4
src/assets/icons/ui/mobile.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="5" y="2" width="14" height="20" rx="2"/>
|
||||||
|
<line x1="12" y1="18" x2="12.01" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 265 B |
6
src/components/FeatureGrid.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
export const FeatureGrid = ({ features, color }) => {
|
||||||
|
return (_jsx("div", { className: "feature-grid", children: features.map((f) => (_jsxs("div", { className: "feature-card", children: [_jsx("span", { className: "feature-check", style: { color }, children: _jsx(SolutionIcon, { slug: "check", size: 16, color: color }) }), _jsxs("div", { children: [_jsx("h4", { className: "feature-title", children: f.title }), _jsx("p", { className: "feature-desc", children: f.desc })] })] }, f.title))) }));
|
||||||
|
};
|
||||||
|
export default FeatureGrid;
|
||||||
27
src/components/FeatureGrid.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
|
||||||
|
interface FeatureGridProps {
|
||||||
|
features: { title: string; desc: string }[];
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureGrid: React.FC<FeatureGridProps> = ({ features, color }) => {
|
||||||
|
return (
|
||||||
|
<div className="feature-grid">
|
||||||
|
{features.map((f) => (
|
||||||
|
<div className="feature-card" key={f.title}>
|
||||||
|
<span className="feature-check" style={{ color }}>
|
||||||
|
<SolutionIcon slug="check" size={16} color={color} />
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<h4 className="feature-title">{f.title}</h4>
|
||||||
|
<p className="feature-desc">{f.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeatureGrid;
|
||||||
30
src/components/LoginBox.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
const CopyField = ({ label, value, color, }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const copy = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// fallback
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = value;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
}
|
||||||
|
catch { /* noop */ }
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
}
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
return (_jsxs("div", { className: "login-field", children: [_jsx("span", { className: "login-field-label", children: label }), _jsx("code", { className: "login-field-value", children: value }), _jsx("button", { className: "login-copy-btn", onClick: copy, title: "\uBCF5\uC0AC", style: { color }, children: copied ? (_jsx("span", { className: "login-copied", children: "\uBCF5\uC0AC\uB428!" })) : (_jsx(SolutionIcon, { slug: "copy", size: 16 })) })] }));
|
||||||
|
};
|
||||||
|
export const LoginBox = ({ access, color, soon }) => {
|
||||||
|
return (_jsxs("div", { className: "login-box", style: { borderColor: `${color}40`, boxShadow: `0 0 32px ${color}18` }, children: [_jsxs("div", { className: "login-box-head", children: [_jsx("span", { className: "login-box-title", children: "\uC811\uC18D \uC815\uBCF4" }), soon && _jsx("span", { className: "badge badge-soon", children: "\uBC30\uD3EC \uC608\uC815" })] }), !soon && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "login-field", children: [_jsx("span", { className: "login-field-label", children: "URL" }), _jsxs("a", { className: "login-url", href: access.url, target: "_blank", rel: "noreferrer", children: [access.url, _jsx(SolutionIcon, { slug: "external-link", size: 14 })] })] }), access.httpsUrl && (_jsxs("div", { className: "login-field", children: [_jsx("span", { className: "login-field-label", children: "HTTPS" }), _jsxs("a", { className: "login-url", href: access.httpsUrl, target: "_blank", rel: "noreferrer", children: [access.httpsUrl, _jsx(SolutionIcon, { slug: "external-link", size: 14 })] })] })), _jsx(CopyField, { label: "ID", value: access.id, color: color }), _jsx(CopyField, { label: "PW", value: access.pw, color: color })] })), soon && (_jsx("p", { className: "login-soon-text", children: "\uBCF8 \uC194\uB8E8\uC158\uC740 \uD604\uC7AC \uC900\uBE44 \uC911\uC785\uB2C8\uB2E4. \uBC30\uD3EC \uD6C4 \uC811\uC18D \uC815\uBCF4\uAC00 \uC81C\uACF5\uB429\uB2C8\uB2E4." }))] }));
|
||||||
|
};
|
||||||
|
export default LoginBox;
|
||||||
91
src/components/LoginBox.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
|
||||||
|
interface LoginBoxProps {
|
||||||
|
access: { url: string; httpsUrl?: string; id: string; pw: string };
|
||||||
|
color: string;
|
||||||
|
soon?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CopyField: React.FC<{ label: string; value: string; color: string }> = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
}) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const copy = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
} catch {
|
||||||
|
// fallback
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = value;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
try { document.execCommand('copy'); } catch { /* noop */ }
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
}
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="login-field">
|
||||||
|
<span className="login-field-label">{label}</span>
|
||||||
|
<code className="login-field-value">{value}</code>
|
||||||
|
<button className="login-copy-btn" onClick={copy} title="복사" style={{ color }}>
|
||||||
|
{copied ? (
|
||||||
|
<span className="login-copied">복사됨!</span>
|
||||||
|
) : (
|
||||||
|
<SolutionIcon slug="copy" size={16} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoginBox: React.FC<LoginBoxProps> = ({ access, color, soon }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="login-box"
|
||||||
|
style={{ borderColor: `${color}40`, boxShadow: `0 0 32px ${color}18` }}
|
||||||
|
>
|
||||||
|
<div className="login-box-head">
|
||||||
|
<span className="login-box-title">접속 정보</span>
|
||||||
|
{soon && <span className="badge badge-soon">배포 예정</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!soon && (
|
||||||
|
<>
|
||||||
|
<div className="login-field">
|
||||||
|
<span className="login-field-label">URL</span>
|
||||||
|
<a className="login-url" href={access.url} target="_blank" rel="noreferrer">
|
||||||
|
{access.url}
|
||||||
|
<SolutionIcon slug="external-link" size={14} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{access.httpsUrl && (
|
||||||
|
<div className="login-field">
|
||||||
|
<span className="login-field-label">HTTPS</span>
|
||||||
|
<a className="login-url" href={access.httpsUrl} target="_blank" rel="noreferrer">
|
||||||
|
{access.httpsUrl}
|
||||||
|
<SolutionIcon slug="external-link" size={14} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CopyField label="ID" value={access.id} color={color} />
|
||||||
|
<CopyField label="PW" value={access.pw} color={color} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{soon && (
|
||||||
|
<p className="login-soon-text">
|
||||||
|
본 솔루션은 현재 준비 중입니다. 배포 후 접속 정보가 제공됩니다.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginBox;
|
||||||
14
src/components/MobileScreenList.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
const TYPE_LABEL = {
|
||||||
|
customer: '사용자',
|
||||||
|
admin: '관리자',
|
||||||
|
field: '현장',
|
||||||
|
};
|
||||||
|
export const MobileScreenList = ({ screens, color }) => {
|
||||||
|
if (!screens.length) {
|
||||||
|
return _jsx("p", { className: "mobile-empty", children: "\uBAA8\uBC14\uC77C \uC571 \uD654\uBA74\uC740 \uC900\uBE44 \uC911\uC785\uB2C8\uB2E4." });
|
||||||
|
}
|
||||||
|
return (_jsx("div", { className: "mobile-screen-list", children: screens.map((s) => (_jsxs("div", { className: "phone-card", children: [_jsxs("div", { className: "phone-frame", style: { borderColor: `${color}55` }, children: [_jsx("div", { className: "phone-notch" }), _jsx("div", { className: "phone-screen", style: { background: `linear-gradient(160deg, ${color}22, #0f172a 70%)` }, children: _jsxs("div", { className: "phone-screen-inner", children: [_jsx("span", { className: "phone-icon", style: { color }, children: _jsx(SolutionIcon, { slug: "mobile", size: 40, color: color }) }), _jsx("span", { className: "phone-screen-name", children: s.name })] }) })] }), _jsxs("div", { className: "phone-label", children: [_jsx("span", { className: "phone-name", children: s.name }), _jsx("span", { className: "phone-type", style: { color, borderColor: `${color}55` }, children: TYPE_LABEL[s.type] || s.type })] })] }, s.name))) }));
|
||||||
|
};
|
||||||
|
export default MobileScreenList;
|
||||||
46
src/components/MobileScreenList.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
|
||||||
|
interface MobileScreenListProps {
|
||||||
|
screens: { name: string; type: string }[];
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPE_LABEL: Record<string, string> = {
|
||||||
|
customer: '사용자',
|
||||||
|
admin: '관리자',
|
||||||
|
field: '현장',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MobileScreenList: React.FC<MobileScreenListProps> = ({ screens, color }) => {
|
||||||
|
if (!screens.length) {
|
||||||
|
return <p className="mobile-empty">모바일 앱 화면은 준비 중입니다.</p>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mobile-screen-list">
|
||||||
|
{screens.map((s) => (
|
||||||
|
<div className="phone-card" key={s.name}>
|
||||||
|
<div className="phone-frame" style={{ borderColor: `${color}55` }}>
|
||||||
|
<div className="phone-notch" />
|
||||||
|
<div className="phone-screen" style={{ background: `linear-gradient(160deg, ${color}22, #0f172a 70%)` }}>
|
||||||
|
<div className="phone-screen-inner">
|
||||||
|
<span className="phone-icon" style={{ color }}>
|
||||||
|
<SolutionIcon slug="mobile" size={40} color={color} />
|
||||||
|
</span>
|
||||||
|
<span className="phone-screen-name">{s.name}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="phone-label">
|
||||||
|
<span className="phone-name">{s.name}</span>
|
||||||
|
<span className="phone-type" style={{ color, borderColor: `${color}55` }}>
|
||||||
|
{TYPE_LABEL[s.type] || s.type}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MobileScreenList;
|
||||||
6
src/components/Navbar.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
export const Navbar = () => {
|
||||||
|
return (_jsx("header", { className: "navbar", children: _jsxs("div", { className: "container navbar-inner", children: [_jsxs(Link, { to: "/", className: "navbar-logo", children: [_jsx("span", { className: "navbar-logo-mark", children: "G" }), _jsx("span", { className: "navbar-logo-text", children: "GUARDiA Solutions" })] }), _jsxs("nav", { className: "navbar-nav", children: [_jsx(Link, { to: "/catalog", className: "navbar-link", children: "\uC804\uCCB4 \uC194\uB8E8\uC158" }), _jsx("a", { className: "navbar-cta", href: "https://zioinfo.co.kr:8443", target: "_blank", rel: "noreferrer", children: "ITSM \uBC14\uB85C\uAC00\uAE30" })] })] }) }));
|
||||||
|
};
|
||||||
|
export default Navbar;
|
||||||
28
src/components/Navbar.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const Navbar: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<header className="navbar">
|
||||||
|
<div className="container navbar-inner">
|
||||||
|
<Link to="/" className="navbar-logo">
|
||||||
|
<span className="navbar-logo-mark">G</span>
|
||||||
|
<span className="navbar-logo-text">GUARDiA Solutions</span>
|
||||||
|
</Link>
|
||||||
|
<nav className="navbar-nav">
|
||||||
|
<Link to="/catalog" className="navbar-link">전체 솔루션</Link>
|
||||||
|
<a
|
||||||
|
className="navbar-cta"
|
||||||
|
href="https://zioinfo.co.kr:8443"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
ITSM 바로가기
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
7
src/components/SolutionCard.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
export const SolutionCard = ({ solution, onClick }) => {
|
||||||
|
const { slug, name, tagline, color, status, port } = solution;
|
||||||
|
return (_jsxs("button", { className: "solution-card", onClick: onClick, style: { ['--card-color']: color }, children: [_jsxs("div", { className: "solution-card-top", children: [_jsx("span", { className: "solution-card-icon", style: { color, background: `${color}1a`, borderColor: `${color}40` }, children: _jsx(SolutionIcon, { slug: slug, size: 28, color: color }) }), status === 'live' ? (_jsx("span", { className: "badge badge-live", children: "LIVE" })) : (_jsx("span", { className: "badge badge-soon", children: "SOON" }))] }), _jsx("h3", { className: "solution-card-name", children: name }), _jsx("p", { className: "solution-card-tagline", children: tagline }), _jsxs("div", { className: "solution-card-foot", children: [_jsxs("span", { className: "solution-card-port", children: [":", port] }), _jsxs("span", { className: "solution-card-more", style: { color }, children: ["\uC0C1\uC138 \uBCF4\uAE30", _jsx(SolutionIcon, { slug: "chevron-right", size: 14, color: color })] })] })] }));
|
||||||
|
};
|
||||||
|
export default SolutionCard;
|
||||||
44
src/components/SolutionCard.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { Solution } from '../data/solutions';
|
||||||
|
import { SolutionIcon } from './SolutionIcon';
|
||||||
|
|
||||||
|
interface SolutionCardProps {
|
||||||
|
solution: Solution;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SolutionCard: React.FC<SolutionCardProps> = ({ solution, onClick }) => {
|
||||||
|
const { slug, name, tagline, color, status, port } = solution;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="solution-card"
|
||||||
|
onClick={onClick}
|
||||||
|
style={{ ['--card-color' as string]: color }}
|
||||||
|
>
|
||||||
|
<div className="solution-card-top">
|
||||||
|
<span
|
||||||
|
className="solution-card-icon"
|
||||||
|
style={{ color, background: `${color}1a`, borderColor: `${color}40` }}
|
||||||
|
>
|
||||||
|
<SolutionIcon slug={slug} size={28} color={color} />
|
||||||
|
</span>
|
||||||
|
{status === 'live' ? (
|
||||||
|
<span className="badge badge-live">LIVE</span>
|
||||||
|
) : (
|
||||||
|
<span className="badge badge-soon">SOON</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h3 className="solution-card-name">{name}</h3>
|
||||||
|
<p className="solution-card-tagline">{tagline}</p>
|
||||||
|
<div className="solution-card-foot">
|
||||||
|
<span className="solution-card-port">:{port}</span>
|
||||||
|
<span className="solution-card-more" style={{ color }}>
|
||||||
|
상세 보기
|
||||||
|
<SolutionIcon slug="chevron-right" size={14} color={color} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SolutionCard;
|
||||||
31
src/components/SolutionIcon.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
const ICON_PATHS = {
|
||||||
|
itsm: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "8", y: "2", width: "8", height: "4", rx: "1" }), _jsx("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }), _jsx("path", { d: "m9 12 2 2 4-4" })] })),
|
||||||
|
erp: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M3 21h18" }), _jsx("path", { d: "M5 21V7l7-4 7 4v14" }), _jsx("path", { d: "M9 21v-4h6v4" }), _jsx("circle", { cx: "16.5", cy: "10.5", r: "1.5" }), _jsx("path", { d: "M14.5 10.5c0-.83.34-1.58.88-2.12" })] })),
|
||||||
|
crm: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "9", cy: "7", r: "3" }), _jsx("path", { d: "M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2" }), _jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" }), _jsx("path", { d: "M21 21v-2a4 4 0 0 0-3-3.85" })] })),
|
||||||
|
ocr: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), _jsx("polyline", { points: "14,2 14,8 20,8" }), _jsx("line", { x1: "4", y1: "13", x2: "20", y2: "13" }), _jsx("line", { x1: "8", y1: "10", x2: "8", y2: "16" })] })),
|
||||||
|
bi: (_jsxs(_Fragment, { children: [_jsx("line", { x1: "18", y1: "20", x2: "18", y2: "10" }), _jsx("line", { x1: "12", y1: "20", x2: "12", y2: "4" }), _jsx("line", { x1: "6", y1: "20", x2: "6", y2: "14" }), _jsx("line", { x1: "2", y1: "20", x2: "22", y2: "20" }), _jsx("polyline", { points: "2,10 7,7 12,9 17,5 22,8" })] })),
|
||||||
|
pms: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "2", y: "3", width: "20", height: "18", rx: "2" }), _jsx("line", { x1: "8", y1: "3", x2: "8", y2: "21" }), _jsx("line", { x1: "16", y1: "3", x2: "16", y2: "21" }), _jsx("rect", { x: "3", y: "6", width: "4", height: "3", rx: "1" }), _jsx("rect", { x: "3", y: "12", width: "4", height: "3", rx: "1" }), _jsx("rect", { x: "9", y: "6", width: "6", height: "3", rx: "1" }), _jsx("rect", { x: "17", y: "6", width: "4", height: "3", rx: "1" })] })),
|
||||||
|
rpa: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "4", y: "6", width: "16", height: "12", rx: "3" }), _jsx("circle", { cx: "9", cy: "11", r: "1.5" }), _jsx("circle", { cx: "15", cy: "11", r: "1.5" }), _jsx("path", { d: "M9 15h6" }), _jsx("line", { x1: "12", y1: "3", x2: "12", y2: "6" }), _jsx("circle", { cx: "12", cy: "2.5", r: "0.5" }), _jsx("line", { x1: "4", y1: "11", x2: "2", y2: "11" }), _jsx("line", { x1: "20", y1: "11", x2: "22", y2: "11" })] })),
|
||||||
|
groupware: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "5", r: "2" }), _jsx("circle", { cx: "19", cy: "12", r: "2" }), _jsx("circle", { cx: "12", cy: "19", r: "2" }), _jsx("circle", { cx: "5", cy: "12", r: "2" }), _jsx("line", { x1: "12", y1: "7", x2: "17.5", y2: "10.5" }), _jsx("line", { x1: "17.5", y1: "13.5", x2: "12", y2: "17" }), _jsx("line", { x1: "12", y1: "17", x2: "6.5", y2: "13.5" }), _jsx("line", { x1: "6.5", y1: "10.5", x2: "12", y2: "7" })] })),
|
||||||
|
portal: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "7", y: "2", width: "10", height: "20", rx: "2" }), _jsx("path", { d: "M12 10v4" }), _jsx("circle", { cx: "12", cy: "12", r: "1" }), _jsx("path", { d: "M4 6C2.5 7.5 2 9.5 2 12s.5 4.5 2 6" }), _jsx("path", { d: "M20 6c1.5 1.5 2 3.5 2 6s-.5 4.5-2 6" })] })),
|
||||||
|
mall: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z" }), _jsx("line", { x1: "3", y1: "6", x2: "21", y2: "6" }), _jsx("path", { d: "M12 10c-1 0-2 .5-2 2s1 2 2 2 2-.5 2-2-1-2-2-2z" }), _jsx("path", { d: "M12 8v1M10.3 9.3l.7.7M14 10l-.7.7" })] })),
|
||||||
|
cms: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), _jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }), _jsx("line", { x1: "3", y1: "15", x2: "21", y2: "15" }), _jsx("line", { x1: "9", y1: "9", x2: "9", y2: "21" })] })),
|
||||||
|
mes: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M3 21h18" }), _jsx("path", { d: "M3 21V11l5-3v3l5-3v3l5-3v10" }), _jsx("rect", { x: "7", y: "14", width: "3", height: "7" }), _jsx("rect", { x: "14", y: "14", width: "3", height: "7" }), _jsx("line", { x1: "18", y1: "5", x2: "18", y2: "8" }), _jsx("path", { d: "M16 5h4" })] })),
|
||||||
|
hrm: (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "9", cy: "7", r: "4" }), _jsx("path", { d: "M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2" }), _jsx("polyline", { points: "16,8 19,5 22,8" }), _jsx("line", { x1: "19", y1: "5", x2: "19", y2: "14" })] })),
|
||||||
|
esn: (_jsxs(_Fragment, { children: [_jsx("path", { d: "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" }), _jsx("line", { x1: "7", y1: "7", x2: "7.01", y2: "7" }), _jsx("path", { d: "M15 5c1 1.5 1.5 3 1.5 5s-.5 3.5-1.5 5" }), _jsx("path", { d: "M18 3c1.5 2 2.5 4.5 2.5 7s-1 5-2.5 7" })] })),
|
||||||
|
fa: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "2", y: "14", width: "4", height: "6", rx: "1" }), _jsx("path", { d: "M6 17h3l3-6h5" }), _jsx("path", { d: "M17 11V7a2 2 0 0 0-2-2h-2" }), _jsx("circle", { cx: "13", cy: "4", r: "2" }), _jsx("path", { d: "M20 9l2 2-2 2" }), _jsx("line", { x1: "17", y1: "11", x2: "22", y2: "11" })] })),
|
||||||
|
// UI icons
|
||||||
|
copy: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })),
|
||||||
|
'external-link': (_jsxs(_Fragment, { children: [_jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }), _jsx("polyline", { points: "15,3 21,3 21,9" }), _jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })] })),
|
||||||
|
mobile: (_jsxs(_Fragment, { children: [_jsx("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2" }), _jsx("line", { x1: "12", y1: "18", x2: "12.01", y2: "18" })] })),
|
||||||
|
check: _jsx("polyline", { points: "20,6 9,17 4,12" }),
|
||||||
|
'chevron-right': _jsx("polyline", { points: "9,18 15,12 9,6" }),
|
||||||
|
};
|
||||||
|
export const SolutionIcon = ({ slug, size = 24, color }) => {
|
||||||
|
const paths = ICON_PATHS[slug];
|
||||||
|
if (!paths)
|
||||||
|
return null;
|
||||||
|
return (_jsx("svg", { viewBox: "0 0 24 24", width: size, height: size, fill: "none", stroke: color || 'currentColor', strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: paths }));
|
||||||
|
};
|
||||||
|
export default SolutionIcon;
|
||||||
193
src/components/SolutionIcon.tsx
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SolutionIconProps {
|
||||||
|
slug: string;
|
||||||
|
size?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Paths = React.ReactNode;
|
||||||
|
|
||||||
|
const ICON_PATHS: Record<string, Paths> = {
|
||||||
|
itsm: (
|
||||||
|
<>
|
||||||
|
<rect x="8" y="2" width="8" height="4" rx="1" />
|
||||||
|
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
|
||||||
|
<path d="m9 12 2 2 4-4" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
erp: (
|
||||||
|
<>
|
||||||
|
<path d="M3 21h18" />
|
||||||
|
<path d="M5 21V7l7-4 7 4v14" />
|
||||||
|
<path d="M9 21v-4h6v4" />
|
||||||
|
<circle cx="16.5" cy="10.5" r="1.5" />
|
||||||
|
<path d="M14.5 10.5c0-.83.34-1.58.88-2.12" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
crm: (
|
||||||
|
<>
|
||||||
|
<circle cx="9" cy="7" r="3" />
|
||||||
|
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2" />
|
||||||
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||||
|
<path d="M21 21v-2a4 4 0 0 0-3-3.85" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
ocr: (
|
||||||
|
<>
|
||||||
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||||
|
<polyline points="14,2 14,8 20,8" />
|
||||||
|
<line x1="4" y1="13" x2="20" y2="13" />
|
||||||
|
<line x1="8" y1="10" x2="8" y2="16" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
bi: (
|
||||||
|
<>
|
||||||
|
<line x1="18" y1="20" x2="18" y2="10" />
|
||||||
|
<line x1="12" y1="20" x2="12" y2="4" />
|
||||||
|
<line x1="6" y1="20" x2="6" y2="14" />
|
||||||
|
<line x1="2" y1="20" x2="22" y2="20" />
|
||||||
|
<polyline points="2,10 7,7 12,9 17,5 22,8" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
pms: (
|
||||||
|
<>
|
||||||
|
<rect x="2" y="3" width="20" height="18" rx="2" />
|
||||||
|
<line x1="8" y1="3" x2="8" y2="21" />
|
||||||
|
<line x1="16" y1="3" x2="16" y2="21" />
|
||||||
|
<rect x="3" y="6" width="4" height="3" rx="1" />
|
||||||
|
<rect x="3" y="12" width="4" height="3" rx="1" />
|
||||||
|
<rect x="9" y="6" width="6" height="3" rx="1" />
|
||||||
|
<rect x="17" y="6" width="4" height="3" rx="1" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
rpa: (
|
||||||
|
<>
|
||||||
|
<rect x="4" y="6" width="16" height="12" rx="3" />
|
||||||
|
<circle cx="9" cy="11" r="1.5" />
|
||||||
|
<circle cx="15" cy="11" r="1.5" />
|
||||||
|
<path d="M9 15h6" />
|
||||||
|
<line x1="12" y1="3" x2="12" y2="6" />
|
||||||
|
<circle cx="12" cy="2.5" r="0.5" />
|
||||||
|
<line x1="4" y1="11" x2="2" y2="11" />
|
||||||
|
<line x1="20" y1="11" x2="22" y2="11" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
groupware: (
|
||||||
|
<>
|
||||||
|
<circle cx="12" cy="5" r="2" />
|
||||||
|
<circle cx="19" cy="12" r="2" />
|
||||||
|
<circle cx="12" cy="19" r="2" />
|
||||||
|
<circle cx="5" cy="12" r="2" />
|
||||||
|
<line x1="12" y1="7" x2="17.5" y2="10.5" />
|
||||||
|
<line x1="17.5" y1="13.5" x2="12" y2="17" />
|
||||||
|
<line x1="12" y1="17" x2="6.5" y2="13.5" />
|
||||||
|
<line x1="6.5" y1="10.5" x2="12" y2="7" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
portal: (
|
||||||
|
<>
|
||||||
|
<rect x="7" y="2" width="10" height="20" rx="2" />
|
||||||
|
<path d="M12 10v4" />
|
||||||
|
<circle cx="12" cy="12" r="1" />
|
||||||
|
<path d="M4 6C2.5 7.5 2 9.5 2 12s.5 4.5 2 6" />
|
||||||
|
<path d="M20 6c1.5 1.5 2 3.5 2 6s-.5 4.5-2 6" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
mall: (
|
||||||
|
<>
|
||||||
|
<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z" />
|
||||||
|
<line x1="3" y1="6" x2="21" y2="6" />
|
||||||
|
<path d="M12 10c-1 0-2 .5-2 2s1 2 2 2 2-.5 2-2-1-2-2-2z" />
|
||||||
|
<path d="M12 8v1M10.3 9.3l.7.7M14 10l-.7.7" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
cms: (
|
||||||
|
<>
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||||
|
<line x1="3" y1="9" x2="21" y2="9" />
|
||||||
|
<line x1="3" y1="15" x2="21" y2="15" />
|
||||||
|
<line x1="9" y1="9" x2="9" y2="21" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
mes: (
|
||||||
|
<>
|
||||||
|
<path d="M3 21h18" />
|
||||||
|
<path d="M3 21V11l5-3v3l5-3v3l5-3v10" />
|
||||||
|
<rect x="7" y="14" width="3" height="7" />
|
||||||
|
<rect x="14" y="14" width="3" height="7" />
|
||||||
|
<line x1="18" y1="5" x2="18" y2="8" />
|
||||||
|
<path d="M16 5h4" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
hrm: (
|
||||||
|
<>
|
||||||
|
<circle cx="9" cy="7" r="4" />
|
||||||
|
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2" />
|
||||||
|
<polyline points="16,8 19,5 22,8" />
|
||||||
|
<line x1="19" y1="5" x2="19" y2="14" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
esn: (
|
||||||
|
<>
|
||||||
|
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" />
|
||||||
|
<line x1="7" y1="7" x2="7.01" y2="7" />
|
||||||
|
<path d="M15 5c1 1.5 1.5 3 1.5 5s-.5 3.5-1.5 5" />
|
||||||
|
<path d="M18 3c1.5 2 2.5 4.5 2.5 7s-1 5-2.5 7" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
fa: (
|
||||||
|
<>
|
||||||
|
<rect x="2" y="14" width="4" height="6" rx="1" />
|
||||||
|
<path d="M6 17h3l3-6h5" />
|
||||||
|
<path d="M17 11V7a2 2 0 0 0-2-2h-2" />
|
||||||
|
<circle cx="13" cy="4" r="2" />
|
||||||
|
<path d="M20 9l2 2-2 2" />
|
||||||
|
<line x1="17" y1="11" x2="22" y2="11" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
// UI icons
|
||||||
|
copy: (
|
||||||
|
<>
|
||||||
|
<rect x="9" y="9" width="13" height="13" rx="2" />
|
||||||
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
'external-link': (
|
||||||
|
<>
|
||||||
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||||
|
<polyline points="15,3 21,3 21,9" />
|
||||||
|
<line x1="10" y1="14" x2="21" y2="3" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
mobile: (
|
||||||
|
<>
|
||||||
|
<rect x="5" y="2" width="14" height="20" rx="2" />
|
||||||
|
<line x1="12" y1="18" x2="12.01" y2="18" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
check: <polyline points="20,6 9,17 4,12" />,
|
||||||
|
'chevron-right': <polyline points="9,18 15,12 9,6" />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SolutionIcon: React.FC<SolutionIconProps> = ({ slug, size = 24, color }) => {
|
||||||
|
const paths = ICON_PATHS[slug];
|
||||||
|
if (!paths) return null;
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
fill="none"
|
||||||
|
stroke={color || 'currentColor'}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{paths}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SolutionIcon;
|
||||||
5
src/components/StepGuide.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
export const StepGuide = ({ steps, color }) => {
|
||||||
|
return (_jsx("ol", { className: "step-guide", children: steps.map((s, i) => (_jsxs("li", { className: "step-guide-item", children: [_jsxs("div", { className: "step-guide-rail", children: [_jsx("span", { className: "step-guide-num", style: { background: color }, children: s.num }), i < steps.length - 1 && _jsx("span", { className: "step-guide-line" })] }), _jsxs("div", { className: "step-guide-body", children: [_jsx("h4", { children: s.title }), _jsx("p", { children: s.desc })] })] }, s.num))) }));
|
||||||
|
};
|
||||||
|
export default StepGuide;
|
||||||
29
src/components/StepGuide.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface StepGuideProps {
|
||||||
|
steps: { num: number; title: string; desc: string }[];
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StepGuide: React.FC<StepGuideProps> = ({ steps, color }) => {
|
||||||
|
return (
|
||||||
|
<ol className="step-guide">
|
||||||
|
{steps.map((s, i) => (
|
||||||
|
<li className="step-guide-item" key={s.num}>
|
||||||
|
<div className="step-guide-rail">
|
||||||
|
<span className="step-guide-num" style={{ background: color }}>
|
||||||
|
{s.num}
|
||||||
|
</span>
|
||||||
|
{i < steps.length - 1 && <span className="step-guide-line" />}
|
||||||
|
</div>
|
||||||
|
<div className="step-guide-body">
|
||||||
|
<h4>{s.title}</h4>
|
||||||
|
<p>{s.desc}</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepGuide;
|
||||||
521
src/data/solutions.js
Normal file
@ -0,0 +1,521 @@
|
|||||||
|
const IP = '101.79.17.164';
|
||||||
|
const url = (port) => `http://${IP}:${port}`;
|
||||||
|
export const SOLUTIONS = [
|
||||||
|
{
|
||||||
|
id: 'itsm',
|
||||||
|
slug: 'itsm',
|
||||||
|
name: 'GUARDiA ITSM',
|
||||||
|
tagline: 'AI 기반 레거시 인프라 자율 운영 플랫폼',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 9001,
|
||||||
|
color: '#6366f1',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(9001), httpsUrl: 'https://zioinfo.co.kr:8443', id: 'admin', pw: '1111' },
|
||||||
|
features: [
|
||||||
|
{ title: 'SR 자동화', desc: 'SR(서비스요청) 접수·처리·완료 전 과정 자동화' },
|
||||||
|
{ title: 'CMDB 자산관리', desc: 'CMDB 서버 자산 등록·조회·의존성 관리' },
|
||||||
|
{ title: 'AI 자연어 명령', desc: '`/배포 서버A 앱B` 자연어 명령으로 운영 자동화' },
|
||||||
|
{ title: '인시던트·변경관리', desc: '인시던트 대응 및 CAB 승인 변경관리' },
|
||||||
|
{ title: 'SLA 모니터링', desc: 'SLA 실시간 모니터링 및 위반 알림' },
|
||||||
|
{ title: '배포 자동화', desc: 'SSH/SFTP 에이전트리스 배포 자동화' },
|
||||||
|
{ title: '보안 점검', desc: 'CSAP·ISMS 보안 점검 자동화' },
|
||||||
|
{ title: '메신저 봇 연동', desc: '`/sr`, `/status` 메신저 봇 명령어 연동' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'https://zioinfo.co.kr:8443 접속 → admin / 1111 로그인' },
|
||||||
|
{ num: 2, title: '대시보드 확인', desc: '오늘 SR 수, 처리율, SLA 현황 확인' },
|
||||||
|
{ num: 3, title: 'SR 접수', desc: '[새 요청] → 제목 입력 → 자동 분류 확인' },
|
||||||
|
{ num: 4, title: 'AI 명령', desc: '메신저 탭 → `/sr 서버 장애 발생` 입력' },
|
||||||
|
{ num: 5, title: '배포', desc: '[배포관리] → [새 배포] → 서버 선택 → 실행' },
|
||||||
|
{ num: 6, title: 'CMDB', desc: '[자산관리] → 서버 목록 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'SR 목록', type: 'customer' },
|
||||||
|
{ name: 'SR 상세', type: 'customer' },
|
||||||
|
{ name: '빠른 SR 등록', type: 'customer' },
|
||||||
|
{ name: '승인 요청', type: 'admin' },
|
||||||
|
{ name: '서버 현황', type: 'admin' },
|
||||||
|
{ name: 'AI 챗봇', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'erp',
|
||||||
|
slug: 'erp',
|
||||||
|
name: 'GUARDiA ERP',
|
||||||
|
tagline: 'AI 기반 스마트 엔터프라이즈 자원관리',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 8003,
|
||||||
|
color: '#10b981',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8003), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '전표 자동 분류', desc: 'AI 기반 전표 자동 분류 및 계정 매핑' },
|
||||||
|
{ title: '예산·KPI', desc: '예산 관리 및 KPI 대시보드' },
|
||||||
|
{ title: '구매 발주', desc: '구매 발주 · 입고 처리 워크플로우' },
|
||||||
|
{ title: '인사·급여 연동', desc: 'HRM 인사 · 급여 시스템 연동' },
|
||||||
|
{ title: '영업·매출 분석', desc: '영업 활동 및 매출 분석' },
|
||||||
|
{ title: 'FinOps', desc: 'FinOps 클라우드 비용 추적' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8003 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '대시보드', desc: '매출·비용·KPI 현황 확인' },
|
||||||
|
{ num: 3, title: '재무', desc: '[전표입력] → 항목 입력 → [AI 분류] 클릭 → 계정 자동 입력 확인' },
|
||||||
|
{ num: 4, title: '예산', desc: '[예산현황] → 부서별 집행률 확인' },
|
||||||
|
{ num: 5, title: '구매', desc: '[발주서] → [신규] → 품목 입력 → 공급업체 선택 → 승인 요청' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'KPI 대시보드', type: 'admin' },
|
||||||
|
{ name: '전표 조회', type: 'admin' },
|
||||||
|
{ name: '예산 현황', type: 'admin' },
|
||||||
|
{ name: '구매 승인', type: 'admin' },
|
||||||
|
{ name: '영업 매출', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'crm',
|
||||||
|
slug: 'crm',
|
||||||
|
name: 'GUARDiA CRM',
|
||||||
|
tagline: 'AI 기반 공공기관 고객관계관리',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 8004,
|
||||||
|
color: '#f59e0b',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8004), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '고객 관리', desc: '고객(기관) 등록 · 히스토리 관리' },
|
||||||
|
{ title: '영업 파이프라인', desc: '영업기회 파이프라인 단계별 관리' },
|
||||||
|
{ title: '계약·입찰 추적', desc: '나라장터 G2B 연동 계약 · 입찰 추적' },
|
||||||
|
{ title: '캠페인 관리', desc: '마케팅 캠페인 기획·실행·분석' },
|
||||||
|
{ title: 'AI 고객 인사이트', desc: 'AI 기반 고객 행동 인사이트' },
|
||||||
|
{ title: 'ITSM SR 연동', desc: '고객 문의 → SR 자동 생성' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8004 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '대시보드', desc: '영업 파이프라인 현황 확인' },
|
||||||
|
{ num: 3, title: '고객', desc: '[고객등록] → 기관명·담당자·연락처 입력' },
|
||||||
|
{ num: 4, title: '영업기회', desc: '[새 기회] → 고객 연결 → 단계 설정 (발굴→제안→협상→계약)' },
|
||||||
|
{ num: 5, title: '계약', desc: '[계약등록] → 나라장터 계약번호 입력 → OCR로 계약서 자동 파싱' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '고객 목록', type: 'admin' },
|
||||||
|
{ name: '영업기회', type: 'admin' },
|
||||||
|
{ name: '계약 현황', type: 'admin' },
|
||||||
|
{ name: '입찰 알림', type: 'admin' },
|
||||||
|
{ name: 'AI 분석', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ocr',
|
||||||
|
slug: 'ocr',
|
||||||
|
name: 'GUARDiA OCR',
|
||||||
|
tagline: 'AI 기반 지능형 문서 인식 플랫폼',
|
||||||
|
category: 'AI문서',
|
||||||
|
port: 8005,
|
||||||
|
color: '#8b5cf6',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8005), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '계약서 등록', desc: '계약서 → 나라장터 계약 자동 등록' },
|
||||||
|
{ title: '납품서 처리', desc: '납품서 → CMDB 서버 자동 등록' },
|
||||||
|
{ title: '청구서 처리', desc: '청구서 → 세금계산서 처리' },
|
||||||
|
{ title: '장애보고서 → SR', desc: '장애보고서 이미지 → SR 자동 생성' },
|
||||||
|
{ title: '회의록 → SR', desc: '회의록 → 액션아이템 SR 생성' },
|
||||||
|
{ title: '감사보고서', desc: '감사보고서 → CSAP 준수율 업데이트' },
|
||||||
|
{ title: '브랜드 계약서', desc: '현대백화점 등 브랜드 계약서 → 계약 이력' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8005 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '문서 업로드', desc: '[문서 업로드] → 문서 이미지/PDF 선택' },
|
||||||
|
{ num: 3, title: '워크플로우 선택', desc: '워크플로우 선택 → 장애보고서→SR 선택' },
|
||||||
|
{ num: 4, title: 'OCR 실행', desc: '[OCR 실행] 클릭 → AI 텍스트 추출 확인' },
|
||||||
|
{ num: 5, title: 'SR 생성', desc: '추출 결과 확인 → [SR 자동 생성] 클릭 → ITSM SR 생성 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '카메라 촬영', type: 'field' },
|
||||||
|
{ name: '문서 업로드', type: 'field' },
|
||||||
|
{ name: 'OCR 결과', type: 'customer' },
|
||||||
|
{ name: '워크플로우', type: 'customer' },
|
||||||
|
{ name: '처리 이력', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bi',
|
||||||
|
slug: 'bi',
|
||||||
|
name: 'GUARDiA BI',
|
||||||
|
tagline: 'AI 기반 비즈니스 인텔리전스 플랫폼',
|
||||||
|
category: 'AI문서',
|
||||||
|
port: 8006,
|
||||||
|
color: '#06b6d4',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8006), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '데이터소스 연결', desc: 'PostgreSQL · API · CSV 데이터소스 연결' },
|
||||||
|
{ title: '대시보드 빌더', desc: '드래그앤드롭 대시보드 빌더' },
|
||||||
|
{ title: 'KPI 위젯·알림', desc: 'KPI 위젯 및 실시간 알림' },
|
||||||
|
{ title: 'AI Text-to-SQL', desc: '자연어 → SQL 쿼리 자동 생성' },
|
||||||
|
{ title: '예측·이상탐지', desc: 'AI 예측 및 이상탐지 분석' },
|
||||||
|
{ title: 'ITSM 데이터 피드', desc: 'SR·SLA·배포 통계 데이터 피드' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8006 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '데이터소스', desc: '[추가] → PostgreSQL 선택 → ITSM DB 연결' },
|
||||||
|
{ num: 3, title: '대시보드', desc: '[새 대시보드] → 위젯 추가 → 막대/라인/KPI 선택' },
|
||||||
|
{ num: 4, title: 'AI 쿼리', desc: '검색창에 "이번 달 미처리 SR 현황" 입력 → SQL 자동 생성 확인' },
|
||||||
|
{ num: 5, title: '리포트', desc: '[내보내기] → PDF 저장' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'KPI 대시보드', type: 'admin' },
|
||||||
|
{ name: '차트 조회', type: 'admin' },
|
||||||
|
{ name: 'AI 인사이트', type: 'admin' },
|
||||||
|
{ name: '리포트', type: 'admin' },
|
||||||
|
{ name: '알림', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pms',
|
||||||
|
slug: 'pms',
|
||||||
|
name: 'GUARDiA PMS',
|
||||||
|
tagline: 'JIRA 호환 AI 프로젝트 관리 시스템',
|
||||||
|
category: '프로젝트',
|
||||||
|
port: 8007,
|
||||||
|
color: '#f97316',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8007), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '이슈 추적', desc: '이슈 생성·추적 (JIRA 호환)' },
|
||||||
|
{ title: 'Scrum·Kanban', desc: 'Scrum · Kanban 보드' },
|
||||||
|
{ title: 'WBS·마일스톤', desc: 'WBS · 마일스톤 관리' },
|
||||||
|
{ title: '전자결재', desc: '다단계 결재선 전자결재' },
|
||||||
|
{ title: 'SI 산출물', desc: 'SI 산출물 관리 (감리/준공)' },
|
||||||
|
{ title: '요구사항 추적', desc: '요구사항 추적 (RTM)' },
|
||||||
|
{ title: 'AI 회의록', desc: 'AI 회의록 자동 작성' },
|
||||||
|
{ title: 'RFP 분석', desc: 'RFP → 요구사항분석서 자동 생성' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8007 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '프로젝트', desc: '[새 프로젝트] → 이름·유형(Scrum) 선택' },
|
||||||
|
{ num: 3, title: '백로그', desc: '[이슈 추가] → 제목·담당자·우선순위 입력' },
|
||||||
|
{ num: 4, title: '보드', desc: 'Kanban 뷰 전환 → 이슈 드래그앤드롭으로 상태 변경' },
|
||||||
|
{ num: 5, title: 'AI 기능', desc: '[회의록] → 텍스트 붙여넣기 → [액션아이템 추출] 클릭' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '내 이슈', type: 'customer' },
|
||||||
|
{ name: '보드', type: 'customer' },
|
||||||
|
{ name: '스프린트', type: 'customer' },
|
||||||
|
{ name: '승인 요청', type: 'admin' },
|
||||||
|
{ name: 'WBS 조회', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rpa',
|
||||||
|
slug: 'rpa',
|
||||||
|
name: 'GUARDiA RPA',
|
||||||
|
tagline: 'AI 기반 노코드 업무 자동화 플랫폼',
|
||||||
|
category: '프로젝트',
|
||||||
|
port: 8008,
|
||||||
|
color: '#ec4899',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8008), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '노코드 워크플로우', desc: '노코드 워크플로우 빌더' },
|
||||||
|
{ title: '봇 스케줄링', desc: '봇 등록 · 스케줄링' },
|
||||||
|
{ title: '자연어 생성', desc: '자연어 → 워크플로우 자동 생성' },
|
||||||
|
{ title: 'ROI 분석', desc: 'ROI 분석 대시보드' },
|
||||||
|
{ title: 'ITSM 연동', desc: 'ITSM SR 자동 처리 연동' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8008 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '워크플로우', desc: '[새 워크플로우] → 노드 드래그앤드롭' },
|
||||||
|
{ num: 3, title: '트리거 설정', desc: '[스케줄] 선택 → 매일 09:00 설정' },
|
||||||
|
{ num: 4, title: '액션 추가', desc: '[ITSM SR 생성] 노드 추가 → 파라미터 입력' },
|
||||||
|
{ num: 5, title: 'AI 생성', desc: '입력창에 "매일 아침 서버 상태 체크 후 이상 시 SR 생성" → [생성] 클릭' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '봇 현황', type: 'admin' },
|
||||||
|
{ name: '워크플로우 목록', type: 'admin' },
|
||||||
|
{ name: '실행 이력', type: 'admin' },
|
||||||
|
{ name: 'ROI 현황', type: 'admin' },
|
||||||
|
{ name: '알림', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'groupware',
|
||||||
|
slug: 'groupware',
|
||||||
|
name: 'GUARDiA Groupware',
|
||||||
|
tagline: 'AI 기반 통합 협업 그룹웨어',
|
||||||
|
category: '협업',
|
||||||
|
port: 8009,
|
||||||
|
color: '#14b8a6',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8009), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '전자결재', desc: '다단계 결재선 · 대리결재' },
|
||||||
|
{ title: '게시판', desc: '공지 · 자유 · 팀별 게시판' },
|
||||||
|
{ title: '일정/캘린더', desc: '팀 공유 일정/캘린더' },
|
||||||
|
{ title: '근태/휴가', desc: '근태/휴가 신청·승인' },
|
||||||
|
{ title: '자원예약', desc: '회의실 · 장비 자원예약' },
|
||||||
|
{ title: '주소록·조직도', desc: '주소록 · 조직도 관리' },
|
||||||
|
{ title: '문서함', desc: '버전관리 문서함' },
|
||||||
|
{ title: '쪽지', desc: '내부 메시지 쪽지' },
|
||||||
|
{ title: 'AI 문서 요약', desc: 'AI 문서 자동 요약' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8009 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '전자결재', desc: '[기안] → 양식 선택 → 내용 작성 → 결재선 설정 → 상신' },
|
||||||
|
{ num: 3, title: '일정', desc: '[일정등록] → 제목·날짜·참석자 입력 → 팀 공유' },
|
||||||
|
{ num: 4, title: '근태', desc: '[휴가신청] → 날짜·종류 선택 → 결재 요청' },
|
||||||
|
{ num: 5, title: '회의실', desc: '[예약] → 날짜·시간·회의실 선택 → 확정' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '결재함', type: 'admin' },
|
||||||
|
{ name: '공지사항', type: 'customer' },
|
||||||
|
{ name: '일정', type: 'customer' },
|
||||||
|
{ name: '근태', type: 'customer' },
|
||||||
|
{ name: '주소록', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'portal',
|
||||||
|
slug: 'portal',
|
||||||
|
name: 'GUARDiA Portal',
|
||||||
|
tagline: 'SSO 단일 로그인 통합 업무 포털',
|
||||||
|
category: '협업',
|
||||||
|
port: 8010,
|
||||||
|
color: '#7c3aed',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8010), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: 'SSO 단일 로그인', desc: 'GUARDiA 전 시스템 SSO 바로가기' },
|
||||||
|
{ title: '위젯 대시보드', desc: '역할별 위젯 대시보드' },
|
||||||
|
{ title: '즐겨찾기', desc: '즐겨찾기 시스템' },
|
||||||
|
{ title: '통합 공지', desc: '통합 공지사항' },
|
||||||
|
{ title: '알림 센터', desc: '전 시스템 통합 알림 센터' },
|
||||||
|
{ title: '통합 검색', desc: '전 시스템 통합 검색' },
|
||||||
|
{ title: '마이페이지', desc: '프로필 · 설정 마이페이지' },
|
||||||
|
{ title: 'AI 공지 요약', desc: 'AI 공지 자동 요약' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8010 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '포털 메인', desc: '9개 시스템 바로가기 카드 확인' },
|
||||||
|
{ num: 3, title: '위젯', desc: '[위젯 추가] → SR현황·KPI·일정 선택 → 대시보드 배치' },
|
||||||
|
{ num: 4, title: '통합검색', desc: '검색창에 "장애 처리" 입력 → ITSM·KB·문서 통합 결과 확인' },
|
||||||
|
{ num: 5, title: '알림센터', desc: '전 시스템 알림 통합 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '포털 홈', type: 'customer' },
|
||||||
|
{ name: '알림', type: 'customer' },
|
||||||
|
{ name: '바로가기', type: 'customer' },
|
||||||
|
{ name: '통합검색', type: 'customer' },
|
||||||
|
{ name: '마이페이지', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mall',
|
||||||
|
slug: 'mall',
|
||||||
|
name: 'GUARDiA Mall',
|
||||||
|
tagline: '미국 다지점 꽃집 옴니채널 e-커머스',
|
||||||
|
category: '커머스',
|
||||||
|
port: 8011,
|
||||||
|
color: '#f43f5e',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8011), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: 'ZIP 배송권역', desc: '다매장 ZIP 배송권역 관리' },
|
||||||
|
{ title: '타임슬롯', desc: '타임슬롯 스케줄링 (당일배송)' },
|
||||||
|
{ title: '구독 서비스', desc: '정기 구독 서비스' },
|
||||||
|
{ title: '게이트웨이 어댑터', desc: 'Stripe · Twilio mock 게이트웨이 어댑터' },
|
||||||
|
{ title: 'AI 추천·예측', desc: 'AI 상품 추천 · 수요예측' },
|
||||||
|
{ title: '매장간 재고 이양', desc: '매장간 재고 이양' },
|
||||||
|
{ title: '관리자 앱', desc: '매장 매니저용 관리자 앱' },
|
||||||
|
{ title: 'i18n 한/영', desc: '한국어/영어 다국어 지원' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8011 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '상품 확인', desc: '상품 목록 확인 (Montvale Florist 실제 상품)' },
|
||||||
|
{ num: 3, title: '주문', desc: '상품 선택 → ZIP 코드 입력 → 배송 슬롯 선택 → 장바구니 추가' },
|
||||||
|
{ num: 4, title: '결제', desc: '카드 정보 (mock: 4242-4242-4242-4242) → 주문 완료' },
|
||||||
|
{ num: 5, title: '관리자', desc: '[주문관리] → 주문 확인 → 상태 변경 (준비중→배송중→완료)' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '상품 목록', type: 'customer' },
|
||||||
|
{ name: '상품 상세', type: 'customer' },
|
||||||
|
{ name: '장바구니', type: 'customer' },
|
||||||
|
{ name: '결제', type: 'customer' },
|
||||||
|
{ name: '주문 칸반', type: 'admin' },
|
||||||
|
{ name: '재고 ON/OFF', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cms',
|
||||||
|
slug: 'cms',
|
||||||
|
name: 'GUARDiA CMS',
|
||||||
|
tagline: 'AI 기반 통합 콘텐츠 관리 시스템',
|
||||||
|
category: '커머스',
|
||||||
|
port: 8012,
|
||||||
|
color: '#0ea5e9',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8012), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '헤드리스 CMS', desc: '페이지 · 포스트 · 블록 헤드리스 CMS' },
|
||||||
|
{ title: '게시 워크플로우', desc: '버전관리 · 게시 워크플로우 (draft→published)' },
|
||||||
|
{ title: '미디어 라이브러리', desc: '미디어 라이브러리 관리' },
|
||||||
|
{ title: '배너·프로모션', desc: '배너 · 프로모션 관리' },
|
||||||
|
{ title: 'AI SEO 제안', desc: 'AI 기반 SEO 자동 제안' },
|
||||||
|
{ title: '다국어·테마', desc: '다국어 · 테마 관리' },
|
||||||
|
{ title: 'UGC 모더레이션', desc: 'UGC 콘텐츠 모더레이션' },
|
||||||
|
{ title: '폼 빌더', desc: '드래그앤드롭 폼 빌더' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8012 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '페이지', desc: '[새 페이지] → 제목 입력 → 블록 에디터에서 콘텐츠 추가' },
|
||||||
|
{ num: 3, title: '미디어', desc: '[업로드] → 이미지 선택 → AI 자동 태그 확인' },
|
||||||
|
{ num: 4, title: '워크플로우', desc: '페이지 상태를 draft → review → published 변경' },
|
||||||
|
{ num: 5, title: 'SEO', desc: '페이지 선택 → [AI SEO 제안] → 메타태그 자동 입력 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '콘텐츠 목록', type: 'admin' },
|
||||||
|
{ name: '콘텐츠 승인', type: 'admin' },
|
||||||
|
{ name: '미디어 촬영업로드', type: 'field' },
|
||||||
|
{ name: 'UGC 모더레이션', type: 'admin' },
|
||||||
|
{ name: '성과', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mes',
|
||||||
|
slug: 'mes',
|
||||||
|
name: 'GUARDiA MES',
|
||||||
|
tagline: 'AI 기반 제조실행시스템 (WMS+MES+QMS)',
|
||||||
|
category: '제조',
|
||||||
|
port: 8013,
|
||||||
|
color: '#84cc16',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8013), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '작업지시·실적', desc: '작업지시 · 생산실적 등록' },
|
||||||
|
{ title: 'BOM·라우팅', desc: 'BOM · 공정/라우팅 관리' },
|
||||||
|
{ title: '설비 OEE', desc: '설비 OEE 모니터링' },
|
||||||
|
{ title: 'LOT 추적', desc: 'LOT 추적' },
|
||||||
|
{ title: '창고관리', desc: '입고·출고·재고·피킹 창고관리' },
|
||||||
|
{ title: '검사', desc: '수입·공정·출하 검사' },
|
||||||
|
{ title: 'SPC 관리도', desc: 'SPC 관리도 (Cp/Cpk)' },
|
||||||
|
{ title: 'CAPA', desc: 'CAPA (시정조치)' },
|
||||||
|
{ title: 'AI 불량분석', desc: 'AI 불량원인 분석' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8013 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '작업지시', desc: '[신규] → 품목·수량·공정 입력 → 발행' },
|
||||||
|
{ num: 3, title: '생산실적', desc: '작업지시 선택 → 생산량·불량량 입력 → 등록' },
|
||||||
|
{ num: 4, title: '검사', desc: '[공정검사] → LOT 번호 입력 → 검사항목 체크 → 합격/불합격' },
|
||||||
|
{ num: 5, title: 'SPC', desc: '관리도 차트 확인 → Cp/Cpk 수치 확인 → 이상 점 알림' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '작업지시 조회', type: 'field' },
|
||||||
|
{ name: '실적입력', type: 'field' },
|
||||||
|
{ name: '바코드스캔', type: 'field' },
|
||||||
|
{ name: '검사입력', type: 'field' },
|
||||||
|
{ name: '설비점검', type: 'field' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hrm',
|
||||||
|
slug: 'hrm',
|
||||||
|
name: 'GUARDiA HRM',
|
||||||
|
tagline: 'AI 기반 인사관리 플랫폼',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 8014,
|
||||||
|
color: '#a855f7',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8014), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '사원 관리', desc: '사원 등록 · 이력관리' },
|
||||||
|
{ title: '조직도·직급', desc: '조직도 · 직급체계' },
|
||||||
|
{ title: '급여·명세서', desc: '급여 계산 · 명세서' },
|
||||||
|
{ title: '근태·휴가', desc: '근태 · 휴가 관리' },
|
||||||
|
{ title: '성과 평가', desc: '성과 평가 · KPI' },
|
||||||
|
{ title: '채용 관리', desc: '채용 공고 · 지원자 관리' },
|
||||||
|
{ title: '교육 관리', desc: '교육 이수 관리' },
|
||||||
|
{ title: 'AI 인사 분석', desc: 'AI 인사 분석' },
|
||||||
|
{ title: 'Groupware 연동', desc: 'Groupware 연동' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8014 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '사원', desc: '[신규 등록] → 기본정보·부서·직급 입력 → 저장' },
|
||||||
|
{ num: 3, title: '급여', desc: '사원 선택 → 월급여 입력 → [명세서 생성] → PDF 확인' },
|
||||||
|
{ num: 4, title: '성과', desc: '[평가 등록] → KPI 항목 입력 → 점수 부여' },
|
||||||
|
{ num: 5, title: 'AI', desc: '[인사 인사이트] → 이탈 위험도·성과 트렌드 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '사원증', type: 'customer' },
|
||||||
|
{ name: '근태 체크인', type: 'customer' },
|
||||||
|
{ name: '급여 명세서', type: 'customer' },
|
||||||
|
{ name: '휴가신청', type: 'customer' },
|
||||||
|
{ name: '성과현황', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'esn',
|
||||||
|
slug: 'esn',
|
||||||
|
name: 'GUARDiA ESN',
|
||||||
|
tagline: 'ESL 전자가격표 통합 관리 플랫폼',
|
||||||
|
category: '커머스',
|
||||||
|
port: 8015,
|
||||||
|
color: '#f59e0b',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8015), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '멀티테넌트', desc: 'LGInnotek · LGIT · Emart · ZIOINFO 멀티테넌트' },
|
||||||
|
{ title: 'ESL 기기 관리', desc: 'ESL 기기 등록 · 상태 모니터링' },
|
||||||
|
{ title: 'POS 자동 변환', desc: 'POS 데이터 → ESL 가격 자동 변환' },
|
||||||
|
{ title: '템플릿 관리', desc: '크기별 레이아웃 템플릿 관리' },
|
||||||
|
{ title: 'HCore 관제', desc: 'HCore 게이트웨이/허브 관제' },
|
||||||
|
{ title: '알람·장애 이력', desc: '알람 · 장애 이력 관리' },
|
||||||
|
{ title: '펌웨어 업데이트', desc: '펌웨어 업데이트 관리' },
|
||||||
|
{ title: 'AI 알람 분석', desc: 'Ollama AI 알람 원인 분석' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8015 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '테넌트 선택', desc: '드롭다운에서 LGINNOTEK 선택' },
|
||||||
|
{ num: 3, title: 'ESL 기기', desc: '기기 목록 확인 → 상태(온라인/오프라인) 확인' },
|
||||||
|
{ num: 4, title: 'POS 변환', desc: '[POS 데이터 업로드] → CSV 파일 선택 → 변환 실행' },
|
||||||
|
{ num: 5, title: '알람', desc: '알람 목록 → [AI 원인분석] 클릭 → Ollama 분석 결과 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'ESL 기기 목록', type: 'field' },
|
||||||
|
{ name: '알람', type: 'field' },
|
||||||
|
{ name: 'HCore 상태', type: 'field' },
|
||||||
|
{ name: '펌웨어', type: 'admin' },
|
||||||
|
{ name: '작업 이력', type: 'field' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fa',
|
||||||
|
slug: 'fa',
|
||||||
|
name: 'GUARDiA FA',
|
||||||
|
tagline: 'AI 기반 스마트 팩토리 자동화 플랫폼 (준비중)',
|
||||||
|
category: '제조',
|
||||||
|
port: 8017,
|
||||||
|
color: '#64748b',
|
||||||
|
status: 'soon',
|
||||||
|
access: { url: url(8017), id: '', pw: '' },
|
||||||
|
features: [
|
||||||
|
{ title: 'e-Paper 지시서', desc: 'e-Paper 전자 공정 지시서' },
|
||||||
|
{ title: 'QR 작업 추적', desc: 'QR 기반 작업 추적' },
|
||||||
|
{ title: 'MES 연동', desc: 'MES 연동 실시간 현황' },
|
||||||
|
{ title: '로봇·PLC 인터페이스', desc: '로봇 · PLC 인터페이스' },
|
||||||
|
{ title: 'AI 공정 최적화', desc: 'AI 공정 최적화' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '배포 예정', desc: 'GUARDiA FA(공장 자동화)는 현재 준비 중입니다.' },
|
||||||
|
],
|
||||||
|
mobileScreens: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const CATEGORIES = [
|
||||||
|
{ name: '운영관리', slugs: ['itsm', 'erp', 'crm', 'hrm'] },
|
||||||
|
{ name: 'AI문서', slugs: ['ocr', 'bi'] },
|
||||||
|
{ name: '프로젝트', slugs: ['pms', 'rpa'] },
|
||||||
|
{ name: '협업', slugs: ['groupware', 'portal'] },
|
||||||
|
{ name: '커머스', slugs: ['mall', 'cms', 'esn'] },
|
||||||
|
{ name: '제조', slugs: ['mes', 'fa'] },
|
||||||
|
];
|
||||||
|
export const getSolution = (slug) => SOLUTIONS.find((s) => s.slug === slug);
|
||||||
545
src/data/solutions.ts
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
export interface Solution {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
tagline: string;
|
||||||
|
category: string;
|
||||||
|
port: number;
|
||||||
|
color: string;
|
||||||
|
status: 'live' | 'soon' | 'dev';
|
||||||
|
access: {
|
||||||
|
url: string;
|
||||||
|
httpsUrl?: string;
|
||||||
|
id: string;
|
||||||
|
pw: string;
|
||||||
|
};
|
||||||
|
features: { title: string; desc: string }[];
|
||||||
|
steps: { num: number; title: string; desc: string }[];
|
||||||
|
mobileScreens: { name: string; type: 'customer' | 'admin' | 'field' }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const IP = '101.79.17.164';
|
||||||
|
const url = (port: number) => `http://${IP}:${port}`;
|
||||||
|
|
||||||
|
export const SOLUTIONS: Solution[] = [
|
||||||
|
{
|
||||||
|
id: 'itsm',
|
||||||
|
slug: 'itsm',
|
||||||
|
name: 'GUARDiA ITSM',
|
||||||
|
tagline: 'AI 기반 레거시 인프라 자율 운영 플랫폼',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 9001,
|
||||||
|
color: '#6366f1',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(9001), httpsUrl: 'https://zioinfo.co.kr:8443', id: 'admin', pw: '1111' },
|
||||||
|
features: [
|
||||||
|
{ title: 'SR 자동화', desc: 'SR(서비스요청) 접수·처리·완료 전 과정 자동화' },
|
||||||
|
{ title: 'CMDB 자산관리', desc: 'CMDB 서버 자산 등록·조회·의존성 관리' },
|
||||||
|
{ title: 'AI 자연어 명령', desc: '`/배포 서버A 앱B` 자연어 명령으로 운영 자동화' },
|
||||||
|
{ title: '인시던트·변경관리', desc: '인시던트 대응 및 CAB 승인 변경관리' },
|
||||||
|
{ title: 'SLA 모니터링', desc: 'SLA 실시간 모니터링 및 위반 알림' },
|
||||||
|
{ title: '배포 자동화', desc: 'SSH/SFTP 에이전트리스 배포 자동화' },
|
||||||
|
{ title: '보안 점검', desc: 'CSAP·ISMS 보안 점검 자동화' },
|
||||||
|
{ title: '메신저 봇 연동', desc: '`/sr`, `/status` 메신저 봇 명령어 연동' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'https://zioinfo.co.kr:8443 접속 → admin / 1111 로그인' },
|
||||||
|
{ num: 2, title: '대시보드 확인', desc: '오늘 SR 수, 처리율, SLA 현황 확인' },
|
||||||
|
{ num: 3, title: 'SR 접수', desc: '[새 요청] → 제목 입력 → 자동 분류 확인' },
|
||||||
|
{ num: 4, title: 'AI 명령', desc: '메신저 탭 → `/sr 서버 장애 발생` 입력' },
|
||||||
|
{ num: 5, title: '배포', desc: '[배포관리] → [새 배포] → 서버 선택 → 실행' },
|
||||||
|
{ num: 6, title: 'CMDB', desc: '[자산관리] → 서버 목록 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'SR 목록', type: 'customer' },
|
||||||
|
{ name: 'SR 상세', type: 'customer' },
|
||||||
|
{ name: '빠른 SR 등록', type: 'customer' },
|
||||||
|
{ name: '승인 요청', type: 'admin' },
|
||||||
|
{ name: '서버 현황', type: 'admin' },
|
||||||
|
{ name: 'AI 챗봇', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'erp',
|
||||||
|
slug: 'erp',
|
||||||
|
name: 'GUARDiA ERP',
|
||||||
|
tagline: 'AI 기반 스마트 엔터프라이즈 자원관리',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 8003,
|
||||||
|
color: '#10b981',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8003), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '전표 자동 분류', desc: 'AI 기반 전표 자동 분류 및 계정 매핑' },
|
||||||
|
{ title: '예산·KPI', desc: '예산 관리 및 KPI 대시보드' },
|
||||||
|
{ title: '구매 발주', desc: '구매 발주 · 입고 처리 워크플로우' },
|
||||||
|
{ title: '인사·급여 연동', desc: 'HRM 인사 · 급여 시스템 연동' },
|
||||||
|
{ title: '영업·매출 분석', desc: '영업 활동 및 매출 분석' },
|
||||||
|
{ title: 'FinOps', desc: 'FinOps 클라우드 비용 추적' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8003 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '대시보드', desc: '매출·비용·KPI 현황 확인' },
|
||||||
|
{ num: 3, title: '재무', desc: '[전표입력] → 항목 입력 → [AI 분류] 클릭 → 계정 자동 입력 확인' },
|
||||||
|
{ num: 4, title: '예산', desc: '[예산현황] → 부서별 집행률 확인' },
|
||||||
|
{ num: 5, title: '구매', desc: '[발주서] → [신규] → 품목 입력 → 공급업체 선택 → 승인 요청' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'KPI 대시보드', type: 'admin' },
|
||||||
|
{ name: '전표 조회', type: 'admin' },
|
||||||
|
{ name: '예산 현황', type: 'admin' },
|
||||||
|
{ name: '구매 승인', type: 'admin' },
|
||||||
|
{ name: '영업 매출', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'crm',
|
||||||
|
slug: 'crm',
|
||||||
|
name: 'GUARDiA CRM',
|
||||||
|
tagline: 'AI 기반 공공기관 고객관계관리',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 8004,
|
||||||
|
color: '#f59e0b',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8004), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '고객 관리', desc: '고객(기관) 등록 · 히스토리 관리' },
|
||||||
|
{ title: '영업 파이프라인', desc: '영업기회 파이프라인 단계별 관리' },
|
||||||
|
{ title: '계약·입찰 추적', desc: '나라장터 G2B 연동 계약 · 입찰 추적' },
|
||||||
|
{ title: '캠페인 관리', desc: '마케팅 캠페인 기획·실행·분석' },
|
||||||
|
{ title: 'AI 고객 인사이트', desc: 'AI 기반 고객 행동 인사이트' },
|
||||||
|
{ title: 'ITSM SR 연동', desc: '고객 문의 → SR 자동 생성' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8004 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '대시보드', desc: '영업 파이프라인 현황 확인' },
|
||||||
|
{ num: 3, title: '고객', desc: '[고객등록] → 기관명·담당자·연락처 입력' },
|
||||||
|
{ num: 4, title: '영업기회', desc: '[새 기회] → 고객 연결 → 단계 설정 (발굴→제안→협상→계약)' },
|
||||||
|
{ num: 5, title: '계약', desc: '[계약등록] → 나라장터 계약번호 입력 → OCR로 계약서 자동 파싱' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '고객 목록', type: 'admin' },
|
||||||
|
{ name: '영업기회', type: 'admin' },
|
||||||
|
{ name: '계약 현황', type: 'admin' },
|
||||||
|
{ name: '입찰 알림', type: 'admin' },
|
||||||
|
{ name: 'AI 분석', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ocr',
|
||||||
|
slug: 'ocr',
|
||||||
|
name: 'GUARDiA OCR',
|
||||||
|
tagline: 'AI 기반 지능형 문서 인식 플랫폼',
|
||||||
|
category: 'AI문서',
|
||||||
|
port: 8005,
|
||||||
|
color: '#8b5cf6',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8005), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '계약서 등록', desc: '계약서 → 나라장터 계약 자동 등록' },
|
||||||
|
{ title: '납품서 처리', desc: '납품서 → CMDB 서버 자동 등록' },
|
||||||
|
{ title: '청구서 처리', desc: '청구서 → 세금계산서 처리' },
|
||||||
|
{ title: '장애보고서 → SR', desc: '장애보고서 이미지 → SR 자동 생성' },
|
||||||
|
{ title: '회의록 → SR', desc: '회의록 → 액션아이템 SR 생성' },
|
||||||
|
{ title: '감사보고서', desc: '감사보고서 → CSAP 준수율 업데이트' },
|
||||||
|
{ title: '브랜드 계약서', desc: '현대백화점 등 브랜드 계약서 → 계약 이력' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8005 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '문서 업로드', desc: '[문서 업로드] → 문서 이미지/PDF 선택' },
|
||||||
|
{ num: 3, title: '워크플로우 선택', desc: '워크플로우 선택 → 장애보고서→SR 선택' },
|
||||||
|
{ num: 4, title: 'OCR 실행', desc: '[OCR 실행] 클릭 → AI 텍스트 추출 확인' },
|
||||||
|
{ num: 5, title: 'SR 생성', desc: '추출 결과 확인 → [SR 자동 생성] 클릭 → ITSM SR 생성 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '카메라 촬영', type: 'field' },
|
||||||
|
{ name: '문서 업로드', type: 'field' },
|
||||||
|
{ name: 'OCR 결과', type: 'customer' },
|
||||||
|
{ name: '워크플로우', type: 'customer' },
|
||||||
|
{ name: '처리 이력', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bi',
|
||||||
|
slug: 'bi',
|
||||||
|
name: 'GUARDiA BI',
|
||||||
|
tagline: 'AI 기반 비즈니스 인텔리전스 플랫폼',
|
||||||
|
category: 'AI문서',
|
||||||
|
port: 8006,
|
||||||
|
color: '#06b6d4',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8006), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '데이터소스 연결', desc: 'PostgreSQL · API · CSV 데이터소스 연결' },
|
||||||
|
{ title: '대시보드 빌더', desc: '드래그앤드롭 대시보드 빌더' },
|
||||||
|
{ title: 'KPI 위젯·알림', desc: 'KPI 위젯 및 실시간 알림' },
|
||||||
|
{ title: 'AI Text-to-SQL', desc: '자연어 → SQL 쿼리 자동 생성' },
|
||||||
|
{ title: '예측·이상탐지', desc: 'AI 예측 및 이상탐지 분석' },
|
||||||
|
{ title: 'ITSM 데이터 피드', desc: 'SR·SLA·배포 통계 데이터 피드' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8006 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '데이터소스', desc: '[추가] → PostgreSQL 선택 → ITSM DB 연결' },
|
||||||
|
{ num: 3, title: '대시보드', desc: '[새 대시보드] → 위젯 추가 → 막대/라인/KPI 선택' },
|
||||||
|
{ num: 4, title: 'AI 쿼리', desc: '검색창에 "이번 달 미처리 SR 현황" 입력 → SQL 자동 생성 확인' },
|
||||||
|
{ num: 5, title: '리포트', desc: '[내보내기] → PDF 저장' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'KPI 대시보드', type: 'admin' },
|
||||||
|
{ name: '차트 조회', type: 'admin' },
|
||||||
|
{ name: 'AI 인사이트', type: 'admin' },
|
||||||
|
{ name: '리포트', type: 'admin' },
|
||||||
|
{ name: '알림', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pms',
|
||||||
|
slug: 'pms',
|
||||||
|
name: 'GUARDiA PMS',
|
||||||
|
tagline: 'JIRA 호환 AI 프로젝트 관리 시스템',
|
||||||
|
category: '프로젝트',
|
||||||
|
port: 8007,
|
||||||
|
color: '#f97316',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8007), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '이슈 추적', desc: '이슈 생성·추적 (JIRA 호환)' },
|
||||||
|
{ title: 'Scrum·Kanban', desc: 'Scrum · Kanban 보드' },
|
||||||
|
{ title: 'WBS·마일스톤', desc: 'WBS · 마일스톤 관리' },
|
||||||
|
{ title: '전자결재', desc: '다단계 결재선 전자결재' },
|
||||||
|
{ title: 'SI 산출물', desc: 'SI 산출물 관리 (감리/준공)' },
|
||||||
|
{ title: '요구사항 추적', desc: '요구사항 추적 (RTM)' },
|
||||||
|
{ title: 'AI 회의록', desc: 'AI 회의록 자동 작성' },
|
||||||
|
{ title: 'RFP 분석', desc: 'RFP → 요구사항분석서 자동 생성' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8007 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '프로젝트', desc: '[새 프로젝트] → 이름·유형(Scrum) 선택' },
|
||||||
|
{ num: 3, title: '백로그', desc: '[이슈 추가] → 제목·담당자·우선순위 입력' },
|
||||||
|
{ num: 4, title: '보드', desc: 'Kanban 뷰 전환 → 이슈 드래그앤드롭으로 상태 변경' },
|
||||||
|
{ num: 5, title: 'AI 기능', desc: '[회의록] → 텍스트 붙여넣기 → [액션아이템 추출] 클릭' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '내 이슈', type: 'customer' },
|
||||||
|
{ name: '보드', type: 'customer' },
|
||||||
|
{ name: '스프린트', type: 'customer' },
|
||||||
|
{ name: '승인 요청', type: 'admin' },
|
||||||
|
{ name: 'WBS 조회', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rpa',
|
||||||
|
slug: 'rpa',
|
||||||
|
name: 'GUARDiA RPA',
|
||||||
|
tagline: 'AI 기반 노코드 업무 자동화 플랫폼',
|
||||||
|
category: '프로젝트',
|
||||||
|
port: 8008,
|
||||||
|
color: '#ec4899',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8008), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '노코드 워크플로우', desc: '노코드 워크플로우 빌더' },
|
||||||
|
{ title: '봇 스케줄링', desc: '봇 등록 · 스케줄링' },
|
||||||
|
{ title: '자연어 생성', desc: '자연어 → 워크플로우 자동 생성' },
|
||||||
|
{ title: 'ROI 분석', desc: 'ROI 분석 대시보드' },
|
||||||
|
{ title: 'ITSM 연동', desc: 'ITSM SR 자동 처리 연동' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8008 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '워크플로우', desc: '[새 워크플로우] → 노드 드래그앤드롭' },
|
||||||
|
{ num: 3, title: '트리거 설정', desc: '[스케줄] 선택 → 매일 09:00 설정' },
|
||||||
|
{ num: 4, title: '액션 추가', desc: '[ITSM SR 생성] 노드 추가 → 파라미터 입력' },
|
||||||
|
{ num: 5, title: 'AI 생성', desc: '입력창에 "매일 아침 서버 상태 체크 후 이상 시 SR 생성" → [생성] 클릭' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '봇 현황', type: 'admin' },
|
||||||
|
{ name: '워크플로우 목록', type: 'admin' },
|
||||||
|
{ name: '실행 이력', type: 'admin' },
|
||||||
|
{ name: 'ROI 현황', type: 'admin' },
|
||||||
|
{ name: '알림', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'groupware',
|
||||||
|
slug: 'groupware',
|
||||||
|
name: 'GUARDiA Groupware',
|
||||||
|
tagline: 'AI 기반 통합 협업 그룹웨어',
|
||||||
|
category: '협업',
|
||||||
|
port: 8009,
|
||||||
|
color: '#14b8a6',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8009), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '전자결재', desc: '다단계 결재선 · 대리결재' },
|
||||||
|
{ title: '게시판', desc: '공지 · 자유 · 팀별 게시판' },
|
||||||
|
{ title: '일정/캘린더', desc: '팀 공유 일정/캘린더' },
|
||||||
|
{ title: '근태/휴가', desc: '근태/휴가 신청·승인' },
|
||||||
|
{ title: '자원예약', desc: '회의실 · 장비 자원예약' },
|
||||||
|
{ title: '주소록·조직도', desc: '주소록 · 조직도 관리' },
|
||||||
|
{ title: '문서함', desc: '버전관리 문서함' },
|
||||||
|
{ title: '쪽지', desc: '내부 메시지 쪽지' },
|
||||||
|
{ title: 'AI 문서 요약', desc: 'AI 문서 자동 요약' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8009 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '전자결재', desc: '[기안] → 양식 선택 → 내용 작성 → 결재선 설정 → 상신' },
|
||||||
|
{ num: 3, title: '일정', desc: '[일정등록] → 제목·날짜·참석자 입력 → 팀 공유' },
|
||||||
|
{ num: 4, title: '근태', desc: '[휴가신청] → 날짜·종류 선택 → 결재 요청' },
|
||||||
|
{ num: 5, title: '회의실', desc: '[예약] → 날짜·시간·회의실 선택 → 확정' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '결재함', type: 'admin' },
|
||||||
|
{ name: '공지사항', type: 'customer' },
|
||||||
|
{ name: '일정', type: 'customer' },
|
||||||
|
{ name: '근태', type: 'customer' },
|
||||||
|
{ name: '주소록', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'portal',
|
||||||
|
slug: 'portal',
|
||||||
|
name: 'GUARDiA Portal',
|
||||||
|
tagline: 'SSO 단일 로그인 통합 업무 포털',
|
||||||
|
category: '협업',
|
||||||
|
port: 8010,
|
||||||
|
color: '#7c3aed',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8010), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: 'SSO 단일 로그인', desc: 'GUARDiA 전 시스템 SSO 바로가기' },
|
||||||
|
{ title: '위젯 대시보드', desc: '역할별 위젯 대시보드' },
|
||||||
|
{ title: '즐겨찾기', desc: '즐겨찾기 시스템' },
|
||||||
|
{ title: '통합 공지', desc: '통합 공지사항' },
|
||||||
|
{ title: '알림 센터', desc: '전 시스템 통합 알림 센터' },
|
||||||
|
{ title: '통합 검색', desc: '전 시스템 통합 검색' },
|
||||||
|
{ title: '마이페이지', desc: '프로필 · 설정 마이페이지' },
|
||||||
|
{ title: 'AI 공지 요약', desc: 'AI 공지 자동 요약' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8010 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '포털 메인', desc: '9개 시스템 바로가기 카드 확인' },
|
||||||
|
{ num: 3, title: '위젯', desc: '[위젯 추가] → SR현황·KPI·일정 선택 → 대시보드 배치' },
|
||||||
|
{ num: 4, title: '통합검색', desc: '검색창에 "장애 처리" 입력 → ITSM·KB·문서 통합 결과 확인' },
|
||||||
|
{ num: 5, title: '알림센터', desc: '전 시스템 알림 통합 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '포털 홈', type: 'customer' },
|
||||||
|
{ name: '알림', type: 'customer' },
|
||||||
|
{ name: '바로가기', type: 'customer' },
|
||||||
|
{ name: '통합검색', type: 'customer' },
|
||||||
|
{ name: '마이페이지', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mall',
|
||||||
|
slug: 'mall',
|
||||||
|
name: 'GUARDiA Mall',
|
||||||
|
tagline: '미국 다지점 꽃집 옴니채널 e-커머스',
|
||||||
|
category: '커머스',
|
||||||
|
port: 8011,
|
||||||
|
color: '#f43f5e',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8011), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: 'ZIP 배송권역', desc: '다매장 ZIP 배송권역 관리' },
|
||||||
|
{ title: '타임슬롯', desc: '타임슬롯 스케줄링 (당일배송)' },
|
||||||
|
{ title: '구독 서비스', desc: '정기 구독 서비스' },
|
||||||
|
{ title: '게이트웨이 어댑터', desc: 'Stripe · Twilio mock 게이트웨이 어댑터' },
|
||||||
|
{ title: 'AI 추천·예측', desc: 'AI 상품 추천 · 수요예측' },
|
||||||
|
{ title: '매장간 재고 이양', desc: '매장간 재고 이양' },
|
||||||
|
{ title: '관리자 앱', desc: '매장 매니저용 관리자 앱' },
|
||||||
|
{ title: 'i18n 한/영', desc: '한국어/영어 다국어 지원' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8011 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '상품 확인', desc: '상품 목록 확인 (Montvale Florist 실제 상품)' },
|
||||||
|
{ num: 3, title: '주문', desc: '상품 선택 → ZIP 코드 입력 → 배송 슬롯 선택 → 장바구니 추가' },
|
||||||
|
{ num: 4, title: '결제', desc: '카드 정보 (mock: 4242-4242-4242-4242) → 주문 완료' },
|
||||||
|
{ num: 5, title: '관리자', desc: '[주문관리] → 주문 확인 → 상태 변경 (준비중→배송중→완료)' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '상품 목록', type: 'customer' },
|
||||||
|
{ name: '상품 상세', type: 'customer' },
|
||||||
|
{ name: '장바구니', type: 'customer' },
|
||||||
|
{ name: '결제', type: 'customer' },
|
||||||
|
{ name: '주문 칸반', type: 'admin' },
|
||||||
|
{ name: '재고 ON/OFF', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cms',
|
||||||
|
slug: 'cms',
|
||||||
|
name: 'GUARDiA CMS',
|
||||||
|
tagline: 'AI 기반 통합 콘텐츠 관리 시스템',
|
||||||
|
category: '커머스',
|
||||||
|
port: 8012,
|
||||||
|
color: '#0ea5e9',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8012), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '헤드리스 CMS', desc: '페이지 · 포스트 · 블록 헤드리스 CMS' },
|
||||||
|
{ title: '게시 워크플로우', desc: '버전관리 · 게시 워크플로우 (draft→published)' },
|
||||||
|
{ title: '미디어 라이브러리', desc: '미디어 라이브러리 관리' },
|
||||||
|
{ title: '배너·프로모션', desc: '배너 · 프로모션 관리' },
|
||||||
|
{ title: 'AI SEO 제안', desc: 'AI 기반 SEO 자동 제안' },
|
||||||
|
{ title: '다국어·테마', desc: '다국어 · 테마 관리' },
|
||||||
|
{ title: 'UGC 모더레이션', desc: 'UGC 콘텐츠 모더레이션' },
|
||||||
|
{ title: '폼 빌더', desc: '드래그앤드롭 폼 빌더' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8012 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '페이지', desc: '[새 페이지] → 제목 입력 → 블록 에디터에서 콘텐츠 추가' },
|
||||||
|
{ num: 3, title: '미디어', desc: '[업로드] → 이미지 선택 → AI 자동 태그 확인' },
|
||||||
|
{ num: 4, title: '워크플로우', desc: '페이지 상태를 draft → review → published 변경' },
|
||||||
|
{ num: 5, title: 'SEO', desc: '페이지 선택 → [AI SEO 제안] → 메타태그 자동 입력 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '콘텐츠 목록', type: 'admin' },
|
||||||
|
{ name: '콘텐츠 승인', type: 'admin' },
|
||||||
|
{ name: '미디어 촬영업로드', type: 'field' },
|
||||||
|
{ name: 'UGC 모더레이션', type: 'admin' },
|
||||||
|
{ name: '성과', type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mes',
|
||||||
|
slug: 'mes',
|
||||||
|
name: 'GUARDiA MES',
|
||||||
|
tagline: 'AI 기반 제조실행시스템 (WMS+MES+QMS)',
|
||||||
|
category: '제조',
|
||||||
|
port: 8013,
|
||||||
|
color: '#84cc16',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8013), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '작업지시·실적', desc: '작업지시 · 생산실적 등록' },
|
||||||
|
{ title: 'BOM·라우팅', desc: 'BOM · 공정/라우팅 관리' },
|
||||||
|
{ title: '설비 OEE', desc: '설비 OEE 모니터링' },
|
||||||
|
{ title: 'LOT 추적', desc: 'LOT 추적' },
|
||||||
|
{ title: '창고관리', desc: '입고·출고·재고·피킹 창고관리' },
|
||||||
|
{ title: '검사', desc: '수입·공정·출하 검사' },
|
||||||
|
{ title: 'SPC 관리도', desc: 'SPC 관리도 (Cp/Cpk)' },
|
||||||
|
{ title: 'CAPA', desc: 'CAPA (시정조치)' },
|
||||||
|
{ title: 'AI 불량분석', desc: 'AI 불량원인 분석' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8013 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '작업지시', desc: '[신규] → 품목·수량·공정 입력 → 발행' },
|
||||||
|
{ num: 3, title: '생산실적', desc: '작업지시 선택 → 생산량·불량량 입력 → 등록' },
|
||||||
|
{ num: 4, title: '검사', desc: '[공정검사] → LOT 번호 입력 → 검사항목 체크 → 합격/불합격' },
|
||||||
|
{ num: 5, title: 'SPC', desc: '관리도 차트 확인 → Cp/Cpk 수치 확인 → 이상 점 알림' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '작업지시 조회', type: 'field' },
|
||||||
|
{ name: '실적입력', type: 'field' },
|
||||||
|
{ name: '바코드스캔', type: 'field' },
|
||||||
|
{ name: '검사입력', type: 'field' },
|
||||||
|
{ name: '설비점검', type: 'field' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hrm',
|
||||||
|
slug: 'hrm',
|
||||||
|
name: 'GUARDiA HRM',
|
||||||
|
tagline: 'AI 기반 인사관리 플랫폼',
|
||||||
|
category: '운영관리',
|
||||||
|
port: 8014,
|
||||||
|
color: '#a855f7',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8014), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '사원 관리', desc: '사원 등록 · 이력관리' },
|
||||||
|
{ title: '조직도·직급', desc: '조직도 · 직급체계' },
|
||||||
|
{ title: '급여·명세서', desc: '급여 계산 · 명세서' },
|
||||||
|
{ title: '근태·휴가', desc: '근태 · 휴가 관리' },
|
||||||
|
{ title: '성과 평가', desc: '성과 평가 · KPI' },
|
||||||
|
{ title: '채용 관리', desc: '채용 공고 · 지원자 관리' },
|
||||||
|
{ title: '교육 관리', desc: '교육 이수 관리' },
|
||||||
|
{ title: 'AI 인사 분석', desc: 'AI 인사 분석' },
|
||||||
|
{ title: 'Groupware 연동', desc: 'Groupware 연동' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8014 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '사원', desc: '[신규 등록] → 기본정보·부서·직급 입력 → 저장' },
|
||||||
|
{ num: 3, title: '급여', desc: '사원 선택 → 월급여 입력 → [명세서 생성] → PDF 확인' },
|
||||||
|
{ num: 4, title: '성과', desc: '[평가 등록] → KPI 항목 입력 → 점수 부여' },
|
||||||
|
{ num: 5, title: 'AI', desc: '[인사 인사이트] → 이탈 위험도·성과 트렌드 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: '사원증', type: 'customer' },
|
||||||
|
{ name: '근태 체크인', type: 'customer' },
|
||||||
|
{ name: '급여 명세서', type: 'customer' },
|
||||||
|
{ name: '휴가신청', type: 'customer' },
|
||||||
|
{ name: '성과현황', type: 'customer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'esn',
|
||||||
|
slug: 'esn',
|
||||||
|
name: 'GUARDiA ESN',
|
||||||
|
tagline: 'ESL 전자가격표 통합 관리 플랫폼',
|
||||||
|
category: '커머스',
|
||||||
|
port: 8015,
|
||||||
|
color: '#f59e0b',
|
||||||
|
status: 'live',
|
||||||
|
access: { url: url(8015), id: 'admin', pw: 'admin123' },
|
||||||
|
features: [
|
||||||
|
{ title: '멀티테넌트', desc: 'LGInnotek · LGIT · Emart · ZIOINFO 멀티테넌트' },
|
||||||
|
{ title: 'ESL 기기 관리', desc: 'ESL 기기 등록 · 상태 모니터링' },
|
||||||
|
{ title: 'POS 자동 변환', desc: 'POS 데이터 → ESL 가격 자동 변환' },
|
||||||
|
{ title: '템플릿 관리', desc: '크기별 레이아웃 템플릿 관리' },
|
||||||
|
{ title: 'HCore 관제', desc: 'HCore 게이트웨이/허브 관제' },
|
||||||
|
{ title: '알람·장애 이력', desc: '알람 · 장애 이력 관리' },
|
||||||
|
{ title: '펌웨어 업데이트', desc: '펌웨어 업데이트 관리' },
|
||||||
|
{ title: 'AI 알람 분석', desc: 'Ollama AI 알람 원인 분석' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '접속·로그인', desc: 'http://101.79.17.164:8015 접속 → admin / admin123 로그인' },
|
||||||
|
{ num: 2, title: '테넌트 선택', desc: '드롭다운에서 LGINNOTEK 선택' },
|
||||||
|
{ num: 3, title: 'ESL 기기', desc: '기기 목록 확인 → 상태(온라인/오프라인) 확인' },
|
||||||
|
{ num: 4, title: 'POS 변환', desc: '[POS 데이터 업로드] → CSV 파일 선택 → 변환 실행' },
|
||||||
|
{ num: 5, title: '알람', desc: '알람 목록 → [AI 원인분석] 클릭 → Ollama 분석 결과 확인' },
|
||||||
|
],
|
||||||
|
mobileScreens: [
|
||||||
|
{ name: 'ESL 기기 목록', type: 'field' },
|
||||||
|
{ name: '알람', type: 'field' },
|
||||||
|
{ name: 'HCore 상태', type: 'field' },
|
||||||
|
{ name: '펌웨어', type: 'admin' },
|
||||||
|
{ name: '작업 이력', type: 'field' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fa',
|
||||||
|
slug: 'fa',
|
||||||
|
name: 'GUARDiA FA',
|
||||||
|
tagline: 'AI 기반 스마트 팩토리 자동화 플랫폼 (준비중)',
|
||||||
|
category: '제조',
|
||||||
|
port: 8017,
|
||||||
|
color: '#64748b',
|
||||||
|
status: 'soon',
|
||||||
|
access: { url: url(8017), id: '', pw: '' },
|
||||||
|
features: [
|
||||||
|
{ title: 'e-Paper 지시서', desc: 'e-Paper 전자 공정 지시서' },
|
||||||
|
{ title: 'QR 작업 추적', desc: 'QR 기반 작업 추적' },
|
||||||
|
{ title: 'MES 연동', desc: 'MES 연동 실시간 현황' },
|
||||||
|
{ title: '로봇·PLC 인터페이스', desc: '로봇 · PLC 인터페이스' },
|
||||||
|
{ title: 'AI 공정 최적화', desc: 'AI 공정 최적화' },
|
||||||
|
],
|
||||||
|
steps: [
|
||||||
|
{ num: 1, title: '배포 예정', desc: 'GUARDiA FA(공장 자동화)는 현재 준비 중입니다.' },
|
||||||
|
],
|
||||||
|
mobileScreens: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CATEGORIES: { name: string; slugs: string[] }[] = [
|
||||||
|
{ name: '운영관리', slugs: ['itsm', 'erp', 'crm', 'hrm'] },
|
||||||
|
{ name: 'AI문서', slugs: ['ocr', 'bi'] },
|
||||||
|
{ name: '프로젝트', slugs: ['pms', 'rpa'] },
|
||||||
|
{ name: '협업', slugs: ['groupware', 'portal'] },
|
||||||
|
{ name: '커머스', slugs: ['mall', 'cms', 'esn'] },
|
||||||
|
{ name: '제조', slugs: ['mes', 'fa'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getSolution = (slug: string): Solution | undefined =>
|
||||||
|
SOLUTIONS.find((s) => s.slug === slug);
|
||||||
9
src/main.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { jsx as _jsx } from "react/jsx-runtime";
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import App from './App';
|
||||||
|
import './styles/tokens.css';
|
||||||
|
import './styles/global.css';
|
||||||
|
import './styles/app.css';
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(_jsx(React.StrictMode, { children: _jsx(BrowserRouter, { children: _jsx(App, {}) }) }));
|
||||||
15
src/main.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import App from './App';
|
||||||
|
import './styles/tokens.css';
|
||||||
|
import './styles/global.css';
|
||||||
|
import './styles/app.css';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
14
src/pages/CatalogPage.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { CATEGORIES, getSolution } from '../data/solutions';
|
||||||
|
import { SolutionCard } from '../components/SolutionCard';
|
||||||
|
export const CatalogPage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (_jsx("main", { className: "section", children: _jsxs("div", { className: "container", children: [_jsx("h1", { className: "section-title", children: "\uC804\uCCB4 \uC194\uB8E8\uC158" }), _jsx("p", { className: "section-sub", children: "\uCE74\uD14C\uACE0\uB9AC\uBCC4 GUARDiA \uC194\uB8E8\uC158 \u2014 \uCD1D 15\uAC1C" }), CATEGORIES.map((cat) => (_jsxs("section", { className: "catalog-cat", children: [_jsxs("h2", { className: "catalog-cat-title", children: [cat.name, _jsx("span", { className: "catalog-cat-count", children: cat.slugs.length })] }), _jsx("div", { className: "card-grid", children: cat.slugs.map((slug) => {
|
||||||
|
const sol = getSolution(slug);
|
||||||
|
if (!sol)
|
||||||
|
return null;
|
||||||
|
return (_jsx(SolutionCard, { solution: sol, onClick: () => navigate(`/solution/${slug}`) }, slug));
|
||||||
|
}) })] }, cat.name)))] }) }));
|
||||||
|
};
|
||||||
|
export default CatalogPage;
|
||||||
41
src/pages/CatalogPage.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { CATEGORIES, getSolution } from '../data/solutions';
|
||||||
|
import { SolutionCard } from '../components/SolutionCard';
|
||||||
|
|
||||||
|
export const CatalogPage: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="section">
|
||||||
|
<div className="container">
|
||||||
|
<h1 className="section-title">전체 솔루션</h1>
|
||||||
|
<p className="section-sub">카테고리별 GUARDiA 솔루션 — 총 15개</p>
|
||||||
|
|
||||||
|
{CATEGORIES.map((cat) => (
|
||||||
|
<section className="catalog-cat" key={cat.name}>
|
||||||
|
<h2 className="catalog-cat-title">
|
||||||
|
{cat.name}
|
||||||
|
<span className="catalog-cat-count">{cat.slugs.length}</span>
|
||||||
|
</h2>
|
||||||
|
<div className="card-grid">
|
||||||
|
{cat.slugs.map((slug) => {
|
||||||
|
const sol = getSolution(slug);
|
||||||
|
if (!sol) return null;
|
||||||
|
return (
|
||||||
|
<SolutionCard
|
||||||
|
key={slug}
|
||||||
|
solution={sol}
|
||||||
|
onClick={() => navigate(`/solution/${slug}`)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CatalogPage;
|
||||||
10
src/pages/HomePage.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { SOLUTIONS } from '../data/solutions';
|
||||||
|
import { SolutionCard } from '../components/SolutionCard';
|
||||||
|
const liveCount = SOLUTIONS.filter((s) => s.status === 'live').length;
|
||||||
|
export const HomePage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (_jsxs("main", { children: [_jsx("section", { className: "hero", children: _jsxs("div", { className: "container hero-inner", children: [_jsx("span", { className: "hero-eyebrow", children: "\uC9C0\uC624\uC815\uBCF4\uAE30\uC220 \u00B7 GUARDiA Platform" }), _jsxs("h1", { className: "hero-title", children: ["GUARDiA ", _jsx("span", { className: "hero-grad", children: "\uC194\uB8E8\uC158 \uD3EC\uD138" })] }), _jsx("p", { className: "hero-sub", children: "15\uAC1C \uC194\uB8E8\uC158 \uC2DC\uC5F0 \uAC00\uC774\uB4DC \u00B7 \uC811\uC18D \uC815\uBCF4 \u00B7 \uBAA8\uBC14\uC77C \uC571" }), _jsxs("div", { className: "hero-actions", children: [_jsx("button", { className: "btn-primary", onClick: () => navigate('/catalog'), children: "\uC804\uCCB4 \uC194\uB8E8\uC158 \uBCF4\uAE30" }), _jsx("a", { className: "btn-ghost", href: "http://101.79.17.164:9001/app", target: "_blank", rel: "noreferrer", children: "\uBAA8\uBC14\uC77C \uC571 \uB2E4\uC6B4\uB85C\uB4DC" })] })] }) }), _jsx("section", { className: "stats-strip", children: _jsxs("div", { className: "container stats-inner", children: [_jsxs("div", { className: "stat", children: [_jsx("span", { className: "stat-value", children: "15\uAC1C" }), _jsx("span", { className: "stat-label", children: "\uC194\uB8E8\uC158" })] }), _jsxs("div", { className: "stat", children: [_jsxs("span", { className: "stat-value", children: [liveCount, "\uAC1C"] }), _jsx("span", { className: "stat-label", children: "LIVE \uC6B4\uC601 \uC911" })] }), _jsxs("div", { className: "stat", children: [_jsx("span", { className: "stat-value", children: "\uC628\uD504\uB808\uBBF8\uC2A4" }), _jsx("span", { className: "stat-label", children: "AI (Ollama)" })] })] }) }), _jsx("section", { className: "section", children: _jsxs("div", { className: "container", children: [_jsx("h2", { className: "section-title", children: "\uC194\uB8E8\uC158 \uCE74\uD0C8\uB85C\uADF8" }), _jsx("p", { className: "section-sub", children: "\uCE74\uB4DC\uB97C \uD074\uB9AD\uD558\uBA74 \uC811\uC18D \uC815\uBCF4\uC640 \uB2E8\uACC4\uBCC4 \uC2DC\uC5F0 \uAC00\uC774\uB4DC\uB97C \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." }), _jsx("div", { className: "card-grid", children: SOLUTIONS.map((s) => (_jsx(SolutionCard, { solution: s, onClick: () => navigate(`/solution/${s.slug}`) }, s.id))) })] }) }), _jsx("footer", { className: "home-footer", children: _jsxs("div", { className: "container", children: [_jsxs("p", { children: ["\uC11C\uBC84 IP: ", _jsx("code", { children: "101.79.17.164" })] }), _jsxs("p", { children: ["\uBAA8\uBC14\uC77C \uC571 \uB2E4\uC6B4\uB85C\uB4DC:", ' ', _jsx("a", { href: "http://101.79.17.164:9001/app", target: "_blank", rel: "noreferrer", children: "http://101.79.17.164:9001/app" })] }), _jsx("p", { className: "home-footer-copy", children: "\u00A9 2026 \uC9C0\uC624\uC815\uBCF4\uAE30\uC220 GUARDiA Platform" })] }) })] }));
|
||||||
|
};
|
||||||
|
export default HomePage;
|
||||||
82
src/pages/HomePage.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { SOLUTIONS } from '../data/solutions';
|
||||||
|
import { SolutionCard } from '../components/SolutionCard';
|
||||||
|
|
||||||
|
const liveCount = SOLUTIONS.filter((s) => s.status === 'live').length;
|
||||||
|
|
||||||
|
export const HomePage: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="hero">
|
||||||
|
<div className="container hero-inner">
|
||||||
|
<span className="hero-eyebrow">지오정보기술 · GUARDiA Platform</span>
|
||||||
|
<h1 className="hero-title">
|
||||||
|
GUARDiA <span className="hero-grad">솔루션 포털</span>
|
||||||
|
</h1>
|
||||||
|
<p className="hero-sub">
|
||||||
|
15개 솔루션 시연 가이드 · 접속 정보 · 모바일 앱
|
||||||
|
</p>
|
||||||
|
<div className="hero-actions">
|
||||||
|
<button className="btn-primary" onClick={() => navigate('/catalog')}>
|
||||||
|
전체 솔루션 보기
|
||||||
|
</button>
|
||||||
|
<a className="btn-ghost" href="http://101.79.17.164:9001/app" target="_blank" rel="noreferrer">
|
||||||
|
모바일 앱 다운로드
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<section className="stats-strip">
|
||||||
|
<div className="container stats-inner">
|
||||||
|
<div className="stat">
|
||||||
|
<span className="stat-value">15개</span>
|
||||||
|
<span className="stat-label">솔루션</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat">
|
||||||
|
<span className="stat-value">{liveCount}개</span>
|
||||||
|
<span className="stat-label">LIVE 운영 중</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat">
|
||||||
|
<span className="stat-value">온프레미스</span>
|
||||||
|
<span className="stat-label">AI (Ollama)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Grid */}
|
||||||
|
<section className="section">
|
||||||
|
<div className="container">
|
||||||
|
<h2 className="section-title">솔루션 카탈로그</h2>
|
||||||
|
<p className="section-sub">카드를 클릭하면 접속 정보와 단계별 시연 가이드를 확인할 수 있습니다.</p>
|
||||||
|
<div className="card-grid">
|
||||||
|
{SOLUTIONS.map((s) => (
|
||||||
|
<SolutionCard key={s.id} solution={s} onClick={() => navigate(`/solution/${s.slug}`)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer info */}
|
||||||
|
<footer className="home-footer">
|
||||||
|
<div className="container">
|
||||||
|
<p>서버 IP: <code>101.79.17.164</code></p>
|
||||||
|
<p>
|
||||||
|
모바일 앱 다운로드:{' '}
|
||||||
|
<a href="http://101.79.17.164:9001/app" target="_blank" rel="noreferrer">
|
||||||
|
http://101.79.17.164:9001/app
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className="home-footer-copy">© 2026 지오정보기술 GUARDiA Platform</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomePage;
|
||||||
19
src/pages/SolutionDetailPage.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
|
import { useParams, Link } from 'react-router-dom';
|
||||||
|
import { getSolution } from '../data/solutions';
|
||||||
|
import { SolutionIcon } from '../components/SolutionIcon';
|
||||||
|
import { LoginBox } from '../components/LoginBox';
|
||||||
|
import { FeatureGrid } from '../components/FeatureGrid';
|
||||||
|
import { StepGuide } from '../components/StepGuide';
|
||||||
|
import { MobileScreenList } from '../components/MobileScreenList';
|
||||||
|
export const SolutionDetailPage = () => {
|
||||||
|
const { slug } = useParams();
|
||||||
|
const solution = slug ? getSolution(slug) : undefined;
|
||||||
|
if (!solution) {
|
||||||
|
return (_jsx("main", { className: "section", children: _jsxs("div", { className: "container", children: [_jsx("h1", { className: "section-title", children: "\uC194\uB8E8\uC158\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4" }), _jsx(Link, { to: "/catalog", className: "btn-primary", style: { marginTop: 16, display: 'inline-block' }, children: "\uC804\uCCB4 \uC194\uB8E8\uC158\uC73C\uB85C" })] }) }));
|
||||||
|
}
|
||||||
|
const { name, tagline, color, status, category, access, features, steps, mobileScreens } = solution;
|
||||||
|
const soon = status === 'soon';
|
||||||
|
return (_jsxs("main", { className: "detail", children: [_jsx("section", { className: "detail-header", style: { background: `radial-gradient(900px 360px at 18% -20%, ${color}33, transparent 70%)` }, children: _jsxs("div", { className: "container", children: [_jsx(Link, { to: "/catalog", className: "detail-back", children: "\u2190 \uC804\uCCB4 \uC194\uB8E8\uC158" }), _jsxs("div", { className: "detail-header-row", children: [_jsx("span", { className: "detail-icon", style: { color, background: `${color}1a`, borderColor: `${color}40` }, children: _jsx(SolutionIcon, { slug: solution.slug, size: 44, color: color }) }), _jsxs("div", { children: [_jsxs("div", { className: "detail-title-row", children: [_jsx("h1", { className: "detail-title", children: name }), status === 'live' ? (_jsx("span", { className: "badge badge-live", children: "LIVE" })) : (_jsx("span", { className: "badge badge-soon", children: "SOON" }))] }), _jsx("p", { className: "detail-tagline", children: tagline }), _jsx("span", { className: "detail-cat", children: category })] })] })] }) }), _jsxs("div", { className: "container detail-body", children: [_jsx("section", { className: "detail-section", children: _jsx(LoginBox, { access: access, color: color, soon: soon }) }), _jsxs("section", { className: "detail-section", children: [_jsx("h2", { className: "section-title", children: "\uC8FC\uC694 \uAE30\uB2A5" }), _jsxs("p", { className: "section-sub", children: [name, "\uC758 \uD575\uC2EC \uAE30\uB2A5\uC785\uB2C8\uB2E4."] }), _jsx(FeatureGrid, { features: features, color: color })] }), _jsxs("section", { className: "detail-section", children: [_jsx("h2", { className: "section-title", children: "\uB2E8\uACC4\uBCC4 \uC2DC\uC5F0 \uAC00\uC774\uB4DC" }), _jsx("p", { className: "section-sub", children: "\uC544\uB798 \uC21C\uC11C\uB300\uB85C \uB530\uB77C \uD558\uBA74 \uC8FC\uC694 \uAE30\uB2A5\uC744 \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." }), _jsx(StepGuide, { steps: steps, color: color })] }), _jsxs("section", { className: "detail-section", children: [_jsx("h2", { className: "section-title", children: "\uBAA8\uBC14\uC77C \uC571 \uD654\uBA74" }), _jsx("p", { className: "section-sub", children: "\uD604\uC7A5\u00B7\uAD00\uB9AC\uC790\u00B7\uC0AC\uC6A9\uC790\uC6A9 \uBAA8\uBC14\uC77C \uD654\uBA74 \uAD6C\uC131\uC785\uB2C8\uB2E4." }), _jsx(MobileScreenList, { screens: mobileScreens, color: color })] })] })] }));
|
||||||
|
};
|
||||||
|
export default SolutionDetailPage;
|
||||||
93
src/pages/SolutionDetailPage.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useParams, Link } from 'react-router-dom';
|
||||||
|
import { getSolution } from '../data/solutions';
|
||||||
|
import { SolutionIcon } from '../components/SolutionIcon';
|
||||||
|
import { LoginBox } from '../components/LoginBox';
|
||||||
|
import { FeatureGrid } from '../components/FeatureGrid';
|
||||||
|
import { StepGuide } from '../components/StepGuide';
|
||||||
|
import { MobileScreenList } from '../components/MobileScreenList';
|
||||||
|
|
||||||
|
export const SolutionDetailPage: React.FC = () => {
|
||||||
|
const { slug } = useParams<{ slug: string }>();
|
||||||
|
const solution = slug ? getSolution(slug) : undefined;
|
||||||
|
|
||||||
|
if (!solution) {
|
||||||
|
return (
|
||||||
|
<main className="section">
|
||||||
|
<div className="container">
|
||||||
|
<h1 className="section-title">솔루션을 찾을 수 없습니다</h1>
|
||||||
|
<Link to="/catalog" className="btn-primary" style={{ marginTop: 16, display: 'inline-block' }}>
|
||||||
|
전체 솔루션으로
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, tagline, color, status, category, access, features, steps, mobileScreens } = solution;
|
||||||
|
const soon = status === 'soon';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="detail">
|
||||||
|
{/* Header */}
|
||||||
|
<section
|
||||||
|
className="detail-header"
|
||||||
|
style={{ background: `radial-gradient(900px 360px at 18% -20%, ${color}33, transparent 70%)` }}
|
||||||
|
>
|
||||||
|
<div className="container">
|
||||||
|
<Link to="/catalog" className="detail-back">← 전체 솔루션</Link>
|
||||||
|
<div className="detail-header-row">
|
||||||
|
<span
|
||||||
|
className="detail-icon"
|
||||||
|
style={{ color, background: `${color}1a`, borderColor: `${color}40` }}
|
||||||
|
>
|
||||||
|
<SolutionIcon slug={solution.slug} size={44} color={color} />
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<div className="detail-title-row">
|
||||||
|
<h1 className="detail-title">{name}</h1>
|
||||||
|
{status === 'live' ? (
|
||||||
|
<span className="badge badge-live">LIVE</span>
|
||||||
|
) : (
|
||||||
|
<span className="badge badge-soon">SOON</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="detail-tagline">{tagline}</p>
|
||||||
|
<span className="detail-cat">{category}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="container detail-body">
|
||||||
|
{/* Login */}
|
||||||
|
<section className="detail-section">
|
||||||
|
<LoginBox access={access} color={color} soon={soon} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<section className="detail-section">
|
||||||
|
<h2 className="section-title">주요 기능</h2>
|
||||||
|
<p className="section-sub">{name}의 핵심 기능입니다.</p>
|
||||||
|
<FeatureGrid features={features} color={color} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Steps */}
|
||||||
|
<section className="detail-section">
|
||||||
|
<h2 className="section-title">단계별 시연 가이드</h2>
|
||||||
|
<p className="section-sub">아래 순서대로 따라 하면 주요 기능을 확인할 수 있습니다.</p>
|
||||||
|
<StepGuide steps={steps} color={color} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Mobile */}
|
||||||
|
<section className="detail-section">
|
||||||
|
<h2 className="section-title">모바일 앱 화면</h2>
|
||||||
|
<p className="section-sub">현장·관리자·사용자용 모바일 화면 구성입니다.</p>
|
||||||
|
<MobileScreenList screens={mobileScreens} color={color} />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SolutionDetailPage;
|
||||||
217
src/styles/app.css
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/* ============ Navbar ============ */
|
||||||
|
.navbar {
|
||||||
|
position: sticky; top: 0; z-index: 50;
|
||||||
|
background: rgba(6, 11, 24, 0.78);
|
||||||
|
backdrop-filter: blur(14px);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.navbar-inner { display: flex; align-items: center; justify-content: space-between; height: 64px; }
|
||||||
|
.navbar-logo { display: flex; align-items: center; gap: 10px; }
|
||||||
|
.navbar-logo-mark {
|
||||||
|
width: 32px; height: 32px; border-radius: 9px;
|
||||||
|
background: var(--accent-grad);
|
||||||
|
display: grid; place-items: center;
|
||||||
|
font-weight: 800; font-size: 18px; color: #fff;
|
||||||
|
}
|
||||||
|
.navbar-logo-text { font-weight: 700; font-size: 16px; letter-spacing: -0.2px; }
|
||||||
|
.navbar-nav { display: flex; align-items: center; gap: 20px; }
|
||||||
|
.navbar-link { font-size: 14px; color: var(--text-secondary); transition: color var(--transition); }
|
||||||
|
.navbar-link:hover { color: var(--text-primary); }
|
||||||
|
.navbar-cta {
|
||||||
|
padding: 8px 16px; border-radius: var(--radius-sm);
|
||||||
|
background: var(--accent-grad); color: #fff; font-size: 13px; font-weight: 600;
|
||||||
|
transition: opacity var(--transition);
|
||||||
|
}
|
||||||
|
.navbar-cta:hover { opacity: 0.88; }
|
||||||
|
|
||||||
|
/* ============ Hero ============ */
|
||||||
|
.hero {
|
||||||
|
position: relative; overflow: hidden;
|
||||||
|
padding: 110px 0 90px;
|
||||||
|
background:
|
||||||
|
radial-gradient(800px 380px at 50% -10%, rgba(99,102,241,0.25), transparent 70%),
|
||||||
|
radial-gradient(600px 300px at 85% 10%, rgba(139,92,246,0.18), transparent 70%);
|
||||||
|
}
|
||||||
|
.hero-inner { text-align: center; }
|
||||||
|
.hero-eyebrow {
|
||||||
|
display: inline-block; font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
|
||||||
|
color: var(--accent-2); font-weight: 600; margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.hero-title { font-size: 56px; font-weight: 800; line-height: 1.1; letter-spacing: -1.5px; }
|
||||||
|
.hero-grad {
|
||||||
|
background: var(--accent-grad);
|
||||||
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||||||
|
}
|
||||||
|
.hero-sub { margin-top: 18px; font-size: 18px; color: var(--text-secondary); }
|
||||||
|
.hero-actions { margin-top: 36px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
padding: 13px 26px; border: none; cursor: pointer;
|
||||||
|
border-radius: var(--radius-sm); background: var(--accent-grad);
|
||||||
|
color: #fff; font-size: 15px; font-weight: 600;
|
||||||
|
transition: transform var(--transition), box-shadow var(--transition);
|
||||||
|
}
|
||||||
|
.btn-primary:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); }
|
||||||
|
.btn-ghost {
|
||||||
|
padding: 13px 26px; cursor: pointer;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border); background: var(--bg-card);
|
||||||
|
color: var(--text-primary); font-size: 15px; font-weight: 600;
|
||||||
|
transition: background var(--transition);
|
||||||
|
}
|
||||||
|
.btn-ghost:hover { background: var(--bg-card-hover); }
|
||||||
|
|
||||||
|
/* ============ Stats ============ */
|
||||||
|
.stats-strip { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); background: var(--bg-surface); }
|
||||||
|
.stats-inner { display: flex; justify-content: center; gap: 80px; padding: 36px 0; flex-wrap: wrap; }
|
||||||
|
.stat { text-align: center; }
|
||||||
|
.stat-value {
|
||||||
|
display: block; font-size: 30px; font-weight: 800;
|
||||||
|
background: var(--accent-grad); -webkit-background-clip: text; background-clip: text; color: transparent;
|
||||||
|
}
|
||||||
|
.stat-label { font-size: 13px; color: var(--text-muted); }
|
||||||
|
|
||||||
|
/* ============ Card Grid ============ */
|
||||||
|
.card-grid {
|
||||||
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;
|
||||||
|
}
|
||||||
|
@media (max-width: 980px) { .card-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
@media (max-width: 640px) { .card-grid { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
.solution-card {
|
||||||
|
text-align: left; cursor: pointer; width: 100%;
|
||||||
|
display: flex; flex-direction: column; gap: 12px;
|
||||||
|
padding: 22px; border-radius: var(--radius-card);
|
||||||
|
background: var(--bg-card); border: 1px solid var(--border);
|
||||||
|
color: inherit; font-family: inherit;
|
||||||
|
transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition);
|
||||||
|
}
|
||||||
|
.solution-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
border-color: var(--card-color);
|
||||||
|
box-shadow: 0 0 36px color-mix(in srgb, var(--card-color) 22%, transparent);
|
||||||
|
}
|
||||||
|
.solution-card-top { display: flex; align-items: center; justify-content: space-between; }
|
||||||
|
.solution-card-icon {
|
||||||
|
width: 52px; height: 52px; border-radius: 14px;
|
||||||
|
display: grid; place-items: center; border: 1px solid;
|
||||||
|
}
|
||||||
|
.solution-card-name { font-size: 18px; font-weight: 700; }
|
||||||
|
.solution-card-tagline { font-size: 13.5px; color: var(--text-muted); line-height: 1.5; flex: 1; }
|
||||||
|
.solution-card-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 4px; }
|
||||||
|
.solution-card-port { font-family: var(--font-mono); font-size: 13px; color: var(--text-muted); }
|
||||||
|
.solution-card-more { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; }
|
||||||
|
|
||||||
|
/* ============ Catalog ============ */
|
||||||
|
.catalog-cat { margin-bottom: 48px; }
|
||||||
|
.catalog-cat-title {
|
||||||
|
display: flex; align-items: center; gap: 10px;
|
||||||
|
font-size: 20px; font-weight: 700; margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.catalog-cat-count {
|
||||||
|
font-size: 12px; font-weight: 700; color: var(--accent-2);
|
||||||
|
background: rgba(139,92,246,0.15); border: 1px solid rgba(139,92,246,0.3);
|
||||||
|
border-radius: 20px; padding: 2px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ Detail ============ */
|
||||||
|
.detail-header { padding: 36px 0 44px; border-bottom: 1px solid var(--border); }
|
||||||
|
.detail-back { display: inline-block; font-size: 14px; color: var(--text-muted); margin-bottom: 24px; transition: color var(--transition); }
|
||||||
|
.detail-back:hover { color: var(--text-primary); }
|
||||||
|
.detail-header-row { display: flex; align-items: center; gap: 22px; }
|
||||||
|
.detail-icon { width: 80px; height: 80px; border-radius: 20px; display: grid; place-items: center; border: 1px solid; flex-shrink: 0; }
|
||||||
|
.detail-title-row { display: flex; align-items: center; gap: 12px; }
|
||||||
|
.detail-title { font-size: 34px; font-weight: 800; letter-spacing: -0.8px; }
|
||||||
|
.detail-tagline { margin-top: 6px; font-size: 16px; color: var(--text-secondary); }
|
||||||
|
.detail-cat {
|
||||||
|
display: inline-block; margin-top: 12px; font-size: 12px; font-weight: 600;
|
||||||
|
color: var(--text-muted); border: 1px solid var(--border); border-radius: 20px; padding: 3px 12px;
|
||||||
|
}
|
||||||
|
.detail-body { padding: 48px 0 90px; }
|
||||||
|
.detail-section { margin-bottom: 56px; }
|
||||||
|
|
||||||
|
/* ============ Login Box ============ */
|
||||||
|
.login-box {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
.login-box-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
|
||||||
|
.login-box-title { font-size: 14px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-muted); }
|
||||||
|
.login-field {
|
||||||
|
display: flex; align-items: center; gap: 12px;
|
||||||
|
padding: 12px 14px; border-radius: var(--radius-sm);
|
||||||
|
background: rgba(255,255,255,0.03); margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.login-field-label { width: 52px; font-size: 12px; font-weight: 700; color: var(--text-muted); flex-shrink: 0; }
|
||||||
|
.login-field-value { flex: 1; font-family: var(--font-mono); font-size: 16px; color: var(--text-primary); letter-spacing: 0.5px; }
|
||||||
|
.login-url {
|
||||||
|
flex: 1; display: inline-flex; align-items: center; gap: 6px;
|
||||||
|
font-family: var(--font-mono); font-size: 14px; color: #60a5fa; word-break: break-all;
|
||||||
|
}
|
||||||
|
.login-url:hover { text-decoration: underline; }
|
||||||
|
.login-copy-btn {
|
||||||
|
display: inline-flex; align-items: center; gap: 4px;
|
||||||
|
background: none; border: none; cursor: pointer; padding: 4px 6px; border-radius: 6px;
|
||||||
|
transition: background var(--transition);
|
||||||
|
}
|
||||||
|
.login-copy-btn:hover { background: rgba(255,255,255,0.08); }
|
||||||
|
.login-copied { font-size: 12px; font-weight: 700; color: #10b981; }
|
||||||
|
.login-soon-text { font-size: 14px; color: var(--text-muted); line-height: 1.6; }
|
||||||
|
|
||||||
|
/* ============ Feature Grid ============ */
|
||||||
|
.feature-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
|
||||||
|
@media (max-width: 640px) { .feature-grid { grid-template-columns: 1fr; } }
|
||||||
|
.feature-card {
|
||||||
|
display: flex; gap: 12px; align-items: flex-start;
|
||||||
|
padding: 16px 18px; border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-card); border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.feature-check { flex-shrink: 0; margin-top: 2px; display: inline-flex; }
|
||||||
|
.feature-title { font-size: 15px; font-weight: 600; margin-bottom: 3px; }
|
||||||
|
.feature-desc { font-size: 13.5px; color: var(--text-muted); line-height: 1.5; }
|
||||||
|
|
||||||
|
/* ============ Step Guide ============ */
|
||||||
|
.step-guide { list-style: none; }
|
||||||
|
.step-guide-item { display: grid; grid-template-columns: 44px 1fr; gap: 18px; }
|
||||||
|
.step-guide-rail { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.step-guide-num {
|
||||||
|
width: 40px; height: 40px; border-radius: 50%;
|
||||||
|
display: grid; place-items: center; font-size: 15px; font-weight: 700; color: #fff; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.step-guide-line { flex: 1; width: 2px; background: var(--border); margin: 6px 0; min-height: 16px; }
|
||||||
|
.step-guide-body { padding-bottom: 24px; }
|
||||||
|
.step-guide-body h4 { font-size: 16px; font-weight: 600; margin-bottom: 5px; }
|
||||||
|
.step-guide-body p { font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
|
||||||
|
|
||||||
|
/* ============ Mobile Screens ============ */
|
||||||
|
.mobile-screen-list { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||||
|
.mobile-empty { font-size: 14px; color: var(--text-muted); }
|
||||||
|
.phone-card { display: flex; flex-direction: column; align-items: center; gap: 12px; }
|
||||||
|
.phone-frame { width: 150px; height: 300px; border-radius: 26px; }
|
||||||
|
.phone-notch { width: 64px; height: 18px; }
|
||||||
|
.phone-screen-inner {
|
||||||
|
width: 100%; height: 100%; display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center; gap: 14px; padding: 16px;
|
||||||
|
}
|
||||||
|
.phone-icon { display: inline-flex; }
|
||||||
|
.phone-screen-name { font-size: 13px; font-weight: 600; color: var(--text-secondary); text-align: center; }
|
||||||
|
.phone-label { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
||||||
|
.phone-name { font-size: 13px; font-weight: 600; }
|
||||||
|
.phone-type { font-size: 11px; font-weight: 600; border: 1px solid; border-radius: 20px; padding: 1px 8px; }
|
||||||
|
|
||||||
|
/* ============ Home Footer ============ */
|
||||||
|
.home-footer { border-top: 1px solid var(--border); padding: 40px 0; background: var(--bg-surface); }
|
||||||
|
.home-footer p { font-size: 14px; color: var(--text-secondary); margin-bottom: 6px; }
|
||||||
|
.home-footer code { font-family: var(--font-mono); color: var(--accent-2); }
|
||||||
|
.home-footer a { color: #60a5fa; }
|
||||||
|
.home-footer a:hover { text-decoration: underline; }
|
||||||
|
.home-footer-copy { margin-top: 16px; font-size: 13px; color: var(--text-muted); }
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.hero-title { font-size: 38px; }
|
||||||
|
.stats-inner { gap: 40px; }
|
||||||
|
.detail-header-row { flex-direction: column; align-items: flex-start; }
|
||||||
|
}
|
||||||
82
src/styles/global.css
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
@import './tokens.css';
|
||||||
|
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
background: var(--bg-base);
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: inherit; text-decoration: none; }
|
||||||
|
|
||||||
|
img { max-width: 100%; display: block; }
|
||||||
|
|
||||||
|
::-webkit-scrollbar { width: 6px; }
|
||||||
|
::-webkit-scrollbar-track { background: var(--bg-base); }
|
||||||
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||||||
|
|
||||||
|
/* 유틸 */
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
|
||||||
|
.badge {
|
||||||
|
display: inline-flex; align-items: center; gap: 6px;
|
||||||
|
padding: 4px 10px; border-radius: 20px; font-size: 11px; font-weight: 600;
|
||||||
|
letter-spacing: 0.5px; text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.badge-live { background: rgba(16,185,129,0.15); color: #10b981; border: 1px solid rgba(16,185,129,0.3); }
|
||||||
|
.badge-soon { background: rgba(251,191,36,0.15); color: #fbbf24; border: 1px solid rgba(251,191,36,0.3); }
|
||||||
|
.badge-mobile { background: rgba(99,102,241,0.15); color: #818cf8; border: 1px solid rgba(99,102,241,0.3); }
|
||||||
|
|
||||||
|
/* 공통 섹션 */
|
||||||
|
.section { padding: 80px 0; }
|
||||||
|
.section-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
|
||||||
|
.section-sub { color: var(--text-muted); font-size: 15px; margin-bottom: 40px; }
|
||||||
|
|
||||||
|
/* 코드 블록 */
|
||||||
|
.code-block {
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 16px 20px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 13px;
|
||||||
|
color: #a5f3fc;
|
||||||
|
overflow-x: auto;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
.code-block .comment { color: #475569; }
|
||||||
|
.code-block .cmd { color: #34d399; }
|
||||||
|
.code-block .url { color: #f472b6; }
|
||||||
|
|
||||||
|
/* 스텝 */
|
||||||
|
.step-list { list-style: none; display: flex; flex-direction: column; gap: 16px; }
|
||||||
|
.step-item {
|
||||||
|
display: grid; grid-template-columns: 40px 1fr; gap: 16px; align-items: start;
|
||||||
|
}
|
||||||
|
.step-num {
|
||||||
|
width: 40px; height: 40px; border-radius: 50%;
|
||||||
|
background: var(--accent-grad); display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 14px; font-weight: 700; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.step-body h4 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
|
||||||
|
.step-body p { font-size: 14px; color: var(--text-muted); }
|
||||||
|
|
||||||
|
/* 모바일 프레임 */
|
||||||
|
.phone-frame {
|
||||||
|
width: 240px; height: 480px;
|
||||||
|
border-radius: 32px;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
background: #0f172a;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
.phone-notch {
|
||||||
|
position: absolute; top: 0; left: 50%; transform: translateX(-50%);
|
||||||
|
width: 100px; height: 24px; background: #000; border-radius: 0 0 16px 16px; z-index: 2;
|
||||||
|
}
|
||||||
|
.phone-screen { width: 100%; height: 100%; overflow: hidden; }
|
||||||
29
src/styles/tokens.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
:root {
|
||||||
|
--bg-base: #060b18;
|
||||||
|
--bg-surface: #0d1525;
|
||||||
|
--bg-card: rgba(255, 255, 255, 0.04);
|
||||||
|
--bg-card-hover: rgba(255, 255, 255, 0.08);
|
||||||
|
--border: rgba(255, 255, 255, 0.10);
|
||||||
|
--border-accent: rgba(99, 102, 241, 0.40);
|
||||||
|
|
||||||
|
--accent-1: #6366f1;
|
||||||
|
--accent-2: #8b5cf6;
|
||||||
|
--accent-grad: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||||
|
|
||||||
|
--text-primary: #f1f5f9;
|
||||||
|
--text-secondary:#cbd5e1;
|
||||||
|
--text-muted: #64748b;
|
||||||
|
|
||||||
|
--radius-sm: 8px;
|
||||||
|
--radius-md: 12px;
|
||||||
|
--radius-lg: 20px;
|
||||||
|
--radius-card:16px;
|
||||||
|
|
||||||
|
--shadow-card: 0 4px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
--shadow-glow: 0 0 40px rgba(99, 102, 241, 0.15);
|
||||||
|
|
||||||
|
--font-sans: 'Pretendard', 'Apple SD Gothic Neo', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
||||||
|
|
||||||
|
--transition: 0.2s ease;
|
||||||
|
}
|
||||||
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
8
vite.config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
build: { outDir: 'dist' },
|
||||||
|
server: { port: 3020 }
|
||||||
|
})
|
||||||