3916 lines
167 KiB
HTML
3916 lines
167 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>품목 기본정보</title>
|
||
|
||
<!-- CSS 파일 연결 -->
|
||
<link rel="stylesheet" href="css/common.css">
|
||
<link rel="stylesheet" href="css/userOptions.css">
|
||
<link rel="stylesheet" href="css/excelUpload.css">
|
||
|
||
<!-- SheetJS 라이브러리 (엑셀 파일 처리) -->
|
||
<script src="https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js"></script>
|
||
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background: #f8f9fa;
|
||
padding: 0;
|
||
margin: 0;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.page-container {
|
||
padding: 10px;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
/* 검색 섹션 스타일 */
|
||
.search-section {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.search-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.search-fields-container {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
align-items: flex-end;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.search-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.search-field label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #374151;
|
||
}
|
||
|
||
.search-field input,
|
||
.search-field select {
|
||
padding: 8px 12px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
min-width: 180px;
|
||
}
|
||
|
||
.search-field input:focus,
|
||
.search-field select:focus {
|
||
outline: none;
|
||
border-color: #3b82f6;
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||
}
|
||
|
||
.search-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.search-right-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-left: auto;
|
||
flex-shrink: 0;
|
||
align-self: flex-start;
|
||
}
|
||
|
||
/* 데이터 테이블 섹션 */
|
||
.table-section {
|
||
flex: 1;
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#dataTableContainer {
|
||
overflow: auto;
|
||
position: relative;
|
||
}
|
||
|
||
.table-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid #e5e7eb;
|
||
}
|
||
|
||
.table-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
/* 그룹바이 컨트롤 */
|
||
.groupby-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.groupby-select {
|
||
padding: 6px 12px;
|
||
border: 2px solid #3b82f6;
|
||
background: #eff6ff;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
color: #3b82f6;
|
||
outline: none;
|
||
transition: all 0.2s;
|
||
min-width: 140px;
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
-moz-appearance: none;
|
||
}
|
||
|
||
.groupby-select:hover {
|
||
background: #dbeafe;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
.groupby-select:focus {
|
||
background: #f3f4f6;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.groupby-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 10px;
|
||
background: #eff6ff;
|
||
border: 1px solid #bfdbfe;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.groupby-tag-remove {
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
transition: color 0.2s;
|
||
}
|
||
|
||
.groupby-tag-remove:hover {
|
||
color: #ef4444;
|
||
}
|
||
|
||
.groupby-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.table-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.table-container {
|
||
flex: 1;
|
||
overflow: auto;
|
||
position: relative;
|
||
}
|
||
|
||
.data-table {
|
||
width: max-content;
|
||
min-width: 100%;
|
||
border-collapse: collapse;
|
||
table-layout: fixed;
|
||
}
|
||
|
||
.data-table thead {
|
||
position: sticky;
|
||
top: 0;
|
||
background: #f9fafb;
|
||
z-index: 10;
|
||
}
|
||
|
||
.data-table th {
|
||
padding: 12px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: #374151;
|
||
font-size: 13px;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
white-space: nowrap;
|
||
position: relative;
|
||
overflow: visible;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.data-table th .resize-handle {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 5px;
|
||
cursor: col-resize;
|
||
user-select: none;
|
||
background: transparent;
|
||
z-index: 10;
|
||
}
|
||
|
||
.data-table th .resize-handle:hover {
|
||
background: #3b82f6;
|
||
}
|
||
|
||
.data-table td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
font-size: 13px;
|
||
color: #1f2937;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 그리드선 숨김 모드 */
|
||
.data-table.hide-grid td,
|
||
.data-table.hide-grid th {
|
||
border-bottom: none;
|
||
border-right: none;
|
||
}
|
||
|
||
.data-table.hide-grid tbody tr {
|
||
border-bottom: 1px solid #f9fafb;
|
||
}
|
||
|
||
.data-table tbody tr:hover {
|
||
background: #f9fafb;
|
||
}
|
||
|
||
.data-table tbody tr.selected {
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.data-table .empty-state {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.empty-state-icon {
|
||
font-size: 48px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.empty-state-text {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 버튼 스타일 */
|
||
.btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #3b82f6;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #2563eb;
|
||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #f3f4f6;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #e5e7eb;
|
||
}
|
||
|
||
.btn-success {
|
||
background: #10b981;
|
||
color: white;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background: #059669;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: #ef4444;
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #dc2626;
|
||
}
|
||
|
||
.btn-small {
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 체크박스 스타일 - 기본적으로 숨김 */
|
||
input[type="checkbox"] {
|
||
width: 18px;
|
||
height: 18px;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
input[type="checkbox"]:checked {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 테이블 행에 마우스 오버 시 체크박스 보이기 */
|
||
.data-table tbody tr:hover input[type="checkbox"] {
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.data-table tbody tr:hover input[type="checkbox"]:checked {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 헤더 체크박스는 항상 보이기 */
|
||
.data-table thead input[type="checkbox"] {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 그룹화된 테이블 스타일 */
|
||
.group-header {
|
||
background: #f3f4f6;
|
||
font-weight: 700;
|
||
color: #374151;
|
||
padding: 12px;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 5;
|
||
border-bottom: 2px solid #e5e7eb;
|
||
}
|
||
|
||
.group-header:hover {
|
||
background: #e5e7eb;
|
||
}
|
||
|
||
.group-header-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.group-header input[type="checkbox"] {
|
||
opacity: 1;
|
||
}
|
||
|
||
.group-toggle {
|
||
font-size: 12px;
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
.group-toggle.collapsed {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
.group-count {
|
||
color: #3b82f6;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.group-rows {
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.group-rows.collapsed {
|
||
display: none;
|
||
}
|
||
|
||
/* 카드형 체크박스 숨김 처리 */
|
||
.card-item {
|
||
position: relative;
|
||
}
|
||
|
||
.card-item input[type="checkbox"] {
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.card-item input[type="checkbox"]:checked {
|
||
opacity: 1;
|
||
}
|
||
|
||
.card-item:hover input[type="checkbox"] {
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.card-item:hover input[type="checkbox"]:checked {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 테이블 내 체크박스 */
|
||
.data-table input[type="checkbox"] {
|
||
width: 18px;
|
||
height: 18px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.data-table tbody tr {
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 뱃지 스타일 */
|
||
.badge {
|
||
display: inline-block;
|
||
padding: 4px 10px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.badge-primary {
|
||
background: #dbeafe;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.badge-success {
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
}
|
||
|
||
.badge-warning {
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
}
|
||
|
||
.badge-danger {
|
||
background: #fee2e2;
|
||
color: #991b1b;
|
||
}
|
||
|
||
/* 모달 공통 스타일 */
|
||
.modal-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 9999;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal-overlay.active {
|
||
display: flex;
|
||
}
|
||
|
||
/* 사용자 옵션 모달 */
|
||
.user-options-modal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 9999;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.user-options-modal.active {
|
||
display: flex;
|
||
}
|
||
|
||
.user-options-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
width: 700px;
|
||
max-width: 95%;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.user-options-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20px 24px;
|
||
border-bottom: 2px solid #e5e7eb;
|
||
}
|
||
|
||
.user-options-header h2 {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 0;
|
||
}
|
||
|
||
.user-options-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #6b7280;
|
||
cursor: pointer;
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 6px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.user-options-close:hover {
|
||
background: #f3f4f6;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.user-options-tabs {
|
||
display: flex;
|
||
gap: 4px;
|
||
padding: 0 24px;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.user-options-tab {
|
||
padding: 12px 20px;
|
||
background: none;
|
||
border: none;
|
||
border-bottom: 3px solid transparent;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #6b7280;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.user-options-tab:hover {
|
||
background: #f9fafb;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.user-options-tab.active {
|
||
color: #3b82f6;
|
||
border-bottom-color: #3b82f6;
|
||
}
|
||
|
||
.user-options-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px 24px;
|
||
}
|
||
|
||
.user-options-footer {
|
||
padding: 16px 24px;
|
||
border-top: 2px solid #e5e7eb;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
}
|
||
|
||
.option-field-item {
|
||
background: #f9fafb;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.option-field-item.dragging {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.drag-handle {
|
||
cursor: move;
|
||
color: #9ca3af;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.option-field-checkbox {
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
|
||
.option-field-name {
|
||
flex: 1;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.option-field-width {
|
||
width: 80px;
|
||
}
|
||
|
||
.freeze-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px;
|
||
background: #f9fafb;
|
||
border-radius: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.freeze-option label {
|
||
flex: 1;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.freeze-option input[type="number"] {
|
||
width: 100px;
|
||
padding: 8px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* 토스트 메시지 */
|
||
.toast-message {
|
||
position: fixed;
|
||
top: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%) translateY(-100px);
|
||
background: white;
|
||
padding: 16px 24px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
z-index: 10000;
|
||
opacity: 0;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.toast-message.show {
|
||
transform: translateX(-50%) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
|
||
.toast-icon {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.toast-text {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
/* 품목 추가/수정 모달 */
|
||
.item-modal-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
width: 600px;
|
||
max-width: 95%;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.item-modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20px 24px;
|
||
border-bottom: 2px solid #e5e7eb;
|
||
}
|
||
|
||
.item-modal-header h2 {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
margin: 0;
|
||
}
|
||
|
||
.item-modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #6b7280;
|
||
cursor: pointer;
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 6px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.item-modal-close:hover {
|
||
background: #f3f4f6;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.item-modal-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 24px;
|
||
}
|
||
|
||
.item-form-field {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.item-form-field input,
|
||
.item-form-field select {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.item-form-field input:focus,
|
||
.item-form-field select:focus {
|
||
outline: none;
|
||
border-color: #3b82f6;
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||
}
|
||
|
||
.item-form-field input::placeholder {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.item-modal-footer {
|
||
padding: 16px 24px;
|
||
border-top: 2px solid #e5e7eb;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.continuous-input-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
color: #6b7280;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.continuous-input-label input[type="checkbox"] {
|
||
opacity: 1;
|
||
}
|
||
|
||
.modal-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 코드변경 모달 */
|
||
.code-change-modal-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
width: 700px;
|
||
max-width: 95%;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.code-change-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.code-change-option {
|
||
padding: 16px;
|
||
border: 2px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.code-change-option:hover {
|
||
border-color: #3b82f6;
|
||
background: #f0f9ff;
|
||
}
|
||
|
||
.code-change-option.selected {
|
||
border-color: #3b82f6;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.code-change-option-title {
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
margin-bottom: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.code-change-option-desc {
|
||
font-size: 13px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.code-change-form {
|
||
display: none;
|
||
margin-top: 16px;
|
||
padding-top: 16px;
|
||
border-top: 2px solid #e5e7eb;
|
||
}
|
||
|
||
.code-change-form.active {
|
||
display: block;
|
||
}
|
||
|
||
.code-change-field {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.code-change-field label {
|
||
display: block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #374151;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.code-change-field input,
|
||
.code-change-field select {
|
||
width: 100%;
|
||
padding: 10px 12px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.warning-box {
|
||
background: #fef3c7;
|
||
border: 2px solid #fbbf24;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.warning-box-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-weight: 600;
|
||
color: #92400e;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.warning-box-content {
|
||
font-size: 13px;
|
||
color: #78350f;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.preview-box {
|
||
background: #f9fafb;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.preview-box-title {
|
||
font-weight: 600;
|
||
color: #374151;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.preview-table {
|
||
width: 100%;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.preview-table td {
|
||
padding: 6px 8px;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.preview-table td:first-child {
|
||
color: #6b7280;
|
||
width: 120px;
|
||
}
|
||
|
||
.preview-table td:last-child {
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.arrow-icon {
|
||
color: #3b82f6;
|
||
font-weight: bold;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page-container">
|
||
<!-- 검색 섹션 -->
|
||
<div id="searchSection"></div>
|
||
|
||
<!-- 데이터 테이블 섹션 -->
|
||
<div class="table-section">
|
||
<div class="table-header">
|
||
<div class="table-title" style="display: flex; align-items: center; gap: 15px; flex: 1;">
|
||
<div style="font-size: 14px; color: #6b7280;">
|
||
총 <span id="totalCount" style="color: #3b82f6; font-weight: 700;">15</span>개
|
||
</div>
|
||
<select class="groupby-select" id="groupByField" onchange="addGroupBy()">
|
||
<option value="">⚙️ Group by</option>
|
||
<option value="status">상태</option>
|
||
<option value="category">구분</option>
|
||
<option value="type">유형</option>
|
||
<option value="stockUnit">재고단위</option>
|
||
<option value="createdBy">등록자</option>
|
||
</select>
|
||
<div class="groupby-tags" id="groupByTags"></div>
|
||
</div>
|
||
<div class="table-actions">
|
||
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||
<input type="checkbox" id="includeInactive" style="opacity: 1;">
|
||
<span style="font-size: 13px; color: #6b7280;">미사용 포함</span>
|
||
</label>
|
||
<button class="btn btn-primary" onclick="openEditModal()">✏️ 수정</button>
|
||
<button class="btn btn-success" onclick="openAddModal()">➕ 추가</button>
|
||
<button class="btn btn-secondary" onclick="openCodeChangeModal()">🔄 코드변경</button>
|
||
<button class="btn btn-danger" onclick="deleteSelected()">🗑️ 삭제</button>
|
||
</div>
|
||
</div>
|
||
<div id="dataTableContainer"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 사용자 옵션 모달 -->
|
||
<div class="user-options-modal" id="userOptionsModal">
|
||
<div class="user-options-content">
|
||
<div class="user-options-header">
|
||
<h2>⚙️ 옵션 설정</h2>
|
||
<button class="user-options-close" onclick="closeUserOptionsModal(true)">✕</button>
|
||
</div>
|
||
<div class="user-options-tabs">
|
||
<button class="user-options-tab active" onclick="switchOptionsTab('searchFields')">검색필드 설정</button>
|
||
<button class="user-options-tab" onclick="switchOptionsTab('columnDisplay')">컬럼 표시/숨기기</button>
|
||
<button class="user-options-tab" onclick="switchOptionsTab('otherOptions')">기타옵션</button>
|
||
</div>
|
||
<div class="user-options-body" id="userOptionsBody">
|
||
<!-- 탭 내용이 여기에 표시됨 -->
|
||
</div>
|
||
<div class="user-options-footer">
|
||
<button class="btn btn-secondary" onclick="closeUserOptionsModal(true)">취소</button>
|
||
<button class="btn btn-primary" id="saveOptionsBtn" onclick="saveUserOptions()">💾 저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 품목 추가/수정 모달 -->
|
||
<div class="modal-overlay" id="itemModal">
|
||
<div class="item-modal-content">
|
||
<div class="item-modal-header">
|
||
<h2 id="itemModalTitle">품목 추가</h2>
|
||
<button class="item-modal-close" onclick="closeItemModal()">✕</button>
|
||
</div>
|
||
<div class="item-modal-body" id="itemModalBody">
|
||
<!-- 폼 필드가 동적으로 생성됨 -->
|
||
</div>
|
||
<div class="item-modal-footer">
|
||
<label class="continuous-input-label">
|
||
<input type="checkbox" id="continuousInput">
|
||
<span>연속입력</span>
|
||
</label>
|
||
<div class="modal-buttons">
|
||
<button class="btn btn-secondary" onclick="closeItemModal()">취소</button>
|
||
<button class="btn btn-primary" onclick="saveItem()">💾 저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 코드변경 모달 -->
|
||
<div class="modal-overlay" id="codeChangeModal">
|
||
<div class="code-change-modal-content">
|
||
<div class="item-modal-header">
|
||
<h2>🔄 코드변경 관리</h2>
|
||
<button class="item-modal-close" onclick="closeCodeChangeModal()">✕</button>
|
||
</div>
|
||
<div class="item-modal-body">
|
||
<div class="code-change-options">
|
||
<div class="code-change-option" id="renameOption" onclick="selectCodeChangeOption('rename')">
|
||
<div class="code-change-option-title">
|
||
<span>✏️</span>
|
||
<span>품번 변경</span>
|
||
</div>
|
||
<div class="code-change-option-desc">
|
||
선택한 품목의 품번코드를 새로운 코드로 변경합니다.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="code-change-option" id="mergeOption" onclick="selectCodeChangeOption('merge')">
|
||
<div class="code-change-option-title">
|
||
<span>🔗</span>
|
||
<span>다른 품번에 합병</span>
|
||
</div>
|
||
<div class="code-change-option-desc">
|
||
선택한 품목을 다른 품목에 합병합니다. 기존 데이터는 통합됩니다.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 품번 변경 폼 -->
|
||
<div class="code-change-form" id="renameForm">
|
||
<div class="code-change-field">
|
||
<label>현재 품번</label>
|
||
<input type="text" id="currentCode" readonly style="background: #f3f4f6;">
|
||
</div>
|
||
<div class="code-change-field">
|
||
<label>새 품번 *</label>
|
||
<input type="text" id="newCode" placeholder="새로운 품번코드를 입력하세요">
|
||
</div>
|
||
|
||
<div class="preview-box" id="renamePreview" style="display: none;">
|
||
<div class="preview-box-title">📋 변경 미리보기</div>
|
||
<table class="preview-table">
|
||
<tr>
|
||
<td>품번코드</td>
|
||
<td><span id="previewOldCode"></span> <span class="arrow-icon">→</span> <span id="previewNewCode"></span></td>
|
||
</tr>
|
||
<tr>
|
||
<td>품명</td>
|
||
<td id="previewItemName"></td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 합병 폼 -->
|
||
<div class="code-change-form" id="mergeForm">
|
||
<div class="code-change-field">
|
||
<label>삭제될 품번 (현재 선택)</label>
|
||
<input type="text" id="sourceCode" readonly style="background: #f3f4f6;">
|
||
</div>
|
||
<div class="code-change-field">
|
||
<label>통합될 품번 *</label>
|
||
<select id="targetCode" onchange="updateMergePreview()">
|
||
<option value="">품번을 선택하세요</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="warning-box">
|
||
<div class="warning-box-title">
|
||
<span>⚠️</span>
|
||
<span>주의사항</span>
|
||
</div>
|
||
<div class="warning-box-content">
|
||
• 합병 작업은 되돌릴 수 없습니다.<br>
|
||
• 삭제될 품번의 <strong>모든 데이터</strong>(재고, 거래내역, 생산이력 등)가 통합될 품번으로 이관됩니다.<br>
|
||
• 삭제될 품번은 영구적으로 삭제되며 복구할 수 없습니다.<br>
|
||
• 관련 문서 및 보고서의 품번도 함께 변경됩니다.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="preview-box" id="mergePreview" style="display: none;">
|
||
<div class="preview-box-title">📋 합병 미리보기</div>
|
||
<table class="preview-table">
|
||
<tr>
|
||
<td>삭제될 품번</td>
|
||
<td id="previewSourceCode"></td>
|
||
</tr>
|
||
<tr>
|
||
<td></td>
|
||
<td style="text-align: center; color: #ef4444; font-size: 20px;">⬇</td>
|
||
</tr>
|
||
<tr>
|
||
<td>통합될 품번</td>
|
||
<td id="previewTargetCode"></td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="2" style="padding-top: 12px; color: #6b7280; font-size: 12px;">
|
||
* 모든 관련 데이터가 통합될 품번으로 이관됩니다.
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="item-modal-footer">
|
||
<div></div>
|
||
<div class="modal-buttons">
|
||
<button class="btn btn-secondary" onclick="closeCodeChangeModal()">취소</button>
|
||
<button class="btn btn-primary" onclick="executeCodeChange()">✓ 실행</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 토스트 메시지 -->
|
||
<div id="toastMessage" class="toast-message">
|
||
<span class="toast-icon">✓</span>
|
||
<span class="toast-text"></span>
|
||
</div>
|
||
|
||
<!-- JavaScript 파일 연결 -->
|
||
<!-- SheetJS 라이브러리 (엑셀 처리용) -->
|
||
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
|
||
|
||
<script src="js/common.js"></script>
|
||
<script src="js/components/searchSection.js"></script>
|
||
<script src="js/components/dataTable.js"></script>
|
||
<script src="js/components/modal.js"></script>
|
||
<script src="js/components/userOptions.js"></script>
|
||
<script src="js/components/excelUpload.js"></script>
|
||
<script src="js/components/groupBy.js"></script>
|
||
<script src="js/components/webcamCapture.js"></script>
|
||
|
||
<script>
|
||
// Group By 컴포넌트 인스턴스
|
||
let groupByComponent;
|
||
window.groupByFields = []; // GroupBy 필드 배열
|
||
|
||
// 품목 모달 관련 변수
|
||
let itemModalMode = 'add'; // 'add' or 'edit'
|
||
let editingItemId = null;
|
||
|
||
// 품목 입력 필드 정의
|
||
const itemFormFields = [
|
||
{ id: 'itemCode', label: '품번코드', type: 'text', required: true },
|
||
{ id: 'itemName', label: '품명', type: 'text', required: true },
|
||
{ id: 'spec', label: '규격', type: 'text', required: false },
|
||
{ id: 'material', label: '재질', type: 'text', required: false },
|
||
{ id: 'stockUnit', label: '재고단위', type: 'select', required: true,
|
||
options: ['EA', 'kg', 'L', 'Sheet', 'Box'] },
|
||
{ id: 'weight', label: '중량', type: 'number', required: false },
|
||
{ id: 'weightUnit', label: '중량단위', type: 'select', required: false,
|
||
options: ['g', 'kg', 'kg/L', 't'] },
|
||
{ id: 'category', label: '구분', type: 'select', required: true,
|
||
options: ['원자재', '중간재', '완제품'] },
|
||
{ id: 'type', label: '유형', type: 'select', required: false,
|
||
options: ['반도체용', '태양광용', '산업용', '의료용', '건축용', '사출용', '화장품용'] },
|
||
{ id: 'memo', label: '메모', type: 'text', required: false },
|
||
{ id: 'status', label: '사용여부', type: 'select', required: true,
|
||
options: ['정상', '품절', '대기', '단종'] }
|
||
];
|
||
|
||
// 샘플 데이터 (실리콘 회사)
|
||
let itemsData = [
|
||
{
|
||
id: 1,
|
||
selected: false,
|
||
status: '정상',
|
||
itemCode: 'SIL-2025-001',
|
||
itemName: '고순도 실리콘 웨이퍼',
|
||
spec: '8인치',
|
||
material: 'Si(99.999%)',
|
||
stockUnit: 'EA',
|
||
weight: 125,
|
||
weightUnit: 'g',
|
||
image: '📦',
|
||
category: '원자재',
|
||
type: '반도체용',
|
||
memo: '클린룸 보관 필수',
|
||
createdBy: '김반도',
|
||
createdDate: '2025-01-20',
|
||
modifiedDate: '2025-01-20'
|
||
},
|
||
{
|
||
id: 2,
|
||
selected: false,
|
||
status: '정상',
|
||
itemCode: 'SIL-2025-002',
|
||
itemName: '실리콘 잉곳',
|
||
spec: 'Φ200mm x 1000mm',
|
||
material: 'Poly-Si',
|
||
stockUnit: 'EA',
|
||
weight: 15.5,
|
||
weightUnit: 'kg',
|
||
image: '📦',
|
||
category: '원자재',
|
||
type: '태양광용',
|
||
memo: '다결정 실리콘',
|
||
createdBy: '박태양',
|
||
createdDate: '2025-01-19',
|
||
modifiedDate: '2025-01-21'
|
||
},
|
||
{
|
||
id: 3,
|
||
selected: false,
|
||
status: '품절',
|
||
itemCode: 'SIL-2025-003',
|
||
itemName: '실리콘 분말',
|
||
spec: '325 mesh',
|
||
material: 'Si powder',
|
||
stockUnit: 'kg',
|
||
weight: 25,
|
||
weightUnit: 'kg',
|
||
image: '📦',
|
||
category: '원자재',
|
||
type: '산업용',
|
||
memo: '내화학성 코팅용',
|
||
createdBy: '이소재',
|
||
createdDate: '2025-01-18',
|
||
modifiedDate: '2025-01-18'
|
||
},
|
||
{
|
||
id: 4,
|
||
selected: false,
|
||
status: '정상',
|
||
itemCode: 'SIL-2025-004',
|
||
itemName: 'PDMS 실리콘',
|
||
spec: '점도 1000 cSt',
|
||
material: 'Polydimethylsiloxane',
|
||
stockUnit: 'L',
|
||
weight: 0.97,
|
||
weightUnit: 'kg/L',
|
||
image: '📦',
|
||
category: '중간재',
|
||
type: '의료용',
|
||
memo: '생체적합성 인증',
|
||
createdBy: '정화학',
|
||
createdDate: '2025-01-17',
|
||
modifiedDate: '2025-01-20'
|
||
},
|
||
{
|
||
id: 5,
|
||
selected: false,
|
||
status: '정상',
|
||
itemCode: 'SIL-2025-005',
|
||
itemName: '실리콘 고무 시트',
|
||
spec: '1000x1000x2mm',
|
||
material: 'VMQ',
|
||
stockUnit: 'Sheet',
|
||
weight: 2.1,
|
||
weightUnit: 'kg',
|
||
image: '📦',
|
||
category: '완제품',
|
||
type: '산업용',
|
||
memo: '내열 200℃',
|
||
createdBy: '최고무',
|
||
createdDate: '2025-01-16',
|
||
modifiedDate: '2025-01-19'
|
||
},
|
||
{
|
||
id: 6,
|
||
selected: false,
|
||
status: '정상',
|
||
itemCode: 'SIL-2025-006',
|
||
itemName: '실리콘 실란트',
|
||
spec: '310ml 카트리지',
|
||
material: 'RTV-1',
|
||
stockUnit: 'EA',
|
||
weight: 0.35,
|
||
weightUnit: 'kg',
|
||
image: '📦',
|
||
category: '완제품',
|
||
type: '건축용',
|
||
memo: '중성 경화형',
|
||
createdBy: '강건축',
|
||
createdDate: '2025-01-15',
|
||
modifiedDate: '2025-01-15'
|
||
},
|
||
{
|
||
id: 7,
|
||
selected: false,
|
||
status: '대기',
|
||
itemCode: 'SIL-2025-007',
|
||
itemName: 'LSR 실리콘',
|
||
spec: 'Shore A 40',
|
||
material: 'Liquid Silicone Rubber',
|
||
stockUnit: 'kg',
|
||
weight: 20,
|
||
weightUnit: 'kg',
|
||
image: '📦',
|
||
category: '중간재',
|
||
type: '사출용',
|
||
memo: '2액형 혼합',
|
||
createdBy: '윤사출',
|
||
createdDate: '2025-01-14',
|
||
modifiedDate: '2025-01-21'
|
||
},
|
||
{
|
||
id: 8,
|
||
selected: false,
|
||
status: '정상',
|
||
itemCode: 'SIL-2025-008',
|
||
itemName: '실리콘 오일',
|
||
spec: '점도 100 cSt',
|
||
material: 'Silicone Oil',
|
||
stockUnit: 'L',
|
||
weight: 0.95,
|
||
weightUnit: 'kg/L',
|
||
image: '📦',
|
||
category: '원자재',
|
||
type: '화장품용',
|
||
memo: 'FDA 승인',
|
||
createdBy: '서화장',
|
||
createdDate: '2025-01-13',
|
||
modifiedDate: '2025-01-13'
|
||
}
|
||
];
|
||
|
||
// 검색 섹션 초기화
|
||
function initSearchSection() {
|
||
// 저장된 설정 가져오기 (컴포넌트 방식)
|
||
const savedFields = getSearchFieldsConfig('itemInfo');
|
||
|
||
// 저장된 설정이 있으면 applySearchFieldsConfig 사용
|
||
if (savedFields) {
|
||
applySearchFieldsConfig();
|
||
return;
|
||
}
|
||
|
||
// 기본 검색 섹션
|
||
const searchHtml = `
|
||
<div class="search-section">
|
||
<div class="search-row">
|
||
<div class="search-fields-container">
|
||
<div class="search-field">
|
||
<select id="status" style="min-width: 120px;">
|
||
<option value="">상태</option>
|
||
<option value="정상">정상</option>
|
||
<option value="품절">품절</option>
|
||
<option value="단종">단종</option>
|
||
</select>
|
||
</div>
|
||
<div class="search-field">
|
||
<input type="text" id="itemCode" placeholder="품번코드" style="min-width: 150px;">
|
||
</div>
|
||
<div class="search-field">
|
||
<input type="text" id="itemName" placeholder="품명" style="min-width: 200px;">
|
||
</div>
|
||
<div class="search-buttons">
|
||
<button class="btn btn-primary" onclick="performSearch()">
|
||
🔍 검색
|
||
</button>
|
||
<button class="btn btn-secondary" onclick="resetSearch()">
|
||
초기화
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="search-right-buttons">
|
||
<button class="btn btn-secondary" onclick="openUserOptions()">
|
||
⚙️ 사용자옵션
|
||
</button>
|
||
<button class="btn btn-primary" onclick="openWebcamModal()">
|
||
📷 사진촬영
|
||
</button>
|
||
<button class="btn btn-success" onclick="downloadExcel()">
|
||
📥 다운로드
|
||
</button>
|
||
<button class="btn btn-success" onclick="uploadExcel()">
|
||
📤 업로드
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById('searchSection').innerHTML = searchHtml;
|
||
}
|
||
|
||
// 그룹바이 함수들은 groupBy.js 컴포넌트로 이동됨
|
||
// addGroupBy, removeGroupBy, renderGroupByTags는 컴포넌트가 자동으로 처리
|
||
|
||
// 데이터 뷰 새로고침
|
||
function refreshDataView() {
|
||
const viewMode = localStorage.getItem('viewMode') || 'table';
|
||
|
||
if (viewMode === 'card') {
|
||
renderCardView();
|
||
} else {
|
||
if (groupByComponent && groupByComponent.isGrouped()) {
|
||
renderGroupedTable();
|
||
} else {
|
||
const columnsConfig = localStorage.getItem('columnsConfig');
|
||
if (columnsConfig) {
|
||
applyColumnsConfig();
|
||
} else {
|
||
initDataTable();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 그룹화된 데이터 생성 - 컴포넌트 사용
|
||
function createGroupedData(data, fields) {
|
||
if (groupByComponent) {
|
||
return groupByComponent.createGroupedData(data, fields);
|
||
}
|
||
if (fields && fields.length === 0) return { ungrouped: data };
|
||
|
||
const grouped = {};
|
||
|
||
data.forEach(item => {
|
||
// 다중 레벨 그룹 키 생성
|
||
const groupKey = fields.map(field => {
|
||
const value = item[field] || '(없음)';
|
||
return `${groupByFieldNames[field]}: ${value}`;
|
||
}).join(' > ');
|
||
|
||
if (!grouped[groupKey]) {
|
||
grouped[groupKey] = [];
|
||
}
|
||
grouped[groupKey].push(item);
|
||
});
|
||
|
||
return grouped;
|
||
}
|
||
|
||
// 그룹화된 테이블 렌더링
|
||
function renderGroupedTable() {
|
||
const container = document.getElementById('dataTableContainer');
|
||
|
||
// 컬럼 설정 가져오기
|
||
const savedColumns = JSON.parse(localStorage.getItem('columnsConfig') || 'null');
|
||
const defaultColumns = [
|
||
{ id: 'selected', name: '선택', checked: true, width: 60 },
|
||
{ id: 'status', name: '상태', checked: true, width: 80 },
|
||
{ id: 'itemCode', name: '품번코드', checked: true, width: 140 },
|
||
{ id: 'itemName', name: '품명', checked: true, width: 200 },
|
||
{ id: 'spec', name: '규격', checked: true, width: 150 },
|
||
{ id: 'material', name: '재질', checked: true, width: 180 },
|
||
{ id: 'stockUnit', name: '재고단위', checked: true, width: 100 },
|
||
{ id: 'weight', name: '중량', checked: true, width: 80 },
|
||
{ id: 'weightUnit', name: '단위', checked: true, width: 80 },
|
||
{ id: 'image', name: '이미지', checked: true, width: 80 },
|
||
{ id: 'category', name: '구분', checked: true, width: 100 },
|
||
{ id: 'type', name: '유형', checked: true, width: 100 },
|
||
{ id: 'memo', name: '메모', checked: true, width: 180 },
|
||
{ id: 'createdBy', name: '등록자', checked: true, width: 100 },
|
||
{ id: 'createdDate', name: '등록일', checked: true, width: 120 },
|
||
{ id: 'modifiedDate', name: '최종수정일', checked: true, width: 120 }
|
||
];
|
||
|
||
const columns = savedColumns || defaultColumns;
|
||
const visibleColumns = columns.filter(c => c.checked);
|
||
|
||
// 컬럼 매핑
|
||
const columnMap = {
|
||
'selected': { field: 'selected', label: '<input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">', align: 'center',
|
||
formatter: (value, row) => `<input type="checkbox" ${value ? 'checked' : ''} onclick="event.stopPropagation();" onchange="toggleSelect(${row.id})">` },
|
||
'status': { field: 'status', label: '상태', align: 'center',
|
||
formatter: (value) => {
|
||
const badgeMap = {
|
||
'정상': { text: '정상', class: 'badge-success' },
|
||
'품절': { text: '품절', class: 'badge-danger' },
|
||
'대기': { text: '대기', class: 'badge-warning' }
|
||
};
|
||
return formatters.badge(value, badgeMap);
|
||
}
|
||
},
|
||
'itemCode': { field: 'itemCode', label: '품번코드' },
|
||
'itemName': { field: 'itemName', label: '품명', formatter: (value) => `<strong>${value}</strong>` },
|
||
'spec': { field: 'spec', label: '규격' },
|
||
'material': { field: 'material', label: '재질' },
|
||
'stockUnit': { field: 'stockUnit', label: '재고단위', align: 'center' },
|
||
'weight': { field: 'weight', label: '중량', align: 'right', formatter: (value) => value.toLocaleString() },
|
||
'weightUnit': { field: 'weightUnit', label: '단위', align: 'center' },
|
||
'image': { field: 'image', label: '이미지', align: 'center' },
|
||
'category': { field: 'category', label: '구분', align: 'center',
|
||
formatter: (value) => {
|
||
const badgeMap = {
|
||
'원자재': { text: '원자재', class: 'badge-primary' },
|
||
'중간재': { text: '중간재', class: 'badge-warning' },
|
||
'완제품': { text: '완제품', class: 'badge-success' }
|
||
};
|
||
return formatters.badge(value, badgeMap);
|
||
}
|
||
},
|
||
'type': { field: 'type', label: '유형', align: 'center' },
|
||
'memo': { field: 'memo', label: '메모' },
|
||
'createdBy': { field: 'createdBy', label: '등록자' },
|
||
'createdDate': { field: 'createdDate', label: '등록일', align: 'center' },
|
||
'modifiedDate': { field: 'modifiedDate', label: '최종수정일', align: 'center' }
|
||
};
|
||
|
||
// 컬럼 구성
|
||
const tableColumns = visibleColumns.map(col => {
|
||
const baseCol = columnMap[col.id] || { field: col.id, label: col.name };
|
||
return { ...baseCol, width: col.width + 'px' };
|
||
});
|
||
|
||
// 그룹화된 데이터 생성
|
||
const groupedData = createGroupedData(itemsData, window.groupByFields || []);
|
||
|
||
// 테이블 헤더
|
||
const headerHtml = tableColumns.map(col => {
|
||
const align = col.align || 'left';
|
||
const width = col.width ? `style="width: ${col.width};"` : '';
|
||
return `<th ${width} style="text-align: ${align};">${col.label}</th>`;
|
||
}).join('');
|
||
|
||
// 그룹별로 행 생성
|
||
let bodyHtml = '';
|
||
let groupIndex = 0;
|
||
|
||
for (const [groupKey, groupItems] of Object.entries(groupedData)) {
|
||
const colSpan = tableColumns.length;
|
||
|
||
// 그룹 내 전체 선택 여부 확인
|
||
const allSelected = groupItems.every(item => item.selected);
|
||
const someSelected = groupItems.some(item => item.selected);
|
||
|
||
// 그룹 헤더
|
||
bodyHtml += `
|
||
<tr class="group-header">
|
||
<td colspan="${colSpan}">
|
||
<div class="group-header-content">
|
||
<input type="checkbox"
|
||
${allSelected ? 'checked' : ''}
|
||
${someSelected && !allSelected ? 'indeterminate' : ''}
|
||
onclick="event.stopPropagation(); toggleGroupSelect(${groupIndex})"
|
||
id="groupCheckbox${groupIndex}"
|
||
style="margin-right: 8px;">
|
||
<span class="group-toggle" id="groupToggle${groupIndex}" onclick="toggleGroup(${groupIndex})">▼</span>
|
||
<span onclick="toggleGroup(${groupIndex})" style="flex: 1;">${groupKey}</span>
|
||
<span class="group-count" onclick="toggleGroup(${groupIndex})">(${groupItems.length}개)</span>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// 그룹 데이터 행들
|
||
const groupRowsHtml = groupItems.map((row, rowIndex) => {
|
||
const cellsHtml = tableColumns.map(col => {
|
||
let value = row[col.field];
|
||
|
||
// 포맷터 적용
|
||
if (col.formatter) {
|
||
value = col.formatter(value, row, rowIndex);
|
||
}
|
||
|
||
// null/undefined 처리
|
||
if (value === null || value === undefined) {
|
||
value = '-';
|
||
}
|
||
|
||
const align = col.align || 'left';
|
||
return `<td style="text-align: ${align};">${value}</td>`;
|
||
}).join('');
|
||
|
||
return `<tr class="group-row">${cellsHtml}</tr>`;
|
||
}).join('');
|
||
|
||
bodyHtml += `
|
||
<tbody class="group-rows" id="groupRows${groupIndex}" data-group-items='${JSON.stringify(groupItems.map(i => i.id))}'>
|
||
${groupRowsHtml}
|
||
</tbody>
|
||
`;
|
||
|
||
groupIndex++;
|
||
}
|
||
|
||
// 전체 테이블 HTML
|
||
const tableHtml = `
|
||
<div class="table-container">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>${headerHtml}</tr>
|
||
</thead>
|
||
${bodyHtml}
|
||
</table>
|
||
</div>
|
||
`;
|
||
|
||
container.innerHTML = tableHtml;
|
||
|
||
// 컬럼 리사이즈 기능 추가
|
||
addColumnResizeHandles();
|
||
|
||
// 행 클릭 이벤트 추가
|
||
addRowClickEvents();
|
||
|
||
updateTotalCount();
|
||
}
|
||
|
||
// 그룹 토글
|
||
function toggleGroup(groupIndex) {
|
||
const groupRows = document.getElementById(`groupRows${groupIndex}`);
|
||
const toggle = document.getElementById(`groupToggle${groupIndex}`);
|
||
|
||
if (groupRows && toggle) {
|
||
groupRows.classList.toggle('collapsed');
|
||
toggle.classList.toggle('collapsed');
|
||
}
|
||
}
|
||
|
||
// 그룹별 전체 선택/해제
|
||
function toggleGroupSelect(groupIndex) {
|
||
const groupRows = document.getElementById(`groupRows${groupIndex}`);
|
||
const checkbox = document.getElementById(`groupCheckbox${groupIndex}`);
|
||
|
||
if (!groupRows || !checkbox) return;
|
||
|
||
const checked = checkbox.checked;
|
||
const groupItemIds = JSON.parse(groupRows.getAttribute('data-group-items'));
|
||
|
||
// 해당 그룹의 모든 아이템 선택/해제
|
||
groupItemIds.forEach(id => {
|
||
const item = itemsData.find(i => i.id === id);
|
||
if (item) {
|
||
item.selected = checked;
|
||
}
|
||
});
|
||
|
||
// 화면 다시 렌더링
|
||
refreshDataView();
|
||
}
|
||
|
||
// 데이터 테이블 초기화
|
||
function initDataTable() {
|
||
const columns = [
|
||
{
|
||
field: 'selected',
|
||
label: '<input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">',
|
||
width: '60px',
|
||
align: 'center',
|
||
formatter: (value, row) => `<input type="checkbox" ${value ? 'checked' : ''} onclick="event.stopPropagation();" onchange="toggleSelect(${row.id})">`
|
||
},
|
||
{
|
||
field: 'status',
|
||
label: '상태',
|
||
width: '80px',
|
||
align: 'center',
|
||
formatter: (value) => {
|
||
const badgeMap = {
|
||
'정상': { text: '정상', class: 'badge-success' },
|
||
'품절': { text: '품절', class: 'badge-danger' },
|
||
'대기': { text: '대기', class: 'badge-warning' }
|
||
};
|
||
return formatters.badge(value, badgeMap);
|
||
}
|
||
},
|
||
{ field: 'itemCode', label: '품번코드', width: '140px' },
|
||
{ field: 'itemName', label: '품명', width: '200px', formatter: (value) => `<strong>${value}</strong>` },
|
||
{ field: 'spec', label: '규격', width: '150px' },
|
||
{ field: 'material', label: '재질', width: '180px' },
|
||
{ field: 'stockUnit', label: '재고단위', width: '100px', align: 'center' },
|
||
{ field: 'weight', label: '중량', width: '80px', align: 'right', formatter: (value) => value.toLocaleString() },
|
||
{ field: 'weightUnit', label: '단위', width: '80px', align: 'center' },
|
||
{ field: 'image', label: '이미지', width: '80px', align: 'center' },
|
||
{
|
||
field: 'category',
|
||
label: '구분',
|
||
width: '100px',
|
||
align: 'center',
|
||
formatter: (value) => {
|
||
const badgeMap = {
|
||
'원자재': { text: '원자재', class: 'badge-primary' },
|
||
'중간재': { text: '중간재', class: 'badge-warning' },
|
||
'완제품': { text: '완제품', class: 'badge-success' }
|
||
};
|
||
return formatters.badge(value, badgeMap);
|
||
}
|
||
},
|
||
{ field: 'type', label: '유형', width: '100px', align: 'center' },
|
||
{ field: 'memo', label: '메모', width: '180px' },
|
||
{ field: 'createdBy', label: '등록자', width: '100px' },
|
||
{ field: 'createdDate', label: '등록일', width: '120px', align: 'center' },
|
||
{ field: 'modifiedDate', label: '최종수정일', width: '120px', align: 'center' }
|
||
];
|
||
|
||
const tableHtml = createDataTable({
|
||
columns: columns,
|
||
data: itemsData,
|
||
emptyMessage: '등록된 품목이 없습니다'
|
||
});
|
||
|
||
document.getElementById('dataTableContainer').innerHTML = tableHtml;
|
||
|
||
// 컬럼 리사이즈 기능 추가
|
||
addColumnResizeHandles();
|
||
|
||
// 행 클릭 이벤트 추가
|
||
addRowClickEvents();
|
||
|
||
updateTotalCount();
|
||
}
|
||
|
||
// 행 클릭 이벤트 추가
|
||
function addRowClickEvents() {
|
||
const table = document.querySelector('.data-table');
|
||
if (!table) return;
|
||
|
||
const rows = table.querySelectorAll('tbody tr');
|
||
rows.forEach((row, index) => {
|
||
row.addEventListener('click', function(e) {
|
||
// 체크박스 클릭인 경우 무시
|
||
if (e.target.type === 'checkbox') {
|
||
return;
|
||
}
|
||
|
||
// 행의 체크박스 찾기
|
||
const checkbox = this.querySelector('input[type="checkbox"]');
|
||
if (checkbox) {
|
||
checkbox.checked = !checkbox.checked;
|
||
|
||
// 데이터 업데이트
|
||
const item = itemsData[index];
|
||
if (item) {
|
||
item.selected = checkbox.checked;
|
||
if (checkbox.checked) {
|
||
this.classList.add('selected');
|
||
} else {
|
||
this.classList.remove('selected');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 컬럼 리사이즈 핸들 추가
|
||
function addColumnResizeHandles() {
|
||
const table = document.querySelector('.data-table');
|
||
if (!table) return;
|
||
|
||
const headers = table.querySelectorAll('th');
|
||
|
||
headers.forEach((th, index) => {
|
||
// 리사이즈 핸들 추가
|
||
const resizeHandle = document.createElement('div');
|
||
resizeHandle.className = 'resize-handle';
|
||
th.appendChild(resizeHandle);
|
||
|
||
let startX, startWidth, thElement;
|
||
|
||
resizeHandle.addEventListener('mousedown', function(e) {
|
||
thElement = this.parentElement;
|
||
startX = e.pageX;
|
||
startWidth = thElement.offsetWidth;
|
||
|
||
document.addEventListener('mousemove', handleMouseMove);
|
||
document.addEventListener('mouseup', handleMouseUp);
|
||
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
});
|
||
|
||
function handleMouseMove(e) {
|
||
if (thElement) {
|
||
const diff = e.pageX - startX;
|
||
const newWidth = Math.max(60, startWidth + diff);
|
||
thElement.style.width = newWidth + 'px';
|
||
|
||
// 같은 인덱스의 td들도 동일한 너비 적용
|
||
const tbody = table.querySelector('tbody');
|
||
if (tbody) {
|
||
const rows = tbody.querySelectorAll('tr');
|
||
rows.forEach(row => {
|
||
const cells = row.querySelectorAll('td');
|
||
if (cells[index]) {
|
||
cells[index].style.width = newWidth + 'px';
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleMouseUp() {
|
||
document.removeEventListener('mousemove', handleMouseMove);
|
||
document.removeEventListener('mouseup', handleMouseUp);
|
||
thElement = null;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 검색 실행
|
||
function performSearch() {
|
||
const searchType = document.getElementById('searchType').value;
|
||
const keyword = document.getElementById('searchKeyword').value.toLowerCase();
|
||
|
||
console.log('검색:', searchType, keyword);
|
||
// TODO: 실제 검색 로직 구현
|
||
alert(`검색 실행: ${searchType} - ${keyword}`);
|
||
}
|
||
|
||
// 검색 초기화
|
||
function resetSearch() {
|
||
const searchType = document.getElementById('searchType');
|
||
const searchKeyword = document.getElementById('searchKeyword');
|
||
|
||
if (searchType) searchType.value = '전체';
|
||
if (searchKeyword) searchKeyword.value = '';
|
||
|
||
// 데이터 뷰 새로고침
|
||
refreshDataView();
|
||
}
|
||
|
||
// 전체 선택/해제 (테이블형)
|
||
function toggleSelectAll() {
|
||
const checkbox = document.getElementById('selectAllCheckbox');
|
||
const checked = checkbox.checked;
|
||
|
||
itemsData.forEach(item => item.selected = checked);
|
||
|
||
// 화면 업데이트
|
||
const checkboxes = document.querySelectorAll('.data-table tbody input[type="checkbox"]');
|
||
const rows = document.querySelectorAll('.data-table tbody tr');
|
||
|
||
checkboxes.forEach((cb, index) => {
|
||
cb.checked = checked;
|
||
if (checked) {
|
||
rows[index].classList.add('selected');
|
||
} else {
|
||
rows[index].classList.remove('selected');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 전체 선택/해제 (카드형)
|
||
function toggleSelectAllCard() {
|
||
const checkbox = document.getElementById('selectAllCheckboxCard');
|
||
const checked = checkbox.checked;
|
||
|
||
itemsData.forEach(item => item.selected = checked);
|
||
|
||
// 카드형 다시 렌더링
|
||
renderCardView();
|
||
}
|
||
|
||
// 개별 선택
|
||
function toggleSelect(id) {
|
||
const item = itemsData.find(i => i.id === id);
|
||
if (item) {
|
||
item.selected = !item.selected;
|
||
const row = event.target.closest('tr');
|
||
if (row) {
|
||
if (item.selected) {
|
||
row.classList.add('selected');
|
||
} else {
|
||
row.classList.remove('selected');
|
||
}
|
||
}
|
||
|
||
// 전체 선택 체크박스 상태 업데이트
|
||
updateSelectAllCheckbox();
|
||
}
|
||
}
|
||
|
||
// 카드형 헤더 컬럼 리사이즈
|
||
let cardResizing = false;
|
||
let cardResizeColId = null;
|
||
let cardResizeStartX = 0;
|
||
let cardResizeStartWidth = 0;
|
||
|
||
function startCardResize(event, colId) {
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
|
||
cardResizing = true;
|
||
cardResizeColId = colId;
|
||
cardResizeStartX = event.clientX;
|
||
|
||
// 현재 컬럼의 넓이 가져오기
|
||
const headerCol = event.target.parentElement;
|
||
cardResizeStartWidth = headerCol.offsetWidth;
|
||
|
||
document.addEventListener('mousemove', doCardResize);
|
||
document.addEventListener('mouseup', stopCardResize);
|
||
|
||
document.body.style.cursor = 'col-resize';
|
||
document.body.style.userSelect = 'none';
|
||
}
|
||
|
||
function doCardResize(event) {
|
||
if (!cardResizing) return;
|
||
|
||
const diff = event.clientX - cardResizeStartX;
|
||
const newWidth = Math.max(40, cardResizeStartWidth + diff);
|
||
|
||
// 헤더 컬럼 넓이 변경
|
||
const headerCols = document.querySelectorAll(`.card-header-col[data-col-id="${cardResizeColId}"]`);
|
||
headerCols.forEach(col => {
|
||
col.style.width = newWidth + 'px';
|
||
});
|
||
|
||
// 데이터 컬럼 넓이 변경
|
||
const dataCols = document.querySelectorAll(`.card-data-col[data-col-id="${cardResizeColId}"]`);
|
||
dataCols.forEach(col => {
|
||
col.style.width = newWidth + 'px';
|
||
});
|
||
}
|
||
|
||
function stopCardResize(event) {
|
||
if (!cardResizing) return;
|
||
|
||
cardResizing = false;
|
||
document.removeEventListener('mousemove', doCardResize);
|
||
document.removeEventListener('mouseup', stopCardResize);
|
||
|
||
document.body.style.cursor = '';
|
||
document.body.style.userSelect = '';
|
||
|
||
// localStorage에 저장
|
||
if (cardResizeColId) {
|
||
const savedColumns = JSON.parse(localStorage.getItem('columnsConfig') || '[]');
|
||
const headerCol = document.querySelector(`.card-header-col[data-col-id="${cardResizeColId}"]`);
|
||
if (headerCol && savedColumns.length > 0) {
|
||
const newWidth = headerCol.offsetWidth;
|
||
const colIndex = savedColumns.findIndex(c => c.id === cardResizeColId);
|
||
if (colIndex !== -1) {
|
||
savedColumns[colIndex].width = newWidth;
|
||
localStorage.setItem('columnsConfig', JSON.stringify(savedColumns));
|
||
}
|
||
}
|
||
}
|
||
|
||
cardResizeColId = null;
|
||
}
|
||
|
||
// 카드형에서 다중 선택/해제
|
||
function toggleCardSelect(id, event) {
|
||
const item = itemsData.find(item => item.id === id);
|
||
if (!item) return;
|
||
|
||
// Ctrl 또는 Cmd 키가 눌려있으면 다중 선택 모드
|
||
if (event.ctrlKey || event.metaKey) {
|
||
// 현재 항목만 토글
|
||
item.selected = !item.selected;
|
||
}
|
||
// Shift 키가 눌려있으면 범위 선택
|
||
else if (event.shiftKey) {
|
||
// 마지막 선택된 항목 찾기
|
||
const selectedItems = itemsData.filter(i => i.selected);
|
||
if (selectedItems.length > 0) {
|
||
const lastSelectedIndex = itemsData.indexOf(selectedItems[selectedItems.length - 1]);
|
||
const currentIndex = itemsData.findIndex(i => i.id === id);
|
||
|
||
const start = Math.min(lastSelectedIndex, currentIndex);
|
||
const end = Math.max(lastSelectedIndex, currentIndex);
|
||
|
||
// 범위 내 모든 항목 선택
|
||
for (let i = start; i <= end; i++) {
|
||
itemsData[i].selected = true;
|
||
}
|
||
} else {
|
||
item.selected = true;
|
||
}
|
||
}
|
||
// 일반 클릭: 단일 선택
|
||
else {
|
||
// 이미 선택된 항목을 다시 클릭하면 해제만
|
||
if (item.selected) {
|
||
item.selected = false;
|
||
} else {
|
||
// 모든 선택 해제 후 현재 항목만 선택
|
||
itemsData.forEach(i => i.selected = false);
|
||
item.selected = true;
|
||
}
|
||
}
|
||
|
||
// 화면 다시 렌더링
|
||
renderCardView();
|
||
updateSelectAllCheckbox();
|
||
}
|
||
|
||
// 전체 선택 체크박스 상태 업데이트
|
||
function updateSelectAllCheckbox() {
|
||
const allSelected = itemsData.every(item => item.selected);
|
||
const someSelected = itemsData.some(item => item.selected);
|
||
|
||
// 테이블형 체크박스
|
||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||
if (selectAllCheckbox) {
|
||
selectAllCheckbox.checked = allSelected;
|
||
selectAllCheckbox.indeterminate = someSelected && !allSelected;
|
||
}
|
||
|
||
// 카드형 체크박스
|
||
const selectAllCheckboxCard = document.getElementById('selectAllCheckboxCard');
|
||
if (selectAllCheckboxCard) {
|
||
selectAllCheckboxCard.checked = allSelected;
|
||
selectAllCheckboxCard.indeterminate = someSelected && !allSelected;
|
||
}
|
||
}
|
||
|
||
// 총 개수 업데이트
|
||
function updateTotalCount() {
|
||
document.getElementById('totalCount').textContent = itemsData.length;
|
||
}
|
||
|
||
// 품목 추가 모달 열기
|
||
function openAddModal() {
|
||
const selected = itemsData.filter(item => item.selected);
|
||
|
||
// 선택된 항목이 있으면 복사하여 추가
|
||
if (selected.length > 0) {
|
||
if (selected.length > 1) {
|
||
showToast('복사는 한 개씩만 가능합니다', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
itemModalMode = 'add';
|
||
editingItemId = null;
|
||
document.getElementById('itemModalTitle').textContent = '품목 복사 추가';
|
||
renderItemForm(selected[0]); // 선택된 데이터로 폼 채우기
|
||
} else {
|
||
// 선택 항목 없으면 빈 폼
|
||
itemModalMode = 'add';
|
||
editingItemId = null;
|
||
document.getElementById('itemModalTitle').textContent = '품목 추가';
|
||
renderItemForm();
|
||
}
|
||
|
||
document.getElementById('itemModal').classList.add('active');
|
||
|
||
// 첫 번째 입력 필드에 포커스
|
||
setTimeout(() => {
|
||
const firstInput = document.querySelector('#itemModalBody input, #itemModalBody select');
|
||
if (firstInput) firstInput.focus();
|
||
}, 100);
|
||
}
|
||
|
||
// 품목 수정 모달 열기
|
||
function openEditModal() {
|
||
const selected = itemsData.filter(item => item.selected);
|
||
if (selected.length === 0) {
|
||
showToast('수정할 항목을 선택하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
if (selected.length > 1) {
|
||
showToast('수정은 한 개씩만 가능합니다', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
itemModalMode = 'edit';
|
||
editingItemId = selected[0].id;
|
||
document.getElementById('itemModalTitle').textContent = '품목 수정';
|
||
renderItemForm(selected[0]);
|
||
document.getElementById('itemModal').classList.add('active');
|
||
|
||
// 첫 번째 입력 필드에 포커스
|
||
setTimeout(() => {
|
||
const firstInput = document.querySelector('#itemModalBody input, #itemModalBody select');
|
||
if (firstInput) firstInput.focus();
|
||
}, 100);
|
||
}
|
||
|
||
// 품목 모달 닫기
|
||
function closeItemModal() {
|
||
document.getElementById('itemModal').classList.remove('active');
|
||
editingItemId = null;
|
||
}
|
||
|
||
// 품목 입력 폼 렌더링
|
||
function renderItemForm(data = null) {
|
||
const body = document.getElementById('itemModalBody');
|
||
let html = '';
|
||
|
||
itemFormFields.forEach((field, index) => {
|
||
const value = data ? (data[field.id] || '') : '';
|
||
const isLast = index === itemFormFields.length - 1;
|
||
|
||
if (field.type === 'select') {
|
||
html += `
|
||
<div class="item-form-field">
|
||
<select id="field_${field.id}"
|
||
data-field-index="${index}"
|
||
${field.required ? 'required' : ''}
|
||
onkeydown="handleFormKeyDown(event, ${index}, ${isLast})">
|
||
<option value="">${field.label}${field.required ? ' *' : ''}</option>
|
||
${field.options.map(opt =>
|
||
`<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`
|
||
).join('')}
|
||
</select>
|
||
</div>
|
||
`;
|
||
} else if (field.type === 'number') {
|
||
html += `
|
||
<div class="item-form-field">
|
||
<input type="number"
|
||
id="field_${field.id}"
|
||
data-field-index="${index}"
|
||
placeholder="${field.label}${field.required ? ' *' : ''}"
|
||
value="${value}"
|
||
${field.required ? 'required' : ''}
|
||
onkeydown="handleFormKeyDown(event, ${index}, ${isLast})"
|
||
step="any">
|
||
</div>
|
||
`;
|
||
} else {
|
||
html += `
|
||
<div class="item-form-field">
|
||
<input type="text"
|
||
id="field_${field.id}"
|
||
data-field-index="${index}"
|
||
placeholder="${field.label}${field.required ? ' *' : ''}"
|
||
value="${value}"
|
||
${field.required ? 'required' : ''}
|
||
onkeydown="handleFormKeyDown(event, ${index}, ${isLast})">
|
||
</div>
|
||
`;
|
||
}
|
||
});
|
||
|
||
body.innerHTML = html;
|
||
}
|
||
|
||
// 폼 엔터키 처리
|
||
function handleFormKeyDown(event, currentIndex, isLast) {
|
||
if (event.key === 'Enter') {
|
||
event.preventDefault();
|
||
|
||
if (isLast) {
|
||
// 마지막 필드에서 엔터: 저장
|
||
saveItem();
|
||
} else {
|
||
// 다음 필드로 이동
|
||
const nextField = document.querySelector(`[data-field-index="${currentIndex + 1}"]`);
|
||
if (nextField) {
|
||
nextField.focus();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 품목 저장
|
||
function saveItem() {
|
||
const formData = {};
|
||
let hasError = false;
|
||
|
||
// 폼 데이터 수집 및 검증
|
||
itemFormFields.forEach(field => {
|
||
const input = document.getElementById(`field_${field.id}`);
|
||
const value = input.value.trim();
|
||
|
||
if (field.required && !value) {
|
||
input.style.borderColor = '#ef4444';
|
||
hasError = true;
|
||
setTimeout(() => {
|
||
input.style.borderColor = '';
|
||
}, 2000);
|
||
} else {
|
||
formData[field.id] = value;
|
||
}
|
||
});
|
||
|
||
if (hasError) {
|
||
showToast('필수 항목을 입력하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
if (itemModalMode === 'add') {
|
||
// 새 품목 추가
|
||
const newItem = {
|
||
id: itemsData.length > 0 ? Math.max(...itemsData.map(i => i.id)) + 1 : 1,
|
||
selected: false,
|
||
...formData,
|
||
image: '📦',
|
||
createdBy: '사용자',
|
||
createdDate: new Date().toISOString().split('T')[0],
|
||
modifiedDate: new Date().toISOString().split('T')[0]
|
||
};
|
||
|
||
itemsData.push(newItem);
|
||
showToast('품목이 추가되었습니다', '✓');
|
||
|
||
} else {
|
||
// 기존 품목 수정
|
||
const item = itemsData.find(i => i.id === editingItemId);
|
||
if (item) {
|
||
Object.assign(item, formData);
|
||
item.modifiedDate = new Date().toISOString().split('T')[0];
|
||
showToast('품목이 수정되었습니다', '✓');
|
||
}
|
||
}
|
||
|
||
// 화면 갱신
|
||
refreshDataView();
|
||
|
||
// 연속입력 체크 여부 확인
|
||
const continuousInput = document.getElementById('continuousInput').checked;
|
||
|
||
if (continuousInput && itemModalMode === 'add') {
|
||
// 연속입력: 폼 초기화 후 첫 필드에 포커스
|
||
renderItemForm();
|
||
setTimeout(() => {
|
||
const firstInput = document.querySelector('#itemModalBody input, #itemModalBody select');
|
||
if (firstInput) firstInput.focus();
|
||
}, 100);
|
||
} else {
|
||
// 모달 닫기
|
||
closeItemModal();
|
||
}
|
||
}
|
||
|
||
// 코드 변경 관리 모달 열기
|
||
function openCodeChangeModal() {
|
||
const selected = itemsData.filter(item => item.selected);
|
||
|
||
if (selected.length === 0) {
|
||
showToast('변경할 품목을 선택하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
if (selected.length > 1) {
|
||
showToast('코드 변경은 한 개씩만 가능합니다', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
// 모달 초기화
|
||
document.querySelectorAll('.code-change-option').forEach(opt => opt.classList.remove('selected'));
|
||
document.querySelectorAll('.code-change-form').forEach(form => form.classList.remove('active'));
|
||
document.getElementById('newCode').value = '';
|
||
document.getElementById('renamePreview').style.display = 'none';
|
||
document.getElementById('mergePreview').style.display = 'none';
|
||
|
||
// 현재 선택된 품목 정보 설정
|
||
const item = selected[0];
|
||
document.getElementById('currentCode').value = item.itemCode;
|
||
document.getElementById('sourceCode').value = item.itemCode;
|
||
|
||
// 합병 대상 품번 목록 생성 (자신 제외)
|
||
const targetSelect = document.getElementById('targetCode');
|
||
targetSelect.innerHTML = '<option value="">품번을 선택하세요</option>';
|
||
itemsData.forEach(i => {
|
||
if (i.id !== item.id) {
|
||
targetSelect.innerHTML += `<option value="${i.id}">${i.itemCode} - ${i.itemName}</option>`;
|
||
}
|
||
});
|
||
|
||
document.getElementById('codeChangeModal').classList.add('active');
|
||
}
|
||
|
||
// 코드 변경 모달 닫기
|
||
function closeCodeChangeModal() {
|
||
document.getElementById('codeChangeModal').classList.remove('active');
|
||
}
|
||
|
||
// 코드 변경 옵션 선택
|
||
let selectedCodeChangeOption = null;
|
||
|
||
function selectCodeChangeOption(option) {
|
||
selectedCodeChangeOption = option;
|
||
|
||
// 모든 옵션 선택 해제
|
||
document.querySelectorAll('.code-change-option').forEach(opt => {
|
||
opt.classList.remove('selected');
|
||
});
|
||
|
||
// 모든 폼 숨기기
|
||
document.querySelectorAll('.code-change-form').forEach(form => {
|
||
form.classList.remove('active');
|
||
});
|
||
|
||
// 선택된 옵션 활성화
|
||
if (option === 'rename') {
|
||
document.getElementById('renameOption').classList.add('selected');
|
||
document.getElementById('renameForm').classList.add('active');
|
||
|
||
// 입력 필드 이벤트 추가
|
||
const newCodeInput = document.getElementById('newCode');
|
||
newCodeInput.oninput = updateRenamePreview;
|
||
|
||
} else if (option === 'merge') {
|
||
document.getElementById('mergeOption').classList.add('selected');
|
||
document.getElementById('mergeForm').classList.add('active');
|
||
}
|
||
}
|
||
|
||
// 품번 변경 미리보기 업데이트
|
||
function updateRenamePreview() {
|
||
const newCode = document.getElementById('newCode').value.trim();
|
||
const currentCode = document.getElementById('currentCode').value;
|
||
const selected = itemsData.filter(item => item.selected)[0];
|
||
|
||
if (newCode) {
|
||
document.getElementById('previewOldCode').textContent = currentCode;
|
||
document.getElementById('previewNewCode').textContent = newCode;
|
||
document.getElementById('previewItemName').textContent = selected.itemName;
|
||
document.getElementById('renamePreview').style.display = 'block';
|
||
} else {
|
||
document.getElementById('renamePreview').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 합병 미리보기 업데이트
|
||
function updateMergePreview() {
|
||
const targetId = document.getElementById('targetCode').value;
|
||
const sourceCode = document.getElementById('sourceCode').value;
|
||
|
||
if (targetId) {
|
||
const targetItem = itemsData.find(i => i.id === parseInt(targetId));
|
||
if (targetItem) {
|
||
document.getElementById('previewSourceCode').textContent = sourceCode;
|
||
document.getElementById('previewTargetCode').textContent =
|
||
`${targetItem.itemCode} - ${targetItem.itemName}`;
|
||
document.getElementById('mergePreview').style.display = 'block';
|
||
}
|
||
} else {
|
||
document.getElementById('mergePreview').style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 코드 변경 실행
|
||
function executeCodeChange() {
|
||
if (!selectedCodeChangeOption) {
|
||
showToast('변경 방식을 선택하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
const selected = itemsData.filter(item => item.selected)[0];
|
||
|
||
if (selectedCodeChangeOption === 'rename') {
|
||
// 품번 변경
|
||
const newCode = document.getElementById('newCode').value.trim();
|
||
|
||
if (!newCode) {
|
||
showToast('새 품번을 입력하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
// 중복 체크
|
||
const exists = itemsData.some(i => i.itemCode === newCode && i.id !== selected.id);
|
||
if (exists) {
|
||
showToast('이미 사용 중인 품번입니다', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
// 확인 메시지
|
||
if (!confirm(`품번을 "${selected.itemCode}"에서 "${newCode}"로 변경하시겠습니까?\n\n※ 관련된 모든 데이터의 품번이 함께 변경됩니다.`)) {
|
||
return;
|
||
}
|
||
|
||
// 품번 변경 실행
|
||
selected.itemCode = newCode;
|
||
selected.modifiedDate = new Date().toISOString().split('T')[0];
|
||
|
||
showToast('품번이 변경되었습니다', '✓');
|
||
|
||
} else if (selectedCodeChangeOption === 'merge') {
|
||
// 품번 합병
|
||
const targetId = document.getElementById('targetCode').value;
|
||
|
||
if (!targetId) {
|
||
showToast('통합될 품번을 선택하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
const targetItem = itemsData.find(i => i.id === parseInt(targetId));
|
||
|
||
// 확인 메시지
|
||
if (!confirm(`⚠️ 품번 합병 확인\n\n삭제될 품번: ${selected.itemCode}\n통합될 품번: ${targetItem.itemCode}\n\n※ 이 작업은 되돌릴 수 없습니다.\n※ ${selected.itemCode}의 모든 데이터(재고, 거래내역 등)가 ${targetItem.itemCode}로 통합됩니다.\n※ ${selected.itemCode}는 영구적으로 삭제됩니다.\n\n정말 실행하시겠습니까?`)) {
|
||
return;
|
||
}
|
||
|
||
// 합병 실행 (실제로는 서버에서 처리)
|
||
// 여기서는 단순히 항목 삭제로 시뮬레이션
|
||
itemsData = itemsData.filter(i => i.id !== selected.id);
|
||
targetItem.modifiedDate = new Date().toISOString().split('T')[0];
|
||
|
||
showToast(`품번이 합병되었습니다 (${selected.itemCode} → ${targetItem.itemCode})`, '✓');
|
||
}
|
||
|
||
// 화면 갱신
|
||
refreshDataView();
|
||
closeCodeChangeModal();
|
||
}
|
||
|
||
// 선택 항목 삭제
|
||
function deleteSelected() {
|
||
const selected = itemsData.filter(item => item.selected);
|
||
if (selected.length === 0) {
|
||
showToast('삭제할 항목을 선택하세요', '⚠️', true);
|
||
return;
|
||
}
|
||
|
||
if (confirm(`선택한 ${selected.length}개 항목을 삭제하시겠습니까?`)) {
|
||
itemsData = itemsData.filter(item => !item.selected);
|
||
refreshDataView();
|
||
showToast('삭제되었습니다', '✓');
|
||
}
|
||
}
|
||
|
||
|
||
// 엑셀 다운로드
|
||
function downloadExcel() {
|
||
showToast('엑셀 다운로드 기능 (추후 구현)', 'ℹ️');
|
||
}
|
||
|
||
// 엑셀 업로드
|
||
function uploadExcel() {
|
||
openExcelUploadModal();
|
||
}
|
||
|
||
// 거래처 목록 반환 (엑셀 업로드 컴포넌트에서 사용)
|
||
function getCompanyList() {
|
||
// TODO: 실제 API에서 거래처 목록 가져오기
|
||
// 현재는 샘플 데이터 반환
|
||
return [
|
||
'ABC 주식회사',
|
||
'XYZ 상사',
|
||
'대한물산',
|
||
'글로벌트레이딩',
|
||
'한국제조',
|
||
'서울상사',
|
||
'부산물산',
|
||
'인천무역'
|
||
];
|
||
}
|
||
|
||
// 전역 함수로 노출
|
||
window.getCompanyList = getCompanyList;
|
||
|
||
// 사용자 옵션 모달 열기
|
||
function openUserOptionsModal() {
|
||
document.getElementById('userOptionsModal').classList.add('active');
|
||
// 첫 번째 탭을 강제로 활성화하고 렌더링
|
||
const firstTab = document.querySelector('.user-options-tab[onclick*="searchFields"]');
|
||
if (firstTab) {
|
||
document.querySelectorAll('.user-options-tab').forEach(tab => tab.classList.remove('active'));
|
||
firstTab.classList.add('active');
|
||
}
|
||
switchOptionsTab('searchFields', true);
|
||
}
|
||
|
||
// 사용자 옵션 모달 닫기
|
||
function closeUserOptionsModal(force = false) {
|
||
// force가 true이거나 자동 닫기 설정이 켜져있으면 닫기
|
||
const shouldAutoClose = localStorage.getItem('autoCloseModal');
|
||
if (force || shouldAutoClose === null || shouldAutoClose === 'true') {
|
||
document.getElementById('userOptionsModal').classList.remove('active');
|
||
}
|
||
}
|
||
|
||
// 옵션 탭 전환
|
||
function switchOptionsTab(tabName, forceRender = false) {
|
||
const body = document.getElementById('userOptionsBody');
|
||
|
||
// 현재 활성화된 탭 확인
|
||
const currentActiveTab = document.querySelector('.user-options-tab.active');
|
||
const isAlreadyActive = currentActiveTab && currentActiveTab.getAttribute('onclick').includes(tabName);
|
||
|
||
// 이미 활성화된 탭이고 강제 렌더링이 아니면 렌더링하지 않음
|
||
if (isAlreadyActive && !forceRender && body.innerHTML.trim() !== '') {
|
||
console.log('✋ 탭이 이미 활성화되어 있어 렌더링을 건너뜁니다:', tabName);
|
||
return;
|
||
}
|
||
|
||
console.log('🔄 탭 전환 및 렌더링:', tabName);
|
||
|
||
// 탭 버튼 활성화
|
||
document.querySelectorAll('.user-options-tab').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
if (tab.getAttribute('onclick').includes(tabName)) {
|
||
tab.classList.add('active');
|
||
}
|
||
});
|
||
|
||
// 탭 내용 표시
|
||
switch(tabName) {
|
||
case 'searchFields':
|
||
body.innerHTML = renderSearchFieldsTab();
|
||
break;
|
||
case 'columnDisplay':
|
||
body.innerHTML = renderColumnDisplayTab();
|
||
addColumnDragEvents();
|
||
break;
|
||
case 'otherOptions':
|
||
body.innerHTML = renderOtherOptionsTab();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 검색필드 설정 탭 렌더링
|
||
function renderSearchFieldsTab() {
|
||
// localStorage에서 저장된 설정 불러오기
|
||
const savedFields = JSON.parse(localStorage.getItem('searchFieldsConfig') || 'null');
|
||
|
||
const defaultFields = [
|
||
{ id: 'status', name: '상태', checked: true, width: 120, type: 'select' },
|
||
{ id: 'itemCode', name: '품번코드', checked: true, width: 150, type: 'text' },
|
||
{ id: 'itemName', name: '품명', checked: true, width: 200, type: 'text' },
|
||
{ id: 'spec', name: '규격', checked: false, width: 150, type: 'text' },
|
||
{ id: 'material', name: '재질', checked: false, width: 180, type: 'text' },
|
||
{ id: 'stockUnit', name: '재고단위', checked: false, width: 100, type: 'select' },
|
||
{ id: 'weight', name: '중량', checked: false, width: 100, type: 'text' },
|
||
{ id: 'weightUnit', name: '단위', checked: false, width: 100, type: 'select' },
|
||
{ id: 'category', name: '구분', checked: false, width: 100, type: 'select' },
|
||
{ id: 'type', name: '유형', checked: false, width: 120, type: 'select' },
|
||
{ id: 'memo', name: '메모', checked: false, width: 180, type: 'text' },
|
||
{ id: 'createdBy', name: '등록자', checked: false, width: 100, type: 'text' },
|
||
{ id: 'createdDate', name: '등록일', checked: false, width: 120, type: 'text' },
|
||
{ id: 'modifiedDate', name: '최종수정일', checked: false, width: 120, type: 'text' }
|
||
];
|
||
|
||
const searchFields = savedFields || defaultFields;
|
||
|
||
// 처음 사용자 옵션을 열었을 때 기본값을 localStorage에 저장
|
||
if (!savedFields) {
|
||
console.log('💾 기본 검색필드 설정 저장 중...');
|
||
localStorage.setItem('searchFieldsConfig', JSON.stringify(defaultFields));
|
||
}
|
||
|
||
let html = '<p style="color: #6b7280; margin-bottom: 16px;">✓ 표시할 필드를 선택하고, 드래그하여 순서를 변경할 수 있습니다.</p>';
|
||
|
||
searchFields.forEach((field, index) => {
|
||
html += `
|
||
<div class="option-field-item" draggable="true" data-index="${index}" data-field-id="${field.id}">
|
||
<div class="drag-handle">☰</div>
|
||
<input type="checkbox" class="option-field-checkbox" ${field.checked ? 'checked' : ''}>
|
||
<div class="option-field-name">${field.name}</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="font-size: 13px; color: #6b7280;">너비:</span>
|
||
<input type="number" class="option-field-width" value="${field.width}" min="60" style="padding: 6px 8px; border: 1px solid #d1d5db; border-radius: 4px;">
|
||
<span style="font-size: 11px; color: #9ca3af;">px</span>
|
||
</div>
|
||
<select class="option-field-type" style="padding: 6px 10px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 13px;">
|
||
<option value="text" ${field.type === 'text' ? 'selected' : ''}>텍스트</option>
|
||
<option value="select" ${field.type === 'select' ? 'selected' : ''}>콤보박스</option>
|
||
<option value="multi" ${field.type === 'multi' ? 'selected' : ''}>다중 검색</option>
|
||
</select>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
setTimeout(() => addSearchFieldDragEvents(), 0);
|
||
|
||
return html;
|
||
}
|
||
|
||
// 컬럼 표시/숨기기 탭 렌더링
|
||
function renderColumnDisplayTab() {
|
||
// localStorage에서 저장된 설정 불러오기
|
||
const savedColumns = JSON.parse(localStorage.getItem('columnsConfig') || 'null');
|
||
const viewMode = localStorage.getItem('viewMode') || 'table';
|
||
|
||
const defaultColumns = [
|
||
{ id: 'selected', name: '선택', checked: true, width: 60 },
|
||
{ id: 'status', name: '상태', checked: true, width: 80 },
|
||
{ id: 'itemCode', name: '품번코드', checked: true, width: 140 },
|
||
{ id: 'itemName', name: '품명', checked: true, width: 200 },
|
||
{ id: 'spec', name: '규격', checked: true, width: 150 },
|
||
{ id: 'material', name: '재질', checked: true, width: 180 },
|
||
{ id: 'stockUnit', name: '재고단위', checked: true, width: 100 },
|
||
{ id: 'weight', name: '중량', checked: true, width: 80 },
|
||
{ id: 'weightUnit', name: '단위', checked: true, width: 80 },
|
||
{ id: 'image', name: '이미지', checked: true, width: 80 },
|
||
{ id: 'category', name: '구분', checked: true, width: 100 },
|
||
{ id: 'type', name: '유형', checked: true, width: 100 },
|
||
{ id: 'memo', name: '메모', checked: true, width: 180 },
|
||
{ id: 'createdBy', name: '등록자', checked: true, width: 100 },
|
||
{ id: 'createdDate', name: '등록일', checked: true, width: 120 },
|
||
{ id: 'modifiedDate', name: '최종수정일', checked: true, width: 120 }
|
||
];
|
||
|
||
const columns = savedColumns || defaultColumns;
|
||
|
||
let html = '<p style="color: #6b7280; margin-bottom: 16px;">✓ 표시할 컬럼을 선택하고, 드래그하여 순서를 변경할 수 있습니다.</p>';
|
||
|
||
columns.forEach((col, index) => {
|
||
const canDrag = true; // 모든 컬럼 드래그 가능
|
||
html += `
|
||
<div class="option-field-item" draggable="${canDrag}" data-index="${index}" data-col-id="${col.id}">
|
||
<div class="drag-handle">☰</div>
|
||
<input type="checkbox" class="option-field-checkbox" ${col.checked ? 'checked' : ''}>
|
||
<div class="option-field-name">${col.name}</div>
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span style="font-size: 13px; color: #6b7280;">너비:</span>
|
||
<input type="number" class="option-field-width" value="${col.width}" min="40" style="padding: 6px 8px; border: 1px solid #d1d5db; border-radius: 4px;">
|
||
<span style="font-size: 11px; color: #9ca3af;">px</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
setTimeout(() => addColumnDragEvents(), 0);
|
||
|
||
return html;
|
||
}
|
||
|
||
// 기타옵션 탭 렌더링
|
||
function renderOtherOptionsTab() {
|
||
// 저장된 설정 불러오기
|
||
const freezeCount = localStorage.getItem('freezeColumnCount') || '0';
|
||
const gridLinesVisible = localStorage.getItem('gridLinesVisible');
|
||
const showGrid = gridLinesVisible === null || gridLinesVisible === 'true';
|
||
const toastEnabled = localStorage.getItem('toastEnabled');
|
||
const showToast = toastEnabled === null || toastEnabled === 'true';
|
||
const savedViewMode = localStorage.getItem('viewMode') || 'table';
|
||
const viewMode = savedViewMode;
|
||
const savedAutoClose = localStorage.getItem('autoCloseModal');
|
||
const autoCloseModal = savedAutoClose === null || savedAutoClose === 'true';
|
||
|
||
return `
|
||
<div style="margin-bottom: 24px;">
|
||
<h3 style="font-size: 15px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">📌 틀고정</h3>
|
||
<p style="color: #6b7280; font-size: 13px; margin-bottom: 12px;">✓ 왼쪽을 기준으로 고정할 컬럼 개수를 선택하세요.</p>
|
||
<div class="freeze-option">
|
||
<label>고정할 컬럼 개수 (왼쪽부터)</label>
|
||
<input type="number" id="freezeColumnCount" value="${freezeCount}" min="0" max="5">
|
||
</div>
|
||
<div style="background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||
<p style="font-size: 12px; color: #075985; margin: 0;">
|
||
<strong>💡 사용 방법:</strong> 예) 2를 입력하면 첫 번째와 두 번째 컬럼이 고정되어 가로 스크롤 시에도 항상 표시됩니다.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border-top: 2px solid #e5e7eb; padding-top: 24px; margin-bottom: 24px;">
|
||
<h3 style="font-size: 15px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">🔲 그리드선</h3>
|
||
<p style="color: #6b7280; font-size: 13px; margin-bottom: 12px;">✓ 데이터 테이블의 그리드선 표시 여부를 선택하세요.</p>
|
||
|
||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${showGrid ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="gridLines" value="show" ${showGrid ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div>
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">그리드선 보이기</div>
|
||
<div style="font-size: 12px; color: #6b7280;">테이블 셀 사이에 구분선이 표시됩니다</div>
|
||
</div>
|
||
</label>
|
||
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${!showGrid ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="gridLines" value="hide" ${!showGrid ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div>
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">그리드선 감추기</div>
|
||
<div style="font-size: 12px; color: #6b7280;">테이블이 깔끔하게 표시됩니다</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border-top: 2px solid #e5e7eb; padding-top: 24px; margin-bottom: 24px;">
|
||
<h3 style="font-size: 15px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">💬 메시지창</h3>
|
||
<p style="color: #6b7280; font-size: 13px; margin-bottom: 12px;">✓ 작업 완료 시 화면 상단에 표시되는 메시지 사용 여부를 선택하세요.</p>
|
||
|
||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${showToast ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="toastMessage" value="show" ${showToast ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div>
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">메시지창 사용</div>
|
||
<div style="font-size: 12px; color: #6b7280;">저장, 삭제 등 작업 완료 시 안내 메시지가 표시됩니다</div>
|
||
</div>
|
||
</label>
|
||
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${!showToast ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="toastMessage" value="hide" ${!showToast ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div>
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">메시지창 사용 안 함</div>
|
||
<div style="font-size: 12px; color: #6b7280;">메시지 없이 조용하게 작업이 수행됩니다</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
|
||
<div style="background: #fef3c7; border: 1px solid #fde68a; border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||
<p style="font-size: 12px; color: #92400e; margin: 0;">
|
||
<strong>⚠️ 참고:</strong> 메시지창을 사용 안 함으로 설정해도 오류 메시지는 계속 표시됩니다.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border-top: 2px solid #e5e7eb; padding-top: 24px; margin-bottom: 24px;">
|
||
<h3 style="font-size: 15px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">👁️ 보기 모드</h3>
|
||
<p style="color: #6b7280; font-size: 13px; margin-bottom: 12px;">✓ 데이터 표시 방식을 선택하세요.</p>
|
||
|
||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${viewMode === 'table' ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="viewMode" value="table" ${viewMode === 'table' ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div style="flex: 1;">
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">📊 테이블형</div>
|
||
<div style="font-size: 12px; color: #6b7280;">행과 열로 구성된 표 형태로 많은 데이터를 한눈에 볼 수 있습니다</div>
|
||
</div>
|
||
</label>
|
||
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${viewMode === 'card' ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="viewMode" value="card" ${viewMode === 'card' ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div style="flex: 1;">
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">📦 카드형</div>
|
||
<div style="font-size: 12px; color: #6b7280;">각 항목이 카드 형태로 표시되어 시각적으로 구분하기 쉽습니다</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
|
||
<div style="background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||
<p style="font-size: 12px; color: #075985; margin: 0;">
|
||
<strong>💡 추천:</strong> 많은 데이터를 비교할 때는 테이블형, 개별 항목을 자세히 볼 때는 카드형이 적합합니다.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border-top: 2px solid #e5e7eb; padding-top: 24px;">
|
||
<h3 style="font-size: 15px; font-weight: 600; color: #1f2937; margin-bottom: 12px;">🔄 모달 자동 닫기</h3>
|
||
<p style="color: #6b7280; font-size: 13px; margin-bottom: 12px;">✓ 옵션 저장 후 모달창 자동 닫힘 여부를 선택하세요.</p>
|
||
|
||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${autoCloseModal ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="autoCloseModal" value="true" ${autoCloseModal ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div style="flex: 1;">
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">자동 닫기</div>
|
||
<div style="font-size: 12px; color: #6b7280;">저장 또는 취소 버튼 클릭 시 모달이 자동으로 닫힙니다</div>
|
||
</div>
|
||
</label>
|
||
|
||
<label style="display: flex; align-items: center; gap: 12px; padding: 16px; background: #f9fafb; border-radius: 8px; cursor: pointer; border: 2px solid ${!autoCloseModal ? '#3b82f6' : '#e5e7eb'}; transition: all 0.2s;">
|
||
<input type="radio" name="autoCloseModal" value="false" ${!autoCloseModal ? 'checked' : ''} style="width: 18px; height: 18px; cursor: pointer;">
|
||
<div style="flex: 1;">
|
||
<div style="font-weight: 600; color: #1f2937; margin-bottom: 4px;">수동 닫기</div>
|
||
<div style="font-size: 12px; color: #6b7280;">저장 후에도 모달이 유지되어 연속 작업이 가능합니다</div>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
|
||
<div style="background: #fef3c7; border: 1px solid #fde68a; border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||
<p style="font-size: 12px; color: #92400e; margin: 0;">
|
||
<strong>💡 팁:</strong> 여러 옵션을 연속으로 수정할 때는 "수동 닫기"를 권장합니다.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 검색필드 드래그 이벤트 추가
|
||
function addSearchFieldDragEvents() {
|
||
const items = document.querySelectorAll('#userOptionsBody .option-field-item[draggable="true"]');
|
||
let draggedItem = null;
|
||
|
||
items.forEach(item => {
|
||
item.addEventListener('dragstart', function(e) {
|
||
draggedItem = this;
|
||
this.style.opacity = '0.5';
|
||
});
|
||
|
||
item.addEventListener('dragend', function(e) {
|
||
this.style.opacity = '1';
|
||
});
|
||
|
||
item.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
});
|
||
|
||
item.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
if (draggedItem !== this) {
|
||
const allItems = [...this.parentElement.querySelectorAll('.option-field-item')];
|
||
const draggedIndex = allItems.indexOf(draggedItem);
|
||
const targetIndex = allItems.indexOf(this);
|
||
|
||
if (draggedIndex < targetIndex) {
|
||
this.parentElement.insertBefore(draggedItem, this.nextSibling);
|
||
} else {
|
||
this.parentElement.insertBefore(draggedItem, this);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 컬럼 드래그 이벤트 추가
|
||
function addColumnDragEvents() {
|
||
const items = document.querySelectorAll('#userOptionsBody .option-field-item[draggable="true"]');
|
||
let draggedItem = null;
|
||
|
||
items.forEach(item => {
|
||
item.addEventListener('dragstart', function(e) {
|
||
draggedItem = this;
|
||
this.style.opacity = '0.5';
|
||
});
|
||
|
||
item.addEventListener('dragend', function(e) {
|
||
this.style.opacity = '1';
|
||
});
|
||
|
||
item.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
});
|
||
|
||
item.addEventListener('drop', function(e) {
|
||
e.preventDefault();
|
||
if (draggedItem !== this) {
|
||
const allItems = [...this.parentElement.querySelectorAll('.option-field-item')];
|
||
const draggedIndex = allItems.indexOf(draggedItem);
|
||
const targetIndex = allItems.indexOf(this);
|
||
|
||
if (draggedIndex < targetIndex) {
|
||
this.parentElement.insertBefore(draggedItem, this.nextSibling);
|
||
} else {
|
||
this.parentElement.insertBefore(draggedItem, this);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 토스트 메시지 표시
|
||
function showToast(message, icon = '✓', force = false) {
|
||
const toastEnabled = localStorage.getItem('toastEnabled');
|
||
|
||
// force가 true이거나 경고/정보 아이콘인 경우는 설정과 관계없이 표시
|
||
if (!force && icon === '✓' && toastEnabled === 'false') {
|
||
return; // 성공 메시지만 설정에 따라 숨김
|
||
}
|
||
|
||
const toast = document.getElementById('toastMessage');
|
||
const toastIcon = toast.querySelector('.toast-icon');
|
||
const toastText = toast.querySelector('.toast-text');
|
||
|
||
toastIcon.textContent = icon;
|
||
toastText.textContent = message;
|
||
|
||
toast.classList.add('show');
|
||
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
}, 3000);
|
||
}
|
||
|
||
// 사용자 옵션 저장
|
||
function saveUserOptions() {
|
||
console.log('🔥🔥🔥 saveUserOptions 함수 호출됨! 🔥🔥🔥');
|
||
|
||
const body = document.getElementById('userOptionsBody');
|
||
const items = body.querySelectorAll('.option-field-item');
|
||
|
||
console.log('📦 body 요소:', body);
|
||
console.log('📋 items 개수:', items.length);
|
||
|
||
// 현재 활성화된 탭 확인
|
||
const activeTab = document.querySelector('.user-options-tab.active');
|
||
const tabText = activeTab ? activeTab.textContent.trim() : '';
|
||
|
||
console.log('💾 저장 시작 - 활성 탭:', tabText, '아이템 수:', items.length);
|
||
|
||
if (tabText === '검색필드 설정') {
|
||
// 검색필드 설정 저장
|
||
const searchFields = [];
|
||
items.forEach((item, index) => {
|
||
const fieldId = item.getAttribute('data-field-id');
|
||
const checkbox = item.querySelector('.option-field-checkbox');
|
||
const widthInput = item.querySelector('.option-field-width');
|
||
const typeSelect = item.querySelector('.option-field-type');
|
||
const name = item.querySelector('.option-field-name').textContent;
|
||
|
||
console.log(` 필드 ${index}: ${name} (${fieldId}) - 체크: ${checkbox.checked}`);
|
||
|
||
searchFields.push({
|
||
id: fieldId,
|
||
name: name,
|
||
checked: checkbox.checked,
|
||
width: parseInt(widthInput.value),
|
||
type: typeSelect.value,
|
||
order: index
|
||
});
|
||
});
|
||
|
||
console.log('✅ 검색필드 저장:', searchFields);
|
||
localStorage.setItem('searchFieldsConfig', JSON.stringify(searchFields));
|
||
|
||
// 검색 섹션 다시 렌더링
|
||
applySearchFieldsConfig();
|
||
|
||
} else if (tabText === '컬럼 표시/숨기기') {
|
||
// 컬럼 설정 저장
|
||
const columns = [];
|
||
items.forEach((item, index) => {
|
||
const colId = item.getAttribute('data-col-id');
|
||
const checkbox = item.querySelector('.option-field-checkbox');
|
||
const widthInput = item.querySelector('.option-field-width');
|
||
const name = item.querySelector('.option-field-name').textContent;
|
||
|
||
columns.push({
|
||
id: colId,
|
||
name: name,
|
||
checked: checkbox.checked,
|
||
width: parseInt(widthInput.value),
|
||
order: index
|
||
});
|
||
});
|
||
|
||
localStorage.setItem('columnsConfig', JSON.stringify(columns));
|
||
|
||
// 테이블 다시 렌더링
|
||
applyColumnsConfig();
|
||
|
||
} else if (tabText === '기타옵션') {
|
||
// 그리드선 설정 적용
|
||
const gridLines = document.querySelector('input[name="gridLines"]:checked');
|
||
if (gridLines) {
|
||
const table = document.querySelector('.data-table');
|
||
if (table) {
|
||
if (gridLines.value === 'hide') {
|
||
table.classList.add('hide-grid');
|
||
} else {
|
||
table.classList.remove('hide-grid');
|
||
}
|
||
}
|
||
|
||
localStorage.setItem('gridLinesVisible', gridLines.value === 'show');
|
||
}
|
||
|
||
// 틀고정 설정 저장
|
||
const freezeCount = document.getElementById('freezeColumnCount');
|
||
if (freezeCount) {
|
||
localStorage.setItem('freezeColumnCount', freezeCount.value);
|
||
applyFreezeColumns(parseInt(freezeCount.value));
|
||
}
|
||
|
||
// 메시지창 설정 저장
|
||
const toastMessage = document.querySelector('input[name="toastMessage"]:checked');
|
||
if (toastMessage) {
|
||
localStorage.setItem('toastEnabled', toastMessage.value === 'show');
|
||
}
|
||
|
||
// 보기 모드 설정 저장
|
||
const viewMode = document.querySelector('input[name="viewMode"]:checked');
|
||
if (viewMode) {
|
||
localStorage.setItem('viewMode', viewMode.value);
|
||
applyViewMode(viewMode.value);
|
||
}
|
||
|
||
// 모달 자동 닫기 설정 저장
|
||
const autoCloseModal = document.querySelector('input[name="autoCloseModal"]:checked');
|
||
if (autoCloseModal) {
|
||
localStorage.setItem('autoCloseModal', autoCloseModal.value);
|
||
}
|
||
}
|
||
|
||
// 자동 닫기 설정 확인
|
||
const shouldAutoClose = localStorage.getItem('autoCloseModal');
|
||
if (shouldAutoClose === null || shouldAutoClose === 'true') {
|
||
closeUserOptionsModal();
|
||
}
|
||
|
||
showToast('사용자 옵션이 저장되었습니다', '✓');
|
||
}
|
||
|
||
// 검색필드 설정 적용
|
||
window.applySearchFieldsConfig = function applySearchFieldsConfig() {
|
||
const savedFields = getSearchFieldsConfig('itemInfo');
|
||
|
||
console.log('🔍 검색필드 설정 적용:', savedFields);
|
||
|
||
if (!savedFields) {
|
||
console.log('❌ 저장된 검색필드 설정이 없습니다.');
|
||
return;
|
||
}
|
||
|
||
// 체크된 필드만 필터링
|
||
const visibleFields = savedFields.filter(f => f.checked);
|
||
|
||
console.log('✅ 체크된 필드:', visibleFields.length, visibleFields);
|
||
|
||
let searchHtml = '<div class="search-section"><div class="search-row">';
|
||
|
||
// 검색 필드들을 감싸는 컨테이너 시작
|
||
searchHtml += '<div class="search-fields-container">';
|
||
|
||
// 필드 생성
|
||
visibleFields.forEach(field => {
|
||
if (field.type === 'select') {
|
||
searchHtml += `
|
||
<div class="search-field">
|
||
<select id="${field.id}" style="min-width: ${field.width}px;">
|
||
<option value="">${field.name}</option>
|
||
<option value="전체">전체</option>
|
||
</select>
|
||
</div>
|
||
`;
|
||
} else if (field.type === 'multi') {
|
||
searchHtml += `
|
||
<div class="search-field">
|
||
<input type="text" id="${field.id}" placeholder="${field.name} (다중검색)" style="min-width: ${field.width}px;">
|
||
</div>
|
||
`;
|
||
} else {
|
||
searchHtml += `
|
||
<div class="search-field">
|
||
<input type="text" id="${field.id}" placeholder="${field.name}" style="min-width: ${field.width}px;">
|
||
</div>
|
||
`;
|
||
}
|
||
});
|
||
|
||
// 검색/초기화 버튼들
|
||
searchHtml += `
|
||
<div class="search-buttons">
|
||
<button class="btn btn-primary" onclick="performSearch()">🔍 검색</button>
|
||
<button class="btn btn-secondary" onclick="resetSearch()">초기화</button>
|
||
</div>
|
||
`;
|
||
|
||
// 검색 필드 컨테이너 닫기
|
||
searchHtml += '</div>';
|
||
|
||
// 오른쪽 버튼들 (항상 맨 오른쪽에 고정)
|
||
searchHtml += `
|
||
<div class="search-right-buttons">
|
||
<button class="btn btn-secondary" onclick="openUserOptions()">⚙️ 사용자옵션</button>
|
||
<button class="btn btn-primary" onclick="openWebcamModal()">📷 사진촬영</button>
|
||
<button class="btn btn-success" onclick="downloadExcel()">📥 다운로드</button>
|
||
<button class="btn btn-success" onclick="uploadExcel()">📤 업로드</button>
|
||
</div>
|
||
</div></div>`;
|
||
|
||
console.log('📝 검색 섹션 HTML 생성 완료');
|
||
|
||
const searchSection = document.getElementById('searchSection');
|
||
if (searchSection) {
|
||
searchSection.innerHTML = searchHtml;
|
||
console.log('✅ 검색 섹션 업데이트 완료');
|
||
} else {
|
||
console.error('❌ searchSection 엘리먼트를 찾을 수 없습니다!');
|
||
}
|
||
}
|
||
|
||
// 컬럼 설정 적용
|
||
function applyColumnsConfig(forceTable = false) {
|
||
const savedColumns = JSON.parse(localStorage.getItem('columnsConfig') || 'null');
|
||
const viewMode = localStorage.getItem('viewMode') || 'table';
|
||
|
||
// 카드형이고 강제 테이블 전환이 아니면 카드형으로 렌더링
|
||
if (viewMode === 'card' && !forceTable) {
|
||
renderCardView();
|
||
return;
|
||
}
|
||
|
||
if (!savedColumns) {
|
||
initDataTable();
|
||
return;
|
||
}
|
||
|
||
// 체크된 컬럼만 필터링
|
||
const visibleColumns = savedColumns.filter(c => c.checked);
|
||
|
||
// 컬럼 매핑
|
||
const columnMap = {
|
||
'selected': { field: 'selected', label: '<input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">', align: 'center',
|
||
formatter: (value, row) => `<input type="checkbox" ${value ? 'checked' : ''} onclick="event.stopPropagation();" onchange="toggleSelect(${row.id})">` },
|
||
'status': { field: 'status', label: '상태', align: 'center',
|
||
formatter: (value) => {
|
||
const badgeMap = {
|
||
'정상': { text: '정상', class: 'badge-success' },
|
||
'품절': { text: '품절', class: 'badge-danger' },
|
||
'대기': { text: '대기', class: 'badge-warning' }
|
||
};
|
||
return formatters.badge(value, badgeMap);
|
||
}
|
||
},
|
||
'itemCode': { field: 'itemCode', label: '품번코드' },
|
||
'itemName': { field: 'itemName', label: '품명', formatter: (value) => `<strong>${value}</strong>` },
|
||
'spec': { field: 'spec', label: '규격' },
|
||
'material': { field: 'material', label: '재질' },
|
||
'stockUnit': { field: 'stockUnit', label: '재고단위', align: 'center' },
|
||
'weight': { field: 'weight', label: '중량', align: 'right', formatter: (value) => value.toLocaleString() },
|
||
'weightUnit': { field: 'weightUnit', label: '단위', align: 'center' },
|
||
'image': { field: 'image', label: '이미지', align: 'center' },
|
||
'category': { field: 'category', label: '구분', align: 'center',
|
||
formatter: (value) => {
|
||
const badgeMap = {
|
||
'원자재': { text: '원자재', class: 'badge-primary' },
|
||
'중간재': { text: '중간재', class: 'badge-warning' },
|
||
'완제품': { text: '완제품', class: 'badge-success' }
|
||
};
|
||
return formatters.badge(value, badgeMap);
|
||
}
|
||
},
|
||
'type': { field: 'type', label: '유형', align: 'center' },
|
||
'memo': { field: 'memo', label: '메모' },
|
||
'createdBy': { field: 'createdBy', label: '등록자' },
|
||
'createdDate': { field: 'createdDate', label: '등록일', align: 'center' },
|
||
'modifiedDate': { field: 'modifiedDate', label: '최종수정일', align: 'center' }
|
||
};
|
||
|
||
// 컬럼 구성
|
||
const columns = visibleColumns.map(col => {
|
||
const baseCol = columnMap[col.id] || { field: col.id, label: col.name };
|
||
return { ...baseCol, width: col.width + 'px' };
|
||
});
|
||
|
||
// 테이블 재생성
|
||
const tableHtml = createDataTable({
|
||
data: itemsData,
|
||
columns: columns
|
||
});
|
||
|
||
document.getElementById('dataTableContainer').innerHTML = tableHtml;
|
||
updateTotalCount();
|
||
addColumnResizeHandles();
|
||
addRowClickEvents();
|
||
}
|
||
|
||
// 보기 모드 적용
|
||
function applyViewMode(mode) {
|
||
const container = document.getElementById('dataTableContainer');
|
||
if (!container) return;
|
||
|
||
if (mode === 'card') {
|
||
renderCardView();
|
||
} else {
|
||
renderTableView();
|
||
}
|
||
}
|
||
|
||
// 카드형 렌더링
|
||
function renderCardView() {
|
||
const container = document.getElementById('dataTableContainer');
|
||
|
||
// 그룹화가 있는 경우
|
||
if (groupByFields.length > 0) {
|
||
renderGroupedCardView();
|
||
return;
|
||
}
|
||
|
||
// 컬럼 설정 가져오기
|
||
const savedColumns = JSON.parse(localStorage.getItem('columnsConfig') || 'null');
|
||
const defaultColumns = [
|
||
{ id: 'selected', name: '선택', checked: true, width: 60 },
|
||
{ id: 'status', name: '상태', checked: true, width: 80 },
|
||
{ id: 'itemCode', name: '품번코드', checked: true, width: 140 },
|
||
{ id: 'itemName', name: '품명', checked: true, width: 200 },
|
||
{ id: 'spec', name: '규격', checked: true, width: 150 },
|
||
{ id: 'material', name: '재질', checked: true, width: 180 },
|
||
{ id: 'stockUnit', name: '재고단위', checked: true, width: 100 },
|
||
{ id: 'weight', name: '중량', checked: true, width: 80 },
|
||
{ id: 'weightUnit', name: '단위', checked: true, width: 80 },
|
||
{ id: 'image', name: '이미지', checked: true, width: 80 },
|
||
{ id: 'category', name: '구분', checked: true, width: 100 },
|
||
{ id: 'type', name: '유형', checked: true, width: 100 },
|
||
{ id: 'memo', name: '메모', checked: true, width: 180 },
|
||
{ id: 'createdBy', name: '등록자', checked: true, width: 100 },
|
||
{ id: 'createdDate', name: '등록일', checked: true, width: 120 },
|
||
{ id: 'modifiedDate', name: '최종수정일', checked: true, width: 120 }
|
||
];
|
||
|
||
const columns = savedColumns || defaultColumns;
|
||
const visibleColumns = columns.filter(c => c.checked);
|
||
|
||
// 그리드선 설정 확인
|
||
const gridLines = localStorage.getItem('gridLines') || 'show';
|
||
const borderStyle = gridLines === 'hide' ? 'border: none;' : 'border: 1px solid #e5e7eb;';
|
||
|
||
// 카드형: 품목별로 가로로 길게 표시
|
||
let html = '<div style="padding: 10px; display: flex; flex-direction: column; gap: 10px;">';
|
||
|
||
itemsData.forEach(item => {
|
||
// 상태 뱃지 색상
|
||
const statusBadge = item.status === '정상' ? 'badge-success' :
|
||
item.status === '품절' ? 'badge-danger' : 'badge-warning';
|
||
|
||
// 구분(category) 뱃지 색상
|
||
const categoryBadge = item.category === '원자재' ? 'badge-primary' :
|
||
item.category === '중간재' ? 'badge-warning' : 'badge-success';
|
||
|
||
const cardBorderStyle = gridLines === 'hide' ?
|
||
`border: 2px solid ${item.selected ? '#3b82f6' : 'transparent'};` :
|
||
`border: 2px solid ${item.selected ? '#3b82f6' : '#e5e7eb'};`;
|
||
|
||
html += `
|
||
<div class="card-item" style="background: white; ${cardBorderStyle} border-radius: 8px; padding: 16px; cursor: pointer; transition: all 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.1); display: flex; flex-wrap: wrap; gap: 20px; align-items: center;"
|
||
onclick="toggleCardSelect(${item.id}, event)"
|
||
onmouseover="this.style.boxShadow='0 4px 12px rgba(0,0,0,0.15)'"
|
||
onmouseout="this.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)'">
|
||
`;
|
||
|
||
visibleColumns.forEach(col => {
|
||
const colWidth = parseInt(col.width) || 100;
|
||
|
||
if (col.id === 'selected') {
|
||
html += `
|
||
<div style="display: flex; align-items: center; gap: 6px; width: ${colWidth}px;">
|
||
<input type="checkbox" ${item.selected ? 'checked' : ''} onclick="event.stopPropagation();"
|
||
style="width: 18px; height: 18px; cursor: pointer; pointer-events: none;">
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'status') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span class="badge ${statusBadge}" style="font-size: 12px; padding: 4px 10px;">${item.status}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'itemCode') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.itemCode}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'itemName') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 14px; font-weight: 600; color: #1f2937;">${item.itemName}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'spec') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.spec}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'material') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.material}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'stockUnit') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.stockUnit}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'weight') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af; text-align: right;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937; font-weight: 600; text-align: right;">${item.weight.toLocaleString()}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'weightUnit') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.weightUnit}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'image') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 32px;">${item.image}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'category') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span class="badge ${categoryBadge}" style="font-size: 12px; padding: 4px 10px;">${item.category}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'type') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.type}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'memo') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #6b7280;">${item.memo || '-'}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'createdBy') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #1f2937;">${item.createdBy}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'createdDate') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #6b7280;">${item.createdDate}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'modifiedDate') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #6b7280;">${item.modifiedDate}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
});
|
||
|
||
html += `</div>`;
|
||
});
|
||
|
||
html += '</div>';
|
||
container.innerHTML = html;
|
||
updateSelectAllCheckbox();
|
||
}
|
||
|
||
// 그룹화된 카드형 렌더링
|
||
function renderGroupedCardView() {
|
||
const container = document.getElementById('dataTableContainer');
|
||
|
||
// 컬럼 설정 가져오기
|
||
const savedColumns = JSON.parse(localStorage.getItem('columnsConfig') || 'null');
|
||
const defaultColumns = [
|
||
{ id: 'selected', name: '선택', checked: true, width: 60 },
|
||
{ id: 'status', name: '상태', checked: true, width: 80 },
|
||
{ id: 'itemCode', name: '품번코드', checked: true, width: 140 },
|
||
{ id: 'itemName', name: '품명', checked: true, width: 200 },
|
||
{ id: 'spec', name: '규격', checked: true, width: 150 },
|
||
{ id: 'material', name: '재질', checked: true, width: 180 },
|
||
{ id: 'stockUnit', name: '재고단위', checked: true, width: 100 },
|
||
{ id: 'weight', name: '중량', checked: true, width: 80 },
|
||
{ id: 'weightUnit', name: '단위', checked: true, width: 80 },
|
||
{ id: 'image', name: '이미지', checked: true, width: 80 },
|
||
{ id: 'category', name: '구분', checked: true, width: 100 },
|
||
{ id: 'type', name: '유형', checked: true, width: 100 },
|
||
{ id: 'memo', name: '메모', checked: true, width: 180 },
|
||
{ id: 'createdBy', name: '등록자', checked: true, width: 100 },
|
||
{ id: 'createdDate', name: '등록일', checked: true, width: 120 },
|
||
{ id: 'modifiedDate', name: '최종수정일', checked: true, width: 120 }
|
||
];
|
||
|
||
const columns = savedColumns || defaultColumns;
|
||
const visibleColumns = columns.filter(c => c.checked);
|
||
|
||
// 그룹화된 데이터 생성
|
||
const groupedData = createGroupedData(itemsData, window.groupByFields || []);
|
||
|
||
// 그리드선 설정 확인
|
||
const gridLines = localStorage.getItem('gridLines') || 'show';
|
||
|
||
let html = '<div style="padding: 10px; display: flex; flex-direction: column; gap: 15px;">';
|
||
|
||
let groupIndex = 0;
|
||
for (const [groupKey, groupItems] of Object.entries(groupedData)) {
|
||
// 그룹 내 전체 선택 여부 확인
|
||
const allSelected = groupItems.every(item => item.selected);
|
||
|
||
// 그룹 헤더
|
||
html += `
|
||
<div style="background: #f3f4f6; border-radius: 8px; padding: 12px; user-select: none;">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<input type="checkbox"
|
||
${allSelected ? 'checked' : ''}
|
||
onclick="toggleCardGroupSelect(${groupIndex})"
|
||
id="cardGroupCheckbox${groupIndex}"
|
||
style="opacity: 1;">
|
||
<span id="cardGroupToggle${groupIndex}"
|
||
style="font-size: 12px; transition: transform 0.2s; cursor: pointer;"
|
||
onclick="toggleCardGroup(${groupIndex})">▼</span>
|
||
<span style="font-weight: 700; color: #374151; flex: 1; cursor: pointer;"
|
||
onclick="toggleCardGroup(${groupIndex})">${groupKey}</span>
|
||
<span style="color: #3b82f6; font-weight: 700; cursor: pointer;"
|
||
onclick="toggleCardGroup(${groupIndex})">(${groupItems.length}개)</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 그룹 카드들
|
||
html += `<div id="cardGroupItems${groupIndex}" style="display: flex; flex-direction: column; gap: 10px;">`;
|
||
|
||
groupItems.forEach(item => {
|
||
// 상태 뱃지 색상
|
||
const statusBadge = item.status === '정상' ? 'badge-success' :
|
||
item.status === '품절' ? 'badge-danger' : 'badge-warning';
|
||
|
||
// 구분(category) 뱃지 색상
|
||
const categoryBadge = item.category === '원자재' ? 'badge-primary' :
|
||
item.category === '중간재' ? 'badge-warning' : 'badge-success';
|
||
|
||
const cardBorderStyle = gridLines === 'hide' ?
|
||
`border: 2px solid ${item.selected ? '#3b82f6' : 'transparent'};` :
|
||
`border: 2px solid ${item.selected ? '#3b82f6' : '#e5e7eb'};`;
|
||
|
||
html += `
|
||
<div class="card-item" style="background: white; ${cardBorderStyle} border-radius: 8px; padding: 16px; cursor: pointer; transition: all 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.1); display: flex; flex-wrap: wrap; gap: 20px; align-items: center;"
|
||
onclick="toggleCardSelect(${item.id}, event)"
|
||
onmouseover="this.style.boxShadow='0 4px 12px rgba(0,0,0,0.15)'"
|
||
onmouseout="this.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)'">
|
||
`;
|
||
|
||
visibleColumns.forEach(col => {
|
||
const colWidth = parseInt(col.width) || 100;
|
||
|
||
if (col.id === 'selected') {
|
||
html += `
|
||
<div style="display: flex; align-items: center; gap: 6px; width: ${colWidth}px;">
|
||
<input type="checkbox" ${item.selected ? 'checked' : ''} onclick="event.stopPropagation();"
|
||
style="width: 18px; height: 18px; cursor: pointer; pointer-events: none;">
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'status') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span class="badge ${statusBadge}" style="font-size: 12px; padding: 4px 10px;">${item.status}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'itemCode') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.itemCode}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'itemName') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 14px; font-weight: 600; color: #1f2937;">${item.itemName}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'spec') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.spec}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'material') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.material}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'stockUnit') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.stockUnit}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'weight') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af; text-align: right;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937; font-weight: 600; text-align: right;">${item.weight.toLocaleString()}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'weightUnit') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.weightUnit}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'image') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 32px;">${item.image}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'category') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span class="badge ${categoryBadge}" style="font-size: 12px; padding: 4px 10px;">${item.category}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'type') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 13px; color: #1f2937;">${item.type}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'memo') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #6b7280;">${item.memo || '-'}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'createdBy') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #1f2937;">${item.createdBy}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'createdDate') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #6b7280;">${item.createdDate}</span>
|
||
</div>
|
||
`;
|
||
} else if (col.id === 'modifiedDate') {
|
||
html += `
|
||
<div style="display: flex; flex-direction: column; gap: 4px; align-items: center; width: ${colWidth}px;">
|
||
<span style="font-size: 11px; font-weight: 600; color: #9ca3af;">${col.name}</span>
|
||
<span style="font-size: 12px; color: #6b7280;">${item.modifiedDate}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
});
|
||
|
||
html += `</div>`;
|
||
});
|
||
|
||
html += '</div>';
|
||
groupIndex++;
|
||
}
|
||
|
||
html += '</div>';
|
||
container.innerHTML = html;
|
||
updateSelectAllCheckbox();
|
||
}
|
||
|
||
// 카드형 그룹 토글
|
||
function toggleCardGroup(groupIndex) {
|
||
const groupItems = document.getElementById(`cardGroupItems${groupIndex}`);
|
||
const toggle = document.getElementById(`cardGroupToggle${groupIndex}`);
|
||
|
||
if (groupItems && toggle) {
|
||
if (groupItems.style.display === 'none') {
|
||
groupItems.style.display = 'flex';
|
||
toggle.style.transform = 'rotate(0deg)';
|
||
} else {
|
||
groupItems.style.display = 'none';
|
||
toggle.style.transform = 'rotate(-90deg)';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 카드형 그룹별 전체 선택/해제
|
||
function toggleCardGroupSelect(groupIndex) {
|
||
const checkbox = document.getElementById(`cardGroupCheckbox${groupIndex}`);
|
||
if (!checkbox) return;
|
||
|
||
const checked = checkbox.checked;
|
||
|
||
// 현재 그룹화된 데이터에서 해당 그룹의 아이템 찾기
|
||
const groupedData = createGroupedData(itemsData, window.groupByFields || []);
|
||
const groupKeys = Object.keys(groupedData);
|
||
|
||
if (groupIndex < groupKeys.length) {
|
||
const groupKey = groupKeys[groupIndex];
|
||
const groupItems = groupedData[groupKey];
|
||
|
||
// 해당 그룹의 모든 아이템 선택/해제
|
||
groupItems.forEach(item => {
|
||
item.selected = checked;
|
||
});
|
||
|
||
// 화면 다시 렌더링
|
||
refreshDataView();
|
||
}
|
||
}
|
||
|
||
// 테이블형 렌더링
|
||
function renderTableView() {
|
||
const columnsConfig = localStorage.getItem('columnsConfig');
|
||
if (columnsConfig) {
|
||
applyColumnsConfig();
|
||
} else {
|
||
initDataTable();
|
||
}
|
||
}
|
||
|
||
// 틀고정 적용
|
||
function applyFreezeColumns(count) {
|
||
if (count <= 0) return;
|
||
|
||
const table = document.querySelector('.data-table');
|
||
if (!table) return;
|
||
|
||
// 모든 고정 스타일 제거
|
||
table.querySelectorAll('th, td').forEach(cell => {
|
||
cell.style.position = '';
|
||
cell.style.left = '';
|
||
cell.style.zIndex = '';
|
||
cell.style.background = '';
|
||
});
|
||
|
||
// 새로운 고정 적용
|
||
const headers = table.querySelectorAll('thead th');
|
||
let leftOffset = 0;
|
||
|
||
for (let i = 0; i < Math.min(count, headers.length); i++) {
|
||
const th = headers[i];
|
||
th.style.position = 'sticky';
|
||
th.style.left = leftOffset + 'px';
|
||
th.style.zIndex = '10';
|
||
th.style.background = '#f9fafb';
|
||
|
||
// 같은 인덱스의 모든 td에도 적용
|
||
const rows = table.querySelectorAll('tbody tr');
|
||
rows.forEach(row => {
|
||
const td = row.children[i];
|
||
if (td) {
|
||
td.style.position = 'sticky';
|
||
td.style.left = leftOffset + 'px';
|
||
td.style.zIndex = '9';
|
||
td.style.background = 'white';
|
||
}
|
||
});
|
||
|
||
leftOffset += th.offsetWidth;
|
||
}
|
||
}
|
||
|
||
// 저장된 설정 불러오기
|
||
function loadUserSettings() {
|
||
// 그리드선 설정 불러오기
|
||
const gridLinesVisible = localStorage.getItem('gridLinesVisible');
|
||
if (gridLinesVisible !== null) {
|
||
const table = document.querySelector('.data-table');
|
||
if (table && gridLinesVisible === 'false') {
|
||
table.classList.add('hide-grid');
|
||
}
|
||
}
|
||
|
||
// 검색필드 설정 적용
|
||
const searchFieldsConfig = localStorage.getItem('searchFieldsConfig');
|
||
if (searchFieldsConfig) {
|
||
applySearchFieldsConfig();
|
||
}
|
||
|
||
// 컬럼 설정 적용
|
||
const columnsConfig = localStorage.getItem('columnsConfig');
|
||
if (columnsConfig) {
|
||
applyColumnsConfig();
|
||
}
|
||
|
||
// 틀고정 설정 적용
|
||
const freezeCount = localStorage.getItem('freezeColumnCount');
|
||
if (freezeCount && parseInt(freezeCount) > 0) {
|
||
setTimeout(() => applyFreezeColumns(parseInt(freezeCount)), 100);
|
||
}
|
||
|
||
// 보기 모드 설정 적용
|
||
const viewMode = localStorage.getItem('viewMode');
|
||
if (viewMode) {
|
||
setTimeout(() => applyViewMode(viewMode), 150);
|
||
}
|
||
}
|
||
|
||
// localStorage 초기화 (개발용 - 순서 수정 반영)
|
||
function resetAllSettings() {
|
||
localStorage.removeItem('columnsConfig');
|
||
localStorage.removeItem('searchFieldsConfig');
|
||
localStorage.removeItem('freezeColumnCount');
|
||
localStorage.removeItem('gridLines');
|
||
localStorage.removeItem('toastEnabled');
|
||
localStorage.removeItem('viewMode');
|
||
localStorage.removeItem('autoCloseModal');
|
||
location.reload();
|
||
}
|
||
|
||
// 초기화
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 개발 모드: 컬럼 구조가 완전히 변경되었으므로 기존 설정 초기화
|
||
const needsReset = localStorage.getItem('columnsVersion');
|
||
if (needsReset !== 'v6') {
|
||
// v5 -> v6: Group By 컴포넌트 전환
|
||
migrateToComponentSettings();
|
||
localStorage.setItem('columnsVersion', 'v6');
|
||
}
|
||
|
||
// Group By 컴포넌트 초기화 (기존 HTML 요소 활용)
|
||
groupByComponent = new GroupByComponent({
|
||
selectId: 'groupByField',
|
||
tagsId: 'groupByTags',
|
||
fields: {
|
||
'status': '상태',
|
||
'category': '구분',
|
||
'type': '유형',
|
||
'stockUnit': '재고단위',
|
||
'createdBy': '등록자'
|
||
},
|
||
onGroupChange: () => {
|
||
// 전역 변수 동기화 후 검색
|
||
window.groupByFields = groupByComponent.getGroupByFields();
|
||
console.log('GroupBy 변경됨:', window.groupByFields);
|
||
refreshDataView();
|
||
}
|
||
});
|
||
|
||
// GroupBy 전역 함수 연결
|
||
window.addGroupBy = function() {
|
||
groupByComponent.addGroupBy();
|
||
};
|
||
|
||
window.removeGroupBy = function(field) {
|
||
groupByComponent.removeGroupBy(field);
|
||
};
|
||
|
||
// 저장된 그룹화 옵션 복원
|
||
setTimeout(() => {
|
||
restoreGroupingOptions_ItemInfo();
|
||
}, 300);
|
||
|
||
// 사용자옵션 컴포넌트 초기화
|
||
initializeUserOptionsComponent();
|
||
|
||
// 사용자 설정 먼저 로드
|
||
loadUserSettings();
|
||
|
||
// 검색필드 설정이 없으면 기본 검색 섹션 초기화
|
||
const searchFieldsConfig = localStorage.getItem('itemInfo_searchFieldsConfig');
|
||
if (!searchFieldsConfig) {
|
||
initSearchSection();
|
||
}
|
||
|
||
// 테이블 초기화
|
||
initDataTable();
|
||
|
||
// 엑셀 업로드 모달 초기화
|
||
createExcelUploadModal();
|
||
});
|
||
|
||
// 기존 설정을 컴포넌트 형식으로 마이그레이션
|
||
function migrateToComponentSettings() {
|
||
// 검색필드 설정 마이그레이션
|
||
const oldSearchFields = localStorage.getItem('searchFieldsConfig');
|
||
if (oldSearchFields) {
|
||
localStorage.setItem('itemInfo_searchFieldsConfig', oldSearchFields);
|
||
}
|
||
|
||
// 컬럼 설정 마이그레이션
|
||
const oldColumns = localStorage.getItem('columnsConfig');
|
||
if (oldColumns) {
|
||
localStorage.setItem('itemInfo_columnsConfig', oldColumns);
|
||
}
|
||
|
||
// 틀고정 설정 마이그레이션
|
||
const oldFreezeCount = localStorage.getItem('freezeColumnCount');
|
||
if (oldFreezeCount) {
|
||
localStorage.setItem('itemInfo_freezeColumnCount', oldFreezeCount);
|
||
}
|
||
|
||
// 그리드선 설정 마이그레이션
|
||
const oldGridLines = localStorage.getItem('gridLinesVisible');
|
||
if (oldGridLines !== null) {
|
||
localStorage.setItem('itemInfo_gridLinesVisible', oldGridLines);
|
||
}
|
||
|
||
// 메시지창 설정 마이그레이션
|
||
const oldToastEnabled = localStorage.getItem('toastEnabled');
|
||
if (oldToastEnabled !== null) {
|
||
localStorage.setItem('itemInfo_toastEnabled', oldToastEnabled);
|
||
}
|
||
|
||
// 보기 모드 설정 마이그레이션
|
||
const oldViewMode = localStorage.getItem('viewMode');
|
||
if (oldViewMode) {
|
||
localStorage.setItem('itemInfo_viewMode', oldViewMode);
|
||
}
|
||
|
||
// 모달 자동 닫기 설정 마이그레이션
|
||
const oldAutoClose = localStorage.getItem('autoCloseModal');
|
||
if (oldAutoClose !== null) {
|
||
localStorage.setItem('itemInfo_autoCloseModal', oldAutoClose);
|
||
}
|
||
}
|
||
|
||
// 사용자옵션 컴포넌트 초기화
|
||
// ========== 저장된 그룹화 옵션 복원 ==========
|
||
function restoreGroupingOptions_ItemInfo() {
|
||
if (typeof getGroupByColumn === 'function') {
|
||
const savedColumn = getGroupByColumn('itemInfo');
|
||
|
||
console.log('💾 [품목정보] 저장된 그룹화 옵션:', { savedColumn });
|
||
|
||
if (savedColumn && groupByComponent) {
|
||
groupByComponent.addGrouping(savedColumn);
|
||
window.groupByFields = groupByComponent.getGroupByFields();
|
||
refreshDataView();
|
||
console.log('✅ [품목정보] 그룹화 옵션 복원 완료');
|
||
}
|
||
}
|
||
}
|
||
|
||
function initializeUserOptionsComponent() {
|
||
const modalHtml = createUserOptionsModal({
|
||
pageId: 'itemInfo',
|
||
enableGrouping: true,
|
||
groupingColumns: [
|
||
{ key: 'status', label: '상태' },
|
||
{ key: 'category', label: '구분' },
|
||
{ key: 'type', label: '유형' },
|
||
{ key: 'stockUnit', label: '재고단위' },
|
||
{ key: 'createdBy', label: '등록자' }
|
||
],
|
||
searchFields: [
|
||
{ id: 'status', label: '상태', type: 'select', width: 120 },
|
||
{ id: 'itemCode', label: '품번코드', type: 'text', width: 150 },
|
||
{ id: 'itemName', label: '품명', type: 'text', width: 200 },
|
||
{ id: 'spec', label: '규격', type: 'text', width: 150 },
|
||
{ id: 'material', label: '재질', type: 'text', width: 180 },
|
||
{ id: 'stockUnit', label: '재고단위', type: 'select', width: 100 },
|
||
{ id: 'weight', label: '중량', type: 'text', width: 100 },
|
||
{ id: 'weightUnit', label: '단위', type: 'select', width: 100 },
|
||
{ id: 'category', label: '구분', type: 'select', width: 100 },
|
||
{ id: 'type', label: '유형', type: 'select', width: 120 },
|
||
{ id: 'memo', label: '메모', type: 'text', width: 180 },
|
||
{ id: 'createdBy', label: '등록자', type: 'text', width: 100 },
|
||
{ id: 'createdDate', label: '등록일', type: 'text', width: 120 },
|
||
{ id: 'modifiedDate', label: '최종수정일', type: 'text', width: 120 }
|
||
],
|
||
columns: [
|
||
{ id: 'selected', label: '선택', width: 60 },
|
||
{ id: 'status', label: '상태', width: 80 },
|
||
{ id: 'itemCode', label: '품번코드', width: 140 },
|
||
{ id: 'itemName', label: '품명', width: 200 },
|
||
{ id: 'spec', label: '규격', width: 150 },
|
||
{ id: 'material', label: '재질', width: 180 },
|
||
{ id: 'stockUnit', label: '재고단위', width: 100 },
|
||
{ id: 'weight', label: '중량', width: 80 },
|
||
{ id: 'weightUnit', label: '단위', width: 80 },
|
||
{ id: 'image', label: '이미지', width: 80 },
|
||
{ id: 'category', label: '구분', width: 100 },
|
||
{ id: 'type', label: '유형', width: 100 },
|
||
{ id: 'memo', label: '메모', width: 180 },
|
||
{ id: 'createdBy', label: '등록자', width: 100 },
|
||
{ id: 'createdDate', label: '등록일', width: 120 },
|
||
{ id: 'modifiedDate', label: '최종수정일', width: 120 }
|
||
],
|
||
enableViewMode: true, // 보기 모드 활성화
|
||
enableFreezeColumns: true, // 틀고정 활성화
|
||
enableGridLines: true, // 그리드선 활성화
|
||
onSave: function() {
|
||
console.log('품목정보 사용자 옵션 저장됨');
|
||
applySearchFieldsConfig();
|
||
applyColumnsConfig();
|
||
// 저장된 그룹화 옵션 즉시 적용
|
||
restoreGroupingOptions_ItemInfo();
|
||
applyAllSettings();
|
||
}
|
||
});
|
||
|
||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||
}
|
||
|
||
// 모든 설정 적용
|
||
function applyAllSettings() {
|
||
// 그리드선 설정
|
||
const gridLinesVisible = getGridLinesVisible('itemInfo');
|
||
const table = document.querySelector('.data-table');
|
||
if (table) {
|
||
if (gridLinesVisible) {
|
||
table.classList.remove('hide-grid');
|
||
} else {
|
||
table.classList.add('hide-grid');
|
||
}
|
||
}
|
||
|
||
// 틀고정 설정
|
||
const freezeCount = getFreezeColumnCount('itemInfo');
|
||
applyFreezeColumns(freezeCount);
|
||
|
||
// 보기 모드 설정
|
||
const viewMode = getViewMode('itemInfo');
|
||
applyViewMode(viewMode);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|