jasan/index.php
2026-03-14 21:51:23 +09:00

322 lines
No EOL
23 KiB
PHP

<?php require_once 'auth_check.php'; ?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>한경협 자산관리 시스템 | Dashboard</title>
<!-- Zero-Mojibake Protocol: UTF-8 and Meta tag included -->
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/style.css">
</head>
<body class="bg-[#f8fafc] text-slate-800" x-data="dashboard()">
<?php include 'nav.php'; ?>
<!-- Main Content -->
<main class="max-w-[1600px] mx-auto p-8 pt-10">
<header class="flex justify-between items-center mb-10">
<div>
<h2 class="text-3xl font-extrabold text-slate-900 tracking-tight">System Statistics</h2>
<p class="text-slate-500 mt-1">실시간 자산 배정 현황 및 가동률을 확인하세요.</p>
</div>
<div class="flex items-center space-x-4">
<button @click="init()"
class="bg-white border border-slate-200 p-2.5 rounded-xl shadow-sm hover:bg-slate-50 transition-all">
<svg class="w-5 h-5 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</button>
<div class="h-8 w-[1px] bg-slate-200"></div>
<span class="text-sm font-bold text-blue-600 bg-blue-50 px-4 py-2 rounded-full">LIVE</span>
</div>
</header>
<!-- Stats Grid -->
<?php
$visible_count = 2; // 노트북, 조직현황은 상시 노출
if (($settings['menu_cards_visible'] ?? '1') === '1') $visible_count++;
if (($settings['menu_mfp_visible'] ?? '1') === '1') $visible_count++;
$grid_cols_class = match ($visible_count) {
2 => 'xl:grid-cols-2 max-w-[800px]',
3 => 'xl:grid-cols-3 max-w-[1200px]',
default => 'xl:grid-cols-4'
};
?>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 transition-all duration-500 mx-auto <?php echo $grid_cols_class; ?>">
<div
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all">
<div class="flex justify-between mb-6">
<div class="p-4 bg-blue-50 rounded-2xl text-blue-600"><svg class="w-8 h-8" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg></div><span class="text-xs font-bold text-blue-500 bg-blue-50 px-2.5 py-1 rounded-lg"
x-text="calcPercent(stats.laptops) + '%'"></span>
</div>
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">Laptop Mobility</h3>
<div class="flex items-baseline space-x-2 mb-4"><span class="text-4xl font-black text-slate-900"
x-text="stats.laptops.assigned || 0">0</span><span
class="text-slate-400 font-medium tracking-tighter">/ <span
x-text="stats.laptops.total || 0">0</span></span></div>
<div class="w-full bg-slate-100 h-2 rounded-full overflow-hidden">
<div class="h-full bg-blue-500 rounded-full transition-all duration-1000"
:style="'width: ' + calcPercent(stats.laptops) + '%'"></div>
</div>
</div>
<?php if (($settings['menu_cards_visible'] ?? '1') === '1'): ?>
<div
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all">
<div class="flex justify-between mb-6">
<div class="p-4 bg-emerald-50 rounded-2xl text-emerald-600"><svg class="w-8 h-8" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg></div><span
class="text-xs font-bold text-emerald-500 bg-emerald-50 px-2.5 py-1 rounded-lg">Security</span>
</div>
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">Access Cards</h3>
<div class="flex items-baseline space-x-2 mb-4"><span class="text-4xl font-black text-slate-900"
x-text="stats.cards.assigned || 0">0</span><span
class="text-slate-400 font-medium tracking-tighter">/ <span
x-text="stats.cards.total || 0">0</span></span></div>
</div>
<?php endif; ?>
<?php if (($settings['menu_mfp_visible'] ?? '1') === '1'): ?>
<div
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all">
<div class="flex justify-between mb-6">
<div class="p-4 bg-amber-50 rounded-2xl text-amber-600"><svg class="w-8 h-8" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg></div><span
class="text-xs font-bold text-amber-500 bg-amber-50 px-2.5 py-1 rounded-lg">Printing</span>
</div>
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">MFP Accounts</h3>
<div class="flex items-baseline space-x-2 mb-4"><span class="text-4xl font-black text-slate-900"
x-text="stats.mfp.assigned || 0">0</span><span
class="text-slate-400 font-medium tracking-tighter">/ <span
x-text="stats.mfp.total || 0">0</span></span></div>
</div>
<?php endif; ?>
<div
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all">
<div class="flex justify-between mb-6">
<div class="p-4 bg-indigo-50 rounded-2xl text-indigo-600"><svg class="w-8 h-8" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg></div><span
class="text-xs font-bold text-indigo-500 bg-indigo-50 px-2.5 py-1 rounded-lg">Assets</span>
</div>
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">Organization</h3>
<div class="mt-4">
<p class="text-sm font-semibold text-slate-600">일반회계 <span class="font-black text-slate-900"
x-text="stats.users.general">0</span></p>
<p class="text-sm font-semibold text-slate-600">특별회계 <span class="font-black text-slate-900"
x-text="stats.users.special">0</span></p>
</div>
</div>
</div>
<section class="mt-16">
<h3 class="text-xl font-bold text-slate-900 mb-6 flex items-center"><svg class="w-6 h-6 mr-2 text-blue-600"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>퀵 네비게이션</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-<?php echo $visible_count; ?> gap-6">
<a href="users.php"
class="group bg-white p-6 rounded-2xl border border-slate-200 hover:border-blue-500 transition-all flex items-center space-x-4">
<div class="bg-slate-50 p-3 rounded-xl group-hover:bg-blue-50 transition-colors"><svg
class="w-6 h-6 text-slate-400 group-hover:text-blue-500" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg></div>
<div>
<p class="font-black text-slate-900 group-hover:text-blue-600 transition-colors">직원 DB 관리</p>
<p class="text-xs text-slate-400">사번/부서/연락처</p>
</div>
</a>
<a href="laptops.php"
class="group bg-white p-6 rounded-2xl border border-slate-200 hover:border-blue-500 transition-all flex items-center space-x-4">
<div class="bg-slate-50 p-3 rounded-xl group-hover:bg-blue-50 transition-colors"><svg
class="w-6 h-6 text-slate-400 group-hover:text-blue-500" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg></div>
<div>
<p class="font-black text-slate-900 group-hover:text-blue-600 transition-colors">노트북 실사</p>
<p class="text-xs text-slate-400">자산태그/배정현황</p>
</div>
</a>
<?php if (($settings['menu_cards_visible'] ?? '1') === '1'): ?>
<a href="cards.php"
class="group bg-white p-6 rounded-2xl border border-slate-200 hover:border-blue-500 transition-all flex items-center space-x-4">
<div class="bg-slate-50 p-3 rounded-xl group-hover:bg-emerald-50 transition-colors"><svg
class="w-6 h-6 text-slate-400 group-hover:text-emerald-500" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg></div>
<div>
<p class="font-black text-slate-900 group-hover:text-emerald-600 transition-colors">출입증 관리</p>
<p class="text-xs text-slate-400">보안실 연동 데이터</p>
</div>
</a>
<?php endif; ?>
<?php if (($settings['menu_mfp_visible'] ?? '1') === '1'): ?>
<a href="mfp.php"
class="group bg-white p-6 rounded-2xl border border-slate-200 hover:border-blue-500 transition-all flex items-center space-x-4">
<div class="bg-slate-50 p-3 rounded-xl group-hover:bg-amber-50 transition-colors"><svg
class="w-6 h-6 text-slate-400 group-hover:text-amber-500" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg></div>
<div>
<p class="font-black text-slate-900 group-hover:text-amber-600 transition-colors">복합기 계정</p>
<p class="text-xs text-slate-400">임시 계정/pw 관리</p>
</div>
</a>
<?php endif; ?>
</div>
</section>
<!-- 자산 교체/매각 대상 리스트 -->
<section class="mt-16 mb-20" x-show="stats.replacements?.targets.length > 0 || stats.replacements?.upcoming.length > 0">
<div class="flex items-center justify-between mb-8">
<div>
<h3 class="text-2xl font-black text-slate-900 flex items-center gap-3">
<div class="w-10 h-10 bg-rose-100 rounded-2xl flex items-center justify-center">
<svg class="w-6 h-6 text-rose-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</div>
자산 교체 및 매각 관리
</h3>
<p class="text-slate-500 mt-2 text-sm font-medium">취득 후 4년이 경과하거나 도래한 자산 리스트입니다. (정책: 4년 단위 교체)</p>
</div>
<div class="flex gap-2">
<span class="px-4 py-2 bg-rose-50 text-rose-600 rounded-xl text-xs font-black border border-rose-100 uppercase tracking-tighter" x-text="'Current: ' + stats.replacements?.current_year"></span>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- 01. 매각 완료 대상 (4년 이상) -->
<div class="bg-white rounded-[2.5rem] border border-slate-200 shadow-sm overflow-hidden flex flex-col">
<div class="p-6 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center">
<div class="flex items-center gap-3">
<span class="w-8 h-8 bg-rose-600 text-white rounded-xl flex items-center justify-center text-xs font-black shadow-lg shadow-rose-200">01</span>
<h4 class="font-black text-slate-800">매각 대상 자산 <span class="text-rose-600 ml-1" x-text="'(' + stats.replacements?.targets.length + ')'"></span></h4>
</div>
<span class="text-[10px] font-black text-rose-500 bg-rose-50 px-2.5 py-1 rounded-lg border border-rose-100 uppercase">Immediate Action</span>
</div>
<div class="flex-1 overflow-y-auto max-h-[500px] no-scrollbar">
<table class="w-full text-left border-collapse">
<thead class="sticky top-0 bg-white/95 backdrop-blur-md z-10">
<tr>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">Asset TAG</th>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">Model & Spec</th>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest text-right">Purchase Year</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
<template x-for="item in stats.replacements?.targets" :key="item.id">
<tr class="hover:bg-rose-50/30 transition-colors group">
<td class="px-6 py-4 whitespace-nowrap">
<span class="text-sm font-black text-slate-900 group-hover:text-rose-600 transition-colors" x-text="item.asset_tag"></span>
</td>
<td class="px-6 py-4">
<div class="text-xs font-bold text-slate-700" x-text="item.model_name"></div>
<div class="text-[10px] text-slate-400 font-bold" x-text="item.manufacturer"></div>
</td>
<td class="px-6 py-4 text-right">
<div class="inline-flex flex-col items-end">
<span class="text-xs font-black text-slate-900" x-text="item.purchase_date.substring(0,4) + '년'"></span>
<span class="text-[9px] font-black text-rose-500 bg-rose-50 px-1.5 py-0.5 rounded mt-1" x-text="(stats.replacements.current_year - parseInt(item.purchase_date.substring(0,4))) + '년 경과'"></span>
</div>
</td>
</tr>
</template>
</tbody>
</table>
<div x-show="stats.replacements?.targets.length === 0" class="py-20 text-center text-slate-300 italic text-sm font-medium">관리 대상 자산이 없습니다.</div>
</div>
</div>
<!-- 02. 올해 매각 예정 (4년차 도래) -->
<div class="bg-white rounded-[2.5rem] border border-slate-200 shadow-sm overflow-hidden flex flex-col">
<div class="p-6 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center">
<div class="flex items-center gap-3">
<span class="w-8 h-8 bg-amber-500 text-white rounded-xl flex items-center justify-center text-xs font-black shadow-lg shadow-amber-200">02</span>
<h4 class="font-black text-slate-800">교체/매각 준비 <span class="text-amber-600 ml-1" x-text="'(' + stats.replacements?.upcoming.length + ')'"></span></h4>
</div>
<span class="text-[10px] font-black text-amber-500 bg-amber-50 px-2.5 py-1 rounded-lg border border-amber-100 uppercase" x-text="stats.replacements?.current_year + ' Replacement'"></span>
</div>
<div class="flex-1 overflow-y-auto max-h-[500px] no-scrollbar">
<table class="w-full text-left border-collapse">
<thead class="sticky top-0 bg-white/95 backdrop-blur-md z-10">
<tr>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">Asset TAG</th>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest">Model & Spec</th>
<th class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest text-right">Target Year</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
<template x-for="item in stats.replacements?.upcoming" :key="item.id">
<tr class="hover:bg-amber-50/30 transition-colors group">
<td class="px-6 py-4 whitespace-nowrap">
<span class="text-sm font-black text-slate-900 group-hover:text-amber-600 transition-colors" x-text="item.asset_tag"></span>
</td>
<td class="px-6 py-4">
<div class="text-xs font-bold text-slate-700" x-text="item.model_name"></div>
<div class="text-[10px] text-slate-400 font-bold" x-text="item.manufacturer"></div>
</td>
<td class="px-6 py-4 text-right">
<div class="inline-flex flex-col items-end">
<span class="text-xs font-black text-slate-900" x-text="(parseInt(item.purchase_date.substring(0,4)) + 4) + '년 매각 대상'"></span>
<span class="text-[9px] font-black text-blue-500 bg-blue-50 px-1.5 py-0.5 rounded mt-1" x-text="'취득: ' + item.purchase_date.substring(0,4) + '년'"></span>
</div>
</td>
</tr>
</template>
</tbody>
</table>
<div x-show="stats.replacements?.upcoming.length === 0" class="py-20 text-center text-slate-300 italic text-sm font-medium">관리 대상 자산이 없습니다.</div>
</div>
</div>
</div>
</section>
</main>
<script>
function dashboard() {
return {
stats: { laptops: {}, cards: {}, mfp: {}, users: {} },
init() {
fetch('api.php?action=get_dashboard_stats').then(res => res.json()).then(data => { this.stats = data; });
},
calcPercent(obj) {
if (!obj || !obj.total || obj.total == 0) return 0;
return Math.round((obj.assigned / obj.total) * 100);
}
}
}
</script>
</body>
</html>