jasan/rental.php
2026-02-08 18:10:54 +09:00

308 lines
No EOL
17 KiB
PHP

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>노트북 임대 관리 | FKI ASSET</title>
<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">
<style>
body {
font-family: 'Pretendard', sans-serif;
}
[x-cloak] {
display: none !important;
}
.modal-bg {
background-color: rgba(15, 23, 42, 0.7);
backdrop-filter: blur(4px);
}
</style>
</head>
<body class="bg-[#f8fafc] text-slate-800" x-data="rentalManagement()">
<?php include 'nav.php'; ?>
<main class="max-w-[1600px] mx-auto p-8 pt-10">
<header class="mb-10 flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h2 class="text-3xl font-extrabold text-slate-900 tracking-tight">노트북 단기 임대</h2>
<p class="text-slate-500 mt-1">업무용 공용 자산의 대여 및 반납 기록 관리 (사원별 임대 현황)</p>
</div>
<div class="flex items-center gap-3">
<div class="relative">
<input type="text" x-model="searchQuery" placeholder="자산번호, 모델명 검색..."
class="pl-10 pr-4 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500 outline-none w-64 shadow-sm">
<svg class="w-4 h-4 absolute left-3.5 top-3 text-slate-400" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
</header>
<!-- Rental List Table -->
<div class="bg-white rounded-3xl shadow-sm border border-slate-200 overflow-hidden">
<table class="min-w-full divide-y divide-slate-100">
<thead class="bg-slate-50/50">
<tr>
<th @click="sortBy('asset_tag')"
class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors">
자산번호 <span x-show="sortKey === 'asset_tag'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
</th>
<th @click="sortBy('model_name')"
class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors">
모델 정보 <span x-show="sortKey === 'model_name'"
x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
</th>
<th @click="sortBy('rental_user_name')"
class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider cursor-pointer hover:bg-slate-100 transition-colors text-blue-600">
현재 임대사원 <span x-show="sortKey === 'rental_user_name'"
x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
</th>
<th class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider">
비고(특기사항)</th>
<th class="px-6 py-4 text-right text-xs font-bold text-slate-500 uppercase tracking-wider">액션
</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
<template x-for="item in sortedAssets" :key="item.id">
<tr class="hover:bg-slate-50/80 transition-colors group">
<td class="px-6 py-4 whitespace-nowrap">
<span class="text-sm font-black text-slate-900 px-2.5 py-1 bg-slate-100 rounded-lg"
x-text="item.asset_tag"></span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-bold text-slate-800" x-text="item.model_name"></div>
<div class="text-[10px] text-slate-400 font-black uppercase tracking-widest"
x-text="item.manufacturer"></div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<template x-if="item.rental_user_name">
<div class="flex items-center">
<div class="w-2 h-2 bg-emerald-500 rounded-full mr-2"></div>
<span class="text-sm font-black text-slate-900"
x-text="item.rental_user_name"></span>
</div>
</template>
<template x-if="!item.rental_user_name">
<span class="text-xs text-slate-300 italic font-medium">임대 가능</span>
</template>
</td>
<td class="px-6 py-4">
<div class="text-xs text-slate-500 truncate max-w-xs" x-text="item.remarks || '-'">
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
<div class="flex justify-end gap-2">
<button @click="openHistoryModal(item)"
class="px-3 py-1.5 text-[11px] font-black text-slate-500 hover:text-slate-900 bg-slate-100 hover:bg-slate-200 rounded-lg transition-all uppercase">History</button>
<button @click="openRentalModal(item)"
:class="item.rental_user_name ? 'bg-amber-100 text-amber-600 hover:bg-amber-200' : 'bg-blue-600 text-white hover:bg-blue-700'"
class="px-4 py-1.5 text-[11px] font-black rounded-lg transition-all uppercase shadow-sm"
x-text="item.rental_user_name ? 'Change/Return' : 'Start Rental'"></button>
</div>
</td>
</tr>
</template>
</tbody>
</table>
<div x-show="filteredAssets.length === 0" class="py-20 text-center">
<div class="text-slate-300 text-5xl mb-4 italic">No Assets Found</div>
<p class="text-slate-400 text-sm font-medium">배정 사용자가 '업무용'인 노트북이 없거나 검색 결과가 없습니다.</p>
</div>
</div>
</main>
<!-- Rental Modal -->
<div x-show="showRentalModal" x-cloak class="fixed inset-0 z-[110] flex items-center justify-center p-4">
<div class="fixed inset-0 modal-bg" @click="showRentalModal = false"></div>
<div class="bg-white rounded-3xl p-8 max-w-md w-full relative z-[111] shadow-2xl">
<h3 class="text-2xl font-black mb-2" x-text="selectedAsset?.asset_tag + ' 임대 설정'"></h3>
<p class="text-slate-400 text-xs font-bold mb-6" x-text="selectedAsset?.model_name"></p>
<form @submit.prevent="submitRental" class="space-y-4">
<div>
<label class="block text-xs font-bold text-slate-500 uppercase mb-2">임대사원 성함</label>
<input type="text" x-model="rentalName" required placeholder="임대받는 사원의 이름을 입력하세요"
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none">
<p class="mt-2 text-[10px] text-slate-400 italic">* 공란으로 입력 후 적용 시 반납 처리됩니다.</p>
</div>
<div class="pt-4 flex gap-3">
<button type="button" @click="showRentalModal = false"
class="flex-1 py-3 bg-slate-100 rounded-xl font-bold">취소</button>
<button type="submit"
class="flex-1 py-3 bg-blue-600 text-white rounded-xl font-bold shadow-lg shadow-blue-200">임대
적용</button>
</div>
</form>
</div>
</div>
<!-- History Modal -->
<div x-show="showHistoryModal" x-cloak class="fixed inset-0 z-[110] flex items-center justify-center p-4">
<div class="fixed inset-0 modal-bg" @click="showHistoryModal = false"></div>
<div class="bg-white rounded-3xl p-8 max-w-2xl w-full relative z-[111] shadow-2xl flex flex-col max-h-[85vh]">
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-2xl font-black text-slate-900" x-text="selectedAsset?.asset_tag + ' 이력 로그'"></h3>
<p class="text-slate-400 text-xs font-bold" x-text="selectedAsset?.model_name"></p>
</div>
<button @click="showHistoryModal = false" class="text-slate-400 hover:text-slate-900">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="flex-1 overflow-y-auto pr-2 no-scrollbar space-y-8">
<!-- Part 1: Rental Logs -->
<div>
<div class="flex items-center gap-3 mb-4">
<div class="h-px bg-slate-100 flex-1"></div>
<h4 class="text-[10px] font-black text-emerald-500 uppercase tracking-[0.2em]">01. PC 임대 기록 (Max
20)</h4>
<div class="h-px bg-slate-100 flex-1"></div>
</div>
<div class="space-y-2">
<template x-for="log in history.rental" :key="log.id">
<div
class="flex items-center justify-between p-3 bg-emerald-50 rounded-xl border border-emerald-100">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center text-[10px] font-black text-emerald-500 shadow-sm"
x-text="log.user_name.charAt(0)"></div>
<span class="text-sm font-black text-slate-900" x-text="log.user_name"></span>
</div>
<span class="text-[10px] font-bold text-emerald-600 bg-white px-2 py-1 rounded-md"
x-text="log.action_date"></span>
</div>
</template>
<div x-show="history.rental.length === 0"
class="text-center py-6 text-slate-300 italic text-xs">임대 기록이 없습니다.</div>
</div>
</div>
<!-- Part 2: Assignment Logs -->
<div>
<div class="flex items-center gap-3 mb-4">
<div class="h-px bg-slate-100 flex-1"></div>
<h4 class="text-[10px] font-black text-blue-500 uppercase tracking-[0.2em]">02. PC 배정 기록 (Max
20)</h4>
<div class="h-px bg-slate-100 flex-1"></div>
</div>
<div class="space-y-2">
<template x-for="log in history.assignment" :key="log.id">
<div
class="flex items-center justify-between p-3 bg-blue-50 rounded-xl border border-blue-100">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center text-[10px] font-black text-blue-500 shadow-sm"
x-text="log.user_name.charAt(0)"></div>
<span class="text-sm font-black text-slate-900" x-text="log.user_name"></span>
</div>
<span class="text-[10px] font-bold text-blue-600 bg-white px-2 py-1 rounded-md"
x-text="log.action_date"></span>
</div>
</template>
<div x-show="history.assignment.length === 0"
class="text-center py-6 text-slate-300 italic text-xs">배정 기록이 없습니다.</div>
</div>
</div>
</div>
</div>
</div>
<script>
function rentalManagement() {
return {
assets: [],
searchQuery: '',
sortKey: 'asset_tag',
sortOrder: 'asc',
showRentalModal: false,
showHistoryModal: false,
selectedAsset: null,
rentalName: '',
history: { rental: [], assignment: [] },
init() {
this.fetchAssets();
},
fetchAssets() {
fetch('api.php?action=get_rental_laptops').then(res => res.json()).then(data => {
this.assets = data;
});
},
get filteredAssets() {
if (!this.searchQuery) return this.assets;
const q = this.searchQuery.toLowerCase();
return this.assets.filter(a =>
a.asset_tag.toLowerCase().includes(q) ||
a.model_name.toLowerCase().includes(q) ||
(a.rental_user_name && a.rental_user_name.toLowerCase().includes(q))
);
},
get sortedAssets() {
return [...this.filteredAssets].sort((a, b) => {
let v1 = a[this.sortKey] || '';
let v2 = b[this.sortKey] || '';
if (this.sortOrder === 'asc') return v1 > v2 ? 1 : -1;
return v1 < v2 ? 1 : -1;
});
},
sortBy(key) {
if (this.sortKey === key) {
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortKey = key;
this.sortOrder = 'asc';
}
},
openRentalModal(asset) {
this.selectedAsset = asset;
this.rentalName = asset.rental_user_name || '';
this.showRentalModal = true;
},
submitRental() {
fetch('api.php?action=update_rental', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: this.selectedAsset.id,
rental_user_name: this.rentalName
})
}).then(res => res.json()).then(data => {
if (data.success) {
this.showRentalModal = false;
this.fetchAssets();
}
});
},
openHistoryModal(asset) {
this.selectedAsset = asset;
fetch(`api.php?action=get_asset_history&asset_id=${asset.id}`).then(res => res.json()).then(data => {
this.history = data;
this.showHistoryModal = true;
});
}
}
}
</script>
</body>
</html>