2403 lines
95 KiB
PHP
2403 lines
95 KiB
PHP
|
|
<?php require_once($_SERVER['DOCUMENT_ROOT'] . '/session.php');
|
||
|
|
|
||
|
|
if (!isset($_SESSION["name"])) {
|
||
|
|
$_SESSION["url"] = '/annualleave/index.php?user_name=' . $user_name;
|
||
|
|
sleep(1);
|
||
|
|
header("Location:/login/logout.php");
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
$title_message = '직원 연차';
|
||
|
|
?>
|
||
|
|
|
||
|
|
<?php include $_SERVER['DOCUMENT_ROOT'] . '/load_header.php' ?>
|
||
|
|
|
||
|
|
<link href="css/style.css" rel="stylesheet">
|
||
|
|
|
||
|
|
<!-- CSS (style.css 혹은 <style> 태그) -->
|
||
|
|
<style>
|
||
|
|
/* style.css 혹은 <style> 태그에 추가 */
|
||
|
|
#monthlyLeaveModal .modal-body {
|
||
|
|
max-height: 750px; /* 최대 높이 750px */
|
||
|
|
overflow-y: auto; /* 넘칠 때만 세로 스크롤 */
|
||
|
|
padding: 1rem; /* 패딩은 필요에 따라 조정 */
|
||
|
|
}
|
||
|
|
/* 테이블 헤더 고정 */
|
||
|
|
#monthlyLeaveModal .modal-body table thead th {
|
||
|
|
position: sticky;
|
||
|
|
top: 0;
|
||
|
|
background: #6c757d; /* 다크 그레이 */
|
||
|
|
color: #fff; /* 텍스트를 흰색으로 */
|
||
|
|
z-index: 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 연차 모달 스크롤 스타일 */
|
||
|
|
#myModal .modal-content {
|
||
|
|
max-height: 90vh; /* 뷰포트 높이의 90% */
|
||
|
|
overflow: hidden; /* 기본 오버플로우 숨김 */
|
||
|
|
}
|
||
|
|
|
||
|
|
#myModal .modal-body {
|
||
|
|
max-height: calc(90vh - 120px); /* 헤더와 푸터 높이 제외 */
|
||
|
|
overflow-y: auto; /* 세로 스크롤 활성화 */
|
||
|
|
padding: 1rem; /* 패딩 추가 */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 스크롤바 스타일링 */
|
||
|
|
#myModal .modal-body::-webkit-scrollbar {
|
||
|
|
width: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
#myModal .modal-body::-webkit-scrollbar-track {
|
||
|
|
background: #f1f1f1;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
#myModal .modal-body::-webkit-scrollbar-thumb {
|
||
|
|
background: #888;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
#myModal .modal-body::-webkit-scrollbar-thumb:hover {
|
||
|
|
background: #555;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|
||
|
|
<title> <?=$title_message?> </title>
|
||
|
|
</head>
|
||
|
|
|
||
|
|
<body>
|
||
|
|
|
||
|
|
<?php if($mycompany ==='경동')
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . '/myheader.php');
|
||
|
|
else
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . '/myheader1.php');
|
||
|
|
?>
|
||
|
|
|
||
|
|
<?php
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/common.php");
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||
|
|
$pdo = db_connect();
|
||
|
|
|
||
|
|
// 배열로 기본정보 불러옴
|
||
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/almember/load_DB.php");
|
||
|
|
|
||
|
|
// echo '<pre>';
|
||
|
|
// print_r($employee_json);
|
||
|
|
// echo '</pre>';
|
||
|
|
|
||
|
|
if (intval($level) == 1 )
|
||
|
|
$admin = 1;
|
||
|
|
else
|
||
|
|
$admin = 2;
|
||
|
|
|
||
|
|
$tablename = "eworks";
|
||
|
|
|
||
|
|
// 변수 초기화
|
||
|
|
$mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : '';
|
||
|
|
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
|
||
|
|
|
||
|
|
// 새로 추가: 소속 검색값 (corpSearch)
|
||
|
|
$corpSearch = isset($_REQUEST["corpSearch"]) ? $_REQUEST["corpSearch"] : $mycompany ;
|
||
|
|
$partSearch = isset($_REQUEST["partSearch"]) ? $_REQUEST["partSearch"] : "";
|
||
|
|
|
||
|
|
// corp.json 파일에서 소속 목록 불러오기
|
||
|
|
$corpFile = $_SERVER['DOCUMENT_ROOT'] . '/member/corp.json';
|
||
|
|
$corpData = [];
|
||
|
|
if (file_exists($corpFile)) {
|
||
|
|
$corpJson = file_get_contents($corpFile);
|
||
|
|
$corpData = json_decode($corpJson, true);
|
||
|
|
if (!is_array($corpData)) { $corpData = []; }
|
||
|
|
}
|
||
|
|
|
||
|
|
// part.json 파일에서 부서 목록 불러오기
|
||
|
|
$partFile = $_SERVER['DOCUMENT_ROOT'] . '/member/part.json';
|
||
|
|
$partData = [];
|
||
|
|
if (file_exists($partFile)) {
|
||
|
|
$partJson = file_get_contents($partFile);
|
||
|
|
$partData = json_decode($partJson, true);
|
||
|
|
if (!is_array($partData)) {
|
||
|
|
$partData = [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// holiday 테이블에서 휴일 목록 불러오기
|
||
|
|
$holidayDates = [];
|
||
|
|
try {
|
||
|
|
$holidaySql = "SELECT startdate, enddate FROM " . $DB . ".holiday WHERE is_deleted IS NULL";
|
||
|
|
$holidayStmt = $pdo->query($holidaySql);
|
||
|
|
$holidayRows = $holidayStmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
|
|
||
|
|
foreach ($holidayRows as $holidayRow) {
|
||
|
|
$startdate = $holidayRow['startdate'];
|
||
|
|
$enddate = $holidayRow['enddate'];
|
||
|
|
|
||
|
|
// enddate가 '0000-00-00'이거나 비어있으면 startdate만 휴일
|
||
|
|
if (empty($enddate) || $enddate === '0000-00-00' || $enddate === null) {
|
||
|
|
$holidayDates[] = $startdate;
|
||
|
|
} else {
|
||
|
|
// 기간 내의 모든 날짜를 배열에 추가
|
||
|
|
$start = new DateTime($startdate);
|
||
|
|
$end = new DateTime($enddate);
|
||
|
|
$end->modify('+1 day'); // 종료일 포함
|
||
|
|
|
||
|
|
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
|
||
|
|
foreach ($period as $date) {
|
||
|
|
$holidayDates[] = $date->format('Y-m-d');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (PDOException $e) {
|
||
|
|
// 오류 발생 시 빈 배열 유지
|
||
|
|
error_log("Holiday 데이터 로드 오류: " . $e->getMessage());
|
||
|
|
}
|
||
|
|
|
||
|
|
if(intval($level) !== 1) {
|
||
|
|
$AndisDeleted = " AND is_deleted IS NULL AND eworks_item='연차' ";
|
||
|
|
$WhereisDeleted = " where is_deleted IS NULL AND eworks_item='연차' ";
|
||
|
|
|
||
|
|
if ($search == "") {
|
||
|
|
if ($admin == 1) {
|
||
|
|
$sql = "SELECT * FROM " . $DB . "." . $tablename . $WhereisDeleted . " ORDER BY al_askdatefrom DESC, registdate DESC";
|
||
|
|
} else {
|
||
|
|
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE author LIKE '%$user_name%' " . $AndisDeleted . " ORDER BY al_askdatefrom DESC, registdate DESC";
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if ($admin == 1) {
|
||
|
|
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE (author LIKE '%$search%') " . $AndisDeleted . " ORDER BY al_askdatefrom DESC, registdate DESC";
|
||
|
|
} else {
|
||
|
|
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE (author = '$user_name') AND (author LIKE '%$search%') " . $AndisDeleted . " ORDER BY al_askdatefrom DESC, registdate DESC";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$stmt = $pdo->prepare($sql);
|
||
|
|
}
|
||
|
|
else // 관리자인 경우
|
||
|
|
{
|
||
|
|
// 기본 SQL 쿼리 (검색조건 미포함)
|
||
|
|
$sql = "SELECT * FROM {$DB}.{$tablename} ORDER BY num DESC";
|
||
|
|
|
||
|
|
// print '관리자';
|
||
|
|
|
||
|
|
// 검색 조건 구성: 검색어와 소속 및 부서 검색 조건을 모두 고려
|
||
|
|
$conditions = [" eworks_item='연차' ", " is_deleted IS NULL "];
|
||
|
|
$bindParams = [];
|
||
|
|
if ($mode == "search" or !empty($corpSearch) ) {
|
||
|
|
if (!empty($search)) {
|
||
|
|
// member 테이블의 모든 컬럼에 대해 LIKE 검색 (예제)
|
||
|
|
$columns = [];
|
||
|
|
$stmt = $pdo->query("SHOW COLUMNS FROM " . $DB . "." . $tablename);
|
||
|
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||
|
|
$columns[] = $row['Field'];
|
||
|
|
}
|
||
|
|
$searchConditions = [];
|
||
|
|
foreach ($columns as $index => $col) {
|
||
|
|
$paramName = ":search{$index}";
|
||
|
|
$searchConditions[] = "$col LIKE $paramName";
|
||
|
|
$bindParams[$paramName] = "%$search%";
|
||
|
|
}
|
||
|
|
$conditions[] = "(" . implode(" OR ", $searchConditions) . ")";
|
||
|
|
}
|
||
|
|
if (!empty($corpSearch)) {
|
||
|
|
// 소속(division) 검색 조건 추가
|
||
|
|
$conditions[] = "al_company LIKE :corpSearch";
|
||
|
|
$bindParams[":corpSearch"] = "%$corpSearch%";
|
||
|
|
}
|
||
|
|
if (!empty($partSearch)) {
|
||
|
|
// 부서(part) 검색 조건 추가
|
||
|
|
$conditions[] = "al_part LIKE :partSearch";
|
||
|
|
$bindParams[":partSearch"] = "%$partSearch%";
|
||
|
|
}
|
||
|
|
if (!empty($conditions)) {
|
||
|
|
$sql = "SELECT * FROM {$DB}.{$tablename} WHERE " . implode(" AND ", $conditions) . " ORDER BY num DESC";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$stmt = $pdo->prepare($sql);
|
||
|
|
if (!empty($bindParams)) {
|
||
|
|
foreach ($bindParams as $paramName => $paramValue) {
|
||
|
|
$stmt->bindValue($paramName, $paramValue, PDO::PARAM_STR);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// print $sql ;
|
||
|
|
|
||
|
|
// echo '<pre>';
|
||
|
|
// print_r($conditions);
|
||
|
|
// echo '</pre>';
|
||
|
|
|
||
|
|
// 여기서 execute()
|
||
|
|
$stmt->execute();
|
||
|
|
|
||
|
|
// 실행 후 결과를 fetchAll()하거나 rowCount() 확인
|
||
|
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
|
$total_row = count($rows);
|
||
|
|
|
||
|
|
// print $total_row ;
|
||
|
|
// print $eworks_level;
|
||
|
|
|
||
|
|
?>
|
||
|
|
|
||
|
|
<form name="board_form" id="board_form" method="post" >
|
||
|
|
|
||
|
|
<input type="hidden" id="username" name="username" value="<?= isset($user_name) ? $user_name : '' ?>">
|
||
|
|
|
||
|
|
<?php if ($chkMobile == false) { ?>
|
||
|
|
<div class="container">
|
||
|
|
<?php } else { ?>
|
||
|
|
<div class="container-fluid">
|
||
|
|
<?php } ?>
|
||
|
|
|
||
|
|
<!-- 월별 연차사용 Modal -->
|
||
|
|
<div id="monthlyLeaveModal" class="modal" tabindex="-1">
|
||
|
|
<div class="modal-dialog modal-fullscreen"> <!-- modal-dialog-scrollable 제거 -->
|
||
|
|
<div class="modal-content">
|
||
|
|
|
||
|
|
<div class="modal-header">
|
||
|
|
<h5 class="modal-title">월별 연차사용</h5>
|
||
|
|
<button type="button" class="btn-close monthlyClose" aria-label="Close"></button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="modal-body">
|
||
|
|
<div class="d-flex mb-3">
|
||
|
|
<select id="monthYearSelect" class="form-select w-auto me-2">
|
||
|
|
<?php
|
||
|
|
$curr = date('Y');
|
||
|
|
echo "<option value='{$curr}' selected>{$curr}</option>";
|
||
|
|
echo "<option value='".($curr-1)."'>".($curr-1)."</option>";
|
||
|
|
?>
|
||
|
|
</select>
|
||
|
|
<select id="corpSelect" class="form-select w-auto">
|
||
|
|
<option value="">전체</option>
|
||
|
|
<option value="경동">경동</option>
|
||
|
|
<option value="주일">주일</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<div id="monthlyLeaveContent"><!-- AJAX 로드된 테이블 --></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="d-flex justify-content-center align-items-center mt-3 mb-3">
|
||
|
|
<span class=" fs-5"> <?=$title_message?> </span>
|
||
|
|
<button type="button" class="btn btn-dark btn-sm mx-2" onclick='location.reload()'> <i class="bi bi-arrow-clockwise"></i> </button>
|
||
|
|
|
||
|
|
<?php if ($admin == 1 || strval($eworks_level) == '2') { ?>
|
||
|
|
<!-- <button type="button" id="openAlmemberBtn" class="btn btn-success btn-sm me-2">
|
||
|
|
<i class="bi bi-pencil-square"></i> 직원 정보 -->
|
||
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="location.href='./admin.php'">
|
||
|
|
<i class="bi bi-pencil-square"></i>
|
||
|
|
관리자모드
|
||
|
|
</button>
|
||
|
|
<? } ?>
|
||
|
|
</div>
|
||
|
|
<div class="d-flex justify-content-center align-items-center mt-3 mb-1">
|
||
|
|
<h6 class="text-center mb-1">
|
||
|
|
<div class="d-flex justify-content-center align-items-center mb-1">
|
||
|
|
<select id="yearSelect" class="form-select form-select-sm d-inline w-auto">
|
||
|
|
<?php
|
||
|
|
$currentYear = date("Y");
|
||
|
|
for ($year = $currentYear; $year >= $currentYear - 3; $year--) {
|
||
|
|
echo "<option value='{$year}'" . ($year == $currentYear ? " selected" : "") . ">{$year}</option>";
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
</select>
|
||
|
|
년도 <?=$user_name?> 님
|
||
|
|
</div>
|
||
|
|
<div class="d-flex justify-content-center align-items-center mb-1">
|
||
|
|
<table class="table table-bordered text-center">
|
||
|
|
<tbody>
|
||
|
|
<tr>
|
||
|
|
<td class="table-success fs-6"><span class="badge bg-success">발생일</span></td>
|
||
|
|
<td class="table-success fs-6"><span class="badge bg-danger">전년도 선사용</span></td>
|
||
|
|
<td class="table-primary fs-6"><span class="badge bg-primary">사용일</span></td>
|
||
|
|
<td class="table-secondary fs-6"><span class="badge bg-secondary">잔여일</span></td>
|
||
|
|
</tr>
|
||
|
|
<tr>
|
||
|
|
<td class="text-success fs-6" id="totalDays"><?=$total?></td>
|
||
|
|
<td class="text-danger fs-6" id="previous_usageDays"><?=$previous_usage?></td>
|
||
|
|
<td class="text-primary fs-6" id="usedDays"><?=$thisyeartotalusedday?></td>
|
||
|
|
<td class="text-dark fs-6" id="remainingDays"><?=$thisyeartotalremainday?></td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</h6>
|
||
|
|
</div>
|
||
|
|
<div class="d-flex justify-content-center mt-1 mb-4">
|
||
|
|
<h6 style="background-color:#a5a5a5;"> 결재완료 후 삭제 불가 </h6>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="d-flex justify-content-center align-items-center mt-2 mb-2">
|
||
|
|
|
||
|
|
<?php if (intval($level) === 1) : ?>
|
||
|
|
<i class="bi bi-caret-right"></i> <?= $total_row ?>
|
||
|
|
<!-- 소속 검색 select 추가 -->
|
||
|
|
<div class="inputWrap30 mx-1">
|
||
|
|
<select name="corpSearch" id="corpSearch" class="form-select w100px mx-1" style="font-size: 0.9rem; height: 32px;">
|
||
|
|
<option value=""><?= "(소속)" ?></option>
|
||
|
|
<?php foreach($corpData as $corp): ?>
|
||
|
|
<option value="<?= htmlspecialchars($corp, ENT_QUOTES, 'UTF-8') ?>" <?= ($corpSearch === $corp) ? 'selected' : '' ?>>
|
||
|
|
<?= htmlspecialchars($corp, ENT_QUOTES, 'UTF-8') ?>
|
||
|
|
</option>
|
||
|
|
<?php endforeach; ?>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<!-- 부서 검색 select 추가 -->
|
||
|
|
<div class="inputWrap30 mx-1">
|
||
|
|
<select name="partSearch" id="partSearch" class="form-select w120px mx-1" style="font-size: 0.9rem; height: 32px;">
|
||
|
|
<option value=""><?= "(부서)" ?></option>
|
||
|
|
<!-- 옵션은 JS에서 동적으로 채워짐 -->
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<input type="text" name="search" id="search" class="form-control me-1" style="width:150px;height:32px;" value="<?=$search?>" onkeydown="JavaScript:SearchEnter();" placeholder="검색어">
|
||
|
|
<button type="button" id="searchBtn" class="btn btn-dark btn-sm me-1"> <i class="bi bi-search"></i> 검색 </button>
|
||
|
|
|
||
|
|
<?php endif ?>
|
||
|
|
<button type="button" id="writeBtn" class="btn btn-dark btn-sm mx-2"> <i class="bi bi-pencil-square"></i> 신청 </button>
|
||
|
|
<?php if ($admin == 1 || strval($eworks_level) == '2') { ?>
|
||
|
|
<button type="button" id="monthlyLeaveBtn" class="btn btn-secondary btn-sm mx-2"> <i class="bi bi-calendar-event"></i> 월별 연차사용 </button>
|
||
|
|
<?php } ?>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="row d-flex justify-content-center">
|
||
|
|
<table class="table table-hover" id="myTable">
|
||
|
|
<thead class="table-primary">
|
||
|
|
<tr>
|
||
|
|
<th class="text-center">번호</th>
|
||
|
|
<th class="text-center">접수일</th>
|
||
|
|
<th class="text-center">회사</th>
|
||
|
|
<th class="text-center">부서</th>
|
||
|
|
<th class="text-center">성명</th>
|
||
|
|
<th class="text-center">시작일</th>
|
||
|
|
<th class="text-center">종료일</th>
|
||
|
|
<th class="text-center">사용일수</th>
|
||
|
|
<th class="text-center">사유</th>
|
||
|
|
<th class="text-center text-primary">결재권자</th>
|
||
|
|
<th class="text-center">결재상태</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<?php
|
||
|
|
$start_num = $total_row;
|
||
|
|
|
||
|
|
// 필요하다면 $rows를 순회
|
||
|
|
foreach ($rows as $row) {
|
||
|
|
include "rowDBask.php";
|
||
|
|
switch ($status) {
|
||
|
|
case 'send':
|
||
|
|
$statusstr = '결재요청';
|
||
|
|
break;
|
||
|
|
case 'ing':
|
||
|
|
$statusstr = '결재중';
|
||
|
|
break;
|
||
|
|
case 'end':
|
||
|
|
$statusstr = '결재완료';
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
$statusstr = '';
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
<tr onclick="loadForm('update','<?=$num?>');">
|
||
|
|
<td class="text-center"><?=$start_num?></td>
|
||
|
|
<td class="text-center"><?=iconv_substr($registdate, 5, 5, "utf-8")?></td>
|
||
|
|
<td class="text-center"><?=$al_company?></td>
|
||
|
|
<td class="text-center"><?=$al_part?></td>
|
||
|
|
<td class="text-center"><?=$author?></td>
|
||
|
|
<td class="text-center"><?=iconv_substr($al_askdatefrom, 5, 5, "utf-8")?></td>
|
||
|
|
<td class="text-center"><?=iconv_substr($al_askdateto, 5, 5, "utf-8")?></td>
|
||
|
|
<td class="text-center"><?=$al_usedday?></td>
|
||
|
|
<td class="text-center"><?=$al_content?></td>
|
||
|
|
<td class="text-center text-primary"><?=$e_line?></td>
|
||
|
|
<td class="text-center"><?=$statusstr?></td>
|
||
|
|
</tr>
|
||
|
|
<?php
|
||
|
|
$start_num--;
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
<div class="d-flex justify-content-center mt-5 mb-5">
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Modal -->
|
||
|
|
<div id="myModal" class="modal" >
|
||
|
|
<div class="modal-content" style="width:600px;">
|
||
|
|
<div class="modal-header">
|
||
|
|
<span class="modal-title">연차</span>
|
||
|
|
<span class="close closeBtn">×</span>
|
||
|
|
</div>
|
||
|
|
<div class="modal-body">
|
||
|
|
<div class="custom-card"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</form>
|
||
|
|
<div class="container mt-3 mb-3">
|
||
|
|
<? include '../footer.php'; ?>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
var ajaxRequest_write = null;
|
||
|
|
var ajaxRequest = null;
|
||
|
|
|
||
|
|
var dataTable; // DataTables 인스턴스 전역 변수
|
||
|
|
var annualleavepageNumber; // 현재 페이지 번호 저장을 위한 전역 변수
|
||
|
|
|
||
|
|
$(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",
|
||
|
|
"search": "Live Search:"
|
||
|
|
},
|
||
|
|
"order": [
|
||
|
|
[0, 'desc']
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
// 페이지 번호 복원 (초기 로드 시)
|
||
|
|
var savedPageNumber = getCookie('annualleavepageNumber');
|
||
|
|
if (savedPageNumber) {
|
||
|
|
dataTable.page(parseInt(savedPageNumber) - 1).draw(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 페이지 변경 이벤트 리스너
|
||
|
|
dataTable.on('page.dt', function() {
|
||
|
|
var annualleavepageNumber = dataTable.page.info().page + 1;
|
||
|
|
setCookie('annualleavepageNumber', annualleavepageNumber, 10); // 쿠키에 페이지 번호 저장
|
||
|
|
});
|
||
|
|
|
||
|
|
// 페이지 길이 셀렉트 박스 변경 이벤트 처리
|
||
|
|
$('#myTable_length select').on('change', function() {
|
||
|
|
var selectedValue = $(this).val();
|
||
|
|
dataTable.page.len(selectedValue).draw(); // 페이지 길이 변경 (DataTable 파괴 및 재초기화 없이)
|
||
|
|
|
||
|
|
// 변경 후 현재 페이지 번호 복원
|
||
|
|
savedPageNumber = getCookie('annualleavepageNumber');
|
||
|
|
if (savedPageNumber) {
|
||
|
|
dataTable.page(parseInt(savedPageNumber) - 1).draw(false);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 파일 업로드 이벤트를 전역적으로 한 번만 바인딩
|
||
|
|
bindFileUploadEvents();
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
function SearchEnter() {
|
||
|
|
if (event.keyCode == 13) {
|
||
|
|
document.getElementById('board_form').submit();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadForm(mode, num = null, al_company = null, al_part = null) {
|
||
|
|
if (num == null) {
|
||
|
|
$("#mode").val('insert');
|
||
|
|
} else {
|
||
|
|
$("#mode").val('update');
|
||
|
|
$("#num").val(num);
|
||
|
|
}
|
||
|
|
|
||
|
|
// alert( al_company + al_part);
|
||
|
|
$.ajax({
|
||
|
|
type: "POST",
|
||
|
|
url: "fetch_modal.php",
|
||
|
|
data: { mode: mode, num: num, al_company : al_company, al_part: al_part },
|
||
|
|
dataType: "html",
|
||
|
|
success: function(response) {
|
||
|
|
document.querySelector(".modal-body .custom-card").innerHTML = response;
|
||
|
|
$("#myModal").show();
|
||
|
|
|
||
|
|
// 모달이 로드된 후 holiday 데이터 다시 초기화 (fetch_modal.php의 스크립트 실행 대기)
|
||
|
|
setTimeout(function() {
|
||
|
|
// fetch_modal.php에서 전달된 holiday 데이터가 있는지 확인하고 업데이트
|
||
|
|
if (typeof window.holidaySet === 'undefined') {
|
||
|
|
// holidaySet이 없으면 기본값으로 초기화
|
||
|
|
window.holidaySet = new Set(holidayDates || []);
|
||
|
|
}
|
||
|
|
// holidaySet이 이미 있으면 그대로 사용 (fetch_modal.php에서 업데이트됨)
|
||
|
|
}, 100);
|
||
|
|
|
||
|
|
$("#closeBtn").off("click").on("click", function() {
|
||
|
|
$("#myModal").hide();
|
||
|
|
});
|
||
|
|
|
||
|
|
$(".closeBtn").off("click").on("click", function() {
|
||
|
|
$("#myModal").hide();
|
||
|
|
});
|
||
|
|
|
||
|
|
// PHP에서 생성된 JSON 데이터를 JavaScript 변수로 변환
|
||
|
|
var employeeData = <?= $employee_json ?>;
|
||
|
|
|
||
|
|
//console.log('employeeData', employeeData);
|
||
|
|
|
||
|
|
// 동적으로 추가된 select 요소에 이벤트 바인딩 (중복 방지)
|
||
|
|
$(document).off('change', '#author').on('change', '#author', function() {
|
||
|
|
var selectedName = $(this).val(); // 선택된 성명
|
||
|
|
|
||
|
|
$.ajax({
|
||
|
|
url: "get_employee_info.php",
|
||
|
|
type: "post",
|
||
|
|
data: { name: selectedName },
|
||
|
|
dataType: "json",
|
||
|
|
success: function(data){
|
||
|
|
//alert(data);
|
||
|
|
// console.log(data);
|
||
|
|
if (data) {
|
||
|
|
// 업데이트: 회사, 부서, 입사일
|
||
|
|
if ($("#al_company").length) $("#al_company").val(data.division);
|
||
|
|
if ($("#al_part").length) $("#al_part").val(data.part);
|
||
|
|
if ($("#dateofentry").length) $("#dateofentry").val(data.enterDate);
|
||
|
|
if ($("#referencedate").length) $("#referencedate").val(data.referencedate);
|
||
|
|
if ($("#initial_less_than_one_year").length) $("#initial_less_than_one_year").val(data.initial_less_than_one_year);
|
||
|
|
if ($("#continueYear").length) $("#continueYear").val(data.continueYear);
|
||
|
|
if ($("#service_based").length) $("#service_based").val(data.service_based);
|
||
|
|
if ($("#previous_year_usage").length) $("#previous_year_usage").val(data.previous_year_usage);
|
||
|
|
if ($("#totalremainday").length) $("#totalremainday").val(data.available);
|
||
|
|
}
|
||
|
|
|
||
|
|
},
|
||
|
|
error: function(xhr, status, error){
|
||
|
|
console.log("Error: " + error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
// 저장 버튼 (중복 방지)
|
||
|
|
$(document).off('click', '#saveBtn').on('click', '#saveBtn', function() {
|
||
|
|
var num = $("#num").val();
|
||
|
|
var part = $("#part").val();
|
||
|
|
var status = $("#status").val();
|
||
|
|
var user_name = $("#user_name").val();
|
||
|
|
var admin = '<?= $admin ?>';
|
||
|
|
|
||
|
|
if(status=='send' || admin === '1') {
|
||
|
|
if(Number(num)>0)
|
||
|
|
$("#mode").val('modify');
|
||
|
|
else
|
||
|
|
$("#mode").val('insert');
|
||
|
|
|
||
|
|
// savetext div의 HTML 내용을 가져옴
|
||
|
|
var htmlContent = document.getElementById('savetext').innerHTML;
|
||
|
|
|
||
|
|
$("#htmltext").val(encodeURIComponent(htmlContent));
|
||
|
|
|
||
|
|
// temp_key를 form에 추가
|
||
|
|
var tempKey = $("#temp_key").val();
|
||
|
|
if (tempKey) {
|
||
|
|
$('<input>').attr({
|
||
|
|
type: 'hidden',
|
||
|
|
name: 'temp_key',
|
||
|
|
value: tempKey
|
||
|
|
}).appendTo('#board_form');
|
||
|
|
}
|
||
|
|
|
||
|
|
$.ajax({
|
||
|
|
url: "insert_ask.php",
|
||
|
|
type: "post",
|
||
|
|
data: $("#board_form").serialize(),
|
||
|
|
dataType:"json",
|
||
|
|
success : function(data) {
|
||
|
|
console.log(data);
|
||
|
|
|
||
|
|
// 파일 업로드 처리 (Google Drive API 사용)
|
||
|
|
var upfileInput = document.getElementById('upfile');
|
||
|
|
var upfileimageInput = document.getElementById('upfileimage');
|
||
|
|
|
||
|
|
if (upfileInput && upfileInput.files.length > 0) {
|
||
|
|
uploadFilesToGoogleDrive(upfileInput.files, 'attached', data.num);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (upfileimageInput && upfileimageInput.files.length > 0) {
|
||
|
|
uploadFilesToGoogleDrive(upfileimageInput.files, 'image', data.num);
|
||
|
|
}
|
||
|
|
|
||
|
|
Toastify({
|
||
|
|
text: "파일 저장완료",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
style: {
|
||
|
|
background: "linear-gradient(to right, #00b09b, #96c93d)"
|
||
|
|
},
|
||
|
|
}).showToast();
|
||
|
|
setTimeout(function() {
|
||
|
|
$("#myModal").modal('hide');
|
||
|
|
location.reload();
|
||
|
|
}, 1000);
|
||
|
|
|
||
|
|
},
|
||
|
|
error : function(jqxhr, status, error) {
|
||
|
|
console.log(jqxhr, status, error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
Toastify({
|
||
|
|
text: "본인과 관리자만 수정 가능",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
style: {
|
||
|
|
background: "linear-gradient(to right, #00b09b, #96c93d)"
|
||
|
|
},
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 삭제 버튼 (중복 방지)
|
||
|
|
$(document).off('click', '#deleteBtn').on('click', '#deleteBtn', function() {
|
||
|
|
var level = '<?= $_SESSION["level"] ?>';
|
||
|
|
var user_id = '<?php echo $user_id; ?>';
|
||
|
|
var author_id = '<?php echo $author_id; ?>';
|
||
|
|
|
||
|
|
if (level !== '1' && user_id !== author_id) {
|
||
|
|
Swal.fire({
|
||
|
|
title: '삭제불가',
|
||
|
|
text: "작성자와 관리자만 삭제 가능합니다.",
|
||
|
|
icon: 'error',
|
||
|
|
confirmButtonText: '확인'
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
Swal.fire({
|
||
|
|
title: '자료 삭제',
|
||
|
|
text: "삭제는 신중! 정말 삭제하시겠습니까?",
|
||
|
|
icon: 'warning',
|
||
|
|
showCancelButton: true,
|
||
|
|
confirmButtonColor: '#3085d6',
|
||
|
|
cancelButtonColor: '#d33',
|
||
|
|
confirmButtonText: '삭제',
|
||
|
|
cancelButtonText: '취소'
|
||
|
|
}).then((result) => {
|
||
|
|
if (result.isConfirmed) {
|
||
|
|
$("#mode").val('delete');
|
||
|
|
var form = $('#board_form')[0];
|
||
|
|
var formData = new FormData(form);
|
||
|
|
|
||
|
|
formData.set('mode', $("#mode").val());
|
||
|
|
formData.set('num', $("#num").val());
|
||
|
|
|
||
|
|
$.ajax({
|
||
|
|
enctype: 'multipart/form-data',
|
||
|
|
processData: false,
|
||
|
|
contentType: false,
|
||
|
|
cache: false,
|
||
|
|
timeout: 1000000,
|
||
|
|
url: "insert_ask.php",
|
||
|
|
type: "post",
|
||
|
|
data: formData,
|
||
|
|
dataType: "json",
|
||
|
|
success: function(data) {
|
||
|
|
console.log(data);
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 신청일 변경시 종료일도 변경함 (중복 방지)
|
||
|
|
$(document).off('change', '#al_askdatefrom').on('change', '#al_askdatefrom', function(){
|
||
|
|
var radioVal = $('input[name="al_item"]:checked').val();
|
||
|
|
// console.log(radioVal);
|
||
|
|
$('#al_askdateto').val($("#al_askdatefrom").val());
|
||
|
|
|
||
|
|
const result = getDateDiff($("#al_askdatefrom").val(), $("#al_askdateto").val());
|
||
|
|
|
||
|
|
switch(radioVal)
|
||
|
|
{
|
||
|
|
case '연차' :
|
||
|
|
$('#al_usedday').val(result);
|
||
|
|
break;
|
||
|
|
case '오전반차' :
|
||
|
|
case '오후반차' :
|
||
|
|
$('#al_usedday').val(result/2);
|
||
|
|
break;
|
||
|
|
case '오전반반차' :
|
||
|
|
case '오후반반차' :
|
||
|
|
$('#al_usedday').val(result/4);
|
||
|
|
break;
|
||
|
|
case '경조사' :
|
||
|
|
$('#al_usedday').val(0);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
$(document).off('change', 'input[name="al_item"]').on('change', 'input[name="al_item"]', function(){
|
||
|
|
var radioVal = $('input[name="al_item"]:checked').val();
|
||
|
|
console.log(radioVal);
|
||
|
|
// $('#al_askdateto').val($("#al_askdatefrom").val());
|
||
|
|
|
||
|
|
const result = getDateDiff($("#al_askdatefrom").val(), $("#al_askdateto").val());
|
||
|
|
|
||
|
|
switch(radioVal)
|
||
|
|
{
|
||
|
|
case '연차' :
|
||
|
|
$('#al_usedday').val(result);
|
||
|
|
break;
|
||
|
|
case '오전반차' :
|
||
|
|
case '오후반차' :
|
||
|
|
$('#al_usedday').val(result/2);
|
||
|
|
break;
|
||
|
|
case '오전반반차' :
|
||
|
|
case '오후반반차' :
|
||
|
|
$('#al_usedday').val(result/4);
|
||
|
|
break;
|
||
|
|
case '경조사' :
|
||
|
|
$('#al_usedday').val(0);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 종료일을 변경해도 자동계산해 주기 (중복 방지)
|
||
|
|
$(document).off('change', '#al_askdateto').on('change', '#al_askdateto', function(){
|
||
|
|
var radioVal = $('input[name="al_item"]:checked').val();
|
||
|
|
console.log(radioVal);
|
||
|
|
// $('#al_askdateto').val($("#al_askdatefrom").val());
|
||
|
|
|
||
|
|
const result = getDateDiff($("#al_askdatefrom").val(), $("#al_askdateto").val());
|
||
|
|
|
||
|
|
switch(radioVal)
|
||
|
|
{
|
||
|
|
case '연차' :
|
||
|
|
$('#al_usedday').val(result);
|
||
|
|
break;
|
||
|
|
case '오전반차' :
|
||
|
|
case '오후반차' :
|
||
|
|
$('#al_usedday').val(result/2);
|
||
|
|
break;
|
||
|
|
case '오전반반차' :
|
||
|
|
case '오후반반차' :
|
||
|
|
$('#al_usedday').val(result/4);
|
||
|
|
break;
|
||
|
|
case '경조사' :
|
||
|
|
$('#al_usedday').val(0);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// setTimeout(function() {
|
||
|
|
// // 페이지 로드 후 #author 변경 이벤트 트리거
|
||
|
|
// $("#author").trigger('change');
|
||
|
|
// }, 1000);
|
||
|
|
|
||
|
|
// 파일 삭제 이벤트 (중복 방지)
|
||
|
|
$(document).off('click', '.remove-file').on('click', '.remove-file', function() {
|
||
|
|
var fileId = $(this).data('file-id');
|
||
|
|
var fileType = $(this).data('file-type');
|
||
|
|
removeFile(fileId, fileType);
|
||
|
|
});
|
||
|
|
|
||
|
|
// 기존 파일 불러오기 (수정 모드일 때)
|
||
|
|
if ($("#num").val() && $("#num").val() > 0) {
|
||
|
|
loadExistingFiles($("#num").val());
|
||
|
|
} else {
|
||
|
|
// 신규 입력 시 임시키로 파일 불러오기
|
||
|
|
var tempKey = $("#temp_key").val();
|
||
|
|
if (tempKey) {
|
||
|
|
loadExistingFiles(tempKey);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
},
|
||
|
|
error: function(jqxhr, status, error) {
|
||
|
|
console.log("AJAX Error: ", status, error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
$(document).ready(function() {
|
||
|
|
$("#writeBtn").off("click").on("click", function() {
|
||
|
|
loadForm('insert', num = null, al_company = "<?=$mycompany?>" , al_part = "<?=$mypart?>");
|
||
|
|
});
|
||
|
|
|
||
|
|
$("#closeModalBtn").off("click").click(function() {
|
||
|
|
$('#myModal').modal('hide');
|
||
|
|
});
|
||
|
|
|
||
|
|
$(".close").off("click").click(function() {
|
||
|
|
$('#myModal').modal('hide');
|
||
|
|
});
|
||
|
|
|
||
|
|
$("#openAlmemberBtn").off("click").click(function() {
|
||
|
|
popupCenter('../almember/list.php', '직원 연차 등록', 900, 800);
|
||
|
|
});
|
||
|
|
|
||
|
|
$("#searchBtn").off("click").click(function() {
|
||
|
|
document.getElementById('board_form').submit();
|
||
|
|
});
|
||
|
|
|
||
|
|
function restorePageNumber() {
|
||
|
|
var savedPageNumber = getCookie('annualleavepageNumber');
|
||
|
|
location.reload(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
$(document).ready(function(){
|
||
|
|
var loader = document.getElementById('loadingOverlay');
|
||
|
|
if(loader)
|
||
|
|
loader.style.display = 'none';
|
||
|
|
|
||
|
|
var modal = document.getElementById("myModal");
|
||
|
|
var span = document.getElementsByClassName("close")[0];
|
||
|
|
|
||
|
|
span.onclick = function() {
|
||
|
|
modal.style.display = "none";
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
// PHP에서 전달된 휴일 목록을 JavaScript 변수로 변환
|
||
|
|
var holidayDates = <?= json_encode($holidayDates, JSON_UNESCAPED_UNICODE) ?>;
|
||
|
|
// 휴일 날짜를 Set으로 변환하여 빠른 검색 가능하도록 함
|
||
|
|
var holidaySet = new Set(holidayDates);
|
||
|
|
|
||
|
|
// 날짜를 YYYY-MM-DD 형식으로 변환하는 함수
|
||
|
|
function formatDate(date) {
|
||
|
|
const year = date.getFullYear();
|
||
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
|
|
const day = String(date.getDate()).padStart(2, '0');
|
||
|
|
return year + '-' + month + '-' + day;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 두날짜 사이 일자 구하기 (주말과 휴일 제외)
|
||
|
|
const getDateDiff = (d1, d2) => {
|
||
|
|
if (!d1 || !d2) return 0;
|
||
|
|
|
||
|
|
const date1 = new Date(d1);
|
||
|
|
const date2 = new Date(d2);
|
||
|
|
|
||
|
|
// 날짜 유효성 검사
|
||
|
|
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
|
||
|
|
console.warn('getDateDiff: 유효하지 않은 날짜', d1, d2);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// holidaySet이 없으면 빈 Set 사용 (방어 코드)
|
||
|
|
const holidays = (typeof holidaySet !== 'undefined' && holidaySet instanceof Set)
|
||
|
|
? holidaySet
|
||
|
|
: (typeof window.holidaySet !== 'undefined' && window.holidaySet instanceof Set)
|
||
|
|
? window.holidaySet
|
||
|
|
: new Set();
|
||
|
|
|
||
|
|
let count = 0;
|
||
|
|
const oneDay = 24 * 60 * 60 * 1000; // 하루의 밀리세컨드 수
|
||
|
|
|
||
|
|
// 시작일과 종료일을 포함하여 계산하기 위해 date1의 복사본 생성
|
||
|
|
const startDate = new Date(date1);
|
||
|
|
const endDate = new Date(date2);
|
||
|
|
|
||
|
|
// 시작일이 종료일보다 큰 경우 교환
|
||
|
|
if (startDate > endDate) {
|
||
|
|
const temp = startDate;
|
||
|
|
startDate = endDate;
|
||
|
|
endDate = temp;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 시작일부터 종료일까지 반복 (포함)
|
||
|
|
const currentDate = new Date(startDate);
|
||
|
|
while (currentDate <= endDate) {
|
||
|
|
const dayOfWeek = currentDate.getDay(); // 요일 (0:일, 1:월, ..., 6:토)
|
||
|
|
const dateString = formatDate(currentDate); // YYYY-MM-DD 형식으로 변환
|
||
|
|
|
||
|
|
// 주말이 아니고 휴일도 아닌 경우에만 count 증가
|
||
|
|
if (dayOfWeek !== 0 && dayOfWeek !== 6 && !holidays.has(dateString)) {
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
|
||
|
|
currentDate.setTime(currentDate.getTime() + oneDay); // 다음 날짜로 이동
|
||
|
|
}
|
||
|
|
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
$(document).ready(function () {
|
||
|
|
$('#yearSelect').change(function () {
|
||
|
|
const selectedYear = $(this).val();
|
||
|
|
$.ajax({
|
||
|
|
url: '/almember/load_al_info.php', // 데이터 업데이트를 처리할 PHP 파일
|
||
|
|
method: 'POST',
|
||
|
|
data: { year: selectedYear, user_name: "<?=$user_name?>" },
|
||
|
|
dataType: 'json',
|
||
|
|
success: function (response) {
|
||
|
|
if (response.success) {
|
||
|
|
$('#totalDays').text(response.total);
|
||
|
|
$('#usedDays').text(response.usedDays);
|
||
|
|
$('#remainingDays').text(response.remainingDays);
|
||
|
|
$('#previous_usageDays').text(response.previous_usageDays); // 이전사용일자 정보 가져오기
|
||
|
|
} else {
|
||
|
|
alert('데이터를 업데이트하는 동안 오류가 발생했습니다.');
|
||
|
|
}
|
||
|
|
},
|
||
|
|
error: function(jqxhr, status, error) {
|
||
|
|
console.log(jqxhr, status, error);
|
||
|
|
alert('서버 요청 중 오류가 발생했습니다.');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
$(document).ready(function(){
|
||
|
|
$("input").attr("autocomplete", "off");
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
|
||
|
|
|
||
|
|
// 전역 업로드 상태 추적
|
||
|
|
var globalUploadState = {
|
||
|
|
upfile: false,
|
||
|
|
upfileimage: false,
|
||
|
|
pendingUploads: new Set()
|
||
|
|
};
|
||
|
|
|
||
|
|
// 파일 업로드 이벤트를 바인딩하는 함수
|
||
|
|
function bindFileUploadEvents() {
|
||
|
|
// 기존 이벤트 핸들러 제거
|
||
|
|
$(document).off('change', '#upfile');
|
||
|
|
$(document).off('change', '#upfileimage');
|
||
|
|
|
||
|
|
// 파일 업로드 이벤트 바인딩
|
||
|
|
$(document).on('change', '#upfile', function() {
|
||
|
|
console.log('[이벤트] 파일 선택됨:', this.files.length, '개');
|
||
|
|
|
||
|
|
// 전역 업로드 상태 확인
|
||
|
|
if (globalUploadState.upfile) {
|
||
|
|
console.warn('[이벤트] 이미 파일 업로드가 진행 중입니다. 중복 실행 차단.');
|
||
|
|
Toastify({
|
||
|
|
text: "파일 업로드가 진행 중입니다. 잠시 기다려주세요.",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#ff9800",
|
||
|
|
}).showToast();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 중복 실행 방지를 위한 디바운싱 강화
|
||
|
|
if (this.uploadTimeout) {
|
||
|
|
clearTimeout(this.uploadTimeout);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 고유 키 생성하여 중복 방지
|
||
|
|
var uploadKey = 'upfile_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||
|
|
|
||
|
|
if (globalUploadState.pendingUploads.has('upfile')) {
|
||
|
|
console.warn('[이벤트] 이미 대기 중인 파일 업로드가 있습니다.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
globalUploadState.pendingUploads.add('upfile');
|
||
|
|
console.log('[이벤트] 파일 업로드 대기열에 추가:', uploadKey);
|
||
|
|
|
||
|
|
this.uploadTimeout = setTimeout(() => {
|
||
|
|
globalUploadState.pendingUploads.delete('upfile');
|
||
|
|
handleFileUpload(this, 'upfile', 'attached');
|
||
|
|
}, 500); // 디바운싱 시간 증가
|
||
|
|
});
|
||
|
|
|
||
|
|
$(document).on('change', '#upfileimage', function() {
|
||
|
|
console.log('[이벤트] 이미지 선택됨:', this.files.length, '개');
|
||
|
|
|
||
|
|
// 전역 업로드 상태 확인
|
||
|
|
if (globalUploadState.upfileimage) {
|
||
|
|
console.warn('[이벤트] 이미 이미지 업로드가 진행 중입니다. 중복 실행 차단.');
|
||
|
|
Toastify({
|
||
|
|
text: "이미지 업로드가 진행 중입니다. 잠시 기다려주세요.",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#ff9800",
|
||
|
|
}).showToast();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 중복 실행 방지를 위한 디바운싱 강화
|
||
|
|
if (this.uploadTimeout) {
|
||
|
|
clearTimeout(this.uploadTimeout);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 고유 키 생성하여 중복 방지
|
||
|
|
var uploadKey = 'upfileimage_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||
|
|
|
||
|
|
if (globalUploadState.pendingUploads.has('upfileimage')) {
|
||
|
|
console.warn('[이벤트] 이미 대기 중인 이미지 업로드가 있습니다.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
globalUploadState.pendingUploads.add('upfileimage');
|
||
|
|
console.log('[이벤트] 이미지 업로드 대기열에 추가:', uploadKey);
|
||
|
|
|
||
|
|
this.uploadTimeout = setTimeout(() => {
|
||
|
|
globalUploadState.pendingUploads.delete('upfileimage');
|
||
|
|
handleFileUpload(this, 'upfileimage', 'image');
|
||
|
|
}, 500); // 디바운싱 시간 증가
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 통합된 파일 업로드 처리 함수
|
||
|
|
function handleFileUpload(input, inputType, itemType) {
|
||
|
|
if (input.files.length === 0) return;
|
||
|
|
|
||
|
|
console.log(`[파일업로드] ${itemType} 업로드 시작 - 파일 개수:`, input.files.length);
|
||
|
|
console.log(`[파일업로드] 현재 업로드 상태: ${input.isUploading ? '진행중' : '대기중'}`);
|
||
|
|
|
||
|
|
// 전역 업로드 상태로 중복 체크 강화
|
||
|
|
var stateKey = inputType === 'upfile' ? 'upfile' : 'upfileimage';
|
||
|
|
if (globalUploadState[stateKey]) {
|
||
|
|
console.warn(`[파일업로드] ${itemType} 전역 상태에서 이미 업로드 진행중 - 중복 실행 차단`);
|
||
|
|
Toastify({
|
||
|
|
text: "파일 업로드가 이미 진행중입니다. 잠시 기다려주세요.",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#ff9800",
|
||
|
|
}).showToast();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 이미 업로드 중인지 한번 더 체크 (강화된 중복 방지)
|
||
|
|
if (input.isUploading) {
|
||
|
|
console.warn(`[파일업로드] ${itemType} 이미 업로드 진행중 - 중복 실행 차단`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 업로드 상태 설정 (전역 및 로컬)
|
||
|
|
globalUploadState[stateKey] = true;
|
||
|
|
input.isUploading = true;
|
||
|
|
console.log(`[파일업로드] ${itemType} 업로드 상태를 true로 설정 (전역: ${stateKey})`);
|
||
|
|
|
||
|
|
// 중복 파일 체크
|
||
|
|
var existingFileNames = [];
|
||
|
|
var displayId = itemType === 'attached' ? 'displayFile' : 'displayImage';
|
||
|
|
|
||
|
|
$(`#${displayId} .row a`).each(function() {
|
||
|
|
existingFileNames.push($(this).text().trim());
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(`[파일업로드] 기존 파일 목록:`, existingFileNames);
|
||
|
|
|
||
|
|
var duplicateFound = false;
|
||
|
|
var duplicateFiles = [];
|
||
|
|
for (var i = 0; i < input.files.length; i++) {
|
||
|
|
if (existingFileNames.includes(input.files[i].name)) {
|
||
|
|
duplicateFiles.push(input.files[i].name);
|
||
|
|
duplicateFound = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (duplicateFound) {
|
||
|
|
console.warn(`[파일업로드] 중복 파일 발견:`, duplicateFiles);
|
||
|
|
Toastify({
|
||
|
|
text: `이미 같은 이름의 파일이 있습니다: ${duplicateFiles.join(', ')}`,
|
||
|
|
duration: 3000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#ff9800",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (duplicateFound) {
|
||
|
|
// 상태 해제 (전역 및 로컬)
|
||
|
|
globalUploadState[stateKey] = false;
|
||
|
|
input.isUploading = false;
|
||
|
|
console.log(`[파일업로드] ${itemType} 중복 파일로 인해 업로드 중단 - 상태 해제 (전역: ${stateKey})`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[파일업로드] ${itemType} Google Drive 업로드 시작...`);
|
||
|
|
|
||
|
|
const form = $('#board_form')[0] || document.getElementById('savetext');
|
||
|
|
|
||
|
|
// 기존 FormData 방식 대신 새로운 FormData 생성하여 필요한 데이터만 전송
|
||
|
|
const data = new FormData();
|
||
|
|
|
||
|
|
console.log(`[파일업로드] 새로운 FormData 생성 - 기존 form 데이터는 제외`);
|
||
|
|
|
||
|
|
// 업로드 중인 파일명 로깅
|
||
|
|
var uploadingFiles = [];
|
||
|
|
for (let i = 0; i < input.files.length; i++) {
|
||
|
|
uploadingFiles.push({
|
||
|
|
name: input.files[i].name,
|
||
|
|
size: input.files[i].size,
|
||
|
|
type: input.files[i].type,
|
||
|
|
lastModified: input.files[i].lastModified
|
||
|
|
});
|
||
|
|
}
|
||
|
|
console.log(`[파일업로드] 업로드할 파일 상세 정보:`, uploadingFiles);
|
||
|
|
|
||
|
|
// 추가 데이터 설정
|
||
|
|
data.append("tablename", "eworks");
|
||
|
|
data.append("item", itemType);
|
||
|
|
data.append("upfilename", inputType);
|
||
|
|
data.append("folderPath", "경동기업/uploads");
|
||
|
|
data.append("DBtable", "picuploads");
|
||
|
|
|
||
|
|
// num이 없으면 임시키 사용
|
||
|
|
var currentNum = $("#num").val();
|
||
|
|
if (!currentNum || currentNum === '') {
|
||
|
|
currentNum = $("#temp_key").val() || "temp_" + Date.now();
|
||
|
|
}
|
||
|
|
data.append("num", currentNum);
|
||
|
|
|
||
|
|
console.log(`[파일업로드] FormData 기본 정보:`, {
|
||
|
|
tablename: "eworks",
|
||
|
|
item: itemType,
|
||
|
|
upfilename: inputType,
|
||
|
|
folderPath: "경동기업/uploads",
|
||
|
|
DBtable: "picuploads",
|
||
|
|
num: currentNum
|
||
|
|
});
|
||
|
|
|
||
|
|
// 파일 데이터 추가 (중복 방지 강화)
|
||
|
|
console.log(`[파일업로드] FormData에 파일 추가 시작...`);
|
||
|
|
|
||
|
|
// 중복 파일 방지를 위한 Set
|
||
|
|
var addedFiles = new Set();
|
||
|
|
var actualAddedCount = 0;
|
||
|
|
|
||
|
|
for (let i = 0; i < input.files.length; i++) {
|
||
|
|
var file = input.files[i];
|
||
|
|
var fileKey = file.name + "_" + file.size + "_" + file.lastModified;
|
||
|
|
|
||
|
|
if (addedFiles.has(fileKey)) {
|
||
|
|
console.warn(`[파일업로드] 중복 파일 감지 및 제외:`, file.name);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
addedFiles.add(fileKey);
|
||
|
|
actualAddedCount++;
|
||
|
|
|
||
|
|
console.log(`[파일업로드] 파일 ${actualAddedCount} 추가:`, {
|
||
|
|
name: file.name,
|
||
|
|
size: file.size,
|
||
|
|
type: file.type,
|
||
|
|
lastModified: file.lastModified,
|
||
|
|
index: i,
|
||
|
|
fieldName: `${inputType}[]`,
|
||
|
|
fileKey: fileKey
|
||
|
|
});
|
||
|
|
data.append(`${inputType}[]`, file);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[파일업로드] FormData에 총 ${actualAddedCount}개 고유 파일 추가 완료 (원본: ${input.files.length}개)`);
|
||
|
|
|
||
|
|
// 최종 검증
|
||
|
|
var formDataFiles = data.getAll(`${inputType}[]`);
|
||
|
|
console.log(`[파일업로드] FormData 최종 검증 - 실제 추가된 파일: ${formDataFiles.length}개`);
|
||
|
|
|
||
|
|
if (formDataFiles.length === 0) {
|
||
|
|
console.warn(`[파일업로드] FormData에 파일이 없습니다. 업로드 중단.`);
|
||
|
|
globalUploadState[stateKey] = false;
|
||
|
|
input.isUploading = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// AJAX 요청 (Google Drive API)
|
||
|
|
$.ajax({
|
||
|
|
enctype: 'multipart/form-data',
|
||
|
|
processData: false,
|
||
|
|
contentType: false,
|
||
|
|
cache: false,
|
||
|
|
timeout: 600000,
|
||
|
|
url: "/filedrive/fileprocess.php",
|
||
|
|
type: "POST",
|
||
|
|
data: data,
|
||
|
|
success: function(response) {
|
||
|
|
console.log(`[파일업로드] ${itemType} Google Drive 업로드 응답:`, response);
|
||
|
|
|
||
|
|
let successCount = 0;
|
||
|
|
let errorCount = 0;
|
||
|
|
let errorMessages = [];
|
||
|
|
let successFiles = [];
|
||
|
|
let errorFiles = [];
|
||
|
|
|
||
|
|
// 응답이 배열인지 객체인지 확인하여 처리
|
||
|
|
if (Array.isArray(response)) {
|
||
|
|
response.forEach((item) => {
|
||
|
|
if (item.status === "success") {
|
||
|
|
successCount++;
|
||
|
|
successFiles.push(item.file || '파일명불명');
|
||
|
|
} else if (item.status === "error") {
|
||
|
|
errorCount++;
|
||
|
|
errorFiles.push(item.file || '파일명불명');
|
||
|
|
errorMessages.push(`파일: ${item.file}, 메시지: ${item.message}`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else if (response && typeof response === 'object') {
|
||
|
|
// 단일 파일 응답인 경우
|
||
|
|
if (response.status === "success") {
|
||
|
|
successCount++;
|
||
|
|
successFiles.push(response.file || '파일명불명');
|
||
|
|
} else if (response.status === "error") {
|
||
|
|
errorCount++;
|
||
|
|
errorFiles.push(response.file || '파일명불명');
|
||
|
|
errorMessages.push(`파일: ${response.file || '알 수 없음'}, 메시지: ${response.message || '알 수 없음'}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[파일업로드] ${itemType} 업로드 결과 - 성공: ${successCount}개`, successFiles);
|
||
|
|
if (errorCount > 0) {
|
||
|
|
console.error(`[파일업로드] ${itemType} 업로드 실패 - 실패: ${errorCount}개`, errorFiles);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (successCount > 0) {
|
||
|
|
console.log(`[파일업로드] ${itemType} 성공 메시지 표시 및 화면 업데이트`);
|
||
|
|
Toastify({
|
||
|
|
text: `${successCount}개의 ${itemType === 'attached' ? '파일' : '이미지'}이 성공적으로 업로드되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#4fbe87",
|
||
|
|
}).showToast();
|
||
|
|
|
||
|
|
// 파일 표시 업데이트
|
||
|
|
if (itemType === 'attached') {
|
||
|
|
displayFile();
|
||
|
|
} else {
|
||
|
|
displayImage();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (errorCount > 0) {
|
||
|
|
Toastify({
|
||
|
|
text: `오류 발생: ${errorCount}개의 ${itemType === 'attached' ? '파일' : '이미지'} 업로드 실패\n상세 오류: ${errorMessages.join("\n")}`,
|
||
|
|
duration: 5000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#f44336",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 업로드 상태 해제 (전역 및 로컬)
|
||
|
|
globalUploadState[stateKey] = false;
|
||
|
|
input.isUploading = false;
|
||
|
|
console.log(`[파일업로드] ${itemType} 업로드 완료. 상태 해제됨 (전역: ${stateKey}).`);
|
||
|
|
},
|
||
|
|
error: function(jqxhr, status, error) {
|
||
|
|
console.error(`[파일업로드] ${itemType} Google Drive 업로드 실패:`, jqxhr, status, error);
|
||
|
|
console.error(`[파일업로드] 실패한 파일 목록:`, uploadingFiles);
|
||
|
|
Toastify({
|
||
|
|
text: "Google Drive 업로드 중 오류가 발생했습니다.",
|
||
|
|
duration: 3000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#f44336",
|
||
|
|
}).showToast();
|
||
|
|
|
||
|
|
// 업로드 상태 해제 (전역 및 로컬)
|
||
|
|
globalUploadState[stateKey] = false;
|
||
|
|
input.isUploading = false;
|
||
|
|
console.log(`[파일업로드] ${itemType} 업로드 실패. 상태 해제됨 (전역: ${stateKey}).`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 파일 미리보기 표시
|
||
|
|
if (itemType === 'attached') {
|
||
|
|
handleFilePreview(input, 'displayFile', 'upfile');
|
||
|
|
} else {
|
||
|
|
handleImagePreview(input, 'displayImage', 'upfileimage');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 파일 미리보기 처리 함수
|
||
|
|
function handleFilePreview(input, displayId, fileType) {
|
||
|
|
var files = input.files;
|
||
|
|
var displayDiv = document.getElementById(displayId);
|
||
|
|
|
||
|
|
if (files.length > 0) {
|
||
|
|
displayDiv.style.display = 'block';
|
||
|
|
displayDiv.innerHTML = '';
|
||
|
|
|
||
|
|
for (var i = 0; i < files.length; i++) {
|
||
|
|
var file = files[i];
|
||
|
|
var fileDiv = document.createElement('div');
|
||
|
|
fileDiv.className = 'd-flex align-items-center justify-content-between p-2 border rounded mb-2';
|
||
|
|
fileDiv.innerHTML = `
|
||
|
|
<div class="d-flex align-items-center">
|
||
|
|
<i class="bi bi-file-earmark me-2"></i>
|
||
|
|
<span class="small">${file.name} (${formatFileSize(file.size)})</span>
|
||
|
|
</div>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-new-file" data-index="${i}">
|
||
|
|
<i class="bi bi-x"></i>
|
||
|
|
</button>
|
||
|
|
`;
|
||
|
|
displayDiv.appendChild(fileDiv);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 새로 추가된 파일 삭제 이벤트 (개선된 버전)
|
||
|
|
$(document).off('click', '.remove-new-file').on('click', '.remove-new-file', function() {
|
||
|
|
console.log('[파일삭제] 새로 추가된 파일 삭제 시작');
|
||
|
|
|
||
|
|
var index = $(this).data('index');
|
||
|
|
var input = document.getElementById(fileType);
|
||
|
|
var fileName = input.files[index]?.name || '알 수 없음';
|
||
|
|
|
||
|
|
console.log(`[파일삭제] 삭제할 파일: ${fileName} (인덱스: ${index})`);
|
||
|
|
|
||
|
|
// 파일 목록에서 해당 인덱스 제거
|
||
|
|
var dt = new DataTransfer();
|
||
|
|
var { files } = input;
|
||
|
|
var removedFileName = '';
|
||
|
|
|
||
|
|
for (var i = 0; i < files.length; i++) {
|
||
|
|
if (i !== index) {
|
||
|
|
dt.items.add(files[i]);
|
||
|
|
} else {
|
||
|
|
removedFileName = files[i].name;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
input.files = dt.files;
|
||
|
|
console.log(`[파일삭제] ${removedFileName} 파일 제거 완료. 남은 파일: ${dt.files.length}개`);
|
||
|
|
|
||
|
|
// 미리보기 업데이트
|
||
|
|
handleFilePreview(input, displayId, fileType);
|
||
|
|
|
||
|
|
// 성공 메시지
|
||
|
|
Toastify({
|
||
|
|
text: `${removedFileName} 파일이 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
displayDiv.style.display = 'none';
|
||
|
|
displayDiv.innerHTML = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 이미지 미리보기 처리 함수
|
||
|
|
function handleImagePreview(input, displayId, fileType) {
|
||
|
|
var files = input.files;
|
||
|
|
var displayDiv = document.getElementById(displayId);
|
||
|
|
|
||
|
|
if (files.length > 0) {
|
||
|
|
displayDiv.style.display = 'block';
|
||
|
|
displayDiv.innerHTML = '';
|
||
|
|
|
||
|
|
for (var i = 0; i < files.length; i++) {
|
||
|
|
var file = files[i];
|
||
|
|
var fileDiv = document.createElement('div');
|
||
|
|
fileDiv.className = 'd-flex align-items-center justify-content-between p-2 border rounded mb-2';
|
||
|
|
|
||
|
|
// 이미지 미리보기
|
||
|
|
var reader = new FileReader();
|
||
|
|
reader.onload = function(e) {
|
||
|
|
fileDiv.innerHTML = `
|
||
|
|
<div class="d-flex align-items-center">
|
||
|
|
<img src="${e.target.result}" class="me-2" style="width: 40px; height: 40px; object-fit: cover;">
|
||
|
|
<span class="small">${file.name} (${formatFileSize(file.size)})</span>
|
||
|
|
</div>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-new-file" data-index="${i}">
|
||
|
|
<i class="bi bi-x"></i>
|
||
|
|
</button>
|
||
|
|
`;
|
||
|
|
};
|
||
|
|
reader.readAsDataURL(file);
|
||
|
|
|
||
|
|
displayDiv.appendChild(fileDiv);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 새로 추가된 이미지 삭제 이벤트 (개선된 버전)
|
||
|
|
$(document).off('click', '.remove-new-file').on('click', '.remove-new-file', function() {
|
||
|
|
console.log('[이미지삭제] 새로 추가된 이미지 삭제 시작');
|
||
|
|
|
||
|
|
var index = $(this).data('index');
|
||
|
|
var input = document.getElementById(fileType);
|
||
|
|
var fileName = input.files[index]?.name || '알 수 없음';
|
||
|
|
|
||
|
|
console.log(`[이미지삭제] 삭제할 이미지: ${fileName} (인덱스: ${index})`);
|
||
|
|
|
||
|
|
// 이미지 목록에서 해당 인덱스 제거
|
||
|
|
var dt = new DataTransfer();
|
||
|
|
var { files } = input;
|
||
|
|
var removedFileName = '';
|
||
|
|
|
||
|
|
for (var i = 0; i < files.length; i++) {
|
||
|
|
if (i !== index) {
|
||
|
|
dt.items.add(files[i]);
|
||
|
|
} else {
|
||
|
|
removedFileName = files[i].name;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
input.files = dt.files;
|
||
|
|
console.log(`[이미지삭제] ${removedFileName} 이미지 제거 완료. 남은 이미지: ${dt.files.length}개`);
|
||
|
|
|
||
|
|
// 미리보기 업데이트
|
||
|
|
handleImagePreview(input, displayId, fileType);
|
||
|
|
|
||
|
|
// 성공 메시지
|
||
|
|
Toastify({
|
||
|
|
text: `${removedFileName} 이미지가 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
displayDiv.style.display = 'none';
|
||
|
|
displayDiv.innerHTML = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
function handleImageUpload(input, displayId, fileType) {
|
||
|
|
var files = input.files;
|
||
|
|
var displayDiv = document.getElementById(displayId);
|
||
|
|
|
||
|
|
if (files.length > 0) {
|
||
|
|
displayDiv.style.display = 'block';
|
||
|
|
displayDiv.innerHTML = '';
|
||
|
|
|
||
|
|
for (var i = 0; i < files.length; i++) {
|
||
|
|
var file = files[i];
|
||
|
|
var fileDiv = document.createElement('div');
|
||
|
|
fileDiv.className = 'd-flex align-items-center justify-content-between p-2 border rounded mb-2';
|
||
|
|
|
||
|
|
// 이미지 미리보기
|
||
|
|
var reader = new FileReader();
|
||
|
|
reader.onload = function(e) {
|
||
|
|
fileDiv.innerHTML = `
|
||
|
|
<div class="d-flex align-items-center">
|
||
|
|
<img src="${e.target.result}" class="me-2" style="width: 40px; height: 40px; object-fit: cover;">
|
||
|
|
<span class="small">${file.name} (${formatFileSize(file.size)})</span>
|
||
|
|
</div>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-new-file" data-index="${i}">
|
||
|
|
<i class="bi bi-x"></i>
|
||
|
|
</button>
|
||
|
|
`;
|
||
|
|
};
|
||
|
|
reader.readAsDataURL(file);
|
||
|
|
|
||
|
|
displayDiv.appendChild(fileDiv);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 새로 추가된 이미지 삭제 이벤트 (통합)
|
||
|
|
$(document).off('click', '.remove-new-file').on('click', '.remove-new-file', function() {
|
||
|
|
console.log('[handleImageUpload] 이미지 삭제 시작');
|
||
|
|
|
||
|
|
var index = $(this).data('index');
|
||
|
|
var input = document.getElementById(fileType);
|
||
|
|
var fileName = input.files[index]?.name || '알 수 없음';
|
||
|
|
|
||
|
|
console.log(`[handleImageUpload] 삭제할 이미지: ${fileName} (인덱스: ${index})`);
|
||
|
|
|
||
|
|
var dt = new DataTransfer();
|
||
|
|
var { files } = input;
|
||
|
|
var removedFileName = '';
|
||
|
|
|
||
|
|
for (var i = 0; i < files.length; i++) {
|
||
|
|
if (i !== index) {
|
||
|
|
dt.items.add(files[i]);
|
||
|
|
} else {
|
||
|
|
removedFileName = files[i].name;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
input.files = dt.files;
|
||
|
|
console.log(`[handleImageUpload] ${removedFileName} 제거 완료. 남은 이미지: ${dt.files.length}개`);
|
||
|
|
|
||
|
|
handleImageUpload(input, displayId, fileType);
|
||
|
|
|
||
|
|
// 성공 메시지
|
||
|
|
Toastify({
|
||
|
|
text: `${removedFileName} 이미지가 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
displayDiv.style.display = 'none';
|
||
|
|
displayDiv.innerHTML = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatFileSize(bytes) {
|
||
|
|
if (bytes === 0) return '0 Bytes';
|
||
|
|
var k = 1024;
|
||
|
|
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||
|
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Google Drive에 파일 업로드하는 함수
|
||
|
|
function uploadFilesToGoogleDrive(files, itemType, num) {
|
||
|
|
if (files.length === 0) {
|
||
|
|
console.warn("[별도업로드] 업로드할 파일이 없습니다.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[별도업로드] ${itemType} 저장 후 업로드 시작 - 파일 개수: ${files.length}`);
|
||
|
|
|
||
|
|
// 새로운 FormData 생성 (form 데이터 제외)
|
||
|
|
const data = new FormData();
|
||
|
|
|
||
|
|
// 추가 데이터 설정
|
||
|
|
data.append("tablename", "eworks");
|
||
|
|
data.append("item", itemType);
|
||
|
|
data.append("upfilename", itemType === 'attached' ? "upfile" : "upfileimage");
|
||
|
|
data.append("folderPath", "경동기업/uploads");
|
||
|
|
data.append("DBtable", "picuploads");
|
||
|
|
|
||
|
|
// num이 없으면 임시키 사용
|
||
|
|
var currentNum = num;
|
||
|
|
if (!currentNum || currentNum === '') {
|
||
|
|
currentNum = $("#temp_key").val() || "temp_" + Date.now();
|
||
|
|
}
|
||
|
|
data.append("num", currentNum);
|
||
|
|
|
||
|
|
console.log(`[별도업로드] 기본 데이터 설정:`, {
|
||
|
|
tablename: "eworks",
|
||
|
|
item: itemType,
|
||
|
|
upfilename: itemType === 'attached' ? "upfile" : "upfileimage",
|
||
|
|
folderPath: "경동기업/uploads",
|
||
|
|
DBtable: "picuploads",
|
||
|
|
num: currentNum
|
||
|
|
});
|
||
|
|
|
||
|
|
// 파일 데이터 추가 (중복 방지)
|
||
|
|
var addedFiles = new Set();
|
||
|
|
var actualAddedCount = 0;
|
||
|
|
|
||
|
|
for (let i = 0; i < files.length; i++) {
|
||
|
|
var file = files[i];
|
||
|
|
var fileKey = file.name + "_" + file.size + "_" + file.lastModified;
|
||
|
|
|
||
|
|
if (addedFiles.has(fileKey)) {
|
||
|
|
console.warn(`[별도업로드] 중복 파일 제외:`, file.name);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
addedFiles.add(fileKey);
|
||
|
|
actualAddedCount++;
|
||
|
|
|
||
|
|
var fieldName = itemType === 'attached' ? 'upfile[]' : 'upfileimage[]';
|
||
|
|
data.append(fieldName, file);
|
||
|
|
|
||
|
|
console.log(`[별도업로드] 파일 ${actualAddedCount} 추가:`, {
|
||
|
|
name: file.name,
|
||
|
|
size: file.size,
|
||
|
|
fieldName: fieldName
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[별도업로드] 총 ${actualAddedCount}개 고유 파일 FormData에 추가 완료 (원본: ${files.length}개)`);
|
||
|
|
|
||
|
|
if (actualAddedCount === 0) {
|
||
|
|
console.warn(`[별도업로드] 추가할 고유 파일이 없습니다. 업로드 중단.`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// AJAX 요청 (Google Drive API)
|
||
|
|
$.ajax({
|
||
|
|
enctype: 'multipart/form-data',
|
||
|
|
processData: false,
|
||
|
|
contentType: false,
|
||
|
|
cache: false,
|
||
|
|
timeout: 600000,
|
||
|
|
url: "/filedrive/fileprocess.php",
|
||
|
|
type: "POST",
|
||
|
|
data: data,
|
||
|
|
success: function(response) {
|
||
|
|
console.log(`[별도업로드] ${itemType} Google Drive 업로드 응답:`, response);
|
||
|
|
|
||
|
|
let successCount = 0;
|
||
|
|
let errorCount = 0;
|
||
|
|
let errorMessages = [];
|
||
|
|
|
||
|
|
// 응답이 배열인지 객체인지 확인하여 처리
|
||
|
|
if (Array.isArray(response)) {
|
||
|
|
response.forEach((item) => {
|
||
|
|
if (item.status === "success") {
|
||
|
|
successCount++;
|
||
|
|
} else if (item.status === "error") {
|
||
|
|
errorCount++;
|
||
|
|
errorMessages.push(`파일: ${item.file}, 메시지: ${item.message}`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else if (response && typeof response === 'object') {
|
||
|
|
// 단일 파일 응답인 경우
|
||
|
|
if (response.status === "success") {
|
||
|
|
successCount++;
|
||
|
|
} else if (response.status === "error") {
|
||
|
|
errorCount++;
|
||
|
|
errorMessages.push(`파일: ${response.file || '알 수 없음'}, 메시지: ${response.message || '알 수 없음'}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (successCount > 0) {
|
||
|
|
Toastify({
|
||
|
|
text: `${successCount}개의 파일이 Google Drive에 성공적으로 업로드되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#4fbe87",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (errorCount > 0) {
|
||
|
|
Toastify({
|
||
|
|
text: `오류 발생: ${errorCount}개의 파일 업로드 실패\n상세 오류: ${errorMessages.join("\n")}`,
|
||
|
|
duration: 5000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#f44336",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
error: function(jqxhr, status, error) {
|
||
|
|
console.error("Google Drive 업로드 실패:", jqxhr, status, error);
|
||
|
|
Toastify({
|
||
|
|
text: "Google Drive 업로드 중 오류가 발생했습니다.",
|
||
|
|
duration: 3000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#f44336",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 화면에서 저장한 첨부된 파일 불러오기
|
||
|
|
function displayFile() {
|
||
|
|
$('#displayFile').show();
|
||
|
|
const params = $("#timekey").val() ? $("#timekey").val() : ($("#num").val() || $("#temp_key").val());
|
||
|
|
|
||
|
|
if (!params) {
|
||
|
|
console.error("ID 값이 없습니다. 파일을 불러올 수 없습니다.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log("요청 ID:", params);
|
||
|
|
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'GET',
|
||
|
|
data: {
|
||
|
|
num: params,
|
||
|
|
tablename: "eworks",
|
||
|
|
item: 'attached',
|
||
|
|
folderPath: '경동기업/uploads',
|
||
|
|
},
|
||
|
|
dataType: 'json',
|
||
|
|
}).done(function (data) {
|
||
|
|
console.log("파일 데이터:", data);
|
||
|
|
|
||
|
|
$("#displayFile").html(''); // 기존 내용 초기화
|
||
|
|
|
||
|
|
if (Array.isArray(data) && data.length > 0) {
|
||
|
|
data.forEach(function (fileData, index) {
|
||
|
|
const realName = fileData.realname || '다운로드 파일';
|
||
|
|
const link = fileData.link || '#';
|
||
|
|
const fileId = fileData.fileId || null;
|
||
|
|
|
||
|
|
if (!fileId) {
|
||
|
|
console.error("fileId가 누락되었습니다. index: " + index, fileData);
|
||
|
|
$("#displayFile").append(
|
||
|
|
"<div class='text-danger'>파일 ID가 누락되었습니다.</div>"
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$("#displayFile").append(
|
||
|
|
"<div class='row mt-1 mb-2'>" +
|
||
|
|
"<div class='d-flex align-items-center justify-content-center'>" +
|
||
|
|
"<span id='file" + index + "'>" +
|
||
|
|
"<a href='#' onclick=\"popupCenter('" + link + "', 'filePopup', 800, 600); return false;\">" + realName + "</a>" +
|
||
|
|
"</span> " +
|
||
|
|
"<button type='button' class='btn btn-danger btn-sm' id='delFile" + index + "' onclick=\"delFileFn('" + index + "', '" + fileId + "')\">" +
|
||
|
|
"<i class='bi bi-trash'></i>" +
|
||
|
|
"</button>" +
|
||
|
|
"</div>" +
|
||
|
|
"</div>"
|
||
|
|
);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
$("#displayFile").append(
|
||
|
|
"<div class='text-center text-muted'>No files</div>"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}).fail(function (error) {
|
||
|
|
console.error("파일 불러오기 오류:", error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 첨부된 이미지 불러오기
|
||
|
|
function displayImage() {
|
||
|
|
$('#displayImage').show();
|
||
|
|
const params = $("#timekey").val() ? $("#timekey").val() : ($("#num").val() || $("#temp_key").val());
|
||
|
|
|
||
|
|
if (!params) {
|
||
|
|
console.error("ID 값이 없습니다. 파일을 불러올 수 없습니다.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log("요청 ID:", params);
|
||
|
|
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'GET',
|
||
|
|
data: {
|
||
|
|
num: params,
|
||
|
|
tablename: "eworks",
|
||
|
|
item: 'image',
|
||
|
|
folderPath: '경동기업/uploads',
|
||
|
|
},
|
||
|
|
dataType: 'json',
|
||
|
|
}).done(function (data) {
|
||
|
|
console.log("파일 데이터:", data);
|
||
|
|
|
||
|
|
$("#displayImage").html(''); // 기존 내용 초기화
|
||
|
|
|
||
|
|
if (Array.isArray(data) && data.length > 0) {
|
||
|
|
data.forEach(function (fileData, index) {
|
||
|
|
const realName = fileData.realname || '다운로드 파일';
|
||
|
|
const thumbnail = fileData.thumbnail || '/assets/default-thumbnail.png';
|
||
|
|
const link = fileData.link || '#';
|
||
|
|
const fileId = fileData.fileId || null;
|
||
|
|
|
||
|
|
if (!fileId) {
|
||
|
|
console.error("fileId가 누락되었습니다. index: " + index, fileData);
|
||
|
|
$("#displayImage").append(
|
||
|
|
"<div class='text-danger'>파일 ID가 누락되었습니다.</div>"
|
||
|
|
);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$("#displayImage").append(
|
||
|
|
"<div class='row mb-3'>" +
|
||
|
|
"<div class='col d-flex align-items-center justify-content-center'>" +
|
||
|
|
"<a href='#' onclick=\"popupCenter('" + link + "', 'imagePopup', 800, 600); return false;\">" +
|
||
|
|
"<img id='image" + index + "' src='" + thumbnail + "' style='width:150px; height:auto;'>" +
|
||
|
|
"</a> " +
|
||
|
|
"<button type='button' class='btn btn-danger btn-sm' id='delImage" + index + "' onclick=\"delImageFn('" + index + "', '" + fileId + "')\">" +
|
||
|
|
"<i class='bi bi-trash'></i>" +
|
||
|
|
"</button>" +
|
||
|
|
"</div>" +
|
||
|
|
"</div>"
|
||
|
|
);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
$("#displayImage").append(
|
||
|
|
"<div class='text-center text-muted'>No files</div>"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}).fail(function (error) {
|
||
|
|
console.error("파일 불러오기 오류:", error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 파일 삭제 처리 함수
|
||
|
|
function delFileFn(divID, fileId) {
|
||
|
|
console.log(`[delFileFn] 파일 삭제 함수 호출 - divID: ${divID}, fileId: ${fileId}`);
|
||
|
|
|
||
|
|
// DOM 상태 미리 확인
|
||
|
|
var fileSpanCheck = $("#file" + divID);
|
||
|
|
var deleteButtonCheck = $("#delFile" + divID);
|
||
|
|
var allRows = $("#displayFile .row");
|
||
|
|
|
||
|
|
console.log(`[delFileFn] 현재 DOM 상태:`, {
|
||
|
|
fileSpan: fileSpanCheck.length,
|
||
|
|
deleteButton: deleteButtonCheck.length,
|
||
|
|
totalRows: allRows.length
|
||
|
|
});
|
||
|
|
|
||
|
|
Swal.fire({
|
||
|
|
title: "파일 삭제 확인",
|
||
|
|
text: "정말 삭제하시겠습니까?",
|
||
|
|
icon: "warning",
|
||
|
|
showCancelButton: true,
|
||
|
|
confirmButtonText: "삭제",
|
||
|
|
cancelButtonText: "취소",
|
||
|
|
reverseButtons: true,
|
||
|
|
}).then((result) => {
|
||
|
|
if (result.isConfirmed) {
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'DELETE',
|
||
|
|
data: JSON.stringify({
|
||
|
|
fileId: fileId,
|
||
|
|
tablename: "eworks",
|
||
|
|
item: "attached",
|
||
|
|
folderPath: "경동기업/uploads",
|
||
|
|
DBtable: "picuploads",
|
||
|
|
}),
|
||
|
|
contentType: "application/json",
|
||
|
|
dataType: 'json',
|
||
|
|
}).done(function (response) {
|
||
|
|
if (response.status === 'success') {
|
||
|
|
console.log(`[기존파일삭제] 파일 삭제 완료:`, response);
|
||
|
|
|
||
|
|
// 화면에서 파일 제거 (수정된 버전 - ID 기반)
|
||
|
|
var fileSpan = $("#file" + divID);
|
||
|
|
var deleteButton = $("#delFile" + divID);
|
||
|
|
var fileName = fileSpan.find('a').text().trim() || '파일';
|
||
|
|
|
||
|
|
console.log(`[기존파일삭제] 화면에서 제거할 파일: ${fileName} (ID: file${divID}, delFile${divID})`);
|
||
|
|
console.log(`[기존파일삭제] 찾은 요소 - fileSpan: ${fileSpan.length}개, deleteButton: ${deleteButton.length}개`);
|
||
|
|
|
||
|
|
if (fileSpan.length > 0 || deleteButton.length > 0) {
|
||
|
|
// 부모 row div를 찾아서 제거
|
||
|
|
var parentRow = fileSpan.closest('.row');
|
||
|
|
if (parentRow.length === 0) {
|
||
|
|
parentRow = deleteButton.closest('.row');
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[기존파일삭제] 부모 row 요소: ${parentRow.length}개`);
|
||
|
|
|
||
|
|
if (parentRow.length > 0) {
|
||
|
|
// 페이드아웃 애니메이션과 함께 전체 행 제거
|
||
|
|
parentRow.fadeOut(400, function() {
|
||
|
|
$(this).remove();
|
||
|
|
console.log(`[기존파일삭제] ${fileName} 화면에서 제거 완료`);
|
||
|
|
|
||
|
|
// 성공 메시지 표시
|
||
|
|
Toastify({
|
||
|
|
text: `${fileName} 파일이 성공적으로 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
|
||
|
|
// 파일이 모두 삭제되었는지 확인
|
||
|
|
setTimeout(function() {
|
||
|
|
var remainingFiles = $("#displayFile .row").length;
|
||
|
|
console.log(`[기존파일삭제] 남은 파일 개수: ${remainingFiles}`);
|
||
|
|
|
||
|
|
if (remainingFiles === 0) {
|
||
|
|
$("#displayFile").html('<div class="text-center text-muted">첨부된 파일이 없습니다.</div>');
|
||
|
|
console.log('[기존파일삭제] 모든 파일이 삭제됨 - 안내 메시지 표시');
|
||
|
|
}
|
||
|
|
}, 100);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// row를 찾지 못한 경우 개별 요소들을 제거
|
||
|
|
console.log(`[기존파일삭제] row를 찾지 못함. 개별 요소 제거 시도`);
|
||
|
|
fileSpan.fadeOut(400, function() { $(this).remove(); });
|
||
|
|
deleteButton.fadeOut(400, function() { $(this).remove(); });
|
||
|
|
|
||
|
|
// 성공 메시지 표시
|
||
|
|
Toastify({
|
||
|
|
text: `${fileName} 파일이 성공적으로 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.warn(`[기존파일삭제] 삭제할 파일 요소를 찾을 수 없음 (divID: ${divID})`);
|
||
|
|
console.log(`[기존파일삭제] 전체 파일 목록을 다시 불러와서 동기화`);
|
||
|
|
// 전체 파일 목록을 다시 불러와서 동기화
|
||
|
|
displayFile();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.log(response.message);
|
||
|
|
Toastify({
|
||
|
|
text: "파일 삭제 중 문제가 발생했습니다.",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
style: {
|
||
|
|
background: "linear-gradient(to right, #ff6b6b, #ee5a24)"
|
||
|
|
},
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
}).fail(function (error) {
|
||
|
|
console.error("삭제 중 오류:", error);
|
||
|
|
Toastify({
|
||
|
|
title: "삭제 실패",
|
||
|
|
text: "파일 삭제 중 문제가 발생했습니다.",
|
||
|
|
icon: "error",
|
||
|
|
confirmButtonText: "확인",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 이미지 삭제 처리 함수
|
||
|
|
function delImageFn(divID, fileId) {
|
||
|
|
console.log(`[delImageFn] 이미지 삭제 함수 호출 - divID: ${divID}, fileId: ${fileId}`);
|
||
|
|
|
||
|
|
// DOM 상태 미리 확인
|
||
|
|
var imageCheck = $("#image" + divID);
|
||
|
|
var deleteButtonCheck = $("#delImage" + divID);
|
||
|
|
var allRows = $("#displayImage .row");
|
||
|
|
|
||
|
|
console.log(`[delImageFn] 현재 DOM 상태:`, {
|
||
|
|
imageElement: imageCheck.length,
|
||
|
|
deleteButton: deleteButtonCheck.length,
|
||
|
|
totalRows: allRows.length
|
||
|
|
});
|
||
|
|
|
||
|
|
Swal.fire({
|
||
|
|
title: "이미지 삭제 확인",
|
||
|
|
text: "정말 삭제하시겠습니까?",
|
||
|
|
icon: "warning",
|
||
|
|
showCancelButton: true,
|
||
|
|
confirmButtonText: "삭제",
|
||
|
|
cancelButtonText: "취소",
|
||
|
|
reverseButtons: true,
|
||
|
|
}).then((result) => {
|
||
|
|
if (result.isConfirmed) {
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'DELETE',
|
||
|
|
data: JSON.stringify({
|
||
|
|
fileId: fileId,
|
||
|
|
tablename: "eworks",
|
||
|
|
item: "image",
|
||
|
|
folderPath: "경동기업/uploads",
|
||
|
|
DBtable: "picuploads",
|
||
|
|
}),
|
||
|
|
contentType: "application/json",
|
||
|
|
dataType: 'json',
|
||
|
|
}).done(function (response) {
|
||
|
|
if (response.status === 'success') {
|
||
|
|
console.log(`[기존이미지삭제] 이미지 삭제 완료:`, response);
|
||
|
|
|
||
|
|
// 화면에서 이미지 제거 (수정된 버전 - ID 기반)
|
||
|
|
var imageElement = $("#image" + divID);
|
||
|
|
var deleteButton = $("#delImage" + divID);
|
||
|
|
var imageName = '이미지';
|
||
|
|
|
||
|
|
// 이미지 이름 찾기 시도
|
||
|
|
if (imageElement.length > 0) {
|
||
|
|
var altText = imageElement.attr('alt') || imageElement.attr('title');
|
||
|
|
if (altText) {
|
||
|
|
imageName = altText;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[기존이미지삭제] 화면에서 제거할 이미지: ${imageName} (ID: image${divID}, delImage${divID})`);
|
||
|
|
console.log(`[기존이미지삭제] 찾은 요소 - imageElement: ${imageElement.length}개, deleteButton: ${deleteButton.length}개`);
|
||
|
|
|
||
|
|
if (imageElement.length > 0 || deleteButton.length > 0) {
|
||
|
|
// 부모 row div를 찾아서 제거
|
||
|
|
var parentRow = imageElement.closest('.row');
|
||
|
|
if (parentRow.length === 0) {
|
||
|
|
parentRow = deleteButton.closest('.row');
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[기존이미지삭제] 부모 row 요소: ${parentRow.length}개`);
|
||
|
|
|
||
|
|
if (parentRow.length > 0) {
|
||
|
|
// 페이드아웃 애니메이션과 함께 전체 행 제거
|
||
|
|
parentRow.fadeOut(400, function() {
|
||
|
|
$(this).remove();
|
||
|
|
console.log(`[기존이미지삭제] ${imageName} 화면에서 제거 완료`);
|
||
|
|
|
||
|
|
// 성공 메시지 표시
|
||
|
|
Toastify({
|
||
|
|
text: `${imageName}가 성공적으로 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
|
||
|
|
// 이미지가 모두 삭제되었는지 확인
|
||
|
|
setTimeout(function() {
|
||
|
|
var remainingImages = $("#displayImage .row").length;
|
||
|
|
console.log(`[기존이미지삭제] 남은 이미지 개수: ${remainingImages}`);
|
||
|
|
|
||
|
|
if (remainingImages === 0) {
|
||
|
|
$("#displayImage").html('<div class="text-center text-muted">첨부된 이미지가 없습니다.</div>');
|
||
|
|
console.log('[기존이미지삭제] 모든 이미지가 삭제됨 - 안내 메시지 표시');
|
||
|
|
}
|
||
|
|
}, 100);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
// row를 찾지 못한 경우 개별 요소들을 제거
|
||
|
|
console.log(`[기존이미지삭제] row를 찾지 못함. 개별 요소 제거 시도`);
|
||
|
|
imageElement.fadeOut(400, function() { $(this).remove(); });
|
||
|
|
deleteButton.fadeOut(400, function() { $(this).remove(); });
|
||
|
|
|
||
|
|
// 성공 메시지 표시
|
||
|
|
Toastify({
|
||
|
|
text: `${imageName}가 성공적으로 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.warn(`[기존이미지삭제] 삭제할 이미지 요소를 찾을 수 없음 (divID: ${divID})`);
|
||
|
|
console.log(`[기존이미지삭제] 전체 이미지 목록을 다시 불러와서 동기화`);
|
||
|
|
// 전체 이미지 목록을 다시 불러와서 동기화
|
||
|
|
displayImage();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
console.log(response.message);
|
||
|
|
Toastify({
|
||
|
|
text: "이미지 삭제 중 문제가 발생했습니다.",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
style: {
|
||
|
|
background: "linear-gradient(to right, #ff6b6b, #ee5a24)"
|
||
|
|
},
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
}).fail(function (error) {
|
||
|
|
console.error("삭제 중 오류:", error);
|
||
|
|
Toastify({
|
||
|
|
title: "삭제 실패",
|
||
|
|
text: "이미지 삭제 중 문제가 발생했습니다.",
|
||
|
|
icon: "error",
|
||
|
|
confirmButtonText: "확인",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 팝업 창을 중앙에 열기
|
||
|
|
function popupCenter(url, title, w, h) {
|
||
|
|
var left = (screen.width/2)-(w/2);
|
||
|
|
var top = (screen.height/2)-(h/2);
|
||
|
|
return window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadExistingFiles(num) {
|
||
|
|
// num이 없으면 임시키 사용
|
||
|
|
var currentNum = num;
|
||
|
|
if (!currentNum || currentNum === '') {
|
||
|
|
currentNum = $("#temp_key").val();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 기존 첨부파일 불러오기 (Google Drive에서)
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'GET',
|
||
|
|
data: {
|
||
|
|
num: currentNum,
|
||
|
|
tablename: 'eworks',
|
||
|
|
item: 'attached',
|
||
|
|
folderPath: '경동기업/uploads'
|
||
|
|
},
|
||
|
|
dataType: 'json',
|
||
|
|
success: function(data) {
|
||
|
|
if (data && data.length > 0) {
|
||
|
|
displayExistingFiles(data, 'displayFile');
|
||
|
|
}
|
||
|
|
},
|
||
|
|
error: function(xhr, status, error) {
|
||
|
|
console.log('기존 파일 불러오기 오류:', error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 기존 이미지 불러오기
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'GET',
|
||
|
|
data: {
|
||
|
|
num: currentNum,
|
||
|
|
tablename: 'eworks',
|
||
|
|
item: 'image',
|
||
|
|
folderPath: '경동기업/uploads'
|
||
|
|
},
|
||
|
|
dataType: 'json',
|
||
|
|
success: function(data) {
|
||
|
|
if (data && data.length > 0) {
|
||
|
|
displayExistingImages(data, 'displayImage');
|
||
|
|
}
|
||
|
|
},
|
||
|
|
error: function(xhr, status, error) {
|
||
|
|
console.log('기존 이미지 불러오기 오류:', error);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function displayExistingFiles(files, displayId) {
|
||
|
|
var displayDiv = document.getElementById(displayId);
|
||
|
|
if (files.length > 0) {
|
||
|
|
displayDiv.style.display = 'block';
|
||
|
|
displayDiv.innerHTML = '<h6 class="mb-2">기존 첨부파일:</h6>';
|
||
|
|
|
||
|
|
files.forEach(function(file, index) {
|
||
|
|
var fileDiv = document.createElement('div');
|
||
|
|
fileDiv.className = 'd-flex align-items-center justify-content-between p-2 border rounded mb-2';
|
||
|
|
fileDiv.innerHTML = `
|
||
|
|
<div class="d-flex align-items-center" data-file-id="${file.fileId}" data-file-type="attached">
|
||
|
|
<i class="bi bi-file-earmark me-2"></i>
|
||
|
|
<a href="${file.link || '#'}" target="_blank" class="text-decoration-none">
|
||
|
|
${file.realname || '다운로드 파일'}
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-file" data-file-id="${file.fileId}" data-file-type="attached">
|
||
|
|
<i class="bi bi-trash"></i>
|
||
|
|
</button>
|
||
|
|
`;
|
||
|
|
displayDiv.appendChild(fileDiv);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function displayExistingImages(images, displayId) {
|
||
|
|
var displayDiv = document.getElementById(displayId);
|
||
|
|
if (images.length > 0) {
|
||
|
|
displayDiv.style.display = 'block';
|
||
|
|
displayDiv.innerHTML = '<h6 class="mb-2">기존 이미지:</h6>';
|
||
|
|
|
||
|
|
images.forEach(function(image, index) {
|
||
|
|
var imageDiv = document.createElement('div');
|
||
|
|
imageDiv.className = 'd-flex align-items-center justify-content-between p-2 border rounded mb-2';
|
||
|
|
imageDiv.innerHTML = `
|
||
|
|
<div class="d-flex align-items-center" data-file-id="${image.fileId}" data-file-type="image">
|
||
|
|
<img src="${image.thumbnail || image.link}" class="me-2" style="width: 40px; height: 40px; object-fit: cover;">
|
||
|
|
<a href="${image.link || '#'}" target="_blank" class="text-decoration-none">
|
||
|
|
${image.realname || '이미지 파일'}
|
||
|
|
</a>
|
||
|
|
</div>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-file" data-file-id="${image.fileId}" data-file-type="image">
|
||
|
|
<i class="bi bi-trash"></i>
|
||
|
|
</button>
|
||
|
|
`;
|
||
|
|
displayDiv.appendChild(imageDiv);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function removeFile(fileId, fileType) {
|
||
|
|
Swal.fire({
|
||
|
|
title: '파일 삭제 확인',
|
||
|
|
text: '정말로 이 파일을 삭제하시겠습니까?',
|
||
|
|
icon: 'warning',
|
||
|
|
showCancelButton: true,
|
||
|
|
confirmButtonColor: '#d33',
|
||
|
|
cancelButtonColor: '#3085d6',
|
||
|
|
confirmButtonText: '삭제',
|
||
|
|
cancelButtonText: '취소'
|
||
|
|
}).then((result) => {
|
||
|
|
if (result.isConfirmed) {
|
||
|
|
$.ajax({
|
||
|
|
url: '/filedrive/fileprocess.php',
|
||
|
|
type: 'DELETE',
|
||
|
|
data: JSON.stringify({
|
||
|
|
fileId: fileId,
|
||
|
|
tablename: 'eworks',
|
||
|
|
item: fileType,
|
||
|
|
folderPath: "경동기업/uploads",
|
||
|
|
DBtable: "picuploads"
|
||
|
|
}),
|
||
|
|
contentType: "application/json",
|
||
|
|
dataType: 'json',
|
||
|
|
success: function(response) {
|
||
|
|
if (response.status === 'success') {
|
||
|
|
console.log(`[모달파일삭제] ${fileType} 파일 삭제 완료:`, response);
|
||
|
|
|
||
|
|
// 해당 파일의 버튼 요소를 찾아서 제거
|
||
|
|
var removeButton = $(`button[data-file-id="${fileId}"]`);
|
||
|
|
var fileContainer = removeButton.closest('.d-flex, .row');
|
||
|
|
var fileName = 'Unknown File';
|
||
|
|
|
||
|
|
// 파일명 찾기
|
||
|
|
var fileNameElement = fileContainer.find('a').first();
|
||
|
|
if (fileNameElement.length > 0) {
|
||
|
|
fileName = fileNameElement.text().trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[모달파일삭제] 화면에서 제거할 파일: ${fileName}`);
|
||
|
|
|
||
|
|
// 애니메이션과 함께 파일 컨테이너 제거
|
||
|
|
fileContainer.fadeOut(400, function() {
|
||
|
|
$(this).remove();
|
||
|
|
console.log(`[모달파일삭제] ${fileName} 화면에서 제거 완료`);
|
||
|
|
|
||
|
|
// 성공 메시지 표시
|
||
|
|
Toastify({
|
||
|
|
text: `${fileName}이 성공적으로 삭제되었습니다.`,
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#28a745",
|
||
|
|
}).showToast();
|
||
|
|
|
||
|
|
// 파일 목록이 비어있는지 확인
|
||
|
|
var displayContainer = fileType === 'attached' ? '#displayFile' : '#displayImage';
|
||
|
|
setTimeout(function() {
|
||
|
|
var remainingFiles = $(displayContainer + ' .d-flex, ' + displayContainer + ' .row').length;
|
||
|
|
console.log(`[모달파일삭제] 남은 ${fileType} 파일 개수: ${remainingFiles}`);
|
||
|
|
|
||
|
|
if (remainingFiles === 0) {
|
||
|
|
var emptyMessage = fileType === 'attached' ? '첨부된 파일이 없습니다.' : '첨부된 이미지가 없습니다.';
|
||
|
|
$(displayContainer).html(`<div class="text-center text-muted">${emptyMessage}</div>`);
|
||
|
|
console.log(`[모달파일삭제] 모든 ${fileType} 파일이 삭제됨 - 안내 메시지 표시`);
|
||
|
|
}
|
||
|
|
}, 100);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
console.error(`[모달파일삭제] ${fileType} 파일 삭제 실패:`, response);
|
||
|
|
Toastify({
|
||
|
|
text: "파일 삭제 중 문제가 발생했습니다.",
|
||
|
|
duration: 2000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#dc3545",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
error: function(xhr, status, error) {
|
||
|
|
console.error(`[모달파일삭제] ${fileType} 파일 삭제 중 오류:`, {
|
||
|
|
status: status,
|
||
|
|
error: error,
|
||
|
|
response: xhr.responseText
|
||
|
|
});
|
||
|
|
|
||
|
|
Toastify({
|
||
|
|
text: "파일 삭제 중 오류가 발생했습니다.",
|
||
|
|
duration: 3000,
|
||
|
|
close: true,
|
||
|
|
gravity: "top",
|
||
|
|
position: "center",
|
||
|
|
backgroundColor: "#dc3545",
|
||
|
|
}).showToast();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
$(document).ready(function(){
|
||
|
|
// PHP의 부서 JSON 데이터를 JS 변수로 저장 (각 항목은 ['corp'=> ..., 'part'=> ...] 형식)
|
||
|
|
var partData = <?= json_encode($partData) ?>;
|
||
|
|
|
||
|
|
// PHP에서 전달된 부서 검색 값 (기존에 선택된 값)
|
||
|
|
var initialPart = "<?= htmlspecialchars($partSearch, ENT_QUOTES, 'UTF-8') ?>";
|
||
|
|
|
||
|
|
// 부서 select 업데이트 함수
|
||
|
|
function updatePartSelect(selectedCorp) {
|
||
|
|
var $partSelect = $('#partSearch');
|
||
|
|
$partSelect.empty();
|
||
|
|
$partSelect.append('<option value=""><?= "(부서)" ?></option>');
|
||
|
|
// 선택된 소속과 일치하는 부서 옵션만 추가
|
||
|
|
$.each(partData, function(index, dept) {
|
||
|
|
if(dept.corp === selectedCorp) {
|
||
|
|
$partSelect.append('<option value="'+ dept.part +'">'+ dept.part +'</option>');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
// 만약 초기 부서값이 있다면 선택
|
||
|
|
if(initialPart !== "") {
|
||
|
|
$partSelect.val(initialPart);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 페이지 로드 시, 이미 소속이 선택되어 있다면 부서 옵션 업데이트
|
||
|
|
var initialCorp = $('#corpSearch').val();
|
||
|
|
updatePartSelect(initialCorp);
|
||
|
|
|
||
|
|
// 소속 select 변경 시 부서 select 업데이트
|
||
|
|
$('#corpSearch').on('change', function(){
|
||
|
|
var selectedCorp = $(this).val();
|
||
|
|
updatePartSelect(selectedCorp);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
$(function(){
|
||
|
|
// 모달 열기
|
||
|
|
$('#monthlyLeaveBtn').on('click', function(){
|
||
|
|
$('#monthlyLeaveModal').show();
|
||
|
|
loadMonthlyLeave();
|
||
|
|
});
|
||
|
|
// 모달 닫기
|
||
|
|
$('.monthlyClose').on('click', function(){
|
||
|
|
$('#monthlyLeaveModal').hide();
|
||
|
|
});
|
||
|
|
// select 변경 시 다시 로드
|
||
|
|
$('#monthYearSelect, #corpSelect').on('change', loadMonthlyLeave);
|
||
|
|
|
||
|
|
function loadMonthlyLeave(){
|
||
|
|
var year = $('#monthYearSelect').val();
|
||
|
|
var corp = $('#corpSelect').val();
|
||
|
|
$.ajax({
|
||
|
|
url: 'monthly_leave_ajax.php',
|
||
|
|
type: 'POST',
|
||
|
|
data: { year: year, corp: corp },
|
||
|
|
success: function(html){
|
||
|
|
$('#monthlyLeaveContent').html(html);
|
||
|
|
},
|
||
|
|
error: function(xhr){
|
||
|
|
console.error(xhr.responseText);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
</script>
|
||
|
|
|
||
|
|
</body>
|
||
|
|
</html>
|