Files
sam-kd/shutterbox/list.php
hskwon aca1767eb9 초기 커밋: 5130 레거시 시스템
- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경
- DB 연결 하드코딩 → .env 기반으로 변경
- MySQL strict mode DATE 오류 수정
2025-12-10 20:14:31 +09:00

3054 lines
143 KiB
PHP

<?php
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
if (!isset($_SESSION["level"]) || $_SESSION["level"] > 5) {
sleep(1);
header("Location:" . $WebSite . "login/login_form.php");
exit;
}
include $_SERVER['DOCUMENT_ROOT'] . '/load_header.php';
$title_message = '셔터박스 생성 관리';
?>
<link href="../css/bending.css?v=<?php echo time(); ?>" rel="stylesheet"> <!-- 절곡관련 (가이드레일,케이스,하단마감재) -->
<title> <?=$title_message?> </title>
</head>
<body>
<?php
// 메뉴를 표현할지 판단하는 header
$header = $_REQUEST['header'] ?? '';
$exit_direction_search = $_REQUEST['exit_direction_search'] ?? '';
$model_name_search = $_REQUEST['model_name_search'] ?? '';
$model_UA_search = $_REQUEST['model_UA_search'] ?? '';
$firstitem_search = $_REQUEST['firstitem_search'] ?? '';
$search_keyword_search = $_REQUEST['search_keyword_search'] ?? '';
// 헤더 파일 로드 (필요시)
if ($header === 'header') {
require_once($_SERVER['DOCUMENT_ROOT'] . '/myheader.php');
}
function checkNull($strtmp) {
return ($strtmp !== null && trim($strtmp) !== '');
}
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
$mode = isset($_REQUEST["mode"]) ? $_REQUEST["mode"] : '';
$tablename = 'shutterbox';
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
$pdo = db_connect();
// 날짜 관련
$today = date("Y-m-d");
$currentDate = date("Y-m-d");
// fromdate, todate 초기화
if (!isset($fromdate) || $fromdate === "" || $fromdate === null ||
!isset($todate) || $todate === "" || $todate === null) {
$fromdate = date("Y-m-d", strtotime("2024-01-01"));
$todate = $currentDate; // 현재 날짜
$Transtodate = $todate;
} else {
$Transtodate = $todate;
}
// 기본 ORDER BY
$orderby = " ORDER BY registration_date DESC, num DESC ";
$SettingDate = " registration_date ";
// WHERE 조건을 담을 배열
$conditions = [];
$bindParams = [];
// 삭제되지 않은 데이터만
$conditions[] = " (is_deleted IS NULL OR is_deleted = '0' OR is_deleted = '' ) ";
// 날짜 범위 필터
$conditions[] = "$SettingDate BETWEEN :fromdate AND :todate";
$bindParams[':fromdate'] = $fromdate;
$bindParams[':todate'] = $Transtodate;
// exit_direction_search 필터
if (!empty($exit_direction_search)) {
$conditions[] = "exit_direction = :exit_direction_search";
$bindParams[':exit_direction_search'] = $exit_direction_search;
}
// SHOW COLUMNS를 이용해 테이블 컬럼 목록 조회
$columnSql = "SHOW COLUMNS FROM {$DB}.{$tablename}";
$columnQuery = $pdo->query($columnSql);
$columns = $columnQuery->fetchAll(PDO::FETCH_COLUMN);
// 검색어 필터
if (!empty($search)) {
// 공백 제거(원한다면 제거하지 않아도 됨)
$searchTrimmed = str_replace(' ', '', $search);
$searchConditions = [];
foreach ($columns as $i => $col) {
// 컬럼마다 고유 파라미터 이름(:search0, :search1 등)을 생성
$paramName = ":search{$i}";
$searchConditions[] = "$col LIKE $paramName";
$bindParams[$paramName] = "%{$searchTrimmed}%";
}
if (!empty($searchConditions)) {
// 여러 컬럼 중 하나라도 일치하면 OR로 연결
$conditions[] = "(" . implode(" OR ", $searchConditions) . ")";
}
}
// 최종 SQL 구성
$sql = "SELECT * FROM {$DB}.{$tablename}";
if (!empty($conditions)) {
$sql .= " WHERE " . implode(" AND ", $conditions);
}
$sql .= $orderby;
try {
$stmt = $pdo->prepare($sql);
$stmt->execute($bindParams);
$total_row = $stmt->rowCount();
?>
<form id="board_form" name="board_form" method="post" enctype="multipart/form-data">
<input type="hidden" id="mode" name="mode" value="<?=$mode?>">
<input type="hidden" id="num" name="num">
<input type="hidden" id="tablename" name="tablename" value="<?=$tablename?>">
<input type="hidden" id="header" name="header" value="<?=$header?>">
<input type="hidden" id="author" name="author" value="<?=$_SESSION['name']?>">
<!-- 절곡 부품 검색 Modal -->
<div id="bendingSearchModal" class="modal fade" tabindex="-1" style="z-index: 999;">
<div class="modal-dialog modal-dialog-scrollable modal-full" >
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">절곡 부품 검색</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-12">
<div class="d-flex flex-wrap gap-2 align-items-center mb-3" style="flex-wrap: wrap;">
<!-- 대분류 (스크린/철재) 라디오 버튼 -->
<div class="mx-2">
<div class="btn-group" role="group" aria-label="대분류 선택">
<?php
// 대분류 목록 가져오기
$sqlL1 = "SELECT DISTINCT item_sep FROM {$DB}.bending WHERE item_sep IS NOT NULL AND item_sep <> '' ORDER BY item_sep ASC";
$stmhL1 = $pdo->prepare($sqlL1);
$stmhL1->execute();
$l1_list = $stmhL1->fetchAll(PDO::FETCH_COLUMN);
// 전체 옵션 추가
echo '<input type="radio" class="btn-check" name="searchItem" id="searchItem_all" value="" checked>';
echo '<label class="btn btn-outline-secondary btn-sm" for="searchItem_all">전체</label>';
foreach ($l1_list as $item) {
echo '<input type="radio" class="btn-check" name="searchItem" id="searchItem_' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '" value="' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '">';
echo '<label class="btn btn-outline-primary btn-sm" for="searchItem_' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '">' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '</label>';
}
?>
</div>
</div>
<!-- 인정/비인정 라디오 버튼 -->
<div class="mx-2">
<div class="btn-group" role="group" aria-label="인정/비인정 선택">
<?php
$UA_list = ['인정', '비인정'];
// 전체 옵션 추가
echo '<input type="radio" class="btn-check" name="searchUA" id="searchUA_all" value="" checked>';
echo '<label class="btn btn-outline-secondary btn-sm" for="searchUA_all">전체</label>';
foreach ($UA_list as $item) {
echo '<input type="radio" class="btn-check " name="searchUA" id="searchUA_' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '" value="' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '">';
echo '<label class="btn btn-outline-success btn-sm" for="searchUA_' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '">' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '</label>';
}
?>
</div>
</div>
<!-- 중분류 (절곡물 분류) -->
<select id="searchBendingCategory" name="searchBendingCategory" class="form-select" style="width: auto; font-size: 0.7rem;">
<option value="">(중분류)</option>
<?php
// 중분류 목록 가져오기
$sqlL3 = "SELECT DISTINCT item_bending FROM {$DB}.bending WHERE item_bending IS NOT NULL AND is_deleted IS NULL AND item_bending <> '' ORDER BY item_bending ASC";
$stmhL3 = $pdo->prepare($sqlL3);
$stmhL3->execute();
$l3_list = $stmhL3->fetchAll(PDO::FETCH_COLUMN);
foreach($l3_list as $itemVal): ?>
<option value="<?= htmlspecialchars($itemVal, ENT_QUOTES, 'UTF-8') ?>" <?= ($itemVal === '가이드레일') ? 'selected' : '' ?>>
<?= htmlspecialchars($itemVal, ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</select>
<!-- 품명 -->
<select id="searchName" name="searchName" class="form-select" style="width: auto; min-width: 120px; font-size: 0.7rem;" readonly >
<option value="">(품명)</option>
<?php
$sqlItem = "SELECT DISTINCT itemName FROM {$DB}.bending WHERE itemName IS NOT NULL AND is_deleted IS NULL AND itemName <> '' ORDER BY itemName ASC";
$stmhItem = $pdo->prepare($sqlItem);
$stmhItem->execute();
$myItemList = $stmhItem->fetchAll(PDO::FETCH_COLUMN);
foreach($myItemList as $itemVal): ?>
<option value="<?= htmlspecialchars($itemVal, ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars($itemVal, ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</select>
<!-- 검색어 입력 -->
<div class="input-group" style="width: auto; min-width: 180px;">
<input type="text" class="form-control text-start" id="bendingSearchInput" placeholder="추가 검색어 입력" style="font-size: 0.7rem; height:30px!important;" autocomplete="off">
<button class="btn btn-primary" type="button" id="bendingSearchBtn" style="font-size: 0.7rem;"><i class="bi bi-search"></i> 검색</button>
</div>
<!-- 검색 조건 초기화 버튼 -->
<button class="btn btn-outline-secondary" type="button" id="resetSearchBtn" style="font-size: 0.7rem;" ><i class="bi bi-arrow-clockwise"></i> 초기화</button>
<!-- 절곡바라시 생성 -->
<button class="btn btn-success" type="button" id="makeBendingBtn" style="font-size: 0.7rem;" ><i class="bi bi-magic"></i> 절곡바라시 생성</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead class="table-secondary">
<tr>
<th style="width: 50px;" class="text-center">
<input class="form-check-input" type="checkbox" id="selectAllBendingItems">
</th>
<th style="width: 60px;" class="text-center">순서</th>
<th class="sortable-header" data-sort="date" style="cursor: pointer;">
등록일 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="category" style="cursor: pointer;">
대분류 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="ua" style="cursor: pointer;">
인정/비인정 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="bending" style="cursor: pointer;">
절곡구분 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="direction" style="cursor: pointer;">
점검구방향 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="box" style="cursor: pointer;">
박스(가로X세로) <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="front" style="cursor: pointer;">
전면부 밑면치수 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="rail" style="cursor: pointer;">
레일(폭) <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="name" style="cursor: pointer;">
품명 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="keyword" style="cursor: pointer;">
품목검색어 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th class="sortable-header" data-sort="material" style="cursor: pointer;">
재질 <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
<th>이미지</th>
<th class="sortable-header" data-sort="width" style="cursor: pointer;">
폭합(mm) <i class="sort-icon bi bi-arrow-down-up"></i>
</th>
</tr>
</thead>
<tbody id="bendingSearchResults">
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">취소</button>
<button type="button" class="btn btn-success btn-sm" id="applyBendingSelectionBtn">선택 적용</button>
</div>
</div>
</div>
</div>
<!-- 이미지 팝업 오버레이 -->
<div id="imagePopupOverlay" class="image-popup-overlay" style="display: none;">
<div class="image-popup-content">
<img id="popupImage" src="" alt="확대된 이미지">
</div>
</div>
<!-- 이미지 확대 모달 -->
<div class="image-zoom-overlay" id="imageZoomOverlay" style="display: none;">
<div class="image-zoom-modal">
<div class="image-zoom-close" onclick="closeImageZoom()">&times;</div>
<img id="zoomedImage" src="" alt="확대된 이미지">
</div>
</div>
<!-- Modal fetch_modal-->
<div id="myModal" class="modal" tabindex="-1" style="z-index: 99;" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-content" >
<div class="modal-header">
<span class="modal-title"><?=$title_message?></span>
<span class="close closeBtn">&times;</span>
</div>
<div class="modal-body">
<div class="modal_detail">
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="card justify-content-center text-center mt-1 mb-5">
<div class="card-header">
<div class="d-flex justify-content-center align-items-center">
<span class="text-center fs-5"> <?=$title_message?> </span>
<button type="button" class="btn btn-dark btn-sm mx-3" onclick='location.reload();' title="새로고침"> <i class="bi bi-arrow-clockwise"></i> </button>
<?php if(!empty($header)) : ?>
<button type="button" class="btn btn-dark btn-sm mx-1" onclick="window.location.href='../bendingfee/list.php?header=header'" title="절곡BOM"> <i class="bi bi-boxes"></i> </button>
<?php endif; ?>
<?php if(empty($header)) : ?>
<button type="button" class="btn btn-dark btn-sm mx-2" onclick='window.close();' > &times; 닫기 </button>
<?php endif; ?>
</div>
</div>
<div class="card-body">
<div class="d-flex justify-content-center align-items-center text-center mt-2 mb-5">
▷ <?= $total_row ?> &nbsp;
<!-- 점검구 형태 라디오 버튼 -->
<div class="mx-2">
<div class="btn-group" role="group" aria-label="점검구 형태 선택">
<?php
$exit_direction_list = ['양면 점검구', '밑면 점검구', '후면 점검구'];
$selected_exit_direction = isset($_REQUEST['exit_direction_search']) ? $_REQUEST['exit_direction_search'] : '';
// 전체 옵션 추가
$all_checked = empty($selected_exit_direction) ? 'checked' : '';
echo '<input type="radio" class="btn-check" name="exit_direction_search" id="exit_direction_all" value="" ' . $all_checked . '>';
echo '<label class="btn btn-outline-secondary btn-sm" for="exit_direction_all">전체</label>';
foreach ($exit_direction_list as $item) {
$checked = ($selected_exit_direction === $item) ? 'checked' : '';
echo '<input type="radio" class="btn-check" name="exit_direction_search" id="exit_direction_' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '" value="' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '" ' . $checked . '>';
echo '<label class="btn btn-outline-primary btn-sm" for="exit_direction_' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '">' . htmlspecialchars($item, ENT_QUOTES, 'UTF-8') . '</label>';
}
?>
</div>
</div>
<div class="inputWrap30">
<input type="text" id="search" class="form-control text-start w150px" name="search" style="height:32px!important;" value="<?=$search?>" autocomplete="off" onKeyPress="if (event.keyCode==13){ enter(); }">
<button class="btnClear"></button>
</div>
&nbsp;&nbsp;
<button class="btn btn-outline-dark btn-sm" type="button" id="searchBtn"> <i class="bi bi-search"></i> </button> &nbsp;&nbsp;&nbsp;&nbsp;
<button id="newBtn" type="button" class="btn btn-dark btn-sm mx-1"> <i class="bi bi-pencil-square"></i> 신규 </button>
<button type="button" class="btn btn-dark btn-sm mx-1 registImageBtn"> <i class="bi bi-pencil-square"></i> 결합형태 이미지 등록 </button>
<button type="button" class="btn btn-dark btn-sm mx-1" id="modelFlatBtn">
<i class="bi bi-diagram-3"></i> 점검구 형태별 기본 전개도
</button>
<?php if($level=='1') { ?>
<!-- <button id="uploadBtn" type="button" class="btn btn-dark btn-sm me-2"> <i class="bi bi-box-arrow-up"></i> 업로드 </button> -->
<?php } ?>
</div>
<div class="d-flex justify-content-center text-center mt-5 mb-2">
<div class="table-responsive mt-3 mb-5">
<table class="table table-hover" id="myTable">
<thead class="table-primary">
<th class="text-center" style="width:70px; " > 번호 </th>
<th class="text-center" style="width:120px;" > 등록일 </th>
<th class="text-center" style="width:150px;" > 박스(가로X세로) </th>
<th class="text-center" style="width:100px;" > 점검구 형태 </th>
<th class="text-center" style="width:100px;" > 전면부 밑면치수 </th>
<th class="text-center" style="width:100px;" > 레일(폭) </th>
<th class="text-center" style="width:120px;" > 소요자재량 </th>
<th class="text-center" style="width:200px;" > 품목 검색어 </th>
<th class="text-center" style="width:120px;" > <i class="bi bi-image"></i> 형태 </th>
<th class="text-center" style="width:100px;" > 작업지시서 </th>
<th class="text-center" style="width:70px; " > 작성 </th>
<th class="text-center" style="width:300px;" > 비고 </th>
</thead>
<tbody>
<?php
// 이미지 찾기 함수
function findImageByConditions($items, $conditions) {
foreach ($items as $item) {
$matched = true;
foreach ($conditions as $key => $value) {
if (empty($item[$key]) || $item[$key] !== $value) {
$matched = false;
break;
}
}
if ($matched && !empty($item['image'])) {
return "<img src='{$item['image']}' alt='이미지' style='width:auto;height:auto;' id='currentImage' class='img-fluid'>";
}
}
return '';
}
$start_num = $total_row;
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
include '_row.php';
// 점검구 형태에 따른 이미지 URL 생성 (셔터박스)
$jsonFile = $_SERVER['DOCUMENT_ROOT'].'/shutterbox/shutterbox.json';
$imgUrl = '';
if (file_exists($jsonFile)) {
$jsonData = file_get_contents($jsonFile);
$shutterboxImages = json_decode($jsonData, true);
// echo "<pre>";
// print_r($shutterboxImages);
// echo "</pre>";
if (is_array($shutterboxImages)) {
// 검색 조건 우선순위대로 정렬
$searchCases = [];
// 1순위: 검색 키워드 (가장 우선)
if (!empty($row['search_keyword'])) {
$searchCases[] = ['search_keyword' => $row['search_keyword']];
}
// 2순위: 모든 조건 일치
$searchCases[] = [
'box_width' => $row['box_width'],
'box_height' => $row['box_height'],
'front_bottom_width' => $row['front_bottom_width'],
'rail_width' => $row['rail_width'],
'exit_direction' => $row['exit_direction']
];
// 순차 검색 - 첫번째 매칭되는 이미지 사용
foreach ($searchCases as $case) {
$imgUrl = findImageByConditions($shutterboxImages, $case);
if ($imgUrl) break;
}
}
}
?>
<tr>
<td class="text-center"><?= $start_num ?></td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['registration_date'] ?></td>
<td class="text-center fw-bold text-primary" onclick="loadForm('view', '<?=$row['num']?>');" ><?= $row['box_width'] ?> X <?= $row['box_height'] ?> </td>
<td class="text-center fw-bold text-success" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['exit_direction'] ?></td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['front_bottom_width'] ?></td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['rail_width'] ?></td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');">
<?php
// material_summary 데이터 파싱 및 표시
$material_summary_display = '';
if (!empty($row['material_summary'])) {
try {
$material_data = json_decode($row['material_summary'], true);
if (is_array($material_data)) {
$summary_items = [];
foreach ($material_data as $material => $total) {
$summary_items[] = $material . '(' . number_format($total) . ')';
}
$material_summary_display = implode('<br>', $summary_items);
}
} catch (Exception $e) {
$material_summary_display = '데이터 오류';
}
}
echo $material_summary_display ?: '-';
?>
</td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['search_keyword'] ?></td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');"> <?= $imgUrl ?> </td>
<td class="text-center" >
<h6> <span class="badge bg-secondary" onclick="viewWork('<?=$row['num']?>');"> 보기 </span> </h6>
</td>
<td class="text-center" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['author'] ?></td>
<td class="text-start" onclick="loadForm('view', '<?=$row['num']?>');"><?= $row['remark'] ?></td>
</tr>
<?php
$start_num--;
}
} catch (PDOException $Exception) {
print "오류: ".$Exception->getMessage();
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</form>
<!-- 미니 합계표 팝업 -->
<div id="miniSummaryPopup" style="display:none; position:fixed; z-index:20000; background:white; border:2px solid #007bff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.3); padding:10px; min-width:400px; min-height:200px;"></div>
<!-- 다이얼로그(편집기) 마크업: imageEditor.js 호출 시 참조 -->
<dialog id="editorDialog">
<div id="editorHeader">
<h5>이미지 편집기</h5>
<button id="applyBtn" class="btn btn-success btn-sm">적용하기</button>
<button type="button" class="btn-close" aria-label="닫기" id="closeEditorBtn"></button>
</div>
<!-- 툴바 -->
<div id="editorToolbar">
<button id="polyBtn" class="btn btn-outline-primary toolbar-btn" title="폴리라인"><i class="bi bi-vector-pen"></i></button>
<button id="freeBtn" class="btn btn-outline-primary toolbar-btn" title="자유선"><i class="bi bi-brush"></i></button>
<button id="lineBtn" class="btn btn-outline-primary toolbar-btn" title="직선 (L키)"><i class="bi bi-slash-lg"></i></button>
<button id="textBtn" class="btn btn-outline-primary toolbar-btn" title="문자입력"><i class="bi bi-type"></i></button>
<button id="eraserBtn" class="btn btn-outline-warning toolbar-btn" title="지우개"><i class="bi bi-eraser-fill"></i></button>
<button id="selectBtn" class="btn btn-outline-secondary toolbar-btn" title="객체선택"><i class="bi bi-cursor-text"></i></button>
<label class="form-check-label" style="font-size: 0.9em; font-weight: 500; cursor: pointer;">
<label style="font-size: 1.4em; font-weight: 500; cursor: pointer; margin-right:100px;">
<input class="form-check-input" type="checkbox" id="rightAngle" checked style="cursor: pointer; z-index: 10; position: relative;">
직각 고정
</label>
<button id="editBtn" class="btn btn-outline-primary btn-sm me-2" title="수정">수정</button>
<button id="clearBtn" class="btn btn-outline-danger btn-sm ms-auto">전체 지우기</button>
</div>
<!-- 지우개 크기 & 색상 -->
<div id="editorToolbar2" class="d-flex align-items-center px-3 pb-2">
<div class="btn-group" role="group" id="colorPicker">
<button type="button" class="btn color-btn active" data-color="#000000" style="background:#000;"></button>
<button type="button" class="btn color-btn" data-color="#ff0000" style="background:#f00;"></button>
<button type="button" class="btn color-btn" data-color="#0000ff" style="background:#00f;"></button>
<button type="button" class="btn color-btn" data-color="#00aa00" style="background:#0a0;"></button>
<button type="button" class="btn color-btn" data-color="#ff8800" style="background:#f80;"></button>
<button type="button" class="btn color-btn" data-color="#800080" style="background:#808;"></button>
<button type="button" class="btn color-btn" data-color="#888888" style="background:#888;"></button>
</div>
<label class="mb-0 me-2">지우개 크기</label>
<input type="range" class="form-range me-2" id="eraserRange" min="5" max="100" step="1" value="20">
<span id="eraserSizeLabel" class="fw-bold">20</span>px
</div>
<!-- 캔버스 영역 -->
<div id="editorBody">
<canvas id="c" width="370" height="300"></canvas>
</div>
</dialog>
<!-- 모듈 스크립트 -->
<script type="module">
import { openImageEditor } from '/js/imageEditor.js';
// 전역 함수로 선언하여 loadForm에서 호출할 수 있도록 함
window.initializeImageEditor = function() {
const openBtn = document.getElementById('openEditorBtn');
const targetImg = document.getElementById('targetImage');
const imageContainer = document.querySelector('.image-container');
const pasteOverlay = document.querySelector('.paste-overlay');
// null 체크 추가
if (!openBtn || !targetImg || !imageContainer || !pasteOverlay) {
console.warn('이미지 편집기 초기화 실패: 필요한 DOM 요소를 찾을 수 없습니다.');
return;
}
// 붙여넣기 이벤트 처리 함수
function handlePaste(event) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
for (let item of items) {
if (item.type.indexOf('image') !== -1) {
const blob = item.getAsFile();
const reader = new FileReader();
reader.onload = function(e) {
targetImg.src = e.target.result;
console.log('이미지가 붙여넣기되었습니다:', e.target.result);
// 붙여넣기 성공 피드백
showPasteFeedback(true);
};
reader.readAsDataURL(blob);
event.preventDefault();
break;
}
}
}
// 붙여넣기 피드백 표시
function showPasteFeedback(success = false) {
if (success) {
pasteOverlay.innerHTML = '<i class="bi bi-check-circle"></i><span>이미지가 붙여넣기되었습니다!</span>';
pasteOverlay.style.background = 'rgba(40, 167, 69, 0.8)';
} else {
pasteOverlay.innerHTML = '<i class="bi bi-clipboard-plus"></i><span>Ctrl+V로 이미지 붙여넣기</span>';
pasteOverlay.style.background = 'rgba(0, 123, 255, 0.8)';
}
pasteOverlay.classList.add('show');
setTimeout(() => {
pasteOverlay.classList.remove('show');
}, 2000);
}
// 기존 이벤트 리스너 제거 (중복 방지)
document.removeEventListener('paste', handlePaste);
imageContainer.removeEventListener('click', imageContainerClickHandler);
imageContainer.removeEventListener('focus', imageContainerFocusHandler);
imageContainer.removeEventListener('blur', imageContainerBlurHandler);
openBtn.removeEventListener('click', openBtnClickHandler);
// 이벤트 핸들러 함수들
function imageContainerClickHandler() {
imageContainer.focus();
}
function imageContainerFocusHandler() {
showPasteFeedback();
}
function imageContainerBlurHandler() {
pasteOverlay.classList.remove('show');
}
function openBtnClickHandler() {
const src = targetImg.src;
openImageEditor(src)
.then(newUrl => {
// 편집된 이미지를 화면에 반영
targetImg.src = newUrl;
})
.catch(err => {
// 사용자가 취소했을 때
console.log('편집 취소:', err.message);
});
}
// 그리기 관련 코드
// 붙여넣기 이벤트 리스너 추가
document.addEventListener('paste', handlePaste);
// 이미지 컨테이너 클릭 시 포커스
imageContainer.addEventListener('click', imageContainerClickHandler);
// 이미지 컨테이너에 포커스 가능하도록 설정
imageContainer.tabIndex = 0;
imageContainer.title = '클릭 후 Ctrl+V로 이미지 붙여넣기';
// 포커스 시 오버레이 표시
imageContainer.addEventListener('focus', imageContainerFocusHandler);
// 포커스 해제 시 오버레이 숨김
imageContainer.addEventListener('blur', imageContainerBlurHandler);
openBtn.addEventListener('click', openBtnClickHandler);
console.log('이미지 편집기 초기화 완료');
};
// 수정 버튼 클릭 이벤트
$(document).on('click', '#editBtn', function() {
// 현재 표시된 데이터 수집
const currentData = collectCurrentData();
// bending/write_form.php로 연결
const popupWidth = 1200;
const popupHeight = 900;
const left = (window.screen.width - popupWidth) / 2;
const top = (window.screen.height - popupHeight) / 2;
// URL 파라미터 구성
const params = new URLSearchParams({
mode: 'get',
item_sep: currentData.item_sep || '',
model_UA: currentData.model_UA || '',
item_bending: '셔터박스',
itemName: currentData.itemName || '',
item_spec: currentData.item_spec || '',
// 추가 데이터 전달
exit_direction: currentData.exit_direction || '',
box_width: currentData.box_width || '',
box_height: currentData.box_height || '',
rail_width: currentData.rail_width || '',
front_bottom_width: currentData.front_bottom_width || ''
});
window.open('/bending/write_form.php?' + params.toString(), '_blank',
'width=' + popupWidth + ',height=' + popupHeight +
',left=' + left + ',top=' + top +
',scrollbars=yes,status=no,toolbar=no,location=no');
});
// 현재 데이터 수집 함수
function collectCurrentData() {
const data = {};
// 기본 정보 수집
data.item_sep = $('input[name="searchItem"]:checked').val() || '';
data.model_UA = $('input[name="searchUA"]:checked').val() || '';
data.itemName = $('#searchName').val() || '';
data.item_spec = $('#searchSpec').val() || '';
// 셔터박스 정보 수집 (model_flat.php에서 사용되는 정보)
data.exit_direction = $('#exit_direction').val() || '양면 점검구';
data.box_width = $('#box_width').val() || '500';
data.box_height = $('#box_height').val() || '380';
data.rail_width = $('#rail_width').val() || '70';
data.front_bottom_width = $('#front_bottom_width').val() || '50';
return data;
}
</script>
<script>
// 검색 기능
function enter() {
performSearch();
}
// 검색 버튼 클릭 이벤트 중복/자기호출 방지
$(document).off('click', '#searchBtn');
$(document).on('click', '#searchBtn', function(e) {
e.preventDefault();
e.stopPropagation();
performSearch();
});
// 새로고침 시 검색 조건 유지
$(document).ready(function() {
// DataTables 초기 설정
dataTable = $('#myTable').DataTable({
"paging": true,
"ordering": true,
"searching": false,
"pageLength": 50, // 한 페이지에 표시할 항목 수
"lengthMenu": [ 50, 100, 200, 500, 1000], // 페이지당 표시 항목 선택 메뉴
"language": {
"lengthMenu": "Show _MENU_ entries"
},
"order": [[1, 'desc']] // 두번재 등록일 기준
});
// 페이지 번호 복원 (초기 로드 시)
var savedPageNumber = getCookie('shutterboxpageNumber');
if (savedPageNumber) {
dataTable.page(parseInt(savedPageNumber) - 1).draw(false); // 쿠키에 저장된 페이지 번호로 이동
}
// 페이지 변경 이벤트 리스너
dataTable.on('page.dt', function() {
var shutterboxpageNumber = dataTable.page.info().page + 1;
setCookie('shutterboxpageNumber', shutterboxpageNumber, 10); // 쿠키에 현재 페이지 번호 저장
});
// 페이지 길이 셀렉트 박스 변경 이벤트 처리
$('#myTable_length select').on('change', function() {
var selectedValue = $(this).val();
dataTable.page.len(selectedValue).draw(); // 페이지 길이 변경
// 변경 후 현재 페이지 번호 복원
savedPageNumber = getCookie('shutterboxpageNumber');
if (savedPageNumber) {
dataTable.page(parseInt(savedPageNumber) - 1).draw(false);
}
});
// 점검구 형태 라디오 버튼 변경 시 자동 검색
$(document).off('change', 'input[name="exit_direction_search"]');
$(document).on("change", 'input[name="exit_direction_search"]', function() {
// 약간의 지연을 두어 라디오 버튼 상태가 완전히 변경된 후 검색 실행
setTimeout(function() {
performSearch();
}, 100);
});
// search_shutterbox.php 이미지 호버 확대 기능 - 마우스 위치에 팝업 표시
let hoverTimeout;
$(document).on('mouseenter', '.search-zoomable-image', function(e) {
e.stopPropagation(); // 행 클릭 이벤트 전파 방지
const imgSrc = $(this).attr('data-src') || $(this).attr('src');
// 기존 타임아웃 클리어
clearTimeout(hoverTimeout);
// 마우스 위치 계산
const mouseX = e.clientX;
const mouseY = e.clientY;
// 팝업 위치 설정 (마우스 위치에서 약간 오프셋)
const popupOffsetX = 20;
const popupOffsetY = 20;
// 팝업이 화면 밖으로 나가지 않도록 조정
const popupWidth = 300; // 팝업 너비
const popupHeight = 300; // 팝업 높이
const windowWidth = $(window).width();
const windowHeight = $(window).height();
let finalX = mouseX + popupOffsetX;
let finalY = mouseY + popupOffsetY;
// 오른쪽 경계 체크
if (finalX + popupWidth > windowWidth) {
finalX = mouseX - popupWidth - popupOffsetX;
}
// 아래쪽 경계 체크
if (finalY + popupHeight > windowHeight) {
finalY = mouseY - popupHeight - popupOffsetY;
}
// 팝업 위치 설정
$('#imagePopupOverlay').css({
'position': 'fixed',
'top': finalY + 'px',
'left': finalX + 'px',
'width': popupWidth + 'px',
'height': popupHeight + 'px',
'background': 'white',
'border': '2px solid #007bff',
'border-radius': '8px',
'box-shadow': '0 4px 20px rgba(0,0,0,0.3)',
'z-index': '10000',
'display': 'none'
});
// 팝업 내용 설정
$('#popupImage').attr('src', imgSrc);
$('#imagePopupOverlay').fadeIn(200);
});
$(document).on('mouseleave', '.search-zoomable-image', function(e) {
// 마우스가 이미지를 벗어날 때 약간의 지연 후 팝업 닫기
hoverTimeout = setTimeout(function() {
$('#imagePopupOverlay').fadeOut(200);
}, 100);
});
// 팝업 오버레이에 마우스가 들어오면 팝업 유지
$('#imagePopupOverlay').on('mouseenter', function() {
clearTimeout(hoverTimeout);
});
// 팝업 오버레이에서 마우스가 벗어나면 팝업 닫기
$('#imagePopupOverlay').on('mouseleave', function() {
$('#imagePopupOverlay').fadeOut(200);
});
// 모달 클릭 시 닫기
$('#imageModal').on('click', function() {
$(this).fadeOut(300);
});
// ESC 키로 모달 닫기
$(document).on('keydown', function(e) {
if (e.key === 'Escape' && $('#imageModal').is(':visible')) {
$('#imageModal').fadeOut(300);
}
});
});
// 검색 수행 함수
function performSearch() {
var search = $("#search").val();
var exit_direction_search = $('input[name="exit_direction_search"]:checked').val();
var url = "list.php?";
if (search) url += "search=" + encodeURIComponent(search) + "&";
if (exit_direction_search) url += "exit_direction_search=" + encodeURIComponent(exit_direction_search) + "&";
if ("<?=$header?>") url += "header=header&";
window.location.href = url;
}
function loadForm(mode, num = null) {
console.log('=== loadForm 함수 시작 ===');
console.log('mode:', mode);
console.log('num:', num);
if (num == null) {
$("#mode").val('insert');
}
else {
$("#mode").val(mode);
$("#num").val(num);
}
var tablename= $("#tablename").val();
$('.viewmode').prop('disabled', false); // 버튼 활성화 false는 활성화
$.ajax({
type: "POST",
url: "/shutterbox/fetch_modal.php",
data: { mode: mode, num: num , tablename : tablename},
dataType: "html",
success: function(response) {
console.log('=== fetch_modal AJAX 성공 ===');
console.log('response 길이:', response.length);
$(".modal_detail").html(response);;
// 일반적인 show() 메서드 사용 (Bootstrap 오류 방지)
$("#myModal").modal('show');
$(document).ready(function() {
$("input").attr("autocomplete", "off");
});
// 모달이 완전히 열린 후 실행될 코드 (setTimeout으로 지연)
setTimeout(function() {
console.log('모달 ID:', document.getElementById('myModal').id);
// 그리기 기능 초기화
if (mode !== 'view') {
console.log('view 모드가 아니므로 그리기 기능 초기화');
// 이미지 편집기 초기화 (모달이 로드된 후)
setTimeout(() => {
if (window.initializeImageEditor) {
window.initializeImageEditor();
} else {
console.warn('initializeImageEditor 함수를 찾을 수 없습니다');
}
}, 300);
} else {
console.log('view 모드이므로 그리기 기능 초기화 건너뜀');
}
}, 300); // 300ms 지연
// 1. 닫기 버튼들은 Bootstrap의 닫기 기능만 호출하도록 합니다.
$("#closeBtn, .closeBtn").off("click").on("click", function() {
$('#myModal').modal('hide');
});
// 2. 모달이 완전히 닫힌 후 실행될 모든 정리 작업을 여기에 모읍니다.
$(document).on('hidden.bs.modal', '#myModal', function() {
console.log("hidden.bs.modal 이벤트 발생!"); // 디버깅용
// 모달 내용 비우기
$(".modal_detail").html('');
// 비활성화된 요소들 다시 활성화
$('.viewmode').prop('disabled', false);
});
$(document).off('click', '#copyBtn').on('click', '#copyBtn', function() { // 복사버튼
var num = $(this).data("num"); // 'data-num' 속성 값 가져오기
$('#myModal').modal('hide');
$(".modal_detail").html(''); // 모달 내용을 비움
setTimeout(function() {
loadForm('copy', num);
}, 500);
});
$(document).off('click', '#modifyBtn').on('click', '#modifyBtn', function() { // 수정버튼
var num = $(this).data("num"); // 'data-num' 속성 값 가져오기
$('#myModal').modal('hide');
$(".modal_detail").html(''); // 모달 내용을 비움
setTimeout(function() {
loadForm('modify', num);
}, 500);
});
// 기본값 설정 함수
function setDefaultValue(selector, defaultValue) {
if (!$(selector).val()) {
$(selector).val(defaultValue);
}
}
// 전면 밑 (front_bottom_width) 기본값 설정
setDefaultValue("#front_bottom_width", 50);
// 레일폭 (rail_width) 기본값 설정
setDefaultValue("#rail_width", 70);
// 셔터박스 가로(폭) 기본값 설정
setDefaultValue("#box_width", 500);
// 셔터박스 세로(높이) 기본값 설정
setDefaultValue("#box_height", 380);
// 셔터박스 선택에 따른 동작 구현
const checkImage = $("#checkImage");
// 선택된 값에 따라 이미지 설정
const initialCheckType = $("#selected_exit_direction").val();
console.log('initialCheckType: ' + initialCheckType);
updateImage(initialCheckType);
// 라디오 버튼 변경 시 이미지 업데이트
$('input[name="exit_direction"]').on("change", function() {
updateImage($(this).val());
});
function updateImage(checkType) {
const mode = $("#mode").val();
if (mode !== 'insert') {
let imageUrl = '../img/box/box_both.png?v=1'; // 기본값
if (checkType === '양면 점검구') {
imageUrl = '../img/box/box_both.png?v=1';
} else if (checkType === '밑면 점검구') {
imageUrl = '../img/box/box_bottom.png?v=1';
} else if (checkType === '후면 점검구') {
imageUrl = '../img/box/box_back.png?v=1';
}
checkImage.attr("src", imageUrl);
}
}
// 1. 전역 변수로 조립 부품 데이터 관리
let assemblyParts = [];
let material_summary = {}; // 재질별 폭합 데이터를 저장할 전역 변수
// 2. JSON 문자열을 안전하게 배열로 파싱하는 헬퍼 함수
function parseJsonString(jsonString) {
if (!jsonString || typeof jsonString !== 'string') return [];
try {
const parsed = JSON.parse(jsonString);
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
console.error("JSON 파싱 오류:", jsonString, e);
return [];
}
}
// 3. 기존 데이터 로드 (수정/조회 시)
function loadInitialAssemblyData() {
const initialData = $("#bending_components_data").val();
const initialMaterialSummary = $("#material_summary").val();
if (initialData) {
assemblyParts = parseJsonString(initialData);
renderAssemblyTable(); // 초기 데이터로 테이블 렌더링 (재질별 폭합 테이블도 함께 업데이트)
$('.viewmode').prop('disabled', true); // 버튼 비활성화 false는 활성화
}
// 기존 재질별 폭합 데이터 복원
if (initialMaterialSummary) {
try {
material_summary = JSON.parse(initialMaterialSummary);
console.log('기존 재질별 폭합 데이터 복원:', material_summary);
} catch (e) {
console.warn('재질별 폭합 데이터 파싱 오류:', e);
material_summary = {};
}
}
}
loadInitialAssemblyData();
// 공통 함수: 현재 페이지의 값들을 가져오기 (셔터박스용)
function getCurrentValues(part) {
const values = {};
// 기본 부품 데이터
values.model_UA = ''; // 셔터박스에서는 model_UA 필드가 없으므로 빈 값으로 설정
const box_width = $("input[name='box_width']").val() || '';
const box_height = $("input[name='box_height']").val() || '';
values.item_spec = '';
values.box_width = box_width;
values.box_height = box_height;
values.exit_direction = $("input[name='exit_direction']:checked").val() || '';
values.front_bottom_width = $("input[name='front_bottom_width']").val() || '';
values.rail_width = $("input[name='rail_width']").val() || '';
values.item_bending = '케이스'; // 절곡그룹은 케이스로 고정
values.material = part.material || '';
values.itemName = part.itemName || '';
// 셔터박스에서는 firstitem 필드가 없으므로 기본값 설정
if(box_width > 590)
values.item_sep = '철재'; // 셔터박스는 일반적으로 철재로 분류
else
values.item_sep = '스크린'; // 셔터박스는 일반적으로 철재로 분류
// 현재 페이지의 검색 키워드 (input)
const currentSearchKeyword = $("#search_keyword").val();
if (currentSearchKeyword) {
values.search_keyword = currentSearchKeyword;
}
return values;
}
// 공통 함수: bending 테이블 검색 및 팝업 열기 (셔터박스용)
function searchBendingAndOpenPopup(part, partIndex, defaultMode = 'write', forceMode = false) {
const currentValues = getCurrentValues(part);
console.clear();
console.log('실시간으로 가져온 검색 값들:', currentValues);
// AJAX로 bending 테이블 검색
$.ajax({
url: 'search_bending.php', // bending테이블의 검색 결과를 가져옴
type: 'POST',
data: {
search_keyword: currentValues.search_keyword,
item_sep: currentValues.item_sep,
exit_direction: currentValues.exit_direction,
front_bottom_width: currentValues.front_bottom_width,
rail_width: currentValues.rail_width,
box_width: currentValues.box_width,
box_height: currentValues.box_height,
itemName: currentValues.itemName,
material: currentValues.material,
item_bending: currentValues.item_bending
},
dataType: 'json',
success: function(response) {
console.log('bending 테이블 검색 결과:', response);
// mode 변수 설정
let mode = defaultMode;
let num = '';
if (response.success && response.data.num) {
// forceMode가 true이면 모드를 강제로 유지, false이면 기존 로직 적용
if (!forceMode && defaultMode === 'write') {
mode = 'modify';
}
num = response.data.num;
}
// 팝업 설정
const popupWidth = 1800;
const popupHeight = 700;
const left = (window.screen.width - popupWidth) / 2;
const top = (window.screen.height - popupHeight) / 2;
const item_sep = currentValues.item_sep;
console.log('partIndex:', partIndex);
console.log('part.imgdata:', part.imgdata);
// URL 파라미터 구성
const params = new URLSearchParams({
mode: mode,
num: num,
inputList: JSON.stringify(part.inputList),
bendingrateList: JSON.stringify(part.bendingrateList),
sumList: JSON.stringify(part.sumList),
colorList: JSON.stringify(part.colorList),
AList: JSON.stringify(part.AList),
item_sep: item_sep,
item_bending: '케이스', // 인정비인정 제거
itemName: part.itemName || '',
exit_direction: currentValues.exit_direction,
front_bottom_width: currentValues.front_bottom_width,
rail_width: currentValues.rail_width,
box_width: currentValues.box_width,
box_height: currentValues.box_height,
material: part.material || '',
imgdata: part.imgdata || '',
// assemblypart의 고유번호 전달
partIndex: partIndex
});
console.log('params:', params.toString());
window.open('/bending/write_form.php?' + params.toString(), '_blank',
'width=' + popupWidth + ',height=' + popupHeight +
',left=' + left + ',top=' + top +
',scrollbars=yes,status=no,toolbar=no,location=no');
},
error: function(xhr, status, error) {
console.error('AJAX 오류:', error);
console.error('상태:', status);
console.error('XHR 상세:', {
responseText: xhr.responseText,
status: xhr.status,
statusText: xhr.statusText
});
alert('검색 중 오류가 발생했습니다. 자세한 내용은 콘솔을 확인해주세요.');
}
});
}
// 각 부품별 수정 버튼 클릭 이벤트 bending 테이블 수정
$(document).off('click', '.edit-part-btn').on('click', '.edit-part-btn', function() {
const partIndex = $(this).data('part-index');
const partNum = $(this).data('part-num');
const part = assemblyParts[partIndex];
if (!part) {
console.log('부품 정보를 찾을 수 없습니다.');
return;
}
searchBendingAndOpenPopup(part, partIndex, 'modify');
});
// 각 부품별 다른이름 저장 버튼 클릭 이벤트 bending 테이블 수정
$(document).off('click', '.new-edit-part-btn').on('click', '.new-edit-part-btn', function() {
const partIndex = $(this).data('part-index');
const partNum = $(this).data('part-num');
const part = assemblyParts[partIndex];
if (!part) {
console.log('부품 정보를 찾을 수 없습니다.');
return;
}
searchBendingAndOpenPopup(part, partIndex, 'write', true);
});
// 5. '+ 부품 추가' 버튼 클릭 -> 신규 모달 열기
$(document).off('click', '#addPartBtn').on('click', '#addPartBtn', function() {
console.log('addPartBtn 클릭');
$('#searchBendingCategory').val('케이스');
$('#bendingSearchModal').modal('show');
// 모달이 완전히 열린 후 초기 검색 실행
$('#bendingSearchModal').on('shown.bs.modal', function() {
searchBendingItems();
$(this).off('shown.bs.modal'); // 이벤트 한 번만 실행
});
});
// 가이드레일 검색 버튼 클릭 이벤트
$(document).off('click', '#searchShutterboxBtn').on('click', '#searchShutterboxBtn', function() {
$('#shutterboxSearchModal').modal('show');
// 모달이 완전히 열린 후 초기 검색 실행
$('#shutterboxSearchModal').on('shown.bs.modal', function() {
console.log('셔터박스 검색 모달이 열렸습니다. 초기 검색을 실행합니다.');
searchShutterboxItems();
$(this).off('shown.bs.modal'); // 이벤트 한 번만 실행
});
});
// 6. 절곡 부품을 검색하고 모달에 표시하는 함수
// 검색 모달에서 체크박스로 선택한 항목들에 순서를 부여하고 관리하는 기능
// - 체크박스를 선택하면 자동으로 순서 번호가 부여됩니다 (1, 2, 3...)
// - 체크를 해제하면 순서 번호가 제거됩니다
// - 선택 적용 시 순서대로 부품이 추가됩니다
function searchBendingItems() {
console.log('=== 검색 함수 시작 ===');
// 이 함수는 이전 답변과 동일하게 유지됩니다.
// (searchbending.php를 호출하여 체크박스가 있는 목록을 만듭니다)
const searchText = $('#bendingSearchInput').val();
const selectedItem = $('input[name="searchItem"]:checked').val(); // 대분류
const selectedUA = $('input[name="searchUA"]:checked').val(); // 인정/비인정
const selectedBendingCategory = $('#searchBendingCategory').val(); // 중분류
const selectedName = $('#searchName').val(); // 품명
//디버깅: 검색 조건 로그 출력
console.log('검색 조건:', {
searchText,
selectedItem,
selectedUA,
selectedBendingCategory,
selectedName
});
const searchData = {
selectedName: selectedName,
selectedBendingCategory: selectedBendingCategory,
selectedItem: selectedItem,
selectedUA: selectedUA,
search: searchText
};
// 빈 값 제거 (선택적)
Object.keys(searchData).forEach(key => {
if (searchData[key] === '' || searchData[key] === null || searchData[key] === undefined) {
delete searchData[key];
}
});
// console.log('전송할 데이터:', searchData);
//console.log('요청 URL:', "/shutterbox/search_shutterbox.php");
$.ajax({
url: "/shutterbox/search_shutterbox.php", // searchbending.php -> search_shutterbox.php로 수정
type: 'GET',
data: searchData,
success: function(htmlResponse) {
console.log('=== 검색 성공 ===');
console.log('검색 결과 HTML 길이:', htmlResponse.length);
console.log('검색 결과 HTML:', htmlResponse.substring(0, 500) + '...');
const tableBody = $("#bendingSearchResults");
tableBody.empty();
const rows = $(htmlResponse);
// 클릭 순서 배열 초기화
checkboxClickOrder = [];
rows.each(function() {
const num = $(this).find('.bending-item-checkbox').data('num');
if (num) {
// 체크박스 변경 이벤트 추가
$(this).find('.bending-item-checkbox').on('change', function() {
const $row = $(this).closest('tr');
const rowIndex = $row.index();
if (this.checked) {
// 체크된 경우: 클릭 순서 배열에 추가 (중복 방지)
if (!checkboxClickOrder.includes(rowIndex)) {
checkboxClickOrder.push(rowIndex);
}
} else {
// 체크 해제된 경우: 클릭 순서 배열에서 제거
const removeIndex = checkboxClickOrder.indexOf(rowIndex);
if (removeIndex > -1) {
checkboxClickOrder.splice(removeIndex, 1);
}
}
updateOrderNumbers();
$row.toggleClass('table-active', this.checked);
});
// 행 클릭 이벤트 추가
$(this).css('cursor', 'pointer').on('click', function(e) {
if (e.target.type !== 'checkbox') {
const checkbox = $(this).find('.bending-item-checkbox');
checkbox.prop('checked', !checkbox.prop('checked')).trigger('change');
}
});
tableBody.append(this);
}
});
// 초기 순서 번호 설정
updateOrderNumbers();
console.log('검색 결과 행 수:', rows.length);
},
error: function(xhr, status, error) {
console.error('=== 검색 오류 ===');
console.error('검색 오류:', error);
console.error('상태:', status);
console.error('응답:', xhr.responseText);
console.error('상태 코드:', xhr.status);
$("#bendingSearchResults").html('<tr><td colspan="9" class="text-center text-danger">검색 중 오류가 발생했습니다.</td></tr>');
}
});
}
// 순서 번호를 업데이트하는 함수 (절곡부품 모달용)
function updateOrderNumbers() {
const $rows = $('#bendingSearchResults tr');
// 모든 순서 번호 초기화
$rows.each(function() {
const $orderCell = $(this).find('.order-number');
if ($orderCell.length > 0) {
$orderCell.text('-');
}
});
// 클릭 순서에 따라 순서 번호 부여
checkboxClickOrder.forEach(function(rowIndex, orderIndex) {
const $row = $rows.eq(rowIndex);
const $checkbox = $row.find('.bending-item-checkbox');
// 체크박스가 여전히 체크되어 있는지 확인
if ($checkbox.length > 0 && $checkbox.is(':checked')) {
const $orderCell = $row.find('.order-number');
if ($orderCell.length > 0) {
$orderCell.text(orderIndex + 1);
}
}
});
}
// 조립 부품 테이블에서 순서 변경을 위한 드래그 앤 드롭 기능
function initializeDragAndDrop() {
let draggedElement = null;
let draggedIndex = -1;
// 드래그 시작
$(document).on('mousedown', '.component-block', function(e) {
if (e.target.type === 'checkbox' || e.target.type === 'input' || $(e.target).hasClass('remove-part-btn')) {
return; // 체크박스, 입력란, 삭제 버튼은 드래그 제외
}
draggedElement = $(this);
draggedIndex = draggedElement.index();
draggedElement.addClass('dragging');
// 드래그 중인 요소의 스타일 설정
draggedElement.css({
'position': 'absolute',
'z-index': 1000,
'pointer-events': 'none'
});
// 마우스 위치에 따라 드래그 요소 위치 설정
const offset = draggedElement.offset();
const mouseOffset = {
x: e.clientX - offset.left,
y: e.clientY - offset.top
};
draggedElement.data('mouseOffset', mouseOffset);
});
// 드래그 중
$(document).on('mousemove', function(e) {
if (!draggedElement) return;
const mouseOffset = draggedElement.data('mouseOffset');
const newLeft = e.clientX - mouseOffset.x;
const newTop = e.clientY - mouseOffset.y;
draggedElement.css({
'left': newLeft + 'px',
'top': newTop + 'px'
});
// 드롭 영역 하이라이트
$('.component-block').removeClass('drag-over');
const dropTarget = $(document.elementFromPoint(e.clientX, e.clientY)).closest('.component-block');
if (dropTarget.length && dropTarget[0] !== draggedElement[0]) {
dropTarget.addClass('drag-over');
}
});
// 드래그 종료
$(document).on('mouseup', function() {
if (!draggedElement) return;
const dropTarget = $('.drag-over');
if (dropTarget.length) {
const dropIndex = dropTarget.index();
reorderComponents(draggedIndex, dropIndex);
}
// 드래그 상태 정리
draggedElement.removeClass('dragging').css({
'position': '',
'z-index': '',
'pointer-events': '',
'left': '',
'top': ''
});
$('.component-block').removeClass('drag-over');
draggedElement = null;
draggedIndex = -1;
});
}
// 부품 순서 재정렬 함수
function reorderComponents(fromIndex, toIndex) {
if (fromIndex === toIndex) return;
// 배열에서 요소 이동
const movedPart = assemblyParts.splice(fromIndex, 1)[0];
assemblyParts.splice(toIndex, 0, movedPart);
// 순서 번호 재할당
assemblyParts.forEach((part, index) => {
part.orderNumber = index + 1;
});
// 테이블 다시 렌더링 (재질별 폭합 테이블도 함께 업데이트)
renderAssemblyTable();
console.log('부품 순서가 변경되었습니다:', assemblyParts.map(p => ({ name: p.itemName, order: p.orderNumber })));
}
// 순서 초기화 함수
function resetOrder() {
assemblyParts.forEach((part, index) => {
part.orderNumber = index + 1;
});
renderAssemblyTable(); // 이 함수 내에서 재질별 폭합 테이블도 업데이트됨
// 모든 체크박스 해제
$('.component-checkbox').prop('checked', false).trigger('change');
}
// 7. 검색 모달 관련 이벤트 핸들러들
// 이벤트 위임을 사용하여 동적으로 생성되는 요소들에 대해서도 이벤트가 작동하도록 함
$(document).off('click change keypress', '#bendingSearchBtn, #searchItem, #searchUA, #searchBendingCategory, #searchName, #searchMaterial, #bendingSearchInput, #resetSearchBtn, #selectAllBendingItems, #applyBendingSelectionBtn, input[name="searchItem"], input[name="searchUA"]');
$(document).on('click change', '#bendingSearchBtn, #searchItem, #searchUA, #searchBendingCategory, #searchName, #searchMaterial', function(e) {
e.preventDefault();
console.log('검색 조건 변경됨:', $(this).attr('id'), $(this).val());
searchBendingItems();
});
// 라디오 버튼 클릭 시 동적 검색
$(document).on('change', 'input[name="searchItem"], input[name="searchUA"]', function(e) {
e.preventDefault();
console.log('라디오 버튼 변경됨:', $(this).attr('name'), $(this).val());
searchBendingItems();
});
// 라디오 버튼 클릭 시 즉시 검색 실행 (추가 이벤트 핸들러)
$(document).on('click', 'input[name="searchItem"], input[name="searchUA"]', function(e) {
setTimeout(function() {
console.log('라디오 버튼 클릭됨 - 검색 실행');
searchBendingItems();
}, 100);
});
// 라디오 버튼 라벨 클릭 시에도 검색 실행
$(document).on('click', 'label[for^="searchItem_"], label[for^="searchUA_"]', function(e) {
setTimeout(function() {
console.log('라디오 버튼 라벨 클릭됨 - 검색 실행');
searchBendingItems();
}, 100);
});
$(document).on('keypress', '#bendingSearchInput', function(e) {
if (e.which === 13) {
e.preventDefault();
console.log('검색어 엔터키 입력됨');
searchBendingItems();
}
});
// 검색 버튼에 대한 명시적 이벤트 핸들러 추가
$(document).on('click', '#bendingSearchBtn', function(e) {
e.preventDefault();
console.log('검색 버튼 클릭됨');
searchBendingItems();
});
$(document).on('click', '#resetSearchBtn', function(e) {
e.preventDefault();
$('#bendingSearchModal .form-select, #bendingSearchInput').val('');
$('#searchBendingCategory').val('케이스');
// 라디오 버튼 초기화
$('input[name="searchItem"]').prop('checked', false);
$('input[name="searchUA"]').prop('checked', false);
$('#searchItem_all').prop('checked', true);
$('#searchUA_all').prop('checked', true);
console.log('검색 조건 초기화됨');
searchBendingItems();
});
// 절곡바라시 생성 클릭시
$(document).on('click', '#makeBendingBtn', function(e) {
e.preventDefault();
// 중복 클릭 방지를 위한 플래그
if ($(this).data('clicked')) {
return;
}
// 클릭 플래그 설정
$(this).data('clicked', true);
// 새 창으로 절곡바라시 작성 폼 열기
var popupWidth = 1800;
var popupHeight = 800;
var left = (window.screen.width - popupWidth) / 2;
var top = (window.screen.height - popupHeight) / 2;
var mode = 'get';
var item_bending = '케이스';
var item_sep = $('input[name="firstitem"]:checked').val();
var model_UA = $('input[name="model_UA"]:checked').val();
var box_width = $('input[name="box_width"]').val();
var box_height = $('input[name="box_height"]').val();
var exit_direction = $('input[name="exit_direction"]').val();
var front_bottom_width = $('input[name="front_bottom_width"]').val();
var rail_width = $('input[name="rail_width"]').val();
var itemName = '';
var item_spec = '';
window.open('/bending/write_form.php?mode=' + mode + '&item_bending=' + item_bending + '&item_sep=' + item_sep + '&model_UA=' + model_UA + '&box_width=' + box_width + '&box_height=' + box_height + '&exit_direction=' + exit_direction + '&front_bottom_width=' + front_bottom_width + '&rail_width=' + rail_width + '&item_name=' + itemName + '&item_spec=' + item_spec, '_blank',
'width=' + popupWidth + ',height=' + popupHeight +
',left=' + left + ',top=' + top +
',scrollbars=yes,status=no,toolbar=no,location=no');
// 1초 후에 클릭 플래그 초기화
setTimeout(() => {
$(this).data('clicked', false);
}, 1000);
});
$(document).on('change', '#selectAllBendingItems', function() {
const isChecked = $(this).prop('checked');
$('#bendingSearchResults .bending-item-checkbox').prop('checked', isChecked).trigger('change');
console.log('전체 선택 상태 변경:', isChecked);
});
// 8. '선택 적용' 버튼 이벤트
$(document).on('click', '#applyBendingSelectionBtn', function(e) {
e.preventDefault();
// checkboxClickOrder 배열을 사용하여 선택된 순서대로 부품 데이터 수집
const orderedParts = [];
checkboxClickOrder.forEach(function(rowIndex, orderIndex) {
const $row = $('#bendingSearchResults tr').eq(rowIndex);
const $checkbox = $row.find('.bending-item-checkbox');
// 체크박스가 여전히 체크되어 있는지 확인
if ($checkbox.length > 0 && $checkbox.is(':checked')) {
const num = $checkbox.data('num');
if (num) {
orderedParts.push({ orderNumber: orderIndex + 1, num });
}
}
});
if (orderedParts.length === 0) {
alert('적용할 부품을 선택하세요.');
return;
}
console.log('선택된 부품 수:', orderedParts.length);
console.log('orderedParts:', orderedParts);
// 순서대로 부품 정보 가져오기
const fetchPromises = orderedParts.map(item =>
$.ajax({
url: "/bending/fetch_bending_detail.php",
type: "POST",
data: { num: item.num },
dataType: 'json'
})
);
// console.log('순서대로 정렬된 부품:', orderedParts);
Promise.all(fetchPromises).then(results => {
// 현재 assemblyParts 배열의 최대 순서 번호 찾기
const maxOrderNumber = assemblyParts.length > 0
? Math.max(...assemblyParts.map(part => part.orderNumber || 0))
: 0;
results.forEach((partData, index) => {
if (partData && !partData.error) {
// 새로운 순서 번호 할당 (기존 최대값 + 1부터 시작)
partData.orderNumber = maxOrderNumber + index + 1;
// 기본 수량 설정
partData.quantity = 1;
assemblyParts.push(partData);
}
});
renderAssemblyTable(); // 이 함수 내에서 재질별 폭합 테이블도 업데이트됨
$('#bendingSearchModal').modal('hide');
alertToast(`${results.length}개 부품이 순서대로 추가되었습니다.`);
}).catch(error => {
console.error('부품 정보 가져오기 오류:', error);
alert("부품 정보를 가져오는 중 오류가 발생했습니다.");
});
});
// =========================================================================
// [핵심 수정] 조립 부품 테이블 렌더링 함수
// =========================================================================
function renderAssemblyTable() {
const container = $("#assemblyTable");
container.empty();
if (assemblyParts.length === 0) {
container.html('<tr><td colspan="2" class="text-center text-muted">추가된 부품이 없습니다.</td></tr>');
// 재질별 폭합 테이블도 비움
updateMaterialSummaryTable([]);
return;
}
// 순서 번호로 정렬
const sortedParts = [...assemblyParts].sort((a, b) => {
const orderA = a.orderNumber || 999;
const orderB = b.orderNumber || 999;
return orderA - orderB;
});
sortedParts.forEach((part, partIndex) => {
part.orderNumber = partIndex + 1;
const componentWrapper = $(`
<tr class="component-block" data-part-index="${partIndex}">
<td style="width: 200px; vertical-align: top;">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center p-2">
<div class="d-flex align-items-center">
<input type="checkbox" class="form-check-input component-checkbox me-2" data-part-index="${partIndex}">
${part.orderNumber ? `<span class="badge bg-primary me-2">순서: ${part.orderNumber}</span>` : ''}
${part.imgdata ? `<img src="/bending/img/${part.imgdata}" class="search-zoomable-image" style="width: 40px; height: auto; object-fit: cover; margin-right: 8px;">` : ''}
<span class="fw-bold mx-2">${part.itemName}</span>
<span class="fw-bold mx-2">재질 : ${part.material}</span>
<div class="d-flex align-items-center ms-2">
<label class="form-label mb-0 me-1" style="font-size: 0.8rem;">수량:</label>
<input type="number" class="form-control form-control-sm quantity-input viewmode"
data-part-index="${partIndex}"
value="${part.quantity || 1}"
min="1"
style="width: 60px; font-size: 0.8rem;">
</div>
</div>
<div class="d-flex gap-1">
<button type="button" class="btn btn-primary btn-sm edit-part-btn viewmode" data-part-index="${partIndex}" data-part-num="${part.num}"><i class="bi bi-pencil"></i> 원본 수정</button>
<button type="button" class="btn btn-primary btn-sm new-edit-part-btn viewmode" data-part-index="${partIndex}" data-part-num="${part.num}"><i class="bi bi-file-earmark-plus"></i> 다른이름 저장</button>
<button type="button" class="btn btn-danger btn-sm remove-part-btn viewmode"><i class="bi bi-trash"></i> 부품 삭제</button>
</div>
</div>
<div class="card-body p-1">
<table class="table table-sm mb-0 component-inner-table"></table>
</div>
</div>
</td>
</tr>
`);
const innerTable = componentWrapper.find('.component-inner-table');
// 각 부품의 상세 데이터 파싱 - 안전한 파싱 함수 사용
const inputList = safeParseJson(part.inputList);
const bendingrateList = safeParseJson(part.bendingrateList);
const sumList = safeParseJson(part.sumList);
const colorList = safeParseJson(part.colorList).map(checked => checked ? '음영' : '');
const aList = safeParseJson(part.AList).map(checked => checked ? 'A각 ' : '');
const calcAfterList = []; // 새로 계산될 배열
console.log('Parsed data for part', partIndex, ':', {
inputList,
bendingrateList,
sumList,
colorList,
aList
});
// 연신율 계산
let accumulated = 0;
for(let i = 0; i < inputList.length; i++) {
const inp = parseInt(inputList[i]) || 0;
const bend = parseInt(bendingrateList[i]) || 0;
const diff = inp + bend; // 덧셈으로 수정
calcAfterList.push(diff);
accumulated += diff;
}
const rowsData = [
{ label: '번호', type: 'span', data: Array.from({length: inputList.length}, (_, i) => i + 1) , className: 'w40px' },
{ label: '입력', type: 'input', name: 'inputList', data: inputList, className: 'yellowBold w40px' },
{ label: '연신율', type: 'input', name: 'bendingrateList', data: bendingrateList },
{ label: '연신율계산 후', type: 'span', data: calcAfterList , className: 'w40px'},
{ label: '합계', type: 'span', data: sumList, className: 'orangeBlackBold w40px' },
{ label: '음영', type: 'span', name: 'colorList', data: colorList, className: 'w40px' },
{ label: 'A각 표시', type: 'span', name: 'AList', data: aList, className: 'w40px'}
];
console.log('inputList', inputList);
console.log('bendingrateList', bendingrateList);
console.log('sumList', sumList);
console.log('colorList', colorList);
console.log('aList', aList);
console.log('rowsData', rowsData);
rowsData.forEach(rowData => {
const $row = $('<tr>').append(`<td class="lightgray" style="width:120px;">${rowData.label}</td>`);
const $cell = $('<td>').addClass('input-container');
for (let i = 0; i < inputList.length; i++) {
const val = rowData.data[i];
let element;
if (rowData.type === 'input') {
element = $('<input type="text">')
.addClass(`form-control text-center ${rowData.className || ''}`)
.attr({
'data-part-index': partIndex,
'data-col-index': i,
'name': `${rowData.name}[${partIndex}]`,
'readonly': true
})
.val(val);
} else if (rowData.type === 'checkbox') {
element = $('<input type="checkbox">')
.addClass('form-check-input')
.attr({
'data-part-index': partIndex,
'data-col-index': i,
'name': `${rowData.name}[${partIndex}]`,
'readonly': true,
'disabled': true
})
.prop('checked', val === true || val === 'true');
} else {
// span 요소 생성
element = $('<span>')
.addClass(`form-control text-center readonly ${rowData.className || ''}`)
.text(val);
// 음영 행인 경우 조건부 스타일 적용
if (rowData.label === '음영' && val === '음영') {
element.addClass('bg-dark text-white');
}
// A각 표시 행인 경우 조건부 스타일 적용
if (rowData.label === 'A각 표시' && val === 'A각 ') {
element.addClass('bg-primary text-white');
}
}
$cell.append(element);
}
$row.append($cell);
innerTable.append($row);
});
container.append(componentWrapper);
});
// 재질별 폭합 테이블 업데이트
updateMaterialSummaryTable(sortedParts);
attachAssemblyTableEvents();
}
// 재질별 폭합 계산 및 테이블 업데이트 함수
function updateMaterialSummaryTable(parts) {
const materialSummary = {};
// 각 부품의 재질과 합계 마지막 값을 수집
parts.forEach(part => {
const material = part.material || '미지정';
const sumList = safeParseJson(part.sumList);
const quantity = parseInt(part.quantity) || 1; // 수량 기본값 1
// 합계 배열의 마지막 값 (가장 큰 값)을 가져옴
let lastSum = 0;
if (sumList && sumList.length > 0) {
// 숫자로 변환하여 가장 큰 값 찾기
const numericSums = sumList.map(val => parseInt(val) || 0);
lastSum = Math.max(...numericSums);
}
// 수량을 곱해서 계산
const totalSum = lastSum * quantity;
if (materialSummary[material]) {
materialSummary[material] += totalSum;
} else {
materialSummary[material] = totalSum;
}
});
// 전역 변수 material_summary 업데이트 (저장용)
window.material_summary = materialSummary;
material_summary = materialSummary; // 전역 변수도 업데이트
// 테이블 렌더링
const tableBody = $("#materialSummaryTable tbody");
tableBody.empty();
if (Object.keys(materialSummary).length === 0) {
tableBody.html('<tr><td colspan="2" class="text-center text-muted">재질별 폭합 데이터가 없습니다.</td></tr>');
return;
}
// 재질별로 정렬하여 테이블 생성
Object.keys(materialSummary).sort().forEach(material => {
const total = materialSummary[material];
const row = $(`
<tr style='width: 200px;'>
<td class="text-center fw-bold">${material}</td>
<td class="text-center fw-bold text-primary">${total.toFixed(0)}</td>
</tr>
`);
tableBody.append(row);
});
console.log('재질별 폭합 업데이트:', materialSummary);
}
// 안전한 JSON 파싱 함수
function safeParseJson(data) {
if (Array.isArray(data)) {
return data;
}
if (typeof data === 'string') {
try {
const parsed = JSON.parse(data);
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
console.warn('JSON 파싱 실패:', e);
return [];
}
}
return [];
}
// [신규] 조립 부품 테이블 이벤트 핸들러 부착 함수
function attachAssemblyTableEvents() {
// 부품 삭제
$("#assemblyTable .remove-part-btn").off('click').on('click', function() {
const partIndex = $(this).closest('.component-block').data('part-index');
assemblyParts.splice(partIndex, 1);
renderAssemblyTable(); // 이 함수 내에서 재질별 폭합 테이블도 업데이트됨
alertToast('부품이 삭제되었습니다.');
});
// 데이터 변경 시 assemblyParts 배열 실시간 업데이트
$("#assemblyTable input").off('input change').on('input change', function() {
const $el = $(this);
const partIndex = $el.data('part-index');
const colIndex = $el.data('col-index');
const nameAttr = $el.attr('name') || '';
const name = nameAttr.split('[')[0]; // inputList, bendingrateList 등
if(partIndex === undefined || colIndex === undefined || !nameAttr) return;
const part = assemblyParts[partIndex];
if (!part) return;
// 기존 배열 데이터 가져오기
let list = safeParseJson(part[name]);
if (!Array.isArray(list)) {
list = [];
}
// 배열 크기 확장 (필요시)
while (list.length <= colIndex) {
list.push('');
}
// 값 업데이트
list[colIndex] = (this.type === 'checkbox') ? this.checked : $el.val();
// assemblyParts 배열에 직접 저장 (JSON 문자열로 변환하지 않음)
part[name] = list;
console.log(`Updated ${name}[${colIndex}] = ${list[colIndex]} for part ${partIndex}`);
});
// 수량 입력 필드 이벤트 핸들러
$("#assemblyTable .quantity-input").off('input change').on('input change', function() {
const $el = $(this);
const partIndex = $el.data('part-index');
const quantity = parseInt($el.val()) || 1;
if (partIndex === undefined) return;
const part = assemblyParts[partIndex];
if (!part) return;
// 수량 업데이트
part.quantity = quantity;
// 재질별 폭합 테이블 업데이트
updateMaterialSummaryTable(assemblyParts);
console.log(`Updated quantity for part ${partIndex}: ${quantity}`);
});
// 체크박스 기반 순서 변경 기능 초기화
initializeCheckboxOrdering();
// 순서 관련 버튼 이벤트
$("#resetOrderBtn").off('click').on('click', function() {
resetOrder();
alertToast('순서가 초기화되었습니다.');
});
// 순서 위로/아래로 이동 버튼
$("#moveUpBtn").off('click').on('click', function() {
moveSelectedComponentsUp();
});
$("#moveDownBtn").off('click').on('click', function() {
moveSelectedComponentsDown();
});
}
// 조립 부품 테이블에서 체크박스 기반 순서 변경 기능
function initializeCheckboxOrdering() {
// 체크박스 선택 시 시각적 피드백
$(document).on('change', '.component-checkbox', function() {
const $row = $(this).closest('.component-block');
if (this.checked) {
$row.addClass('selected');
} else {
$row.removeClass('selected');
}
});
// 전체 선택 체크박스
$(document).on('change', '#selectAllComponents', function() {
const isChecked = $(this).prop('checked');
$('.component-checkbox').prop('checked', isChecked).trigger('change');
});
}
// 선택된 부품들을 위로 이동
function moveSelectedComponentsUp() {
const selectedCheckboxes = $('.component-checkbox:checked');
if (selectedCheckboxes.length === 0) {
alertToast('이동할 부품을 선택하세요.');
return;
}
const selectedIndices = selectedCheckboxes.map(function() {
return parseInt($(this).data('part-index'));
}).get().sort((a, b) => a - b);
// 위로 이동 가능한 부품들만 필터링
const movableIndices = selectedIndices.filter(index => index > 0);
if (movableIndices.length === 0) {
alertToast('선택된 부품들이 이미 맨 위에 있습니다.');
return;
}
// 위로 이동 실행
movableIndices.forEach(index => {
if (index > 0) {
reorderComponents(index, index - 1);
}
});
// 체크박스 상태 유지
setTimeout(() => {
selectedIndices.forEach(index => {
$(`.component-checkbox[data-part-index="${index - 1}"]`).prop('checked', true).trigger('change');
});
}, 100);
alertToast(`${movableIndices.length}개 부품이 위로 이동되었습니다.`);
}
// 선택된 부품들을 아래로 이동
function moveSelectedComponentsDown() {
const selectedCheckboxes = $('.component-checkbox:checked');
if (selectedCheckboxes.length === 0) {
alertToast('이동할 부품을 선택하세요.');
return;
}
const selectedIndices = selectedCheckboxes.map(function() {
return parseInt($(this).data('part-index'));
}).get().sort((a, b) => b - a); // 역순으로 정렬
// 아래로 이동 가능한 부품들만 필터링
const movableIndices = selectedIndices.filter(index => index < assemblyParts.length - 1);
if (movableIndices.length === 0) {
alertToast('선택된 부품들이 이미 맨 아래에 있습니다.');
return;
}
// 아래로 이동 실행
movableIndices.forEach(index => {
if (index < assemblyParts.length - 1) {
reorderComponents(index, index + 1);
}
});
// 체크박스 상태 유지
setTimeout(() => {
selectedIndices.forEach(index => {
$(`.component-checkbox[data-part-index="${index + 1}"]`).prop('checked', true).trigger('change');
});
}, 100);
alertToast(`${movableIndices.length}개 부품이 아래로 이동되었습니다.`);
}
// 부품 순서 재정렬 함수
function reorderComponents(fromIndex, toIndex) {
if (fromIndex === toIndex) return;
// 배열에서 요소 이동
const movedPart = assemblyParts.splice(fromIndex, 1)[0];
assemblyParts.splice(toIndex, 0, movedPart);
// 순서 번호 재할당
assemblyParts.forEach((part, index) => {
part.orderNumber = index + 1;
});
// 테이블 다시 렌더링 (재질별 폭합 테이블도 함께 업데이트)
renderAssemblyTable();
console.log('부품 순서가 변경되었습니다:', assemblyParts.map(p => ({ name: p.itemName, order: p.orderNumber })));
}
// 순서 초기화 함수
function resetOrder() {
assemblyParts.forEach((part, index) => {
part.orderNumber = index + 1;
});
renderAssemblyTable(); // 이 함수 내에서 재질별 폭합 테이블도 업데이트됨
// 모든 체크박스 해제
$('.component-checkbox').prop('checked', false).trigger('change');
}
// 10. 조립 부품 테이블의 모든 계산을 수행하고 이벤트를 연결하는 함수
function calculateAllComponents() {
$('.component-block').each(function() {
const partIndex = $(this).data('part-index');
const $componentTable = $(this).find('.component-inner-table');
// 계산 함수
const calculate = () => {
let accumulated = 0;
const inputs = $componentTable.find('input[name="inputList"]');
const bends = $componentTable.find('input[name="bendingrateList"]');
const calcs = $componentTable.find('tr:eq(3) span'); // 연신율 계산 후
const sums = $componentTable.find('tr:eq(4) span'); // 합계
inputs.each(function(i) {
const inpVal = parseInt($(this).val()) || 0;
const bendVal = parseInt(bends.eq(i).val()) || 0;
const diff = inpVal + bendVal; // 덧셈으로 수정
// 해당 부품의 `calcAfterList` span을 찾아 값 업데이트
calcs.eq(i).text(diff.toFixed(0));
accumulated += diff;
// 해당 부품의 `sumList` span을 찾아 값 업데이트
sums.eq(i).text(accumulated.toFixed(0));
// assemblyParts 배열 데이터도 업데이트
const part = assemblyParts[partIndex];
if (part) {
// 배열이 없으면 생성
if (!Array.isArray(part.inputList)) part.inputList = [];
if (!Array.isArray(part.bendingrateList)) part.bendingrateList = [];
if (!Array.isArray(part.sumList)) part.sumList = [];
// 배열 크기 확장
while (part.inputList.length <= i) part.inputList.push('');
while (part.bendingrateList.length <= i) part.bendingrateList.push('');
while (part.sumList.length <= i) part.sumList.push('');
part.inputList[i] = inpVal;
part.bendingrateList[i] = bendVal;
part.sumList[i] = accumulated.toFixed(0);
}
});
};
// 입력/연신율 변경 시 계산 다시 실행
$componentTable.find('input[name="inputList"], input[name="bendingrateList"]').on('input change', calculate);
// 체크박스 변경 시 데이터 업데이트
$componentTable.find('input[type="checkbox"]').on('change', function() {
const colIndex = $(this).data('col-index');
const name = $(this).closest('tr').find('td:first').text(); // 음영 or A각 표시
const part = assemblyParts[partIndex];
if (part) {
if (name.includes('음영')) {
if (!Array.isArray(part.colorList)) part.colorList = [];
while (part.colorList.length <= colIndex) part.colorList.push(false);
part.colorList[colIndex] = this.checked;
} else if (name.includes('A각')) {
if (!Array.isArray(part.AList)) part.AList = [];
while (part.AList.length <= colIndex) part.AList.push(false);
part.AList[colIndex] = this.checked;
}
}
});
// 초기 계산 실행
calculate();
});
}
// 11. 부품 삭제 이벤트 (이벤트 위임)
$("#assemblyTable").on("click", ".remove-part-btn", function() {
const partIndex = $(this).closest('.component-block').data('part-index');
assemblyParts.splice(partIndex, 1);
renderAssemblyTable(); // 테이블 전체를 다시 그림
alertToast('부품이 삭제되었습니다.');
});
// 12. 전체 삭제 버튼 이벤트
$("#removeAllPartBtn").on("click", function() {
if (assemblyParts.length === 0) {
alertToast('삭제할 부품이 없습니다.');
return;
}
Swal.fire({
title: '전체 부품 삭제',
text: `현재 ${assemblyParts.length}개의 부품이 있습니다. 모든 부품을 삭제하시겠습니까?`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: '전체 삭제',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
assemblyParts = []; // 모든 부품 삭제
renderAssemblyTable(); // 이 함수 내에서 재질별 폭합 테이블도 업데이트됨
alertToast('모든 부품이 삭제되었습니다.');
}
});
});
// 13. 부품 정보 업데이트 함수 (팝업에서 호출됨)
window.updateAssemblyPart = function(updatedPartData) {
console.log('부품 정보 업데이트 시작:', updatedPartData);
const partIndex = updatedPartData.partIndex;
// assemblyParts 배열에서 해당 부품 찾기
if (assemblyParts[partIndex]) {
// 기존 부품 정보 업데이트
const part = assemblyParts[partIndex];
// 기본 정보 업데이트
part.itemName = updatedPartData.itemName || part.itemName;
part.material = updatedPartData.material || part.material;
part.imgdata = updatedPartData.imgdata || part.imgdata;
part.num = updatedPartData.num || part.num;
// 배열 데이터 업데이트
if (updatedPartData.inputList) {
part.inputList = updatedPartData.inputList;
}
if (updatedPartData.bendingrateList) {
part.bendingrateList = updatedPartData.bendingrateList;
}
if (updatedPartData.sumList) {
part.sumList = updatedPartData.sumList;
}
if (updatedPartData.colorList) {
part.colorList = updatedPartData.colorList;
}
if (updatedPartData.AList) {
part.AList = updatedPartData.AList;
}
console.log('업데이트된 부품 정보:', part);
// 테이블 다시 렌더링
renderAssemblyTable();
// 성공 메시지
alertToast('부품 정보가 업데이트되었습니다.');
// 팝업 닫기
// setTimeout(() => {
// if (window.opener) {
// window.opener.focus();
// }
// }, 1000);
} else {
console.error('업데이트할 부품을 찾을 수 없습니다. partIndex:', partIndex);
alertToast('부품 정보 업데이트에 실패했습니다.');
}
};
// 조회인 경우 사용금지 처리
if (mode === 'view') {
$('.viewmode').prop('readonly', true);
// data-readonly 속성이 없는 viewmode 요소만 disabled 처리
// checkbox와 radio는 클릭 불가능하게 하고 시각적 강조
$('input[type="checkbox"], input[type="radio"]').each(function() {
$(this).addClass('readonly-checkbox readonly-radio');
});
} else {
$('.viewmode').prop('disabled', false).css({
'pointer-events': 'auto',
'opacity': '1'
});
}
// 저장 버튼 이벤트
$(document)
.off('click', '#saveBtn')
.on('click', '#saveBtn', function() {
console.log('saveBtn 클릭');
// 1) 이미지 요소 확인
const targetImg = document.getElementById('targetImage');
if (targetImg && targetImg.src) {
const imageDataUrl = targetImg.src;
console.log('imageDataUrl:', imageDataUrl);
// 2) 업로드용 file input 준비
let fileInput = document.getElementById('upfile');
if (!fileInput) {
fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.id = 'upfile';
fileInput.name = 'upfile';
fileInput.style.display = 'none';
document.querySelector('#myModal .modal-body')
.appendChild(fileInput);
}
// 3) 이미지 → Blob → File → input 설정
fetch(imageDataUrl)
.then(res => res.blob())
.then(blob => {
const file = new File([blob], 'drawing.png', { type: 'image/png' });
const dt = new DataTransfer();
dt.items.add(file);
fileInput.files = dt.files;
// 4) 검색 조건(payload) 구성
const payload = {
box_width: $('#box_width').val(),
box_height: $('#box_height').val(),
front_bottom_width: $('#front_bottom_width').val(),
rail_width: $('#rail_width').val(),
exit_direction: $('input[name="exit_direction"]:checked').val(),
search_keyword: $('#search_keyword').val()
};
console.log('payload:', payload);
// 5) 기존 이미지 인덱스 검색
const existingIndex = (shutterboxImages || []).findIndex(item =>
item.box_width === payload.box_width &&
item.box_height === payload.box_height &&
item.front_bottom_width === payload.front_bottom_width &&
item.rail_width === payload.rail_width &&
item.exit_direction === payload.exit_direction &&
item.search_keyword === payload.search_keyword
);
console.log('existingIndex:', existingIndex);
// 6) 이미지+폼 저장 함수
function saveAll(overwrite = false) {
const data = new FormData();
Object.entries(payload).forEach(([k, v]) => data.append(k, v));
data.append('upfile', file);
if (overwrite && existingIndex > -1) {
data.append('index', existingIndex);
}
// 폼 내부 JSON 필드 설정
$("#bending_components_data").val(JSON.stringify(assemblyParts));
if (material_summary && Object.keys(material_summary).length) {
$("#material_summary").val(JSON.stringify(material_summary));
} else {
$("#material_summary").val('');
}
// 6-1) 1차 AJAX: 이미지 저장
$.ajax({
url: '/shutterbox/put_shutterbox_image.php',
type: 'POST',
data: data,
processData: false,
contentType: false,
dataType: 'json',
success(res) {
if (!res.success) {
alert('이미지 저장 오류: ' + res.message);
return;
}
// 6-2) 2차 AJAX: 폼 데이터 저장
$.ajax({
url: '/shutterbox/process.php',
type: 'POST',
data: $("#board_form").serialize(),
dataType: 'json',
success(response) {
Toastify({
text: overwrite
? '덮어쓰기 및 저장 완료'
: '저장 완료',
duration: 1500,
close: true,
gravity: 'top',
position: 'center',
backgroundColor: '#4fbe87'
}).showToast();
setTimeout(() => {
window.close();
location.reload();
}, 1000);
},
error() {
alert('폼 데이터 저장 중 오류가 발생했습니다.');
}
});
},
error() {
alert('이미지 업로드 중 오류가 발생했습니다.');
}
});
}
// 7) 덮어쓰기 확인 → 저장 or 새로 저장
if (existingIndex > -1) {
Swal.fire({
title: '이미지 덮어쓰기',
text: '동일한 조건의 이미지가 이미 존재합니다. 덮어쓰시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: '덮어쓰기',
cancelButtonText: '취소'
}).then(result => {
if (result.isConfirmed) {
saveAll(true);
}
});
} else {
saveAll(false);
}
});
}
// 8) 이미지 없으면 폼만 저장
else {
console.log('이미지 없음 → 폼 데이터만 저장');
$("#bending_components_data").val(JSON.stringify(assemblyParts));
if (material_summary && Object.keys(material_summary).length) {
$("#material_summary").val(JSON.stringify(material_summary));
} else {
$("#material_summary").val('');
}
$.ajax({
url: '/shutterbox/process.php',
type: 'POST',
data: $("#board_form").serialize(),
dataType: 'json',
success(response) {
Toastify({
text: '저장 완료',
duration: 1500,
close: true,
gravity: 'top',
position: 'center',
backgroundColor: '#4fbe87'
}).showToast();
setTimeout(() => {
window.close();
location.reload();
}, 1000);
},
error() {
alert('폼 데이터 저장 중 오류가 발생했습니다.');
}
});
}
});
$("#deleteBtn").on("click", function() {
var level = '<?= $_SESSION["level"] ?>';
if (level !== '1') {
Swal.fire({
title: '삭제불가',
text: "관리자만 삭제 가능합니다.",
icon: 'error',
confirmButtonText: '확인'
});
return;
}
Swal.fire({
title: '자료 삭제',
text: "삭제는 신중! 정말 삭제하시겠습니까?",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '삭제',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
$("#mode").val('delete');
var formData = $("#board_form").serialize();
$.ajax({
url: "process.php",
type: "post",
data: formData,
success: function(response) {
Toastify({
text: "파일 삭제완료",
duration: 2000,
close: true,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)"
},
}).showToast();
$('#myModal').modal('hide');
location.reload();
},
error: function(jqxhr, status, error) {
console.log(jqxhr, status, error);
}
});
}
});
});
// 모달이 닫힐 때 drawBtn의 data('initialized') 해제
$(document).on('hidden.bs.modal', '#myModal', function() {
$('#drawBtn').removeData('initialized');
// 레이아웃 원복
const modalRow = document.querySelector('#myModal .modal-body .row');
if (modalRow) {
modalRow.classList.remove('drawing-mode');
}
// col-sm-4을 원래대로 복원
const colSm4 = document.querySelector('#myModal .col-sm-4');
if (colSm4) {
colSm4.classList.remove('col-sm-8');
colSm4.classList.add('col-sm-4');
// 강제로 스타일 적용
colSm4.style.width = '25%';
colSm4.style.flex = '0 0 25%';
colSm4.style.maxWidth = '25%';
}
});
function saveDrawingData(type, data) {
// 현재 상태를 실행 취소 스택에 저장
saveToUndoStack();
if (type === 'text') {
drawingData.push({
type,
data,
color: ctx.fillStyle || '#000000',
font: ctx.font || '16px Arial'
});
} else {
drawingData.push({ type, data, color: ctx.strokeStyle, lineWidth: ctx.lineWidth });
}
}
},
error: function(jqxhr, status, error) {
console.log("AJAX Error: ", status, error);
}
});
} // end of loadForm
function viewWork(num) {
// 이벤트의 기본 동작을 방지 (모달창 닫힘 방지)
event.preventDefault();
var tablename = $("#tablename").val();
var url = "view.php?mode=view&num=" + num + "&tablename=" + tablename;
customPopup(url, '절곡 작업지시서', 1200, 850);
}
// 신규 버튼 클릭 이벤트
$(document).off("click", "#newBtn").on("click", "#newBtn", function() {
loadForm('insert');
});
// 결합형태 이미지 등록 버튼 클릭 이벤트
$(document).on("click", ".registImageBtn", function() {
popupCenter('shutterboxlist.php' ,'' , 1600, 900);
});
// 절곡 부품 검색 관련 JavaScript
$(document).ready(function() {
// 절곡 부품 검색 버튼
$("#bendingSearchBtn").on("click", function() {
performBendingSearch();
});
// 라디오 버튼 변경 시 검색
$(document).on('change', 'input[name="searchItem"]', function() {
performBendingSearch();
});
$(document).on('change', 'input[name="searchUA"]', function() {
performBendingSearch();
});
// 검색 조건 초기화
$("#resetSearchBtn").on("click", function() {
// 라디오 버튼 초기화
$('#bendingSearchModal input[name="searchItem"]').prop('checked', false);
$('#bendingSearchModal input[name="searchUA"]').prop('checked', false);
$('#searchItem_all').prop('checked', true);
$('#searchUA_all').prop('checked', true);
// select 박스 초기화
$("#searchBendingCategory").val('');
$("#searchName").val('');
$("#searchMaterial").val('');
$("#bendingSearchInput").val('');
performBendingSearch();
});
// 전체 선택 체크박스
$("#selectAllBendingItems").on("change", function() {
var isChecked = $(this).is(":checked");
$("#bendingSearchResults input[type='checkbox']").prop("checked", isChecked);
});
// 선택 적용 버튼
$("#applyBendingSelectionBtn").on("click", function() {
var selectedItems = [];
$("#bendingSearchResults input[type='checkbox']:checked").each(function() {
var row = $(this).closest("tr");
var itemData = {
item_sep: row.find("td:eq(2)").text().trim(),
UA: row.find("td:eq(3)").text().trim(),
item_bending: row.find("td:eq(4)").text().trim(),
itemName: row.find("td:eq(5)").text().trim(),
material: row.find("td:eq(6)").text().trim(),
width_sum: row.find("td:eq(8)").text().trim()
};
selectedItems.push(itemData);
});
if (selectedItems.length > 0) {
// 선택된 아이템들을 조립 테이블에 추가
addSelectedItemsToAssembly(selectedItems);
$("#bendingSearchModal").modal('hide');
} else {
alert("선택된 아이템이 없습니다.");
}
});
// 절곡바라시 생성 버튼
$("#makeBendingBtn").on("click", function() {
// 절곡바라시 생성 로직
generateBendingBarasi();
});
// 이미지 클릭 이벤트 (동적으로 생성된 이미지에 대해서도 작동)
$(document).on('click', '.image-zoom', function(e) {
e.stopPropagation(); // 이벤트 버블링 방지
var imageSrc = $(this).data('src');
if (imageSrc) {
openImageZoom(imageSrc);
}
});
// 이미지 컨테이너 클릭 이벤트도 처리
$(document).on('click', '.image-zoom-container', function(e) {
e.stopPropagation(); // 이벤트 버블링 방지
var imageSrc = $(this).find('.image-zoom').data('src');
if (imageSrc) {
openImageZoom(imageSrc);
}
});
});
function performBendingSearch() {
var searchData = {
item_sep: $('input[name="searchItem"]:checked').val(),
UA: $('input[name="searchUA"]:checked').val(),
item_bending: $("#searchBendingCategory").val(),
itemName: $("#searchName").val(),
material: $("#searchMaterial").val(),
searchKeyword: $("#bendingSearchInput").val()
};
$.ajax({
url: "search_shutterbox_items.php",
type: "POST",
data: searchData,
dataType: "json",
success: function(response) {
var tbody = $("#bendingSearchResults");
tbody.empty();
if (response.success && response.data.length > 0) {
response.data.forEach(function(item, index) {
var row = `
<tr>
<td class="text-center">
<input type="checkbox" class="form-check-input" value="${item.num}">
</td>
<td class="text-center">${index + 1}</td>
<td>${item.item_sep || ''}</td>
<td>${item.UA || ''}</td>
<td>${item.item_bending || ''}</td>
<td>${item.itemName || ''}</td>
<td>${item.material || ''}</td>
<td class="text-center">
${item.image ? `<div class="image-zoom-container"><img src="${item.image}" alt="이미지" class="image-zoom" style="width:50px;height:50px;" data-src="${item.image}"></div>` : ''}
</td>
<td class="text-end">${item.width_sum || ''}</td>
</tr>
`;
tbody.append(row);
});
} else {
tbody.append('<tr><td colspan="9" class="text-center">검색 결과가 없습니다.</td></tr>');
}
},
error: function(xhr, status, error) {
console.error("검색 오류:", error);
$("#bendingSearchResults").html('<tr><td colspan="9" class="text-center">검색 중 오류가 발생했습니다.</td></tr>');
}
});
}
function addSelectedItemsToAssembly(selectedItems) {
// 조립 테이블에 선택된 아이템들을 추가하는 로직
// 이 함수는 fetch_modal.php에서 구현된 조립 기능과 연동됩니다
console.log("선택된 아이템들:", selectedItems);
// 모달이 열려있는 경우에만 실행
if ($("#myModal").is(":visible")) {
// 선택된 아이템들을 조립 테이블에 추가하는 로직
// 이 부분은 fetch_modal.php의 JavaScript와 연동되어야 합니다
}
}
function generateBendingBarasi() {
// 절곡바라시 생성 로직
console.log("절곡바라시 생성");
// 실제 구현은 별도 파일에서 처리
}
// 이미지 확대 기능
function openImageZoom(imageSrc) {
document.getElementById('zoomedImage').src = imageSrc;
document.getElementById('imageZoomOverlay').style.display = 'flex';
document.body.style.overflow = 'hidden'; // 스크롤 방지
}
function closeImageZoom() {
document.getElementById('imageZoomOverlay').style.display = 'none';
document.body.style.overflow = 'auto'; // 스크롤 복원
}
// 모달 외부 클릭으로 닫기
document.getElementById('imageZoomOverlay').addEventListener('click', function(event) {
if (event.target === this) {
closeImageZoom();
}
});
$(document).ready(function(){
// 로더 숨기기
var loader = document.getElementById('loadingOverlay');
if(loader)
loader.style.display = 'none';
// 방문기록 남김
saveMenuLog('<?=$title_message?>');
});
// shutterbox.json 파일의 내용을 JavaScript 배열로 로드
var shutterboxImages = <?php
$jsonFile = $_SERVER['DOCUMENT_ROOT'].'/shutterbox/shutterbox.json';
echo file_exists($jsonFile) ? file_get_contents($jsonFile) : '[]';
?>;
// 미니 합계표 팝업 기능 (ajax로 불러온 행에도 동작)
let miniSummaryTimeout;
$(document).on('mouseenter', '.mini-summary-trigger', function(e) {
e.stopPropagation();
const json = $(this).attr('data-summary');
if (!json) return;
let data;
try {
data = JSON.parse(json);
} catch (err) {
return;
}
// DB에서 가져온 데이터가 JSON 문자열일 수 있으므로 파싱 처리
let inputList = data.inputList;
let bendingrateList = data.bendingrateList;
let sumList = data.sumList;
let colorList = data.colorList;
let AList = data.AList;
let calcAfterList = data.calcAfterList;
// 문자열인 경우 JSON 파싱
if (typeof inputList === 'string') {
try { inputList = JSON.parse(inputList); } catch(e) { inputList = []; }
}
if (typeof bendingrateList === 'string') {
try { bendingrateList = JSON.parse(bendingrateList); } catch(e) { bendingrateList = []; }
}
if (typeof sumList === 'string') {
try { sumList = JSON.parse(sumList); } catch(e) { sumList = []; }
}
if (typeof colorList === 'string') {
try { colorList = JSON.parse(colorList); } catch(e) { colorList = []; }
}
if (typeof AList === 'string') {
try { AList = JSON.parse(AList); } catch(e) { AList = []; }
}
if (typeof calcAfterList === 'string') {
try { calcAfterList = JSON.parse(calcAfterList); } catch(e) { calcAfterList = []; }
}
// 배열이 아닌 경우 빈 배열로 설정
if (!Array.isArray(inputList)) inputList = [];
if (!Array.isArray(bendingrateList)) bendingrateList = [];
if (!Array.isArray(sumList)) sumList = [];
if (!Array.isArray(colorList)) colorList = [];
if (!Array.isArray(AList)) AList = [];
if (!Array.isArray(calcAfterList)) calcAfterList = [];
// 미니 테이블 생성 (bending/write_form.php와 동일한 구조)
let html = '<table class="table table-bordered table-sm mb-0">';
html += '<tr><th>번호</th>';
for(let i=1; i<=inputList.length; i++) html += `<td>${i}</td>`;
html += '</tr>';
html += '<tr><th>입력</th>';
inputList.forEach(v=>html+=`<td>${v}</td>`);
html += '</tr>';
html += '<tr><th>연신율</th>';
bendingrateList.forEach(v=>html+=`<td>${v}</td>`);
html += '</tr>';
html += '<tr><th>연신율계산 후</th>';
calcAfterList.forEach(v=>html+=`<td>${v}</td>`);
html += '</tr>';
html += '<tr><th>합계</th>';
sumList.forEach(v=>html+=`<td>${v}</td>`);
html += '</tr>';
html += '<tr><th>음영</th>';
colorList.forEach(v=>html+=`<td>${v?'음영':''}</td>`);
html += '</tr>';
html += '<tr><th>A각 표시</th>';
AList.forEach(v=>html+=`<td>${v?'A각':''}</td>`);
html += '</tr>';
html += '</table>';
// 기존 타임아웃 클리어
clearTimeout(miniSummaryTimeout);
// 팝업 위치 설정 - 마우스 왼쪽에 표시
const mouseX = e.clientX, mouseY = e.clientY;
const popupWidth = 400, popupHeight = 200; // 가로 길이 증가
let left = mouseX - popupWidth - 20, top = mouseY - 20; // 왼쪽에 표시
// 화면 경계 체크 - 왼쪽이 잘리면 오른쪽에 표시
if(left < 10) left = mouseX + 20; // 왼쪽 경계를 벗어나면 오른쪽에 표시
if(top + popupHeight > window.innerHeight) top = mouseY - popupHeight - 20; // 아래쪽 경계 체크
if(top < 10) top = 10; // 위쪽 경계 체크
// 팝업 표시
$('#miniSummaryPopup').html(html).css({
'left': left + 'px',
'top': top + 'px',
'display': 'block'
});
});
$(document).on('mouseleave', '.mini-summary-trigger', function(e) {
// 마우스가 셀을 벗어날 때 약간의 지연 후 팝업 닫기
miniSummaryTimeout = setTimeout(function() {
$('#miniSummaryPopup').fadeOut(200);
}, 100);
});
// 팝업에 마우스가 들어오면 팝업 유지
$('#miniSummaryPopup').on('mouseenter', function() {
clearTimeout(miniSummaryTimeout);
});
// 팝업에서 마우스가 벗어나면 팝업 닫기
$('#miniSummaryPopup').on('mouseleave', function() {
$(this).fadeOut(200);
});
$(document).on("click", "#modelFlatBtn", function() {
window.open('/shutterbox/model_flat.php', 'modelFlat', 'width=1200,height=900,scrollbars=yes');
});
// 테이블 정렬 기능
let currentSort = {
column: null,
direction: 'asc'
};
// 체크박스 클릭 순서를 추적하는 변수
let checkboxClickOrder = [];
// 정렬 헤더 클릭 이벤트
$(document).on('click', '.sortable-header', function() {
const sortType = $(this).data('sort');
const $table = $(this).closest('table');
const $tbody = $table.find('tbody');
const $rows = $tbody.find('tr').toArray();
// 이전 정렬 헤더에서 active 클래스 제거
$('.sortable-header').removeClass('active sort-asc sort-desc').addClass('sort-default');
// 현재 헤더에 active 클래스 추가
$(this).removeClass('sort-default').addClass('active');
// 정렬 방향 결정
if (currentSort.column === sortType && currentSort.direction === 'asc') {
currentSort.direction = 'desc';
$(this).addClass('sort-desc');
} else {
currentSort.direction = 'asc';
$(this).addClass('sort-asc');
}
currentSort.column = sortType;
// 행 정렬
$rows.sort(function(a, b) {
let aValue, bValue;
switch(sortType) {
case 'date':
// 등록일 정렬 (첫 번째 td의 텍스트)
aValue = $(a).find('td:eq(2)').text().trim();
bValue = $(b).find('td:eq(2)').text().trim();
break;
case 'category':
// 대분류 정렬
aValue = $(a).find('td:eq(3)').text().trim();
bValue = $(b).find('td:eq(3)').text().trim();
break;
case 'ua':
// 인정/비인정 정렬
aValue = $(a).find('td:eq(4)').text().trim();
bValue = $(b).find('td:eq(4)').text().trim();
break;
case 'bending':
// 절곡구분 정렬
aValue = $(a).find('td:eq(5)').text().trim();
bValue = $(b).find('td:eq(5)').text().trim();
break;
case 'direction':
// 점검구방향 정렬
aValue = $(a).find('td:eq(6)').text().trim();
bValue = $(b).find('td:eq(6)').text().trim();
break;
case 'box':
// 박스(가로X세로) 정렬
aValue = $(a).find('td:eq(7)').text().trim();
bValue = $(b).find('td:eq(7)').text().trim();
break;
case 'front':
// 전면부 밑면치수 정렬
aValue = $(a).find('td:eq(8)').text().trim();
bValue = $(b).find('td:eq(8)').text().trim();
break;
case 'rail':
// 레일(폭) 정렬 (숫자로 변환)
aValue = parseFloat($(a).find('td:eq(9)').text().trim()) || 0;
bValue = parseFloat($(b).find('td:eq(9)').text().trim()) || 0;
break;
case 'name':
// 품명 정렬
aValue = $(a).find('td:eq(10)').text().trim();
bValue = $(b).find('td:eq(10)').text().trim();
break;
case 'keyword':
// 품목검색어 정렬
aValue = $(a).find('td:eq(11)').text().trim();
bValue = $(b).find('td:eq(11)').text().trim();
break;
case 'material':
// 재질 정렬
aValue = $(a).find('td:eq(12)').text().trim();
bValue = $(b).find('td:eq(12)').text().trim();
break;
case 'width':
// 폭합 정렬 (숫자로 변환)
aValue = parseFloat($(a).find('td:eq(14)').text().trim()) || 0;
bValue = parseFloat($(b).find('td:eq(14)').text().trim()) || 0;
break;
default:
return 0;
}
// 문자열 비교
if (typeof aValue === 'string' && typeof bValue === 'string') {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
}
if (currentSort.direction === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
// 정렬된 행을 테이블에 다시 추가
$tbody.empty();
$rows.forEach(function(row) {
$tbody.append(row);
});
// 정렬 후 클릭 순서 배열 업데이트 (행 인덱스가 변경되었으므로)
const newClickOrder = [];
checkboxClickOrder.forEach(function(oldRowIndex) {
// 정렬 전의 행을 찾아서 정렬 후의 새로운 인덱스로 변환
const $oldRow = $rows[oldRowIndex];
const newIndex = $tbody.find('tr').index($oldRow);
if (newIndex !== -1) {
newClickOrder.push(newIndex);
}
});
checkboxClickOrder = newClickOrder;
// 정렬 후 행 클릭 이벤트 다시 바인딩
$tbody.find('tr').each(function() {
// 체크박스 변경 이벤트 추가
$(this).find('.bending-item-checkbox').off('change').on('change', function() {
const $row = $(this).closest('tr');
const rowIndex = $row.index();
if (this.checked) {
// 체크된 경우: 클릭 순서 배열에 추가 (중복 방지)
if (!checkboxClickOrder.includes(rowIndex)) {
checkboxClickOrder.push(rowIndex);
}
} else {
// 체크 해제된 경우: 클릭 순서 배열에서 제거
const removeIndex = checkboxClickOrder.indexOf(rowIndex);
if (removeIndex > -1) {
checkboxClickOrder.splice(removeIndex, 1);
}
}
updateOrderNumbers();
$row.toggleClass('table-active', this.checked);
});
// 행 클릭 이벤트 추가
$(this).css('cursor', 'pointer').off('click').on('click', function(e) {
if (e.target.type !== 'checkbox') {
const checkbox = $(this).find('.bending-item-checkbox');
checkbox.prop('checked', !checkbox.prop('checked')).trigger('change');
}
});
});
// 순서 번호는 정렬 시 재설정하지 않음 (사용자가 직접 선택한 순서 유지)
});
// 순서 번호 업데이트 함수
function updateOrderNumbers() {
const $rows = $('#bendingSearchResults tr');
// 모든 순서 번호 초기화
$rows.each(function() {
const $orderCell = $(this).find('.order-number');
if ($orderCell.length > 0) {
$orderCell.text('-');
}
});
// 클릭 순서에 따라 순서 번호 부여
checkboxClickOrder.forEach(function(rowIndex, orderIndex) {
const $row = $rows.eq(rowIndex);
const $checkbox = $row.find('.bending-item-checkbox');
// 체크박스가 여전히 체크되어 있는지 확인
if ($checkbox.length > 0 && $checkbox.is(':checked')) {
const $orderCell = $row.find('.order-number');
if ($orderCell.length > 0) {
$orderCell.text(orderIndex + 1);
}
}
});
// 디버깅용 로그
console.log('checkboxClickOrder:', checkboxClickOrder);
console.log('체크된 행 수:', $rows.find('.bending-item-checkbox:checked').length);
}
</script>
<?php include $_SERVER['DOCUMENT_ROOT'] . '/common/enlargeImage.php'; // 이미지 확대4배 JS코드 ?>
</body>
</html>