feat: 설비 상세 패널 및 임시 이동 기능 구현

- 설비 마커 클릭 시 슬라이드 패널로 상세 정보 표시
- 설비 사진 업로드/삭제 기능
- 설비 임시 이동 기능 (3단계 지도 기반 선택)
  - Step 1: 공장 선택
  - Step 2: 레이아웃 지도에서 작업장 선택
  - Step 3: 상세 지도에서 위치 선택
- 설비 외부 반출/반입 기능
- 설비 수리 신청 기능 (기존 신고 시스템 연동)
- DB 마이그레이션 추가 (사진, 임시이동, 외부반출 테이블)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-04 13:45:56 +09:00
parent 90d3e32992
commit 4d83f10b07
13 changed files with 6043 additions and 74 deletions

View File

@@ -20,19 +20,26 @@ try {
}
// 업로드 디렉토리 설정
const UPLOAD_DIR = path.join(__dirname, '../public/uploads/issues');
const UPLOAD_DIRS = {
issues: path.join(__dirname, '../public/uploads/issues'),
equipments: path.join(__dirname, '../public/uploads/equipments')
};
const UPLOAD_DIR = UPLOAD_DIRS.issues; // 기존 호환성 유지
const MAX_SIZE = { width: 1920, height: 1920 };
const QUALITY = 85;
/**
* 업로드 디렉토리 확인 및 생성
* @param {string} category - 카테고리 ('issues' 또는 'equipments')
*/
async function ensureUploadDir() {
async function ensureUploadDir(category = 'issues') {
const uploadDir = UPLOAD_DIRS[category] || UPLOAD_DIRS.issues;
try {
await fs.access(UPLOAD_DIR);
await fs.access(uploadDir);
} catch {
await fs.mkdir(UPLOAD_DIR, { recursive: true });
await fs.mkdir(uploadDir, { recursive: true });
}
return uploadDir;
}
/**
@@ -68,10 +75,11 @@ function getImageExtension(base64String) {
/**
* Base64 이미지를 파일로 저장
* @param {string} base64String - Base64 인코딩된 이미지 (data:image/...;base64,... 형식)
* @param {string} prefix - 파일명 접두사 (예: 'issue', 'resolution')
* @param {string} prefix - 파일명 접두사 (예: 'issue', 'resolution', 'equipment')
* @param {string} category - 저장 카테고리 ('issues' 또는 'equipments')
* @returns {Promise<string|null>} 저장된 파일의 웹 경로 또는 null
*/
async function saveBase64Image(base64String, prefix = 'issue') {
async function saveBase64Image(base64String, prefix = 'issue', category = 'issues') {
try {
if (!base64String || typeof base64String !== 'string') {
return null;
@@ -92,14 +100,14 @@ async function saveBase64Image(base64String, prefix = 'issue') {
}
// 디렉토리 확인
await ensureUploadDir();
const uploadDir = await ensureUploadDir(category);
// 파일명 생성
const timestamp = getTimestamp();
const uniqueId = generateId();
const extension = 'jpg'; // 모든 이미지를 JPEG로 저장
const filename = `${prefix}_${timestamp}_${uniqueId}.${extension}`;
const filepath = path.join(UPLOAD_DIR, filename);
const filepath = path.join(uploadDir, filename);
// sharp가 설치되어 있으면 리사이징 및 최적화
if (sharp) {
@@ -122,7 +130,7 @@ async function saveBase64Image(base64String, prefix = 'issue') {
}
// 웹 접근 경로 반환
return `/uploads/issues/${filename}`;
return `/uploads/${category}/${filename}`;
} catch (error) {
console.error('이미지 저장 실패:', error);
return null;