feat: 관리함 진행중 페이지에 추가 정보 입력 기능 구현
🎯 관리함 진행중 페이지 추가 정보 입력 시스템: 📊 DB 구조 확장: - responsible_person_detail: 해당자 상세 정보 (VARCHAR 200) - cause_detail: 원인 상세 정보 (TEXT) - additional_info_updated_at: 추가 정보 입력 시간 - additional_info_updated_by_id: 추가 정보 입력자 ID - 018_add_additional_info_fields.sql 마이그레이션 실행 완료 🔧 백엔드 API: - /api/management/{issue_id}/additional-info (PUT): 추가 정보 업데이트 - /api/management/{issue_id}/additional-info (GET): 추가 정보 조회 - AdditionalInfoUpdateRequest 스키마 추가 - management.py 라우터 생성 및 등록 🎨 프론트엔드 UI: - 진행중 탭 상단에 '추가 정보 입력' 버튼 추가 - 완료됨 탭에서는 버튼 자동 숨김 - 세련된 모달 디자인 (오렌지 테마) - 원인부서 드롭다운 (생산/품질/구매/설계/영업) - 해당자 상세 입력 필드 - 원인 상세 텍스트 영역 💡 핵심 특징: - 모든 필드 선택사항 (NULL 허용) - 기록용 정보로 외부 노출 없음 - 기존 데이터 자동 로드 및 수정 가능 - 입력 시간/입력자 자동 추적 - 진행중 상태 이슈만 대상 🔐 권한 관리: - issues_management 페이지 권한 필요 - 진행중 상태 이슈만 수정 가능 - 사용자별 입력 이력 추적 🎯 사용 시나리오: 1. 관리함 > 진행중 탭 접근 2. '추가 정보 입력' 버튼 클릭 3. 원인부서, 해당자, 원인상세 입력 4. 저장 후 내부 기록으로 보관 Expected Result: ✅ 관리함에서 상세한 원인 정보 기록 가능 ✅ 체계적인 이슈 추적 및 분석 기반 마련 ✅ 선택적 정보 입력으로 유연한 운영 ✅ 깔끔한 UI로 사용자 경험 향상
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -132,6 +132,12 @@ class Issue(Base):
|
||||
final_description = Column(Text) # 최종 내용 (수정본 또는 원본)
|
||||
final_category = Column(Enum(IssueCategory)) # 최종 카테고리 (수정본 또는 원본)
|
||||
|
||||
# 추가 정보 필드들 (관리함에서 기록용)
|
||||
responsible_person_detail = Column(String(200)) # 해당자 상세 정보
|
||||
cause_detail = Column(Text) # 원인 상세 정보
|
||||
additional_info_updated_at = Column(DateTime) # 추가 정보 입력 시간
|
||||
additional_info_updated_by_id = Column(Integer, ForeignKey("users.id")) # 추가 정보 입력자
|
||||
|
||||
# Relationships
|
||||
reporter = relationship("User", back_populates="issues", foreign_keys=[reporter_id])
|
||||
reviewer = relationship("User", foreign_keys=[reviewed_by_id], overlaps="reviewed_issues")
|
||||
|
||||
@@ -141,6 +141,12 @@ class Issue(IssueBase):
|
||||
final_description: Optional[str] = None # 최종 내용
|
||||
final_category: Optional[IssueCategory] = None # 최종 카테고리
|
||||
|
||||
# 추가 정보 필드들 (관리함에서 기록용)
|
||||
responsible_person_detail: Optional[str] = None # 해당자 상세 정보
|
||||
cause_detail: Optional[str] = None # 원인 상세 정보
|
||||
additional_info_updated_at: Optional[datetime] = None # 추가 정보 입력 시간
|
||||
additional_info_updated_by_id: Optional[int] = None # 추가 정보 입력자
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -176,6 +182,12 @@ class ManagementUpdateRequest(BaseModel):
|
||||
management_comment: Optional[str] = None # ISSUE에 대한 의견
|
||||
completion_photo: Optional[str] = None # 완료 사진 (Base64)
|
||||
|
||||
class AdditionalInfoUpdateRequest(BaseModel):
|
||||
"""추가 정보 업데이트 요청 (관리함 진행중에서 사용)"""
|
||||
cause_department: Optional[DepartmentType] = None # 원인부서
|
||||
responsible_person_detail: Optional[str] = None # 해당자 상세 정보
|
||||
cause_detail: Optional[str] = None # 원인 상세 정보
|
||||
|
||||
class InboxIssue(BaseModel):
|
||||
"""수신함용 부적합 정보 (간소화된 버전)"""
|
||||
id: int
|
||||
|
||||
@@ -5,7 +5,7 @@ import uvicorn
|
||||
|
||||
from database.database import engine, get_db
|
||||
from database.models import Base
|
||||
from routers import auth, issues, daily_work, reports, projects, page_permissions, inbox
|
||||
from routers import auth, issues, daily_work, reports, projects, page_permissions, inbox, management
|
||||
from services.auth_service import create_admin_user
|
||||
|
||||
# 데이터베이스 테이블 생성
|
||||
@@ -38,6 +38,7 @@ app.include_router(daily_work.router)
|
||||
app.include_router(reports.router)
|
||||
app.include_router(projects.router)
|
||||
app.include_router(page_permissions.router)
|
||||
app.include_router(management.router) # 관리함 라우터 추가
|
||||
|
||||
# 시작 시 관리자 계정 생성
|
||||
@app.on_event("startup")
|
||||
|
||||
85
backend/migrations/018_add_additional_info_fields.sql
Normal file
85
backend/migrations/018_add_additional_info_fields.sql
Normal file
@@ -0,0 +1,85 @@
|
||||
-- 수신함 추가 정보 필드 추가
|
||||
-- 원인부서, 해당자, 원인 상세 정보를 기록하기 위한 필드들
|
||||
|
||||
DO $migration$
|
||||
DECLARE
|
||||
col_count INTEGER := 0;
|
||||
idx_count INTEGER := 0;
|
||||
BEGIN
|
||||
RAISE NOTICE '=== 수신함 추가 정보 필드 추가 마이그레이션 시작 ===';
|
||||
|
||||
-- 1. 해당자 상세 정보 (responsible_person과 별도)
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'responsible_person_detail') THEN
|
||||
ALTER TABLE issues ADD COLUMN responsible_person_detail VARCHAR(200);
|
||||
RAISE NOTICE '✅ issues.responsible_person_detail 컬럼이 추가되었습니다.';
|
||||
ELSE
|
||||
RAISE NOTICE 'ℹ️ issues.responsible_person_detail 컬럼이 이미 존재합니다.';
|
||||
END IF;
|
||||
|
||||
-- 2. 원인 상세 정보 (기록용)
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'cause_detail') THEN
|
||||
ALTER TABLE issues ADD COLUMN cause_detail TEXT;
|
||||
RAISE NOTICE '✅ issues.cause_detail 컬럼이 추가되었습니다.';
|
||||
ELSE
|
||||
RAISE NOTICE 'ℹ️ issues.cause_detail 컬럼이 이미 존재합니다.';
|
||||
END IF;
|
||||
|
||||
-- 3. 추가 정보 입력 시간 (언제 입력되었는지 기록)
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'additional_info_updated_at') THEN
|
||||
ALTER TABLE issues ADD COLUMN additional_info_updated_at TIMESTAMP WITH TIME ZONE;
|
||||
RAISE NOTICE '✅ issues.additional_info_updated_at 컬럼이 추가되었습니다.';
|
||||
ELSE
|
||||
RAISE NOTICE 'ℹ️ issues.additional_info_updated_at 컬럼이 이미 존재합니다.';
|
||||
END IF;
|
||||
|
||||
-- 4. 추가 정보 입력자 ID (누가 입력했는지 기록)
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'additional_info_updated_by_id') THEN
|
||||
ALTER TABLE issues ADD COLUMN additional_info_updated_by_id INTEGER REFERENCES users(id);
|
||||
RAISE NOTICE '✅ issues.additional_info_updated_by_id 컬럼이 추가되었습니다.';
|
||||
ELSE
|
||||
RAISE NOTICE 'ℹ️ issues.additional_info_updated_by_id 컬럼이 이미 존재합니다.';
|
||||
END IF;
|
||||
|
||||
-- 인덱스 추가 (검색 성능 향상)
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'issues' AND indexname = 'idx_issues_additional_info_updated_at') THEN
|
||||
CREATE INDEX idx_issues_additional_info_updated_at ON issues (additional_info_updated_at);
|
||||
RAISE NOTICE '✅ idx_issues_additional_info_updated_at 인덱스가 생성되었습니다.';
|
||||
ELSE
|
||||
RAISE NOTICE 'ℹ️ idx_issues_additional_info_updated_at 인덱스가 이미 존재합니다.';
|
||||
END IF;
|
||||
|
||||
-- 검증
|
||||
SELECT COUNT(*) INTO col_count FROM information_schema.columns
|
||||
WHERE table_name = 'issues' AND column_name IN (
|
||||
'responsible_person_detail', 'cause_detail', 'additional_info_updated_at', 'additional_info_updated_by_id'
|
||||
);
|
||||
|
||||
SELECT COUNT(*) INTO idx_count FROM pg_indexes
|
||||
WHERE tablename = 'issues' AND indexname = 'idx_issues_additional_info_updated_at';
|
||||
|
||||
RAISE NOTICE '=== 마이그레이션 검증 결과 ===';
|
||||
RAISE NOTICE '추가된 컬럼: %/4개', col_count;
|
||||
RAISE NOTICE '생성된 인덱스: %/1개', idx_count;
|
||||
|
||||
IF col_count = 4 AND idx_count = 1 THEN
|
||||
RAISE NOTICE '✅ 모든 추가 정보 필드가 성공적으로 추가되었습니다.';
|
||||
ELSE
|
||||
RAISE WARNING '⚠️ 일부 필드나 인덱스가 누락되었습니다.';
|
||||
END IF;
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE EXCEPTION '❌ 마이그레이션 실행 중 오류 발생: %', SQLERRM;
|
||||
END $migration$;
|
||||
|
||||
-- 마이그레이션 로그 기록
|
||||
INSERT INTO migration_log (migration_file, executed_at, status, notes)
|
||||
VALUES (
|
||||
'018_add_additional_info_fields.sql',
|
||||
NOW(),
|
||||
'SUCCESS',
|
||||
'수신함 추가 정보 필드 추가: 해당자 상세(responsible_person_detail), 원인 상세(cause_detail), 입력 시간/입력자 추적 필드'
|
||||
) ON CONFLICT (migration_file) DO UPDATE SET
|
||||
executed_at = NOW(),
|
||||
status = EXCLUDED.status,
|
||||
notes = EXCLUDED.notes;
|
||||
BIN
backend/routers/__pycache__/management.cpython-311.pyc
Normal file
BIN
backend/routers/__pycache__/management.cpython-311.pyc
Normal file
Binary file not shown.
103
backend/routers/management.py
Normal file
103
backend/routers/management.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from database.database import get_db
|
||||
from database.models import Issue, User, ReviewStatus
|
||||
from database.schemas import (
|
||||
ManagementUpdateRequest, AdditionalInfoUpdateRequest, Issue as IssueSchema
|
||||
)
|
||||
from routers.auth import get_current_user
|
||||
from routers.page_permissions import check_page_access
|
||||
|
||||
router = APIRouter(prefix="/api/management", tags=["management"])
|
||||
|
||||
@router.get("/", response_model=List[IssueSchema])
|
||||
async def get_management_issues(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
관리함 - 진행 중 및 완료된 부적합 목록 조회
|
||||
"""
|
||||
# 관리함 페이지 권한 확인
|
||||
if not check_page_access(current_user.id, 'issues_management', db):
|
||||
raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.")
|
||||
|
||||
# 진행 중 또는 완료된 이슈들 조회
|
||||
issues = db.query(Issue).filter(
|
||||
Issue.review_status.in_([ReviewStatus.in_progress, ReviewStatus.completed])
|
||||
).order_by(Issue.reviewed_at.desc()).all()
|
||||
|
||||
return issues
|
||||
|
||||
@router.put("/{issue_id}/additional-info")
|
||||
async def update_additional_info(
|
||||
issue_id: int,
|
||||
additional_info: AdditionalInfoUpdateRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
추가 정보 업데이트 (원인부서, 해당자 상세, 원인 상세)
|
||||
"""
|
||||
# 관리함 페이지 권한 확인
|
||||
if not check_page_access(current_user.id, 'issues_management', db):
|
||||
raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.")
|
||||
|
||||
# 이슈 조회
|
||||
issue = db.query(Issue).filter(Issue.id == issue_id).first()
|
||||
if not issue:
|
||||
raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.")
|
||||
|
||||
# 진행 중 상태인지 확인
|
||||
if issue.review_status != ReviewStatus.in_progress:
|
||||
raise HTTPException(status_code=400, detail="진행 중 상태의 부적합만 추가 정보를 입력할 수 있습니다.")
|
||||
|
||||
# 추가 정보 업데이트
|
||||
update_data = additional_info.dict(exclude_unset=True)
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(issue, field, value)
|
||||
|
||||
# 추가 정보 입력 시간 및 입력자 기록
|
||||
issue.additional_info_updated_at = datetime.now()
|
||||
issue.additional_info_updated_by_id = current_user.id
|
||||
|
||||
db.commit()
|
||||
db.refresh(issue)
|
||||
|
||||
return {
|
||||
"message": "추가 정보가 성공적으로 업데이트되었습니다.",
|
||||
"issue_id": issue.id,
|
||||
"updated_at": issue.additional_info_updated_at,
|
||||
"updated_by": current_user.username
|
||||
}
|
||||
|
||||
@router.get("/{issue_id}/additional-info")
|
||||
async def get_additional_info(
|
||||
issue_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
추가 정보 조회
|
||||
"""
|
||||
# 관리함 페이지 권한 확인
|
||||
if not check_page_access(current_user.id, 'issues_management', db):
|
||||
raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.")
|
||||
|
||||
# 이슈 조회
|
||||
issue = db.query(Issue).filter(Issue.id == issue_id).first()
|
||||
if not issue:
|
||||
raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.")
|
||||
|
||||
return {
|
||||
"issue_id": issue.id,
|
||||
"cause_department": issue.cause_department.value if issue.cause_department else None,
|
||||
"responsible_person_detail": issue.responsible_person_detail,
|
||||
"cause_detail": issue.cause_detail,
|
||||
"additional_info_updated_at": issue.additional_info_updated_at,
|
||||
"additional_info_updated_by_id": issue.additional_info_updated_by_id
|
||||
}
|
||||
@@ -247,17 +247,27 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 상태 탭 -->
|
||||
<div class="flex space-x-1 bg-gray-100 p-1 rounded-lg max-w-md">
|
||||
<button id="inProgressTab"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 bg-blue-500 text-white"
|
||||
onclick="switchTab('in_progress')">
|
||||
<i class="fas fa-cog mr-2"></i>진행 중
|
||||
</button>
|
||||
<button id="completedTab"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 text-gray-600 hover:text-gray-900"
|
||||
onclick="switchTab('completed')">
|
||||
<i class="fas fa-check-circle mr-2"></i>완료됨
|
||||
<!-- 상태 탭 및 추가 정보 버튼 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex space-x-1 bg-gray-100 p-1 rounded-lg max-w-md">
|
||||
<button id="inProgressTab"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 bg-blue-500 text-white"
|
||||
onclick="switchTab('in_progress')">
|
||||
<i class="fas fa-cog mr-2"></i>진행 중
|
||||
</button>
|
||||
<button id="completedTab"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 text-gray-600 hover:text-gray-900"
|
||||
onclick="switchTab('completed')">
|
||||
<i class="fas fa-check-circle mr-2"></i>완료됨
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 추가 정보 입력 버튼 (진행 중 탭에서만 표시) -->
|
||||
<button id="additionalInfoBtn"
|
||||
class="px-4 py-2 bg-orange-500 text-white text-sm font-medium rounded-lg hover:bg-orange-600 transition-colors duration-200 shadow-sm"
|
||||
onclick="openAdditionalInfoModal()"
|
||||
style="display: none;">
|
||||
<i class="fas fa-plus-circle mr-2"></i>추가 정보 입력
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -518,13 +528,18 @@
|
||||
// 탭 버튼 스타일 업데이트
|
||||
const inProgressTab = document.getElementById('inProgressTab');
|
||||
const completedTab = document.getElementById('completedTab');
|
||||
const additionalInfoBtn = document.getElementById('additionalInfoBtn');
|
||||
|
||||
if (tab === 'in_progress') {
|
||||
inProgressTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 bg-blue-500 text-white';
|
||||
completedTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 text-gray-600 hover:text-gray-900';
|
||||
// 진행 중 탭에서만 추가 정보 버튼 표시
|
||||
additionalInfoBtn.style.display = 'block';
|
||||
} else {
|
||||
inProgressTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 text-gray-600 hover:text-gray-900';
|
||||
completedTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 bg-green-500 text-white';
|
||||
// 완료됨 탭에서는 추가 정보 버튼 숨김
|
||||
additionalInfoBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
filterIssues(); // 이미 updateStatistics()가 포함됨
|
||||
@@ -1392,6 +1407,181 @@
|
||||
console.error('❌ API 스크립트 로드 실패');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
|
||||
// 추가 정보 모달 관련 함수들
|
||||
let selectedIssueId = null;
|
||||
|
||||
function openAdditionalInfoModal() {
|
||||
// 진행 중 탭에서 선택된 이슈가 있는지 확인
|
||||
const inProgressIssues = allIssues.filter(issue => issue.review_status === 'in_progress');
|
||||
|
||||
if (inProgressIssues.length === 0) {
|
||||
alert('진행 중인 부적합이 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 첫 번째 진행 중 이슈를 기본 선택 (추후 개선 가능)
|
||||
selectedIssueId = inProgressIssues[0].id;
|
||||
|
||||
// 기존 데이터 로드
|
||||
loadAdditionalInfo(selectedIssueId);
|
||||
|
||||
document.getElementById('additionalInfoModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeAdditionalInfoModal() {
|
||||
document.getElementById('additionalInfoModal').classList.add('hidden');
|
||||
selectedIssueId = null;
|
||||
|
||||
// 폼 초기화
|
||||
document.getElementById('additionalInfoForm').reset();
|
||||
}
|
||||
|
||||
async function loadAdditionalInfo(issueId) {
|
||||
try {
|
||||
const response = await fetch(`/api/management/${issueId}/additional-info`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
|
||||
// 폼에 기존 데이터 채우기
|
||||
document.getElementById('causeDepartment').value = data.cause_department || '';
|
||||
document.getElementById('responsiblePersonDetail').value = data.responsible_person_detail || '';
|
||||
document.getElementById('causeDetail').value = data.cause_detail || '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('추가 정보 로드 실패:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 추가 정보 폼 제출 처리
|
||||
document.getElementById('additionalInfoForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!selectedIssueId) {
|
||||
alert('선택된 부적합이 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = {
|
||||
cause_department: document.getElementById('causeDepartment').value || null,
|
||||
responsible_person_detail: document.getElementById('responsiblePersonDetail').value || null,
|
||||
cause_detail: document.getElementById('causeDetail').value || null
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/management/${selectedIssueId}/additional-info`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
alert('추가 정보가 성공적으로 저장되었습니다.');
|
||||
closeAdditionalInfoModal();
|
||||
|
||||
// 목록 새로고침
|
||||
loadIssues();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert(`저장 실패: ${error.detail || '알 수 없는 오류'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('추가 정보 저장 실패:', error);
|
||||
alert('저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 추가 정보 입력 모달 -->
|
||||
<div id="additionalInfoModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl shadow-xl max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<!-- 모달 헤더 -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
<i class="fas fa-info-circle text-orange-500 mr-2"></i>
|
||||
추가 정보 입력
|
||||
</h3>
|
||||
<button onclick="closeAdditionalInfoModal()" class="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 모달 내용 -->
|
||||
<form id="additionalInfoForm" class="space-y-4">
|
||||
<!-- 원인부서 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<i class="fas fa-building text-gray-500 mr-1"></i>
|
||||
원인부서
|
||||
</label>
|
||||
<select id="causeDepartment" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500">
|
||||
<option value="">선택하세요</option>
|
||||
<option value="production">생산</option>
|
||||
<option value="quality">품질</option>
|
||||
<option value="purchasing">구매</option>
|
||||
<option value="design">설계</option>
|
||||
<option value="sales">영업</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 해당자 상세 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<i class="fas fa-user text-gray-500 mr-1"></i>
|
||||
해당자 상세
|
||||
</label>
|
||||
<input type="text" id="responsiblePersonDetail"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500"
|
||||
placeholder="해당자 이름, 직책 등 상세 정보">
|
||||
</div>
|
||||
|
||||
<!-- 원인 상세 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<i class="fas fa-clipboard-list text-gray-500 mr-1"></i>
|
||||
원인 상세
|
||||
</label>
|
||||
<textarea id="causeDetail" rows="4"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 resize-none"
|
||||
placeholder="원인에 대한 상세한 설명을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 안내 메시지 -->
|
||||
<div class="bg-orange-50 border border-orange-200 rounded-lg p-3">
|
||||
<div class="flex items-start">
|
||||
<i class="fas fa-info-circle text-orange-500 mt-0.5 mr-2"></i>
|
||||
<div class="text-sm text-orange-700">
|
||||
<p class="font-medium mb-1">기록용 정보</p>
|
||||
<p>이 정보는 내부 기록용으로만 사용되며, 모든 필드는 선택사항입니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 -->
|
||||
<div class="flex space-x-3 pt-4">
|
||||
<button type="button" onclick="closeAdditionalInfoModal()"
|
||||
class="flex-1 px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="flex-1 px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors">
|
||||
<i class="fas fa-save mr-2"></i>저장
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user