diff --git a/docs/roadmap/INTEGRATED_ROADMAP.md b/docs/roadmap/INTEGRATED_ROADMAP.md index 18a0934..b9a3b05 100644 --- a/docs/roadmap/INTEGRATED_ROADMAP.md +++ b/docs/roadmap/INTEGRATED_ROADMAP.md @@ -3,7 +3,7 @@ **프로젝트명:** SOKUREE Platform - Smart Integrated Management System **최초 작성일:** 2026-01-25 **최근 업데이트:** 2026-01-26 -**버전:** v0.4.3.1 +**버전:** v0.4.4.0 --- @@ -84,6 +84,24 @@ - [x] **라이선스 발급 표준 정의** - [x] 발급 관리 프로그램 연동을 위한 모듈별 고유 코드(`asset`, `production`, `material`, `quality`, `cctv`) 및 필수 규격 확정 +#### 🏷️ Tag: `v0.4.3.5` (완료) +- [x] **버전 관리 표준 확립** + - MAJOR.MINOR.PATCH.BUILD 4단계 체계 도입 및 문서화 (`version manage.md`) + - 배포 표준 절차(Standard Deployment Procedure) 정의 +- [x] **시스템 안정성 및 빌드 최적화** + - 빌드 시 정적 자산 강제 업데이트 스크립트 보강 + - 미사용 변수 및 린트 에러 수정을 통한 빌드 실패 방지 + - 버전 정보 동기화 로직 최적화 (package.json 기반) + +#### 🏷️ Tag: `v0.4.4.0` (진행중) +- [x] **모듈 카테고리 동적 생성 기능** + - 모듈 정의(`IModuleDefinition`) 시 카테고리를 하드코딩하지 않고 직접 문자열로 정의 가능하도록 개선 + - 사이드바 그룹화 로직을 동적으로 처리하여 신규 카테고리 추가 시 코드 수정 최소화 +- [x] **UI 시각화 및 사용성 개선** + - 사이드바 메뉴 선택 시 하이라이트 색상을 브랜드 테마(`var(--sokuree-brand-primary)`)와 일치시켜 시각적 일관성 확보 +- [x] **통합 로드맵 및 문서 현행화** + - `INTEGRATED_ROADMAP.md` 업데이트 및 버전 관리 규정 반영 + #### 🏷️ Tag: `v0.5.0.x` - [ ] 모듈 간 연동을 위한 API 인터페이스 정의 - [ ] 자재/재고 관리 모듈 추가 diff --git a/package.json b/package.json index 5697d61..86a0845 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "smartims", "private": true, - "version": "0.4.3.5", + "version": "0.4.4.0", "type": "module", "scripts": { "dev": "vite", diff --git a/server/package.json b/server/package.json index ebb11e6..7bd208b 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.4.3.5", + "version": "0.4.4.0", "description": "", "main": "index.js", "scripts": { diff --git a/server/routes/system.js b/server/routes/system.js index ca16aa5..e87d324 100644 --- a/server/routes/system.js +++ b/server/routes/system.js @@ -310,7 +310,7 @@ router.get('/modules', isAuthenticated, async (req, res) => { const [rows] = await db.query('SELECT * FROM system_modules'); const modules = {}; - const defaults = ['asset', 'production', 'cctv']; + const defaults = ['asset', 'production', 'cctv', 'material', 'quality']; // Get stored subscriber ID const [subRows] = await db.query("SELECT setting_value FROM system_settings WHERE setting_key = 'subscriber_id'"); @@ -415,17 +415,21 @@ router.post('/modules/:code/activate', isAuthenticated, hasRole('admin'), async const names = { 'asset': '자산 관리', 'production': '생산 관리', - 'cctv': 'CCTV' + 'cctv': 'CCTV', + 'material': '자재/재고 관리', + 'quality': '품질 관리' }; await db.query(sql, [code, names[code] || code, licenseKey, result.type, result.expiryDate, result.subscriberId]); // 5. Sync status with License Manager - const licenseManagerUrl = process.env.LICENSE_MANAGER_URL || 'http://localhost:3006/api'; + const licenseManagerUrl = process.env.LICENSE_MANAGER_URL || 'http://sokuree.com:3006/api'; try { + console.log(`📡 Syncing with License Manager: ${licenseManagerUrl}/licenses/activate`); await axios.post(`${licenseManagerUrl}/licenses/activate`, { licenseKey }); console.log(`✅ Synced activation status for key: ${licenseKey.substring(0, 20)}...`); } catch (syncErr) { + console.error(`❌ Sync Error Object:`, syncErr); const errorDetail = syncErr.response ? `Status: ${syncErr.response.status}, Data: ${JSON.stringify(syncErr.response.data)}` : syncErr.message; diff --git a/server/test_sync.js b/server/test_sync.js new file mode 100644 index 0000000..9f9ede2 --- /dev/null +++ b/server/test_sync.js @@ -0,0 +1,19 @@ +const axios = require('axios'); + +async function test() { + const licenseManagerUrl = 'http://localhost:3006/api'; + const licenseKey = 'test-key'; + try { + console.log(`📡 Testing sync with ${licenseManagerUrl}/licenses/activate`); + const response = await axios.post(`${licenseManagerUrl}/licenses/activate`, { licenseKey }); + console.log('✅ Success:', response.data); + } catch (err) { + if (err.response) { + console.error('❌ Error Response:', err.response.status, err.response.data); + } else { + console.error('❌ Error Message:', err.message); + } + } +} + +test(); diff --git a/src/modules/asset/module.tsx b/src/modules/asset/module.tsx index 4ab3760..00ff0b7 100644 --- a/src/modules/asset/module.tsx +++ b/src/modules/asset/module.tsx @@ -4,7 +4,7 @@ import { AssetListPage } from './pages/AssetListPage'; import { AssetRegisterPage } from './pages/AssetRegisterPage'; import { AssetSettingsPage } from './pages/AssetSettingsPage'; import { AssetDetailPage } from './pages/AssetDetailPage'; -import { BasicSettingsPage } from '../../platform/pages/BasicSettingsPage'; +import { PlaceholderPage } from '../../shared/ui/PlaceholderPage'; export const assetModule: IModuleDefinition = { moduleName: 'asset-management', @@ -14,10 +14,10 @@ export const assetModule: IModuleDefinition = { { path: '/list', element: , label: '자산 목록', group: '기준정보' }, { path: '/register', element: , label: '자산 등록', group: '기준정보' }, - { path: '/settings/modules', element: , label: '모듈 관리', group: '설정' }, + { path: '/settings/modules', element: , label: '모듈 관리', group: '설정' }, { path: '/settings', element: , label: '자산 설정', group: '설정' }, - { path: '/settings/iot', element: , label: 'IOT 센서 관리', group: '설정' }, // Using AssetSettingsPage as placeholder - { path: '/settings/facilities', element: , label: '설비 관리', group: '설정' }, + { path: '/settings/iot', element: , label: 'IOT 센서 관리', group: '설정' }, + { path: '/settings/facilities', element: , label: '설비 관리', group: '설정' }, { path: '/detail/:assetId', element: }, diff --git a/src/modules/quality/module.tsx b/src/modules/quality/module.tsx index 449192d..2024cd6 100644 --- a/src/modules/quality/module.tsx +++ b/src/modules/quality/module.tsx @@ -14,5 +14,6 @@ export const qualityModule: IModuleDefinition = { label: '품질 현황', icon: } - ] + ], + requiredRoles: ['admin', 'manager', 'user'] }; diff --git a/src/shared/context/SystemContext.tsx b/src/shared/context/SystemContext.tsx index 1484897..05184f1 100644 --- a/src/shared/context/SystemContext.tsx +++ b/src/shared/context/SystemContext.tsx @@ -25,11 +25,10 @@ export function SystemProvider({ children }: { children: ReactNode }) { const refreshModules = async () => { try { const response = await apiClient.get('/system/modules'); + console.log('[SystemContext] Modules from server:', response.data.modules); if (response.data.modules) { setModules(response.data.modules); } else { - // Fallback or assume old format (unsafe with new backend) - // Better to assume new format based on my changes setModules(response.data.modules || response.data); } } catch (error) { diff --git a/src/shared/ui/PlaceholderPage.tsx b/src/shared/ui/PlaceholderPage.tsx new file mode 100644 index 0000000..6c64d33 --- /dev/null +++ b/src/shared/ui/PlaceholderPage.tsx @@ -0,0 +1,49 @@ +import { Construction, ArrowRight } from 'lucide-react'; + +interface PlaceholderPageProps { + title: string; + description: string; +} + +export function PlaceholderPage({ title, description }: PlaceholderPageProps) { + return ( +
+
+ +
+ +

+ {title} +

+ +

+ {description} +

+ +
+

현재 진행 상황

+
+
+
+ 기본 정적 레이아웃 설계 완료 +
+
+
+ 데이터베이스 스키마 정의 진행 중 +
+
+
+ 프론트엔드 비즈니스 로직 개발 예정 +
+
+
+ + +
+ ); +} diff --git a/src/widgets/layout/MainLayout.tsx b/src/widgets/layout/MainLayout.tsx index d9a81be..afd1e4e 100644 --- a/src/widgets/layout/MainLayout.tsx +++ b/src/widgets/layout/MainLayout.tsx @@ -21,9 +21,9 @@ export function MainLayout({ modulesList }: MainLayoutProps) { const isSupervisor = user?.role === 'supervisor'; const checkModulePermission = (moduleKey: string) => { - // Supervisor always has access - if (isSupervisor) return true; - // Check allowed_modules list + // Supervisor and Admin always have access to see active modules + if (isSupervisor || isAdmin) return true; + // Check allowed_modules list for other roles (manager, user, etc.) return user?.allowed_modules?.includes(moduleKey); }; @@ -60,7 +60,7 @@ export function MainLayout({ modulesList }: MainLayoutProps) { {isAdmin && (