diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 8fbb16e..c14afab 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -62,6 +62,7 @@ $routes->group('bag', ['filter' => 'loginAuth'], static function ($routes): void $routes->get('manual', 'Bag::manual'); $routes->get('manual/search', 'Bag::manualSearch'); // (:segment) 보다 먼저 $routes->get('manual/(:segment)', 'Bag::manualPage/$1'); + $routes->post('activity/print-log', 'Bag::printLog'); // 인쇄 활동 기록(beacon) }); $routes->get('bag/number-lookup', 'Bag::numberLookup'); diff --git a/app/Controllers/Admin/ActivityLog.php b/app/Controllers/Admin/ActivityLog.php index 0125cfc..02923cd 100644 --- a/app/Controllers/Admin/ActivityLog.php +++ b/app/Controllers/Admin/ActivityLog.php @@ -46,6 +46,8 @@ class ActivityLog extends BaseController 'feedback' => '사용자 의견', 'feedback_file' => '의견 첨부', 'blockchain_ledger' => '원장', + 'export' => '엑셀 다운로드', + 'print' => '인쇄', ]; /** action → 한글 라벨 */ @@ -53,6 +55,8 @@ class ActivityLog extends BaseController 'create' => '등록', 'update' => '수정', 'delete' => '삭제', + 'export' => '엑셀 다운로드', + 'print' => '인쇄', ]; /** 컬럼 → 한글 라벨 (없으면 접미사 추정 → 원문) */ @@ -156,6 +160,10 @@ class ActivityLog extends BaseController if ($isLogin) { $text = '로그인'; + } elseif ($action === 'export') { + $text = '엑셀 다운로드 — ' . (string) ($after['파일'] ?? ''); + } elseif ($action === 'print') { + $text = '인쇄 — ' . (string) ($after['화면'] ?? $after['경로'] ?? ''); } elseif ($action === 'create') { $text = $tlabel . $namePart . ' 등록'; } elseif ($action === 'delete') { diff --git a/app/Controllers/Bag.php b/app/Controllers/Bag.php index 2d7ffc5..9718909 100644 --- a/app/Controllers/Bag.php +++ b/app/Controllers/Bag.php @@ -3580,6 +3580,26 @@ SQL); /** * 사용자 매뉴얼(설명서) — 목차 첫 페이지로 이동. */ + /** + * 인쇄 활동 기록 (클라이언트 beforeprint beacon 수신). + * 화면(경로·제목)을 활동 로그에 'print' 액션으로 남긴다. + */ + public function printLog(): \CodeIgniter\HTTP\ResponseInterface + { + $title = trim((string) $this->request->getPost('title')); + $path = trim((string) $this->request->getPost('path')); + if (mb_strlen($title) > 120) { + $title = mb_substr($title, 0, 120); + } + if (mb_strlen($path) > 200) { + $path = mb_substr($path, 0, 200); + } + helper('audit'); + audit_log('print', 'print', 0, null, ['화면' => $title, '경로' => $path]); + + return $this->response->setStatusCode(204); + } + public function manual(): \CodeIgniter\HTTP\RedirectResponse { $first = (new \App\Libraries\ManualRenderer())->firstSlug(); diff --git a/app/Helpers/export_helper.php b/app/Helpers/export_helper.php index e6af3a4..69ecf97 100644 --- a/app/Helpers/export_helper.php +++ b/app/Helpers/export_helper.php @@ -8,6 +8,22 @@ declare(strict_types=1); * UTF-8 BOM 포함으로 한글 엑셀 호환성 보장 */ +if (! function_exists('audit_export_log')) { + /** + * 엑셀/CSV 다운로드를 활동 로그에 기록 (누가·무엇을 다운로드). + * 모든 export_* 함수 진입 시 호출 — 실패해도 다운로드에 영향 없음. + */ + function audit_export_log(string $filename): void + { + try { + helper('audit'); + audit_log('export', 'export', 0, null, ['파일' => $filename]); + } catch (\Throwable $e) { + // 무시: 로깅 실패가 다운로드를 막지 않도록 + } + } +} + if (! function_exists('export_csv')) { /** * CSV 파일을 브라우저로 다운로드 전송 @@ -18,6 +34,7 @@ if (! function_exists('export_csv')) { */ function export_csv(string $filename, array $headers, array $rows): void { + audit_export_log($filename); // 파일명에 .csv 확장자 보장 if (! str_ends_with($filename, '.csv')) { $filename .= '.csv'; @@ -80,6 +97,7 @@ if (! function_exists('export_excel_2003_xml')) { */ function export_excel_2003_xml(string $filename, string $sheetName, array $headers, array $rows, array $topRows = []): void { + audit_export_log($filename); $filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xls'; $safeSheet = str_replace(['/', '\\', '?', '*', '[', ']'], '', $sheetName); @@ -147,6 +165,7 @@ if (! function_exists('export_excel_2003_xml_workbook')) { */ function export_excel_2003_xml_workbook(string $filename, array $sheets): void { + audit_export_log($filename); $filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xls'; $esc = static function (mixed $v): string { @@ -402,6 +421,7 @@ if (! function_exists('export_bag_flow_report_excel')) { array $metaLines, array $reportRows ): void { + audit_export_log($filename); $baseName = preg_replace('/\.[^.]+$/u', '', $filename); $baseName = preg_replace('/[^\p{L}\p{N}_\-]+/u', '_', $baseName) ?? 'bag_flow'; $baseName = trim($baseName, '_') !== '' ? trim($baseName, '_') : 'bag_flow'; @@ -452,6 +472,7 @@ if (! function_exists('export_xlsx')) { */ function export_xlsx(string $filename, string $sheetName, array $headers, array $rows): void { + audit_export_log($filename); $filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xlsx'; $safeSheet = str_replace(['/', '\\', '?', '*', '[', ']'], '', $sheetName); diff --git a/app/Views/admin/access/activity_log.php b/app/Views/admin/access/activity_log.php index d86902e..d6c9c2b 100644 --- a/app/Views/admin/access/activity_log.php +++ b/app/Views/admin/access/activity_log.php @@ -15,6 +15,8 @@ $actionBadge = static function (string $a): string { 'create' => 'background:#dcfce7;color:#166534;', 'update' => 'background:#dbeafe;color:#1e40af;', 'delete' => 'background:#fee2e2;color:#991b1b;', + 'export' => 'background:#cffafe;color:#155e75;', + 'print' => 'background:#f3e8ff;color:#6b21a8;', ]; return $map[$a] ?? 'background:#f3f4f6;color:#374151;'; }; diff --git a/app/Views/admin/layout.php b/app/Views/admin/layout.php index 8a1bdd5..512ed63 100644 --- a/app/Views/admin/layout.php +++ b/app/Views/admin/layout.php @@ -201,5 +201,9 @@ tailwind.config = { })(); + diff --git a/app/Views/bag/layout/embed.php b/app/Views/bag/layout/embed.php index 4da288f..c0c45d1 100644 --- a/app/Views/bag/layout/embed.php +++ b/app/Views/bag/layout/embed.php @@ -240,5 +240,9 @@ tailwind.config = { })(); + diff --git a/app/Views/bag/layout/main.php b/app/Views/bag/layout/main.php index fb90a4d..a59b658 100644 --- a/app/Views/bag/layout/main.php +++ b/app/Views/bag/layout/main.php @@ -196,5 +196,9 @@ body { overflow: hidden; } window.addEventListener('pagehide', closeStuckOverlays); })(); + diff --git a/app/Views/bag/layout/portal.php b/app/Views/bag/layout/portal.php index f6ca7c0..30c2a7d 100644 --- a/app/Views/bag/layout/portal.php +++ b/app/Views/bag/layout/portal.php @@ -296,5 +296,9 @@ tailwind.config = { })(); +