- sw.js: Network-First 캐시 전략 (GET + same-origin + res.ok만 캐시) - tkfb-core.js: SW 등록 + 업데이트 감지 시 자동 새로고침 (최초 설치 시 토스트 방지: controller 체크) - manifest.json: start_url → dashboard-new.html - nginx: sw.js, manifest.json no-cache 헤더 - 배포 시 sw.js의 APP_VERSION만 변경하면 전 사용자 자동 갱신 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
235 lines
10 KiB
HTML
235 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>설비 관리 - TK 공장관리</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/tkfb.css?v=2026040103">
|
|
<link rel="stylesheet" href="/css/equipment-management.css?v=2026031401">
|
|
</head>
|
|
<body class="bg-gray-50">
|
|
<header class="bg-orange-700 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-14">
|
|
<div class="flex items-center gap-3">
|
|
<button id="mobileMenuBtn" class="lg:hidden text-orange-200 hover:text-white"><i class="fas fa-bars text-xl"></i></button>
|
|
<i class="fas fa-industry text-xl text-orange-200"></i>
|
|
<h1 class="text-lg font-semibold">TK 공장관리</h1>
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<span id="headerUserName" class="text-sm hidden sm:block">-</span>
|
|
<div id="headerUserAvatar" class="w-8 h-8 bg-orange-600 rounded-full flex items-center justify-center text-sm font-bold">-</div>
|
|
<button onclick="doLogout()" class="text-orange-200 hover:text-white" title="로그아웃"><i class="fas fa-sign-out-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<div id="mobileOverlay" class="hidden fixed inset-0 bg-black/50 z-30 lg:hidden"></div>
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 fade-in">
|
|
<div class="flex gap-6">
|
|
<nav id="sideNav" class="hidden lg:flex flex-col gap-1 w-52 flex-shrink-0 pt-2 fixed lg:static z-40 bg-white lg:bg-transparent p-4 lg:p-0 rounded-lg lg:rounded-none shadow-lg lg:shadow-none top-14 left-0 bottom-0 overflow-y-auto"></nav>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="page-header">
|
|
<div class="page-title-section">
|
|
<h1 class="page-title">설비 관리</h1>
|
|
<p class="page-description">작업장별 설비 정보를 등록하고 관리합니다</p>
|
|
</div>
|
|
|
|
<div class="page-actions">
|
|
<button class="btn btn-primary" onclick="openEquipmentModal()">
|
|
<span>+ 설비 추가</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 통계 요약 -->
|
|
<div id="statsSection" class="eq-stats-section">
|
|
<!-- JS에서 동적으로 렌더링 -->
|
|
</div>
|
|
|
|
<!-- 필터 영역 -->
|
|
<div class="eq-filter-section">
|
|
<div class="eq-filter-group">
|
|
<label for="filterWorkplace">작업장</label>
|
|
<select id="filterWorkplace" class="form-control" onchange="filterEquipments()">
|
|
<option value="">전체</option>
|
|
</select>
|
|
</div>
|
|
<div class="eq-filter-group">
|
|
<label for="filterType">설비 유형</label>
|
|
<select id="filterType" class="form-control" onchange="filterEquipments()">
|
|
<option value="">전체</option>
|
|
</select>
|
|
</div>
|
|
<div class="eq-filter-group">
|
|
<label for="filterStatus">상태</label>
|
|
<select id="filterStatus" class="form-control" onchange="filterEquipments()">
|
|
<option value="">전체</option>
|
|
<option value="active">활성</option>
|
|
<option value="maintenance">정비중</option>
|
|
<option value="inactive">비활성</option>
|
|
</select>
|
|
</div>
|
|
<div class="eq-filter-group eq-search-group">
|
|
<label for="searchInput">검색</label>
|
|
<input type="text" id="searchInput" class="form-control" placeholder="설비명, 코드, 제조사 검색..." oninput="filterEquipments()">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 설비 목록 -->
|
|
<div id="equipmentList" class="eq-table-container">
|
|
<!-- 설비 목록이 여기에 동적으로 렌더링됩니다 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 설비 추가/수정 모달 -->
|
|
<div id="equipmentModal" class="modal-overlay" style="display: none;">
|
|
<div class="modal-container" style="max-width: 720px;">
|
|
<div class="modal-header">
|
|
<h2 id="modalTitle">설비 추가</h2>
|
|
<button class="btn-close" onclick="closeEquipmentModal()">×</button>
|
|
</div>
|
|
<div class="modal-body eq-modal-body">
|
|
<form id="equipmentForm">
|
|
<input type="hidden" id="equipmentId">
|
|
|
|
<!-- 기본 정보 -->
|
|
<div class="eq-form-section">
|
|
<div class="eq-form-section-title">기본 정보</div>
|
|
<div class="eq-form-row">
|
|
<div class="form-group">
|
|
<label for="equipmentCode">관리번호 *</label>
|
|
<input type="text" id="equipmentCode" class="form-control" placeholder="예: TKP-001" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="equipmentName">설비명 *</label>
|
|
<input type="text" id="equipmentName" class="form-control" placeholder="예: TIG용접기" required>
|
|
</div>
|
|
</div>
|
|
<div class="eq-form-row">
|
|
<div class="form-group">
|
|
<label for="modelName">모델명</label>
|
|
<input type="text" id="modelName" class="form-control" placeholder="예: Perfect-500PT">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="specifications">규격</label>
|
|
<input type="text" id="specifications" class="form-control" placeholder="예: 500A/DC">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 제조사 및 구입 정보 -->
|
|
<div class="eq-form-section">
|
|
<div class="eq-form-section-title">제조사 및 구입 정보</div>
|
|
<div class="eq-form-row">
|
|
<div class="form-group">
|
|
<label for="manufacturer">제조사 (메이커)</label>
|
|
<input type="text" id="manufacturer" class="form-control" placeholder="예: 퍼펙트대대">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="supplier">구입처</label>
|
|
<input type="text" id="supplier" class="form-control" placeholder="예: 현대용접기">
|
|
</div>
|
|
</div>
|
|
<div class="eq-form-row">
|
|
<div class="form-group">
|
|
<label for="purchasePrice">구입가격 (원)</label>
|
|
<input type="number" id="purchasePrice" class="form-control" placeholder="예: 1600000">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="installationDate">구입일자</label>
|
|
<input type="date" id="installationDate" class="form-control">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 상세 정보 -->
|
|
<div class="eq-form-section">
|
|
<div class="eq-form-section-title">상세 정보</div>
|
|
<div class="eq-form-row">
|
|
<div class="form-group">
|
|
<label for="serialNumber">시리얼 번호 (S/N)</label>
|
|
<input type="text" id="serialNumber" class="form-control">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="equipmentStatus">상태</label>
|
|
<select id="equipmentStatus" class="form-control">
|
|
<option value="active">활성</option>
|
|
<option value="maintenance">정비중</option>
|
|
<option value="inactive">비활성</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="eq-form-row">
|
|
<div class="form-group">
|
|
<label for="equipmentType">설비 유형</label>
|
|
<input type="text" id="equipmentType" class="form-control" placeholder="예: 용접기, 크레인 등">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="workplaceId">작업장</label>
|
|
<select id="workplaceId" class="form-control">
|
|
<option value="">선택 안함</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="notes">비고</label>
|
|
<textarea id="notes" class="form-control" rows="2" placeholder="기타 메모사항"></textarea>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="closeEquipmentModal()">취소</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveEquipment()">저장</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/static/js/tkfb-core.js?v=2026040105"></script>
|
|
<script src="/js/api-base.js?v=2026031401"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
<script type="module">
|
|
import '/js/api-config.js?v=2026031401
|
|
</script>
|
|
<script>
|
|
(function() {
|
|
const checkApiConfig = setInterval(() => {
|
|
if (window.API_BASE_URL) {
|
|
clearInterval(checkApiConfig);
|
|
axios.defaults.baseURL = window.API_BASE_URL;
|
|
const token = localStorage.getItem('sso_token');
|
|
if (token) {
|
|
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
axios.interceptors.request.use(
|
|
config => {
|
|
const token = localStorage.getItem('sso_token');
|
|
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
return config;
|
|
},
|
|
error => Promise.reject(error)
|
|
);
|
|
axios.interceptors.response.use(
|
|
response => response,
|
|
error => {
|
|
if (error.response?.status === 401) {
|
|
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
|
window.location.href = '/pages/login.html';
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
}
|
|
}, 50);
|
|
})();
|
|
</script>
|
|
<script src="/js/equipment-management.js?v=2026031401"></script>
|
|
<script>initAuth();</script>
|
|
</body>
|
|
</html>
|