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

511 lines
18 KiB
PHP

<?php
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/load_header.php");
require_once(__DIR__ . '/../lib/mydb.php');
// 권한 체크 (레벨 5 이하만 접근)
if ($level > 5) {
echo "<script>alert('접근 권한이 없습니다.'); history.back();</script>";
exit;
}
$pdo = db_connect();
// 페이징 설정
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$per_page = 20;
$offset = ($page - 1) * $per_page;
// 검색 기능
$search_keyword = isset($_GET['search']) ? trim($_GET['search']) : '';
$where_clause = '';
$params = [];
if ($search_keyword) {
$where_clause = " WHERE biz_no LIKE :search OR company_name LIKE :search OR representative LIKE :search";
$params[':search'] = '%' . $search_keyword . '%';
}
// 전체 레코드 수 조회
$count_sql = "SELECT COUNT(*) as total FROM biz_cert" . $where_clause;
$count_stmt = $pdo->prepare($count_sql);
$count_stmt->execute($params);
$total_records = $count_stmt->fetch(PDO::FETCH_ASSOC)['total'];
$total_pages = ceil($total_records / $per_page);
// 데이터 조회
$sql = "SELECT id, biz_no, company_name, representative, open_date, issue_date, created_at
FROM biz_cert" . $where_clause . "
ORDER BY id DESC
LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value, PDO::PARAM_STR);
}
$stmt->bindValue(':limit', $per_page, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>사업자등록증 OCR 목록</title>
<style>
.ocr-container {
max-width: 1400px;
margin: 20px auto;
padding: 20px;
}
.header-section {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.search-section {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
}
.search-section input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
width: 300px;
}
.btn {
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: #0d6efd;
color: white;
}
.btn-primary:hover {
background: #0b5ed7;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5c636a;
}
.btn-success {
background: #198754;
color: white;
}
.btn-success:hover {
background: #157347;
}
.data-table {
width: 100%;
border-collapse: collapse;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 4px;
overflow: hidden;
}
.data-table th {
background: #f8f9fa;
padding: 12px;
text-align: left;
font-weight: bold;
border-bottom: 2px solid #dee2e6;
}
.data-table td {
padding: 12px;
border-bottom: 1px solid #dee2e6;
}
.data-table tr:hover {
background: #f8f9fa;
}
.data-table tr:last-child td {
border-bottom: none;
}
.pagination {
display: flex;
justify-content: center;
gap: 5px;
margin-top: 20px;
flex-wrap: wrap;
}
.pagination a, .pagination span {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
text-decoration: none;
color: #333;
}
.pagination a:hover {
background: #f8f9fa;
}
.pagination .active {
background: #0d6efd;
color: white;
border-color: #0d6efd;
}
.stats {
color: #6c757d;
font-size: 14px;
margin-bottom: 10px;
}
.view-link {
color: #0d6efd;
text-decoration: none;
cursor: pointer;
}
.view-link:hover {
text-decoration: underline;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 5% auto;
padding: 20px;
border: 1px solid #888;
border-radius: 8px;
width: 80%;
max-width: 800px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #000;
}
.detail-group {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.detail-group:last-child {
border-bottom: none;
}
.detail-label {
font-weight: bold;
color: #333;
display: inline-block;
width: 150px;
}
.detail-value {
color: #666;
}
.raw-text-box {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<?php include $_SERVER['DOCUMENT_ROOT'] . "/myheader.php"; ?>
<div class="ocr-container">
<div class="header-section">
<div>
<h3><i class="bi bi-list-ul"></i> 사업자등록증 OCR 목록</h3>
<p class="stats">
총 <?php echo number_format($total_records); ?>건
(<?php echo $page; ?> / <?php echo $total_pages; ?> 페이지)
</p>
</div>
<div>
<a href="index.php" class="btn btn-success">
<i class="bi bi-plus-circle"></i> 새로 등록
</a>
</div>
</div>
<div class="search-section">
<form method="get" action="" style="display: flex; gap: 10px; align-items: center;">
<input type="text" name="search" placeholder="사업자번호, 상호명, 대표자명 검색..."
value="<?php echo htmlspecialchars($search_keyword); ?>">
<button type="submit" class="btn btn-primary">
<i class="bi bi-search"></i> 검색
</button>
<?php if ($search_keyword): ?>
<a href="list.php" class="btn btn-secondary">
<i class="bi bi-x-circle"></i> 초기화
</a>
<?php endif; ?>
</form>
</div>
<?php if (empty($rows)): ?>
<div style="text-align: center; padding: 40px; background: white; border-radius: 4px;">
<i class="bi bi-inbox" style="font-size: 48px; color: #ccc;"></i>
<p style="color: #999; margin-top: 10px;">
<?php echo $search_keyword ? '검색 결과가 없습니다.' : '등록된 데이터가 없습니다.'; ?>
</p>
</div>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th style="width: 60px;">ID</th>
<th style="width: 150px;">사업자번호</th>
<th>상호명</th>
<th style="width: 120px;">대표자</th>
<th style="width: 120px;">개업일자</th>
<th style="width: 120px;">발급일자</th>
<th style="width: 150px;">등록일시</th>
<th style="width: 150px;">관리</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<tr>
<td><?php echo htmlspecialchars($row['id']); ?></td>
<td><?php echo htmlspecialchars($row['biz_no']); ?></td>
<td><?php echo htmlspecialchars($row['company_name']); ?></td>
<td><?php echo htmlspecialchars($row['representative']); ?></td>
<td><?php echo $row['open_date'] ? htmlspecialchars($row['open_date']) : '-'; ?></td>
<td><?php echo $row['issue_date'] ? htmlspecialchars($row['issue_date']) : '-'; ?></td>
<td><?php echo htmlspecialchars($row['created_at']); ?></td>
<td>
<a href="javascript:void(0)" class="view-link" onclick="viewDetail(<?php echo $row['id']; ?>)" title="상세보기">
<i class="bi bi-eye"></i>
</a>
<a href="edit.php?id=<?php echo $row['id']; ?>" class="view-link" style="color: #198754; margin-left: 8px;" title="수정">
<i class="bi bi-pencil"></i>
</a>
<a href="javascript:void(0)" class="view-link" style="color: #dc3545; margin-left: 8px;" onclick="confirmDelete(<?php echo $row['id']; ?>, '<?php echo htmlspecialchars($row['company_name'], ENT_QUOTES); ?>')" title="삭제">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if ($total_pages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=1<?php echo $search_keyword ? '&search=' . urlencode($search_keyword) : ''; ?>">처음</a>
<a href="?page=<?php echo $page - 1; ?><?php echo $search_keyword ? '&search=' . urlencode($search_keyword) : ''; ?>">이전</a>
<?php endif; ?>
<?php
$start_page = max(1, $page - 5);
$end_page = min($total_pages, $page + 5);
for ($i = $start_page; $i <= $end_page; $i++):
if ($i == $page):
?>
<span class="active"><?php echo $i; ?></span>
<?php else: ?>
<a href="?page=<?php echo $i; ?><?php echo $search_keyword ? '&search=' . urlencode($search_keyword) : ''; ?>">
<?php echo $i; ?>
</a>
<?php
endif;
endfor;
?>
<?php if ($page < $total_pages): ?>
<a href="?page=<?php echo $page + 1; ?><?php echo $search_keyword ? '&search=' . urlencode($search_keyword) : ''; ?>">다음</a>
<a href="?page=<?php echo $total_pages; ?><?php echo $search_keyword ? '&search=' . urlencode($search_keyword) : ''; ?>">마지막</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<!-- 상세보기 모달 -->
<div id="detailModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h4 style="margin-top: 0;"><i class="bi bi-file-earmark-text"></i> 사업자등록증 상세정보</h4>
<div id="detailContent">
<p style="text-align: center; color: #999;">로딩중...</p>
</div>
</div>
</div>
<script>
function viewDetail(id) {
const modal = document.getElementById('detailModal');
const content = document.getElementById('detailContent');
modal.style.display = 'block';
content.innerHTML = '<p style="text-align: center; color: #999;">로딩중...</p>';
fetch('view_detail.php?id=' + id)
.then(response => response.json())
.then(data => {
if (data.ok) {
const d = data.data;
content.innerHTML = `
<div class="detail-group">
<span class="detail-label">ID:</span>
<span class="detail-value">${escapeHtml(d.id)}</span>
</div>
<div class="detail-group">
<span class="detail-label">사업자등록번호:</span>
<span class="detail-value">${escapeHtml(d.biz_no)}</span>
</div>
<div class="detail-group">
<span class="detail-label">상호명:</span>
<span class="detail-value">${escapeHtml(d.company_name)}</span>
</div>
<div class="detail-group">
<span class="detail-label">대표자명:</span>
<span class="detail-value">${escapeHtml(d.representative)}</span>
</div>
<div class="detail-group">
<span class="detail-label">개업일자:</span>
<span class="detail-value">${d.open_date ? escapeHtml(d.open_date) : '-'}</span>
</div>
<div class="detail-group">
<span class="detail-label">본점 소재지:</span>
<span class="detail-value">${d.address ? escapeHtml(d.address) : '-'}</span>
</div>
<div class="detail-group">
<span class="detail-label">업태:</span>
<span class="detail-value">${d.type ? escapeHtml(d.type) : '-'}</span>
</div>
<div class="detail-group">
<span class="detail-label">종목:</span>
<span class="detail-value">${d.item ? escapeHtml(d.item) : '-'}</span>
</div>
<div class="detail-group">
<span class="detail-label">발급일자:</span>
<span class="detail-value">${d.issue_date ? escapeHtml(d.issue_date) : '-'}</span>
</div>
<div class="detail-group">
<span class="detail-label">등록일시:</span>
<span class="detail-value">${escapeHtml(d.created_at)}</span>
</div>
${d.raw_text ? `
<div class="detail-group">
<span class="detail-label">OCR 원문:</span><br>
<div class="raw-text-box">${escapeHtml(d.raw_text)}</div>
</div>
` : ''}
<div style="margin-top: 20px; text-align: right; display: flex; gap: 10px; justify-content: flex-end;">
<a href="edit.php?id=${d.id}" class="btn btn-success">
<i class="bi bi-pencil"></i> 수정
</a>
<button type="button" class="btn" style="background: #dc3545; color: white;" onclick="confirmDeleteFromModal(${d.id}, '${escapeHtml(d.company_name).replace(/'/g, "\\'")}')">
<i class="bi bi-trash"></i> 삭제
</button>
</div>
`;
} else {
content.innerHTML = '<p style="color: red;">오류: ' + escapeHtml(data.error) + '</p>';
}
})
.catch(error => {
content.innerHTML = '<p style="color: red;">데이터 로드 실패</p>';
console.error('Error:', error);
});
}
function closeModal() {
document.getElementById('detailModal').style.display = 'none';
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return (text || '').toString().replace(/[&<>"']/g, m => map[m]);
}
// 삭제 확인 (목록에서)
function confirmDelete(id, companyName) {
if (confirm(`정말로 "${companyName}" 데이터를 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다.`)) {
deleteRecord(id);
}
}
// 삭제 확인 (모달에서)
function confirmDeleteFromModal(id, companyName) {
if (confirm(`정말로 "${companyName}" 데이터를 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다.`)) {
deleteRecord(id);
}
}
// 삭제 실행
function deleteRecord(id) {
fetch('delete.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: id })
})
.then(response => response.json())
.then(data => {
if (data.ok) {
alert('삭제되었습니다.');
closeModal();
location.reload();
} else {
alert('삭제 실패: ' + (data.error || '알 수 없는 오류'));
}
})
.catch(error => {
alert('삭제 중 오류가 발생했습니다.');
console.error('Error:', error);
});
}
// 모달 외부 클릭시 닫기
window.onclick = function(event) {
const modal = document.getElementById('detailModal');
if (event.target == modal) {
closeModal();
}
}
// 페이지 로드 완료 시 loader 숨기기
window.addEventListener('load', function() {
const loadingOverlay = document.getElementById('loadingOverlay');
if (loadingOverlay) {
loadingOverlay.style.display = 'none';
}
});
</script>
</body>
</html>