refactor(tkqc): UI 스타일 통일 + 일일공수 제거 + 메뉴 정리
- UI: tkuser 스타일로 통일 (dark slate 헤더, flat 배경, gradient/glass 제거) - tkqc-common.css 공통 스타일시트 신규 생성 - 의견 제시 API 별도 엔드포인트 추가 (모든 사용자 접근 가능) - 일일 공수 기능 완전 제거 (라우터, 모델, 스키마, DB 테이블 DROP) - 프로젝트 관리/사용자 관리 메뉴 숨김 (통합관리 페이지로 이관) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,6 @@ class User(Base):
|
||||
# Relationships
|
||||
issues = relationship("Issue", back_populates="reporter", foreign_keys="Issue.reporter_id")
|
||||
reviewed_issues = relationship("Issue", foreign_keys="Issue.reviewed_by_id")
|
||||
daily_works = relationship("DailyWork", back_populates="created_by")
|
||||
page_permissions = relationship("UserPagePermission", back_populates="user", foreign_keys="UserPagePermission.user_id")
|
||||
|
||||
class UserPagePermission(Base):
|
||||
@@ -183,37 +182,6 @@ class Project(Base):
|
||||
primaryjoin="Project.id == Issue.project_id",
|
||||
foreign_keys="[Issue.project_id]")
|
||||
|
||||
class DailyWork(Base):
|
||||
__tablename__ = "qc_daily_works"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(DateTime, nullable=False, index=True)
|
||||
worker_count = Column(Integer, nullable=False)
|
||||
regular_hours = Column(Float, nullable=False)
|
||||
overtime_workers = Column(Integer, default=0)
|
||||
overtime_hours = Column(Float, default=0)
|
||||
overtime_total = Column(Float, default=0)
|
||||
total_hours = Column(Float, nullable=False)
|
||||
created_by_id = Column(Integer, ForeignKey("sso_users.user_id"))
|
||||
created_at = Column(DateTime, default=get_kst_now)
|
||||
|
||||
# Relationships
|
||||
created_by = relationship("User", back_populates="daily_works")
|
||||
|
||||
class ProjectDailyWork(Base):
|
||||
__tablename__ = "qc_project_daily_works"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(DateTime, nullable=False, index=True)
|
||||
project_id = Column(Integer, ForeignKey("projects.project_id"), nullable=False)
|
||||
hours = Column(Float, nullable=False)
|
||||
created_by_id = Column(Integer, ForeignKey("sso_users.user_id"))
|
||||
created_at = Column(DateTime, default=get_kst_now)
|
||||
|
||||
# Relationships
|
||||
project = relationship("Project")
|
||||
created_by = relationship("User")
|
||||
|
||||
class DeletionLog(Base):
|
||||
__tablename__ = "qc_deletion_logs"
|
||||
|
||||
|
||||
@@ -250,6 +250,10 @@ class ManagementUpdateRequest(BaseModel):
|
||||
completion_photo5: Optional[str] = None # Base64 - 완료 사진 5
|
||||
review_status: Optional[ReviewStatus] = None
|
||||
|
||||
class OpinionRequest(BaseModel):
|
||||
"""의견 제시 요청 (로그인한 모든 사용자 가능)"""
|
||||
opinion: str # 의견 내용
|
||||
|
||||
class InboxIssue(BaseModel):
|
||||
"""수신함용 부적합 정보 (간소화된 버전)"""
|
||||
id: int
|
||||
@@ -296,33 +300,6 @@ class Project(ProjectBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Daily Work schemas
|
||||
class DailyWorkBase(BaseModel):
|
||||
date: datetime
|
||||
worker_count: int = Field(gt=0)
|
||||
overtime_workers: Optional[int] = 0
|
||||
overtime_hours: Optional[float] = 0
|
||||
|
||||
class DailyWorkCreate(DailyWorkBase):
|
||||
pass
|
||||
|
||||
class DailyWorkUpdate(BaseModel):
|
||||
worker_count: Optional[int] = Field(None, gt=0)
|
||||
overtime_workers: Optional[int] = None
|
||||
overtime_hours: Optional[float] = None
|
||||
|
||||
class DailyWork(DailyWorkBase):
|
||||
id: int
|
||||
regular_hours: float
|
||||
overtime_total: float
|
||||
total_hours: float
|
||||
created_by_id: int
|
||||
created_by: User
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Report schemas
|
||||
class ReportRequest(BaseModel):
|
||||
start_date: datetime
|
||||
@@ -342,24 +319,6 @@ class ReportSummary(BaseModel):
|
||||
completed_issues: int
|
||||
average_resolution_time: float
|
||||
|
||||
# Project Daily Work schemas
|
||||
class ProjectDailyWorkBase(BaseModel):
|
||||
date: datetime
|
||||
project_id: int
|
||||
hours: float
|
||||
|
||||
class ProjectDailyWorkCreate(ProjectDailyWorkBase):
|
||||
pass
|
||||
|
||||
class ProjectDailyWork(ProjectDailyWorkBase):
|
||||
id: int
|
||||
created_by_id: int
|
||||
created_at: datetime
|
||||
project: Project
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# 일일보고서 관련 스키마
|
||||
class DailyReportRequest(BaseModel):
|
||||
project_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, management
|
||||
from routers import auth, issues, reports, projects, page_permissions, inbox, management
|
||||
from services.auth_service import create_admin_user
|
||||
|
||||
# 데이터베이스 테이블 생성 (sso_users, projects는 이미 존재하므로 제외)
|
||||
@@ -36,7 +36,6 @@ app.add_middleware(
|
||||
app.include_router(auth.router)
|
||||
app.include_router(issues.router)
|
||||
app.include_router(inbox.router) # 수신함 라우터 추가
|
||||
app.include_router(daily_work.router)
|
||||
app.include_router(reports.router)
|
||||
app.include_router(projects.router)
|
||||
app.include_router(page_permissions.router)
|
||||
|
||||
@@ -246,6 +246,42 @@ async def delete_issue(
|
||||
db.commit()
|
||||
return {"detail": "Issue deleted successfully", "logged": True}
|
||||
|
||||
@router.post("/{issue_id}/opinion")
|
||||
async def add_opinion(
|
||||
issue_id: int,
|
||||
opinion_request: schemas.OpinionRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
의견 제시 — 로그인한 모든 사용자가 사용 가능 (권한 체크 없음)
|
||||
solution 필드에 의견을 추가합니다.
|
||||
"""
|
||||
issue = db.query(Issue).filter(Issue.id == issue_id).first()
|
||||
if not issue:
|
||||
raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.")
|
||||
|
||||
# 새 의견 형식: [작성자] (날짜시간)\n내용
|
||||
now = datetime.now()
|
||||
date_str = now.strftime("%Y. %m. %d. %H:%M")
|
||||
author = current_user.full_name or current_user.username
|
||||
new_opinion = f"[{author}] ({date_str})\n{opinion_request.opinion}"
|
||||
|
||||
# 기존 solution에 추가 (최신이 위로)
|
||||
separator = "─" * 50
|
||||
if issue.solution:
|
||||
issue.solution = f"{new_opinion}\n{separator}\n{issue.solution}"
|
||||
else:
|
||||
issue.solution = new_opinion
|
||||
|
||||
db.commit()
|
||||
db.refresh(issue)
|
||||
|
||||
return {
|
||||
"message": "의견이 추가되었습니다.",
|
||||
"issue_id": issue.id
|
||||
}
|
||||
|
||||
@router.get("/stats/summary")
|
||||
async def get_issue_stats(
|
||||
current_user: User = Depends(get_current_user),
|
||||
|
||||
@@ -50,10 +50,7 @@ DEFAULT_PAGES = {
|
||||
'issues_management': {'title': '관리함', 'default_access': False},
|
||||
'issues_archive': {'title': '폐기함', 'default_access': False},
|
||||
'issues_dashboard': {'title': '현황판', 'default_access': True},
|
||||
'projects_manage': {'title': '프로젝트 관리', 'default_access': False},
|
||||
'daily_work': {'title': '일일 공수', 'default_access': False},
|
||||
'reports': {'title': '보고서', 'default_access': False},
|
||||
'users_manage': {'title': '사용자 관리', 'default_access': False}
|
||||
'reports': {'title': '보고서', 'default_access': False}
|
||||
}
|
||||
|
||||
@router.post("/page-permissions/grant")
|
||||
|
||||
@@ -13,7 +13,7 @@ from openpyxl.drawing.image import Image as XLImage
|
||||
import os
|
||||
|
||||
from database.database import get_db
|
||||
from database.models import Issue, DailyWork, IssueStatus, IssueCategory, User, UserRole, ReviewStatus
|
||||
from database.models import Issue, IssueStatus, IssueCategory, User, UserRole, ReviewStatus
|
||||
from database import schemas
|
||||
from routers.auth import get_current_user
|
||||
from utils.tkuser_client import get_token_from_request
|
||||
@@ -30,14 +30,9 @@ async def generate_report_summary(
|
||||
"""보고서 요약 생성"""
|
||||
start_date = report_request.start_date
|
||||
end_date = report_request.end_date
|
||||
|
||||
# 일일 공수 합계
|
||||
daily_works = db.query(DailyWork).filter(
|
||||
DailyWork.date >= start_date.date(),
|
||||
DailyWork.date <= end_date.date()
|
||||
).all()
|
||||
total_hours = sum(w.total_hours for w in daily_works)
|
||||
|
||||
|
||||
total_hours = 0
|
||||
|
||||
# 이슈 통계
|
||||
issues_query = db.query(Issue).filter(
|
||||
Issue.report_date >= start_date,
|
||||
@@ -116,29 +111,6 @@ async def get_report_issues(
|
||||
"detail_notes": issue.detail_notes
|
||||
} for issue in issues]
|
||||
|
||||
@router.get("/daily-works")
|
||||
async def get_report_daily_works(
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""보고서용 일일 공수 목록"""
|
||||
works = db.query(DailyWork).filter(
|
||||
DailyWork.date >= start_date.date(),
|
||||
DailyWork.date <= end_date.date()
|
||||
).order_by(DailyWork.date).all()
|
||||
|
||||
return [{
|
||||
"date": work.date,
|
||||
"worker_count": work.worker_count,
|
||||
"regular_hours": work.regular_hours,
|
||||
"overtime_workers": work.overtime_workers,
|
||||
"overtime_hours": work.overtime_hours,
|
||||
"overtime_total": work.overtime_total,
|
||||
"total_hours": work.total_hours
|
||||
} for work in works]
|
||||
|
||||
@router.get("/daily-preview")
|
||||
async def preview_daily_report(
|
||||
project_id: int,
|
||||
|
||||
@@ -714,8 +714,6 @@
|
||||
'issues_inbox': { title: '수신함', defaultAccess: true },
|
||||
'issues_management': { title: '관리함', defaultAccess: false },
|
||||
'issues_archive': { title: '폐기함', defaultAccess: false },
|
||||
'projects_manage': { title: '프로젝트 관리', defaultAccess: false },
|
||||
'daily_work': { title: '일일 공수', defaultAccess: false },
|
||||
'reports': { title: '보고서', defaultAccess: false }
|
||||
};
|
||||
|
||||
@@ -769,10 +767,7 @@
|
||||
'issues_archive': { title: '🗃️ 폐기함', icon: 'fas fa-archive', color: 'text-gray-600' }
|
||||
},
|
||||
'시스템 관리': {
|
||||
'projects_manage': { title: '프로젝트 관리', icon: 'fas fa-folder-open', color: 'text-indigo-600' },
|
||||
'daily_work': { title: '일일 공수', icon: 'fas fa-calendar-check', color: 'text-blue-600' },
|
||||
'reports': { title: '보고서', icon: 'fas fa-chart-bar', color: 'text-red-600' },
|
||||
'users_manage': { title: '사용자 관리', icon: 'fas fa-users-cog', color: 'text-purple-600' }
|
||||
'reports': { title: '보고서', icon: 'fas fa-chart-bar', color: 'text-red-600' }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -839,7 +834,7 @@
|
||||
const allPages = [
|
||||
'issues_create', 'issues_view', 'issues_manage',
|
||||
'issues_inbox', 'issues_management', 'issues_archive',
|
||||
'projects_manage', 'daily_work', 'reports', 'users_manage'
|
||||
'reports'
|
||||
];
|
||||
const permissions = {};
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<title>일일 공수 입력</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary: #3b82f6;
|
||||
@@ -16,62 +20,55 @@
|
||||
--gray-200: #e5e7eb;
|
||||
--gray-300: #d1d5db;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--gray-50);
|
||||
}
|
||||
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-dark);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.btn-success {
|
||||
background-color: var(--success);
|
||||
color: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #059669;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
|
||||
.input-field {
|
||||
border: 1px solid var(--gray-300);
|
||||
background: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.input-field:focus {
|
||||
border-color: var(--primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.work-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.work-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.summary-card {
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.nav-link {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -79,61 +76,24 @@
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: #f3f4f6;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
|
||||
.nav-link.active {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 부드러운 페이드인 애니메이션 */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 헤더 전용 빠른 페이드인 */
|
||||
.header-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
.header-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 본문 컨텐츠 지연 페이드인 */
|
||||
.content-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
|
||||
.content-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<div class="min-h-screen">
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- 메인 컨텐츠 -->
|
||||
<main class="container mx-auto px-4 py-6 max-w-2xl content-fade-in" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-6 max-w-2xl content-fade-in" style="padding-top: 72px;">
|
||||
<!-- 입력 카드 -->
|
||||
<div class="work-card p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-6">
|
||||
|
||||
@@ -11,42 +11,19 @@
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 50%, #f0f9ff 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.glass-effect {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.input-field {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: #60a5fa;
|
||||
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
|
||||
.nav-link {
|
||||
color: #6b7280;
|
||||
padding: 0.5rem 1rem;
|
||||
@@ -54,49 +31,23 @@
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: #f3f4f6;
|
||||
color: #3b82f6;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
|
||||
.nav-link.active {
|
||||
background-color: #3b82f6;
|
||||
background-color: #334155;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 부드러운 페이드인 애니메이션 */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 헤더 전용 빠른 페이드인 */
|
||||
.header-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
.header-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 본문 컨텐츠 지연 페이드인 */
|
||||
|
||||
.content-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
transition-delay: 0.2s;
|
||||
transform: translateY(10px);
|
||||
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.content-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@@ -107,7 +58,7 @@
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-4 mb-6">
|
||||
<div class="mb-4">
|
||||
|
||||
@@ -13,60 +13,33 @@
|
||||
|
||||
<!-- 모바일 캘린더 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/mobile-calendar.css">
|
||||
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.issue-card {
|
||||
transition: all 0.2s ease;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.issue-card:hover {
|
||||
opacity: 1;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.archived-card {
|
||||
border-left: 4px solid #6b7280;
|
||||
background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
|
||||
.completed-card {
|
||||
border-left: 4px solid #10b981;
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 100%);
|
||||
background: #f0fdf4;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge-completed { background: #d1fae5; color: #065f46; }
|
||||
.badge-archived { background: #f3f4f6; color: #374151; }
|
||||
.badge-cancelled { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -6,96 +6,55 @@
|
||||
<title>부적합 현황판</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<style>
|
||||
.fade-in { opacity: 0; animation: fadeIn 0.5s ease-in forwards; }
|
||||
@keyframes fadeIn { to { opacity: 1; } }
|
||||
|
||||
.header-fade-in { opacity: 0; animation: headerFadeIn 0.6s ease-out forwards; }
|
||||
@keyframes headerFadeIn { to { opacity: 1; transform: translateY(0); } from { transform: translateY(-10px); } }
|
||||
|
||||
.content-fade-in { opacity: 0; animation: contentFadeIn 0.7s ease-out 0.2s forwards; }
|
||||
@keyframes contentFadeIn { to { opacity: 1; transform: translateY(0); } from { transform: translateY(20px); } }
|
||||
|
||||
.content-fade-in { opacity: 0; animation: contentFadeIn 0.4s ease-out 0.2s forwards; }
|
||||
@keyframes contentFadeIn { to { opacity: 1; transform: translateY(0); } from { transform: translateY(10px); } }
|
||||
|
||||
/* 대시보드 카드 스타일 */
|
||||
.dashboard-card {
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.dashboard-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
/* 이슈 카드 스타일 (세련된 모던 스타일) */
|
||||
.issue-card {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-left: 4px solid transparent;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||
}
|
||||
|
||||
.issue-card:hover {
|
||||
transform: translateY(-8px) scale(1.02);
|
||||
border-left-color: #3b82f6;
|
||||
box-shadow:
|
||||
0 25px 50px -12px rgba(0, 0, 0, 0.15),
|
||||
0 0 0 1px rgba(59, 130, 246, 0.1),
|
||||
0 0 20px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.issue-card label {
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
|
||||
.issue-card .bg-gray-50 {
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.issue-card .bg-gray-50:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.issue-card .fas.fa-image:hover {
|
||||
transform: scale(1.2);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* 진행 중 애니메이션 */
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* 날짜 그룹 스타일 */
|
||||
.date-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.date-header {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.date-header:hover {
|
||||
background-color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
|
||||
.collapse-content {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.progress-bar {
|
||||
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
|
||||
background: #10b981;
|
||||
transition: width 0.8s ease;
|
||||
}
|
||||
|
||||
|
||||
/* 반응형 그리드 */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
@@ -104,7 +63,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 로딩 스크린 -->
|
||||
<div id="loadingScreen" class="fixed inset-0 bg-white z-50 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
@@ -139,7 +98,7 @@
|
||||
<div id="commonHeader"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -155,7 +114,7 @@
|
||||
|
||||
<!-- 전체 통계 대시보드 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="dashboard-card text-white p-6 rounded-xl">
|
||||
<div class="bg-blue-500 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-blue-100 text-sm flex items-center space-x-1">
|
||||
@@ -167,43 +126,43 @@
|
||||
<i class="fas fa-tasks text-4xl text-blue-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-green-400 to-green-600 text-white p-6 rounded-xl dashboard-card">
|
||||
|
||||
<div class="bg-emerald-500 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-green-100 text-sm flex items-center space-x-1">
|
||||
<p class="text-emerald-100 text-sm flex items-center space-x-1">
|
||||
<span>오늘 신규</span>
|
||||
<div class="w-1.5 h-1.5 bg-green-200 rounded-full animate-pulse"></div>
|
||||
<div class="w-1.5 h-1.5 bg-emerald-200 rounded-full animate-pulse"></div>
|
||||
</p>
|
||||
<p class="text-3xl font-bold" id="todayNew">0</p>
|
||||
</div>
|
||||
<i class="fas fa-plus-circle text-4xl text-green-200"></i>
|
||||
<i class="fas fa-plus-circle text-4xl text-emerald-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-purple-400 to-purple-600 text-white p-6 rounded-xl dashboard-card">
|
||||
|
||||
<div class="bg-amber-500 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-purple-100 text-sm flex items-center space-x-1">
|
||||
<p class="text-amber-100 text-sm flex items-center space-x-1">
|
||||
<span>완료 대기</span>
|
||||
<div class="w-1.5 h-1.5 bg-purple-200 rounded-full animate-pulse"></div>
|
||||
<div class="w-1.5 h-1.5 bg-amber-200 rounded-full animate-pulse"></div>
|
||||
</p>
|
||||
<p class="text-3xl font-bold" id="pendingCompletion">0</p>
|
||||
</div>
|
||||
<i class="fas fa-hourglass-half text-4xl text-purple-200"></i>
|
||||
<i class="fas fa-hourglass-half text-4xl text-amber-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-red-400 to-red-600 text-white p-6 rounded-xl dashboard-card">
|
||||
|
||||
<div class="bg-slate-500 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-red-100 text-sm flex items-center space-x-1">
|
||||
<p class="text-slate-200 text-sm flex items-center space-x-1">
|
||||
<span>지연 중</span>
|
||||
<div class="w-1.5 h-1.5 bg-red-200 rounded-full animate-pulse"></div>
|
||||
<div class="w-1.5 h-1.5 bg-slate-300 rounded-full animate-pulse"></div>
|
||||
</p>
|
||||
<p class="text-3xl font-bold" id="overdue">0</p>
|
||||
</div>
|
||||
<i class="fas fa-clock text-4xl text-red-200"></i>
|
||||
<i class="fas fa-clock text-4xl text-slate-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2131,44 +2090,15 @@
|
||||
}
|
||||
|
||||
try {
|
||||
// 현재 이슈 정보 가져오기
|
||||
const issueResponse = await fetch(`/api/issues/${selectedOpinionIssueId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${TokenManager.getToken()}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!issueResponse.ok) {
|
||||
throw new Error('이슈 정보를 가져올 수 없습니다.');
|
||||
}
|
||||
|
||||
const issue = await issueResponse.json();
|
||||
|
||||
// 새 의견 형식: [작성자] (날짜시간)\n내용
|
||||
const now = new Date();
|
||||
const dateStr = now.toLocaleString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
const newOpinion = `[${currentUser.full_name || currentUser.username}] (${dateStr})\n${opinionText}`;
|
||||
|
||||
// 기존 solution에 추가 (최신이 위로)
|
||||
const updatedSolution = issue.solution
|
||||
? `${newOpinion}\n${'─'.repeat(50)}\n${issue.solution}`
|
||||
: newOpinion;
|
||||
|
||||
// 백엔드 업데이트
|
||||
const response = await fetch(`/api/issues/${selectedOpinionIssueId}/management`, {
|
||||
method: 'PUT',
|
||||
// 별도 의견 제시 API 사용 (권한 체크 없음, 로그인만 필요)
|
||||
const response = await fetch(`/api/issues/${selectedOpinionIssueId}/opinion`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${TokenManager.getToken()}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
solution: updatedSolution
|
||||
opinion: opinionText
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -13,15 +13,12 @@
|
||||
|
||||
<!-- 모바일 캘린더 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/mobile-calendar.css">
|
||||
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -37,81 +34,32 @@
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.loading-overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.issue-card {
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
|
||||
.issue-card.unread {
|
||||
border-left-color: #3b82f6;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%);
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.issue-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.priority-high { border-left-color: #ef4444; }
|
||||
.priority-medium { border-left-color: #f59e0b; }
|
||||
.priority-low { border-left-color: #10b981; }
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge-new { background: #dbeafe; color: #1e40af; }
|
||||
.badge-processing { background: #fef3c7; color: #92400e; }
|
||||
.badge-completed { background: #d1fae5; color: #065f46; }
|
||||
|
||||
/* 부드러운 페이드인 애니메이션 */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 헤더 전용 빠른 페이드인 */
|
||||
.header-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
.header-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 본문 컨텐츠 지연 페이드인 */
|
||||
|
||||
.content-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
transition-delay: 0.2s;
|
||||
transform: translateY(10px);
|
||||
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.content-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
|
||||
/* 사진 미리보기 스타일 */
|
||||
.photo-preview {
|
||||
max-width: 150px;
|
||||
@@ -121,18 +69,18 @@
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.photo-preview:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
|
||||
.photo-gallery {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
|
||||
.photo-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -145,14 +93,14 @@
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
|
||||
.photo-modal img {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
.photo-modal .close-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
@@ -167,7 +115,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 로딩 오버레이 -->
|
||||
<div id="loadingOverlay" class="loading-overlay">
|
||||
<div class="text-center">
|
||||
@@ -179,7 +127,7 @@
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -13,69 +13,43 @@
|
||||
|
||||
<!-- 모바일 캘린더 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/mobile-calendar.css">
|
||||
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.issue-card {
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.issue-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.priority-high { border-left-color: #ef4444; }
|
||||
.priority-medium { border-left-color: #f59e0b; }
|
||||
.priority-low { border-left-color: #10b981; }
|
||||
|
||||
|
||||
.status-new { border-left-color: #3b82f6; }
|
||||
.status-processing { border-left-color: #f59e0b; }
|
||||
.status-pending { border-left-color: #8b5cf6; }
|
||||
.status-completed { border-left-color: #10b981; }
|
||||
|
||||
|
||||
.action-btn {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 날짜 그룹 스타일 */
|
||||
.date-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.date-header {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.date-header:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
|
||||
/* 좌우 스크롤 가능한 이슈 테이블 */
|
||||
.issue-table-container {
|
||||
overflow-x: auto;
|
||||
@@ -83,13 +57,13 @@
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.issue-table {
|
||||
min-width: 2000px; /* 더 넓은 최소 너비 설정 */
|
||||
min-width: 2000px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
||||
.issue-table th,
|
||||
.issue-table td {
|
||||
padding: 0.75rem;
|
||||
@@ -97,7 +71,7 @@
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
||||
.issue-table th {
|
||||
background-color: #f9fafb;
|
||||
font-weight: 600;
|
||||
@@ -105,11 +79,11 @@
|
||||
font-size: 0.875rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.issue-table tbody tr:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
|
||||
/* 컬럼별 너비 조정 */
|
||||
.col-no { min-width: 60px; }
|
||||
.col-project { min-width: 120px; }
|
||||
@@ -125,7 +99,7 @@
|
||||
.col-photos { min-width: 150px; }
|
||||
.col-completion { min-width: 80px; }
|
||||
.col-actions { min-width: 120px; }
|
||||
|
||||
|
||||
.issue-photo {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
@@ -134,13 +108,13 @@
|
||||
cursor: pointer;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
|
||||
.photo-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
|
||||
/* 편집 가능한 필드 스타일 */
|
||||
.editable-field {
|
||||
min-width: 100%;
|
||||
@@ -149,19 +123,19 @@
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
|
||||
.editable-field:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
border-color: #64748b;
|
||||
box-shadow: 0 0 0 1px #64748b;
|
||||
}
|
||||
|
||||
|
||||
.text-wrap {
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
|
||||
.btn-sm {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.75rem;
|
||||
@@ -170,58 +144,39 @@
|
||||
white-space: nowrap;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
|
||||
.collapse-content {
|
||||
max-height: 1000px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.collapse-content.collapsed {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
/* 진행 중 카드 스타일 */
|
||||
.issue-card {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.issue-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
|
||||
.issue-card label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.issue-card input:focus,
|
||||
.issue-card select:focus,
|
||||
.issue-card textarea:focus {
|
||||
transform: scale(1.01);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
|
||||
.issue-card .bg-gray-50 {
|
||||
border-left: 4px solid #e5e7eb;
|
||||
}
|
||||
|
||||
|
||||
/* 카드 내 아이콘 스타일 */
|
||||
.issue-card i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.badge-new { background: #dbeafe; color: #1e40af; }
|
||||
.badge-processing { background: #fef3c7; color: #92400e; }
|
||||
|
||||
.badge-pending { background: #ede9fe; color: #7c3aed; }
|
||||
.badge-completed { background: #d1fae5; color: #065f46; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<title>프로젝트 관리 - 작업보고서 시스템</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary: #3b82f6;
|
||||
@@ -18,78 +22,35 @@
|
||||
--gray-200: #e5e7eb;
|
||||
--gray-300: #d1d5db;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--gray-50);
|
||||
}
|
||||
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-dark);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.input-field {
|
||||
border: 1px solid var(--gray-300);
|
||||
background: white;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.input-field:focus {
|
||||
border-color: var(--primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* 부드러운 페이드인 애니메이션 */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 헤더 전용 빠른 페이드인 */
|
||||
.header-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||
}
|
||||
|
||||
.header-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 본문 컨텐츠 지연 페이드인 */
|
||||
.content-fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||
transition-delay: 0.2s;
|
||||
}
|
||||
|
||||
.content-fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- 메인 컨텐츠 -->
|
||||
<main class="container mx-auto px-4 py-8 max-w-4xl content-fade-in" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8 max-w-4xl content-fade-in" style="padding-top: 72px;">
|
||||
<!-- 프로젝트 생성 섹션 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">
|
||||
|
||||
@@ -11,22 +11,17 @@
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.report-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
border-left-color: #10b981;
|
||||
}
|
||||
|
||||
@@ -34,10 +29,6 @@
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stats-card:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.issue-row {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
@@ -47,11 +38,11 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -10,21 +10,15 @@
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -10,21 +10,15 @@
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<body>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
@@ -10,49 +10,44 @@
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
|
||||
<!-- 공통 스타일 -->
|
||||
<link rel="stylesheet" href="/static/css/tkqc-common.css">
|
||||
|
||||
<!-- Custom Styles -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
|
||||
.report-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
|
||||
border-left-color: #3b82f6;
|
||||
}
|
||||
|
||||
|
||||
.report-card.daily-report {
|
||||
border-left-color: #10b981;
|
||||
}
|
||||
|
||||
|
||||
.report-card.daily-report:hover {
|
||||
border-left-color: #059669;
|
||||
}
|
||||
|
||||
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: #6366f1;
|
||||
}
|
||||
|
||||
|
||||
.stats-card {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
background: #ec4899;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<body>
|
||||
<!-- 공통 헤더 -->
|
||||
<div id="commonHeader"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
|
||||
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
|
||||
39
system3-nonconformance/web/static/css/tkqc-common.css
Normal file
39
system3-nonconformance/web/static/css/tkqc-common.css
Normal file
@@ -0,0 +1,39 @@
|
||||
/* tkqc-common.css — tkuser 스타일 통일 */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: #f1f5f9;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
background: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: #64748b;
|
||||
box-shadow: 0 0 0 3px rgba(100,116,139,0.1);
|
||||
}
|
||||
|
||||
.issue-card {
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
.issue-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.badge { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; }
|
||||
.badge-new { background: #dbeafe; color: #1e40af; }
|
||||
.badge-processing { background: #fef3c7; color: #92400e; }
|
||||
.badge-completed { background: #d1fae5; color: #065f46; }
|
||||
.badge-archived { background: #f3f4f6; color: #374151; }
|
||||
.badge-cancelled { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
.fade-in { opacity: 0; transform: translateY(10px); transition: opacity 0.4s, transform 0.4s; }
|
||||
.fade-in.visible { opacity: 1; transform: translateY(0); }
|
||||
|
||||
.toast-message { transition: all 0.3s ease; }
|
||||
@@ -273,35 +273,6 @@ const IssuesAPI = {
|
||||
getStats: () => apiRequest('/issues/stats/summary')
|
||||
};
|
||||
|
||||
// Daily Work API
|
||||
const DailyWorkAPI = {
|
||||
create: (workData) => apiRequest('/daily-work/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(workData)
|
||||
}),
|
||||
|
||||
getAll: (params = {}) => {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
return apiRequest(`/daily-work/${queryString ? '?' + queryString : ''}`);
|
||||
},
|
||||
|
||||
get: (id) => apiRequest(`/daily-work/${id}`),
|
||||
|
||||
update: (id, workData) => apiRequest(`/daily-work/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(workData)
|
||||
}),
|
||||
|
||||
delete: (id) => apiRequest(`/daily-work/${id}`, {
|
||||
method: 'DELETE'
|
||||
}),
|
||||
|
||||
getStats: (params = {}) => {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
return apiRequest(`/daily-work/stats/summary${queryString ? '?' + queryString : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Reports API
|
||||
const ReportsAPI = {
|
||||
getSummary: (startDate, endDate) => apiRequest('/reports/summary', {
|
||||
@@ -320,13 +291,6 @@ const ReportsAPI = {
|
||||
return apiRequest(`/reports/issues?${params}`);
|
||||
},
|
||||
|
||||
getDailyWorks: (startDate, endDate) => {
|
||||
const params = new URLSearchParams({
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
}).toString();
|
||||
return apiRequest(`/reports/daily-works?${params}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 권한 체크
|
||||
|
||||
@@ -270,10 +270,7 @@ class App {
|
||||
const titles = {
|
||||
'dashboard': '대시보드',
|
||||
'issues': '부적합 사항',
|
||||
'projects': '프로젝트',
|
||||
'daily_work': '일일 공수',
|
||||
'reports': '보고서',
|
||||
'users': '사용자 관리'
|
||||
'reports': '보고서'
|
||||
};
|
||||
|
||||
const title = titles[module] || module;
|
||||
|
||||
@@ -32,8 +32,8 @@ class CommonHeader {
|
||||
icon: 'fas fa-chart-line',
|
||||
url: '/issues-dashboard.html',
|
||||
pageName: 'issues_dashboard',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100'
|
||||
color: 'text-slate-300',
|
||||
bgColor: 'text-slate-300 hover:bg-slate-700'
|
||||
},
|
||||
{
|
||||
id: 'issues_inbox',
|
||||
@@ -41,8 +41,8 @@ class CommonHeader {
|
||||
icon: 'fas fa-inbox',
|
||||
url: '/issues-inbox.html',
|
||||
pageName: 'issues_inbox',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100'
|
||||
color: 'text-slate-300',
|
||||
bgColor: 'text-slate-300 hover:bg-slate-700'
|
||||
},
|
||||
{
|
||||
id: 'issues_management',
|
||||
@@ -50,8 +50,8 @@ class CommonHeader {
|
||||
icon: 'fas fa-cog',
|
||||
url: '/issues-management.html',
|
||||
pageName: 'issues_management',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100'
|
||||
color: 'text-slate-300',
|
||||
bgColor: 'text-slate-300 hover:bg-slate-700'
|
||||
},
|
||||
{
|
||||
id: 'issues_archive',
|
||||
@@ -59,17 +59,8 @@ class CommonHeader {
|
||||
icon: 'fas fa-archive',
|
||||
url: '/issues-archive.html',
|
||||
pageName: 'issues_archive',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100'
|
||||
},
|
||||
{
|
||||
id: 'daily_work',
|
||||
title: '일일 공수',
|
||||
icon: 'fas fa-calendar-check',
|
||||
url: '/daily-work.html',
|
||||
pageName: 'daily_work',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100'
|
||||
color: 'text-slate-300',
|
||||
bgColor: 'text-slate-300 hover:bg-slate-700'
|
||||
},
|
||||
{
|
||||
id: 'reports',
|
||||
@@ -77,8 +68,8 @@ class CommonHeader {
|
||||
icon: 'fas fa-chart-bar',
|
||||
url: '/reports.html',
|
||||
pageName: 'reports',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100',
|
||||
color: 'text-slate-300',
|
||||
bgColor: 'text-slate-300 hover:bg-slate-700',
|
||||
subMenus: [
|
||||
{
|
||||
id: 'reports_daily',
|
||||
@@ -86,7 +77,7 @@ class CommonHeader {
|
||||
icon: 'fas fa-file-excel',
|
||||
url: '/reports-daily.html',
|
||||
pageName: 'reports_daily',
|
||||
color: 'text-slate-600'
|
||||
color: 'text-slate-300'
|
||||
},
|
||||
{
|
||||
id: 'reports_weekly',
|
||||
@@ -94,7 +85,7 @@ class CommonHeader {
|
||||
icon: 'fas fa-calendar-week',
|
||||
url: '/reports-weekly.html',
|
||||
pageName: 'reports_weekly',
|
||||
color: 'text-slate-600'
|
||||
color: 'text-slate-300'
|
||||
},
|
||||
{
|
||||
id: 'reports_monthly',
|
||||
@@ -102,29 +93,10 @@ class CommonHeader {
|
||||
icon: 'fas fa-calendar-alt',
|
||||
url: '/reports-monthly.html',
|
||||
pageName: 'reports_monthly',
|
||||
color: 'text-slate-600'
|
||||
color: 'text-slate-300'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'projects_manage',
|
||||
title: '프로젝트 관리',
|
||||
icon: 'fas fa-folder-open',
|
||||
url: '/project-management.html',
|
||||
pageName: 'projects_manage',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100'
|
||||
},
|
||||
{
|
||||
id: 'users_manage',
|
||||
title: '사용자 관리',
|
||||
icon: 'fas fa-users-cog',
|
||||
url: this._getUserManageUrl(),
|
||||
pageName: 'users_manage',
|
||||
color: 'text-slate-600',
|
||||
bgColor: 'text-slate-600 hover:bg-slate-100',
|
||||
external: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -205,14 +177,14 @@ class CommonHeader {
|
||||
const userRole = this.getUserRoleDisplay();
|
||||
|
||||
return `
|
||||
<header class="bg-white shadow-sm border-b sticky top-0 z-50">
|
||||
<header class="bg-slate-800 text-white sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex justify-between items-center h-14">
|
||||
<!-- 로고 및 제목 -->
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 flex items-center">
|
||||
<i class="fas fa-shield-halved text-2xl text-slate-700 mr-3"></i>
|
||||
<h1 class="text-xl font-bold text-gray-900">부적합 관리</h1>
|
||||
<i class="fas fa-shield-halved text-2xl text-slate-300 mr-3"></i>
|
||||
<h1 class="text-xl font-bold text-white">부적합 관리</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -226,8 +198,8 @@ class CommonHeader {
|
||||
<!-- 사용자 정보 -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="text-right">
|
||||
<div class="text-sm font-medium text-gray-900">${userDisplayName}</div>
|
||||
<div class="text-xs text-gray-500">${userRole}</div>
|
||||
<div class="text-sm font-medium text-white">${userDisplayName}</div>
|
||||
<div class="text-xs text-slate-400">${userRole}</div>
|
||||
</div>
|
||||
<div class="w-8 h-8 bg-slate-600 rounded-full flex items-center justify-center">
|
||||
<span class="text-white text-sm font-semibold">
|
||||
@@ -238,7 +210,7 @@ class CommonHeader {
|
||||
|
||||
<!-- 드롭다운 메뉴 -->
|
||||
<div class="relative">
|
||||
<button id="user-menu-button" class="p-2 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-md">
|
||||
<button id="user-menu-button" class="p-2 text-slate-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 focus:ring-offset-slate-800 rounded-md">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="user-menu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring-black ring-opacity-5">
|
||||
@@ -252,14 +224,14 @@ class CommonHeader {
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 버튼 -->
|
||||
<button id="mobile-menu-button" class="md:hidden p-2 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-md">
|
||||
<button id="mobile-menu-button" class="md:hidden p-2 text-slate-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 focus:ring-offset-slate-800 rounded-md">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 모바일 메뉴 -->
|
||||
<div id="mobile-menu" class="md:hidden hidden border-t border-gray-200 py-3">
|
||||
<div id="mobile-menu" class="md:hidden hidden border-t border-slate-700 py-3">
|
||||
<div class="space-y-1">
|
||||
${accessibleMenus.map(menu => this.generateMobileMenuItemHTML(menu)).join('')}
|
||||
</div>
|
||||
@@ -370,7 +342,7 @@ class CommonHeader {
|
||||
*/
|
||||
generateMobileMenuItemHTML(menu) {
|
||||
const isActive = this.currentPage === menu.id;
|
||||
const activeClass = isActive ? 'bg-slate-100 text-slate-800 border-slate-600' : 'text-gray-700 hover:bg-gray-50';
|
||||
const activeClass = isActive ? 'bg-slate-700 text-white border-white' : 'text-slate-300 hover:bg-slate-700';
|
||||
|
||||
// 하위 메뉴가 있는 경우
|
||||
if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) {
|
||||
@@ -390,7 +362,7 @@ class CommonHeader {
|
||||
<div class="hidden ml-6 mt-1 space-y-1">
|
||||
${menu.accessibleSubMenus.map(subMenu => `
|
||||
<a href="${subMenu.url}"
|
||||
class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-600 hover:bg-slate-100 ${this.currentPage === subMenu.id ? 'bg-slate-100 text-slate-800' : ''}"
|
||||
class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-slate-400 hover:bg-slate-700 hover:text-white ${this.currentPage === subMenu.id ? 'bg-slate-700 text-white' : ''}"
|
||||
data-page="${subMenu.id}"
|
||||
onclick="CommonHeader.navigateToPage(event, '${subMenu.url}', '${subMenu.id}')">
|
||||
<i class="${subMenu.icon} mr-3 ${subMenu.color}"></i>
|
||||
@@ -491,7 +463,7 @@ class CommonHeader {
|
||||
loader.className = 'fixed inset-0 bg-white bg-opacity-75 flex items-center justify-center z-50';
|
||||
loader.innerHTML = `
|
||||
<div class="text-center">
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-slate-600"></div>
|
||||
<p class="mt-2 text-sm text-gray-600">페이지를 로드하는 중...</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -515,7 +487,7 @@ class CommonHeader {
|
||||
<div class="bg-white rounded-xl p-6 w-96 max-w-md mx-4 shadow-2xl">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">
|
||||
<i class="fas fa-key mr-2 text-blue-500"></i>비밀번호 변경
|
||||
<i class="fas fa-key mr-2 text-slate-500"></i>비밀번호 변경
|
||||
</h3>
|
||||
<button onclick="CommonHeader.hidePasswordModal()" class="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="fas fa-times text-lg"></i>
|
||||
@@ -526,21 +498,21 @@ class CommonHeader {
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">현재 비밀번호</label>
|
||||
<input type="password" id="currentPasswordInput"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
|
||||
required placeholder="현재 비밀번호를 입력하세요">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호</label>
|
||||
<input type="password" id="newPasswordInput"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
|
||||
required minlength="6" placeholder="새 비밀번호 (최소 6자)">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호 확인</label>
|
||||
<input type="password" id="confirmPasswordInput"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
|
||||
required placeholder="새 비밀번호를 다시 입력하세요">
|
||||
</div>
|
||||
|
||||
@@ -550,7 +522,7 @@ class CommonHeader {
|
||||
취소
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
|
||||
class="flex-1 px-4 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-800 transition-colors">
|
||||
<i class="fas fa-save mr-1"></i>변경
|
||||
</button>
|
||||
</div>
|
||||
@@ -683,10 +655,10 @@ class CommonHeader {
|
||||
const itemPageId = item.getAttribute('data-page');
|
||||
if (itemPageId === pageId) {
|
||||
item.classList.add('bg-slate-700', 'text-white');
|
||||
item.classList.remove('text-slate-600', 'hover:bg-slate-100');
|
||||
item.classList.remove('text-slate-300', 'hover:bg-slate-700');
|
||||
} else {
|
||||
item.classList.remove('bg-slate-700', 'text-white');
|
||||
item.classList.add('text-slate-600');
|
||||
item.classList.add('text-slate-300');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,10 +28,7 @@ class KeyboardShortcutManager {
|
||||
// 네비게이션 단축키
|
||||
this.register('g h', () => this.navigateToPage('/index.html', 'issues_create'), '홈 (부적합 등록)');
|
||||
this.register('g v', () => this.navigateToPage('/issue-view.html', 'issues_view'), '부적합 조회');
|
||||
this.register('g d', () => this.navigateToPage('/daily-work.html', 'daily_work'), '일일 공수');
|
||||
this.register('g p', () => this.navigateToPage('/project-management.html', 'projects_manage'), '프로젝트 관리');
|
||||
this.register('g r', () => this.navigateToPage('/reports.html', 'reports'), '보고서');
|
||||
this.register('g a', () => this.navigateToPage('/admin.html', 'users_manage'), '관리자');
|
||||
|
||||
// 액션 단축키
|
||||
this.register('n', () => this.triggerNewAction(), '새 항목 생성');
|
||||
@@ -40,8 +37,6 @@ class KeyboardShortcutManager {
|
||||
this.register('f', () => this.focusSearchField(), '검색 포커스');
|
||||
|
||||
// 관리자 전용 단축키
|
||||
this.register('ctrl+shift+u', () => this.navigateToPage('/admin.html', 'users_manage'), '사용자 관리 (관리자)');
|
||||
|
||||
console.log('⌨️ 키보드 단축키 등록 완료');
|
||||
}
|
||||
|
||||
|
||||
@@ -169,14 +169,8 @@ class PageManager {
|
||||
return new IssuesViewModule(options);
|
||||
case 'issues_manage':
|
||||
return new IssuesManageModule(options);
|
||||
case 'projects_manage':
|
||||
return new ProjectsManageModule(options);
|
||||
case 'daily_work':
|
||||
return new DailyWorkModule(options);
|
||||
case 'reports':
|
||||
return new ReportsModule(options);
|
||||
case 'users_manage':
|
||||
return new UsersManageModule(options);
|
||||
default:
|
||||
console.warn(`알 수 없는 페이지 ID: ${pageId}`);
|
||||
return null;
|
||||
|
||||
@@ -57,10 +57,7 @@ class PagePreloader {
|
||||
{ id: 'issues_create', url: '/index.html', priority: 1 },
|
||||
{ id: 'issues_view', url: '/issue-view.html', priority: 1 },
|
||||
{ id: 'issues_manage', url: '/index.html#list', priority: 2 },
|
||||
{ id: 'projects_manage', url: '/project-management.html', priority: 3 },
|
||||
{ id: 'daily_work', url: '/daily-work.html', priority: 2 },
|
||||
{ id: 'reports', url: '/reports.html', priority: 3 },
|
||||
{ id: 'users_manage', url: '/admin.html', priority: 4 }
|
||||
{ id: 'reports', url: '/reports.html', priority: 3 }
|
||||
];
|
||||
|
||||
// 권한 체크
|
||||
|
||||
@@ -20,10 +20,7 @@ class PagePermissionManager {
|
||||
'issues_inbox': { title: '수신함', defaultAccess: true },
|
||||
'issues_management': { title: '관리함', defaultAccess: false },
|
||||
'issues_archive': { title: '폐기함', defaultAccess: false },
|
||||
'projects_manage': { title: '프로젝트 관리', defaultAccess: false },
|
||||
'daily_work': { title: '일일 공수', defaultAccess: false },
|
||||
'reports': { title: '보고서', defaultAccess: false },
|
||||
'users_manage': { title: '사용자 관리', defaultAccess: false }
|
||||
'reports': { title: '보고서', defaultAccess: false }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -167,20 +164,6 @@ class PagePermissionManager {
|
||||
path: '#issues/manage',
|
||||
pageName: 'issues_manage'
|
||||
},
|
||||
{
|
||||
id: 'projects_manage',
|
||||
title: '프로젝트 관리',
|
||||
icon: 'fas fa-folder-open',
|
||||
path: '#projects/manage',
|
||||
pageName: 'projects_manage'
|
||||
},
|
||||
{
|
||||
id: 'daily_work',
|
||||
title: '일일 공수',
|
||||
icon: 'fas fa-calendar-check',
|
||||
path: '#daily-work',
|
||||
pageName: 'daily_work'
|
||||
},
|
||||
{
|
||||
id: 'reports',
|
||||
title: '보고서',
|
||||
@@ -188,13 +171,6 @@ class PagePermissionManager {
|
||||
path: '#reports',
|
||||
pageName: 'reports'
|
||||
},
|
||||
{
|
||||
id: 'users_manage',
|
||||
title: '사용자 관리',
|
||||
icon: 'fas fa-users-cog',
|
||||
path: '#users/manage',
|
||||
pageName: 'users_manage'
|
||||
}
|
||||
];
|
||||
|
||||
// 페이지 권한에 따라 메뉴 필터링
|
||||
|
||||
Reference in New Issue
Block a user