+ + 작업장 관리 +
+공장 및 작업장을 등록하고 관리합니다
+diff --git a/api.hyungi.net/config/routes.js b/api.hyungi.net/config/routes.js index f5223c2..0ffe581 100644 --- a/api.hyungi.net/config/routes.js +++ b/api.hyungi.net/config/routes.js @@ -40,6 +40,7 @@ function setupRoutes(app) { const attendanceRoutes = require('../routes/attendanceRoutes'); const monthlyStatusRoutes = require('../routes/monthlyStatusRoutes'); const pageAccessRoutes = require('../routes/pageAccessRoutes'); + const workplaceRoutes = require('../routes/workplaceRoutes'); // const tbmRoutes = require('../routes/tbmRoutes'); // 임시 비활성화 - db/connection 문제 // Rate Limiters 설정 @@ -127,6 +128,7 @@ function setupRoutes(app) { app.use('/api/projects', projectRoutes); app.use('/api/tools', toolsRoute); app.use('/api/users', userRoutes); + app.use('/api/workplaces', workplaceRoutes); app.use('/api', pageAccessRoutes); // 페이지 접근 권한 관리 // app.use('/api/tbm', tbmRoutes); // TBM 시스템 - 임시 비활성화 app.use('/api', uploadBgRoutes); diff --git a/api.hyungi.net/controllers/workplaceController.js b/api.hyungi.net/controllers/workplaceController.js new file mode 100644 index 0000000..3543b86 --- /dev/null +++ b/api.hyungi.net/controllers/workplaceController.js @@ -0,0 +1,314 @@ +/** + * 작업장 관리 컨트롤러 + * + * 작업장 카테고리(공장) 및 작업장 CRUD API 엔드포인트 핸들러 + * + * @author TK-FB-Project + * @since 2026-01-26 + */ + +const workplaceModel = require('../models/workplaceModel'); +const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); +const { asyncHandler } = require('../middlewares/errorHandler'); +const logger = require('../utils/logger'); + +// ==================== 카테고리(공장) 관련 ==================== + +/** + * 카테고리 생성 + */ +exports.createCategory = asyncHandler(async (req, res) => { + const categoryData = req.body; + + if (!categoryData.category_name) { + throw new ValidationError('카테고리명은 필수 입력 항목입니다'); + } + + logger.info('카테고리 생성 요청', { name: categoryData.category_name }); + + const id = await new Promise((resolve, reject) => { + workplaceModel.createCategory(categoryData, (err, lastID) => { + if (err) reject(new DatabaseError('카테고리 생성 중 오류가 발생했습니다')); + else resolve(lastID); + }); + }); + + logger.info('카테고리 생성 성공', { category_id: id }); + + res.status(201).json({ + success: true, + data: { category_id: id }, + message: '카테고리가 성공적으로 생성되었습니다' + }); +}); + +/** + * 전체 카테고리 조회 + */ +exports.getAllCategories = asyncHandler(async (req, res) => { + const rows = await new Promise((resolve, reject) => { + workplaceModel.getAllCategories((err, data) => { + if (err) reject(new DatabaseError('카테고리 목록 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + res.json({ + success: true, + data: rows, + message: '카테고리 목록 조회 성공' + }); +}); + +/** + * 활성 카테고리만 조회 + */ +exports.getActiveCategories = asyncHandler(async (req, res) => { + const rows = await new Promise((resolve, reject) => { + workplaceModel.getActiveCategories((err, data) => { + if (err) reject(new DatabaseError('활성 카테고리 목록 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + res.json({ + success: true, + data: rows, + message: '활성 카테고리 목록 조회 성공' + }); +}); + +/** + * 단일 카테고리 조회 + */ +exports.getCategoryById = asyncHandler(async (req, res) => { + const categoryId = req.params.id; + + const category = await new Promise((resolve, reject) => { + workplaceModel.getCategoryById(categoryId, (err, data) => { + if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + if (!category) { + throw new NotFoundError('카테고리를 찾을 수 없습니다'); + } + + res.json({ + success: true, + data: category, + message: '카테고리 조회 성공' + }); +}); + +/** + * 카테고리 수정 + */ +exports.updateCategory = asyncHandler(async (req, res) => { + const categoryId = req.params.id; + const categoryData = req.body; + + if (!categoryData.category_name) { + throw new ValidationError('카테고리명은 필수 입력 항목입니다'); + } + + logger.info('카테고리 수정 요청', { category_id: categoryId }); + + await new Promise((resolve, reject) => { + workplaceModel.updateCategory(categoryId, categoryData, (err, result) => { + if (err) reject(new DatabaseError('카테고리 수정 중 오류가 발생했습니다')); + else resolve(result); + }); + }); + + logger.info('카테고리 수정 성공', { category_id: categoryId }); + + res.json({ + success: true, + message: '카테고리가 성공적으로 수정되었습니다' + }); +}); + +/** + * 카테고리 삭제 + */ +exports.deleteCategory = asyncHandler(async (req, res) => { + const categoryId = req.params.id; + + logger.info('카테고리 삭제 요청', { category_id: categoryId }); + + await new Promise((resolve, reject) => { + workplaceModel.deleteCategory(categoryId, (err, result) => { + if (err) reject(new DatabaseError('카테고리 삭제 중 오류가 발생했습니다')); + else resolve(result); + }); + }); + + logger.info('카테고리 삭제 성공', { category_id: categoryId }); + + res.json({ + success: true, + message: '카테고리가 성공적으로 삭제되었습니다' + }); +}); + +// ==================== 작업장 관련 ==================== + +/** + * 작업장 생성 + */ +exports.createWorkplace = asyncHandler(async (req, res) => { + const workplaceData = req.body; + + if (!workplaceData.workplace_name) { + throw new ValidationError('작업장명은 필수 입력 항목입니다'); + } + + logger.info('작업장 생성 요청', { name: workplaceData.workplace_name }); + + const id = await new Promise((resolve, reject) => { + workplaceModel.createWorkplace(workplaceData, (err, lastID) => { + if (err) reject(new DatabaseError('작업장 생성 중 오류가 발생했습니다')); + else resolve(lastID); + }); + }); + + logger.info('작업장 생성 성공', { workplace_id: id }); + + res.status(201).json({ + success: true, + data: { workplace_id: id }, + message: '작업장이 성공적으로 생성되었습니다' + }); +}); + +/** + * 전체 작업장 조회 + */ +exports.getAllWorkplaces = asyncHandler(async (req, res) => { + const categoryId = req.query.category_id; + + // 카테고리별 필터링 + if (categoryId) { + const rows = await new Promise((resolve, reject) => { + workplaceModel.getWorkplacesByCategory(categoryId, (err, data) => { + if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + return res.json({ + success: true, + data: rows, + message: '작업장 목록 조회 성공' + }); + } + + // 전체 조회 + const rows = await new Promise((resolve, reject) => { + workplaceModel.getAllWorkplaces((err, data) => { + if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + res.json({ + success: true, + data: rows, + message: '작업장 목록 조회 성공' + }); +}); + +/** + * 활성 작업장만 조회 + */ +exports.getActiveWorkplaces = asyncHandler(async (req, res) => { + const rows = await new Promise((resolve, reject) => { + workplaceModel.getActiveWorkplaces((err, data) => { + if (err) reject(new DatabaseError('활성 작업장 목록 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + res.json({ + success: true, + data: rows, + message: '활성 작업장 목록 조회 성공' + }); +}); + +/** + * 단일 작업장 조회 + */ +exports.getWorkplaceById = asyncHandler(async (req, res) => { + const workplaceId = req.params.id; + + const workplace = await new Promise((resolve, reject) => { + workplaceModel.getWorkplaceById(workplaceId, (err, data) => { + if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다')); + else resolve(data); + }); + }); + + if (!workplace) { + throw new NotFoundError('작업장을 찾을 수 없습니다'); + } + + res.json({ + success: true, + data: workplace, + message: '작업장 조회 성공' + }); +}); + +/** + * 작업장 수정 + */ +exports.updateWorkplace = asyncHandler(async (req, res) => { + const workplaceId = req.params.id; + const workplaceData = req.body; + + if (!workplaceData.workplace_name) { + throw new ValidationError('작업장명은 필수 입력 항목입니다'); + } + + logger.info('작업장 수정 요청', { workplace_id: workplaceId }); + + await new Promise((resolve, reject) => { + workplaceModel.updateWorkplace(workplaceId, workplaceData, (err, result) => { + if (err) reject(new DatabaseError('작업장 수정 중 오류가 발생했습니다')); + else resolve(result); + }); + }); + + logger.info('작업장 수정 성공', { workplace_id: workplaceId }); + + res.json({ + success: true, + message: '작업장이 성공적으로 수정되었습니다' + }); +}); + +/** + * 작업장 삭제 + */ +exports.deleteWorkplace = asyncHandler(async (req, res) => { + const workplaceId = req.params.id; + + logger.info('작업장 삭제 요청', { workplace_id: workplaceId }); + + await new Promise((resolve, reject) => { + workplaceModel.deleteWorkplace(workplaceId, (err, result) => { + if (err) reject(new DatabaseError('작업장 삭제 중 오류가 발생했습니다')); + else resolve(result); + }); + }); + + logger.info('작업장 삭제 성공', { workplace_id: workplaceId }); + + res.json({ + success: true, + message: '작업장이 성공적으로 삭제되었습니다' + }); +}); diff --git a/api.hyungi.net/db/migrations/20260126000001_create_workplace_categories.js b/api.hyungi.net/db/migrations/20260126000001_create_workplace_categories.js new file mode 100644 index 0000000..2b000a1 --- /dev/null +++ b/api.hyungi.net/db/migrations/20260126000001_create_workplace_categories.js @@ -0,0 +1,24 @@ +/** + * 작업장 카테고리(공장) 테이블 생성 마이그레이션 + * 대분류: 제 1공장, 제 2공장 등 + */ + +exports.up = function(knex) { + return knex.schema.createTable('workplace_categories', function(table) { + table.increments('category_id').primary().comment('카테고리 ID'); + table.string('category_name', 100).notNullable().comment('카테고리명 (예: 제 1공장)'); + table.text('description').nullable().comment('설명'); + table.integer('display_order').defaultTo(0).comment('표시 순서'); + table.boolean('is_active').defaultTo(true).comment('활성화 여부'); + table.timestamp('created_at').defaultTo(knex.fn.now()).comment('생성일시'); + table.timestamp('updated_at').defaultTo(knex.fn.now()).comment('수정일시'); + + // 인덱스 + table.index('is_active'); + table.index('display_order'); + }); +}; + +exports.down = function(knex) { + return knex.schema.dropTableIfExists('workplace_categories'); +}; diff --git a/api.hyungi.net/db/migrations/20260126000002_create_workplaces.js b/api.hyungi.net/db/migrations/20260126000002_create_workplaces.js new file mode 100644 index 0000000..6751b74 --- /dev/null +++ b/api.hyungi.net/db/migrations/20260126000002_create_workplaces.js @@ -0,0 +1,31 @@ +/** + * 작업장(작업 구역) 테이블 생성 마이그레이션 + * 소분류: 서스작업장, 조립구역 등 + */ + +exports.up = function(knex) { + return knex.schema.createTable('workplaces', function(table) { + table.increments('workplace_id').primary().comment('작업장 ID'); + table.integer('category_id').unsigned().nullable().comment('카테고리 ID (공장)'); + table.string('workplace_name', 255).notNullable().comment('작업장명'); + table.text('description').nullable().comment('설명'); + table.boolean('is_active').defaultTo(true).comment('활성화 여부'); + table.timestamp('created_at').defaultTo(knex.fn.now()).comment('생성일시'); + table.timestamp('updated_at').defaultTo(knex.fn.now()).comment('수정일시'); + + // 외래키 + table.foreign('category_id') + .references('category_id') + .inTable('workplace_categories') + .onDelete('SET NULL') + .onUpdate('CASCADE'); + + // 인덱스 + table.index('category_id'); + table.index('is_active'); + }); +}; + +exports.down = function(knex) { + return knex.schema.dropTableIfExists('workplaces'); +}; diff --git a/api.hyungi.net/models/workplaceModel.js b/api.hyungi.net/models/workplaceModel.js new file mode 100644 index 0000000..e1e4506 --- /dev/null +++ b/api.hyungi.net/models/workplaceModel.js @@ -0,0 +1,297 @@ +const { getDb } = require('../dbPool'); + +// ==================== 카테고리(공장) 관련 ==================== + +/** + * 카테고리 생성 + */ +const createCategory = async (category, callback) => { + try { + const db = await getDb(); + const { + category_name, + description = null, + display_order = 0, + is_active = true + } = category; + + const [result] = await db.query( + `INSERT INTO workplace_categories + (category_name, description, display_order, is_active) + VALUES (?, ?, ?, ?)`, + [category_name, description, display_order, is_active] + ); + + callback(null, result.insertId); + } catch (err) { + callback(err); + } +}; + +/** + * 모든 카테고리 조회 + */ +const getAllCategories = async (callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT category_id, category_name, description, display_order, is_active, created_at, updated_at + FROM workplace_categories + ORDER BY display_order ASC, category_id ASC` + ); + callback(null, rows); + } catch (err) { + callback(err); + } +}; + +/** + * 활성 카테고리만 조회 + */ +const getActiveCategories = async (callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT category_id, category_name, description, display_order, is_active, created_at, updated_at + FROM workplace_categories + WHERE is_active = TRUE + ORDER BY display_order ASC, category_id ASC` + ); + callback(null, rows); + } catch (err) { + callback(err); + } +}; + +/** + * ID로 카테고리 조회 + */ +const getCategoryById = async (categoryId, callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT category_id, category_name, description, display_order, is_active, created_at, updated_at + FROM workplace_categories + WHERE category_id = ?`, + [categoryId] + ); + callback(null, rows[0]); + } catch (err) { + callback(err); + } +}; + +/** + * 카테고리 수정 + */ +const updateCategory = async (categoryId, category, callback) => { + try { + const db = await getDb(); + const { + category_name, + description, + display_order, + is_active + } = category; + + const [result] = await db.query( + `UPDATE workplace_categories + SET category_name = ?, description = ?, display_order = ?, is_active = ?, updated_at = NOW() + WHERE category_id = ?`, + [category_name, description, display_order, is_active, categoryId] + ); + + callback(null, result); + } catch (err) { + callback(err); + } +}; + +/** + * 카테고리 삭제 + */ +const deleteCategory = async (categoryId, callback) => { + try { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplace_categories WHERE category_id = ?`, + [categoryId] + ); + callback(null, result); + } catch (err) { + callback(err); + } +}; + +// ==================== 작업장 관련 ==================== + +/** + * 작업장 생성 + */ +const createWorkplace = async (workplace, callback) => { + try { + const db = await getDb(); + const { + category_id = null, + workplace_name, + description = null, + is_active = true + } = workplace; + + const [result] = await db.query( + `INSERT INTO workplaces + (category_id, workplace_name, description, is_active) + VALUES (?, ?, ?, ?)`, + [category_id, workplace_name, description, is_active] + ); + + callback(null, result.insertId); + } catch (err) { + callback(err); + } +}; + +/** + * 모든 작업장 조회 (카테고리 정보 포함) + */ +const getAllWorkplaces = async (callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, + w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + ORDER BY wc.display_order ASC, w.workplace_id DESC` + ); + callback(null, rows); + } catch (err) { + callback(err); + } +}; + +/** + * 활성 작업장만 조회 + */ +const getActiveWorkplaces = async (callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, + w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + WHERE w.is_active = TRUE + ORDER BY wc.display_order ASC, w.workplace_id DESC` + ); + callback(null, rows); + } catch (err) { + callback(err); + } +}; + +/** + * 카테고리별 작업장 조회 + */ +const getWorkplacesByCategory = async (categoryId, callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, + w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + WHERE w.category_id = ? + ORDER BY w.workplace_id DESC`, + [categoryId] + ); + callback(null, rows); + } catch (err) { + callback(err); + } +}; + +/** + * ID로 작업장 조회 + */ +const getWorkplaceById = async (workplaceId, callback) => { + try { + const db = await getDb(); + const [rows] = await db.query( + `SELECT w.workplace_id, w.category_id, w.workplace_name, w.description, w.is_active, + w.created_at, w.updated_at, + wc.category_name + FROM workplaces w + LEFT JOIN workplace_categories wc ON w.category_id = wc.category_id + WHERE w.workplace_id = ?`, + [workplaceId] + ); + callback(null, rows[0]); + } catch (err) { + callback(err); + } +}; + +/** + * 작업장 수정 + */ +const updateWorkplace = async (workplaceId, workplace, callback) => { + try { + const db = await getDb(); + const { + category_id, + workplace_name, + description, + is_active + } = workplace; + + const [result] = await db.query( + `UPDATE workplaces + SET category_id = ?, workplace_name = ?, description = ?, is_active = ?, updated_at = NOW() + WHERE workplace_id = ?`, + [category_id, workplace_name, description, is_active, workplaceId] + ); + + callback(null, result); + } catch (err) { + callback(err); + } +}; + +/** + * 작업장 삭제 + */ +const deleteWorkplace = async (workplaceId, callback) => { + try { + const db = await getDb(); + const [result] = await db.query( + `DELETE FROM workplaces WHERE workplace_id = ?`, + [workplaceId] + ); + callback(null, result); + } catch (err) { + callback(err); + } +}; + +module.exports = { + // 카테고리 + createCategory, + getAllCategories, + getActiveCategories, + getCategoryById, + updateCategory, + deleteCategory, + + // 작업장 + createWorkplace, + getAllWorkplaces, + getActiveWorkplaces, + getWorkplacesByCategory, + getWorkplaceById, + updateWorkplace, + deleteWorkplace +}; diff --git a/api.hyungi.net/routes/workplaceRoutes.js b/api.hyungi.net/routes/workplaceRoutes.js new file mode 100644 index 0000000..57cd1e9 --- /dev/null +++ b/api.hyungi.net/routes/workplaceRoutes.js @@ -0,0 +1,46 @@ +// routes/workplaceRoutes.js +const express = require('express'); +const router = express.Router(); +const workplaceController = require('../controllers/workplaceController'); + +// ==================== 카테고리(공장) 관리 ==================== + +// CREATE 카테고리 +router.post('/categories', workplaceController.createCategory); + +// READ ALL 카테고리 +router.get('/categories', workplaceController.getAllCategories); + +// READ ACTIVE 카테고리 +router.get('/categories/active/list', workplaceController.getActiveCategories); + +// READ ONE 카테고리 +router.get('/categories/:id', workplaceController.getCategoryById); + +// UPDATE 카테고리 +router.put('/categories/:id', workplaceController.updateCategory); + +// DELETE 카테고리 +router.delete('/categories/:id', workplaceController.deleteCategory); + +// ==================== 작업장 관리 ==================== + +// CREATE 작업장 +router.post('/', workplaceController.createWorkplace); + +// READ ALL 작업장 (쿼리 파라미터로 카테고리 필터링 가능: ?category_id=1) +router.get('/', workplaceController.getAllWorkplaces); + +// READ ACTIVE 작업장 +router.get('/active/list', workplaceController.getActiveWorkplaces); + +// READ ONE 작업장 +router.get('/:id', workplaceController.getWorkplaceById); + +// UPDATE 작업장 +router.put('/:id', workplaceController.updateWorkplace); + +// DELETE 작업장 +router.delete('/:id', workplaceController.deleteWorkplace); + +module.exports = router; diff --git a/web-ui/js/workplace-management.js b/web-ui/js/workplace-management.js new file mode 100644 index 0000000..daa743c --- /dev/null +++ b/web-ui/js/workplace-management.js @@ -0,0 +1,551 @@ +// 작업장 관리 페이지 JavaScript + +// 전역 변수 +let categories = []; +let workplaces = []; +let currentCategoryId = ''; +let currentEditingCategory = null; +let currentEditingWorkplace = null; + +// 페이지 초기화 +document.addEventListener('DOMContentLoaded', function() { + console.log('🏗️ 작업장 관리 페이지 초기화 시작'); + + loadAllData(); +}); + +// 모든 데이터 로드 +async function loadAllData() { + try { + await Promise.all([ + loadCategories(), + loadWorkplaces() + ]); + + renderCategoryTabs(); + renderWorkplaces(); + updateStatistics(); + } catch (error) { + console.error('데이터 로딩 오류:', error); + showToast('데이터를 불러오는데 실패했습니다.', 'error'); + } +} + +// ==================== 카테고리(공장) 관련 ==================== + +// 카테고리 목록 로드 +async function loadCategories() { + try { + const response = await apiCall('/workplaces/categories', 'GET'); + + let categoryData = []; + if (response && response.success && Array.isArray(response.data)) { + categoryData = response.data; + } else if (Array.isArray(response)) { + categoryData = response; + } + + categories = categoryData; + console.log(`✅ 카테고리 ${categories.length}개 로드 완료`); + } catch (error) { + console.error('카테고리 로딩 오류:', error); + categories = []; + } +} + +// 카테고리 탭 렌더링 +function renderCategoryTabs() { + const tabsContainer = document.getElementById('categoryTabs'); + if (!tabsContainer) return; + + // 전체 탭은 항상 표시 + let tabsHtml = ` + + `; + + // 각 카테고리 탭 추가 + categories.forEach(category => { + const count = workplaces.filter(w => w.category_id === category.category_id).length; + const isActive = currentCategoryId === category.category_id; + + tabsHtml += ` + + `; + }); + + tabsContainer.innerHTML = tabsHtml; +} + +// 카테고리 전환 +function switchCategory(categoryId) { + currentCategoryId = categoryId === '' ? '' : categoryId; + renderCategoryTabs(); + renderWorkplaces(); +} + +// 카테고리 모달 열기 +function openCategoryModal(categoryData = null) { + const modal = document.getElementById('categoryModal'); + const modalTitle = document.getElementById('categoryModalTitle'); + const deleteBtn = document.getElementById('deleteCategoryBtn'); + + if (!modal) return; + + currentEditingCategory = categoryData; + + if (categoryData) { + // 수정 모드 + modalTitle.textContent = '공장 수정'; + deleteBtn.style.display = 'inline-flex'; + + document.getElementById('categoryId').value = categoryData.category_id; + document.getElementById('categoryName').value = categoryData.category_name || ''; + document.getElementById('categoryDescription').value = categoryData.description || ''; + document.getElementById('categoryOrder').value = categoryData.display_order || 0; + } else { + // 신규 등록 모드 + modalTitle.textContent = '공장 추가'; + deleteBtn.style.display = 'none'; + + document.getElementById('categoryForm').reset(); + document.getElementById('categoryId').value = ''; + } + + modal.style.display = 'flex'; + document.body.style.overflow = 'hidden'; + + setTimeout(() => { + document.getElementById('categoryName').focus(); + }, 100); +} + +// 카테고리 모달 닫기 +function closeCategoryModal() { + const modal = document.getElementById('categoryModal'); + if (modal) { + modal.style.display = 'none'; + document.body.style.overflow = ''; + currentEditingCategory = null; + } +} + +// 카테고리 저장 +async function saveCategory() { + try { + const categoryId = document.getElementById('categoryId').value; + + const categoryData = { + category_name: document.getElementById('categoryName').value.trim(), + description: document.getElementById('categoryDescription').value.trim() || null, + display_order: parseInt(document.getElementById('categoryOrder').value) || 0, + is_active: true + }; + + if (!categoryData.category_name) { + showToast('공장명은 필수 입력 항목입니다.', 'error'); + return; + } + + console.log('💾 저장할 카테고리 데이터:', categoryData); + + let response; + if (categoryId) { + // 수정 + response = await apiCall(`/workplaces/categories/${categoryId}`, 'PUT', categoryData); + } else { + // 신규 등록 + response = await apiCall('/workplaces/categories', 'POST', categoryData); + } + + if (response && (response.success || response.category_id)) { + const action = categoryId ? '수정' : '등록'; + showToast(`공장이 성공적으로 ${action}되었습니다.`, 'success'); + + closeCategoryModal(); + await loadAllData(); + } else { + throw new Error(response?.message || '저장에 실패했습니다.'); + } + } catch (error) { + console.error('카테고리 저장 오류:', error); + showToast(error.message || '카테고리 저장 중 오류가 발생했습니다.', 'error'); + } +} + +// 카테고리 삭제 +async function deleteCategory() { + if (!currentEditingCategory) return; + + if (!confirm(`"${currentEditingCategory.category_name}" 공장을 정말 삭제하시겠습니까?\n\n⚠️ 이 공장에 속한 모든 작업장의 공장 정보가 제거됩니다.`)) { + return; + } + + try { + const response = await apiCall(`/workplaces/categories/${currentEditingCategory.category_id}`, 'DELETE'); + + if (response && response.success) { + showToast('공장이 성공적으로 삭제되었습니다.', 'success'); + + closeCategoryModal(); + await loadAllData(); + } else { + throw new Error(response?.message || '삭제에 실패했습니다.'); + } + } catch (error) { + console.error('카테고리 삭제 오류:', error); + showToast(error.message || '카테고리 삭제 중 오류가 발생했습니다.', 'error'); + } +} + +// ==================== 작업장 관련 ==================== + +// 작업장 목록 로드 +async function loadWorkplaces() { + try { + const response = await apiCall('/workplaces', 'GET'); + + let workplaceData = []; + if (response && response.success && Array.isArray(response.data)) { + workplaceData = response.data; + } else if (Array.isArray(response)) { + workplaceData = response; + } + + workplaces = workplaceData; + console.log(`✅ 작업장 ${workplaces.length}개 로드 완료`); + } catch (error) { + console.error('작업장 로딩 오류:', error); + workplaces = []; + } +} + +// 작업장 렌더링 +function renderWorkplaces() { + const grid = document.getElementById('workplaceGrid'); + if (!grid) return; + + // 현재 카테고리별 필터링 + const filtered = currentCategoryId === '' + ? workplaces + : workplaces.filter(w => w.category_id == currentCategoryId); + + if (filtered.length === 0) { + grid.innerHTML = ` +
"작업장 추가" 버튼을 눌러 작업장을 등록해보세요.
+ +${workplace.description}
` : ''} + +공장 및 작업장을 등록하고 관리합니다
+