1417 lines
60 KiB
HTML
1417 lines
60 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>CLASSIFIED: Personnel Database</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js"></script>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap');
|
|
|
|
body {
|
|
font-family: 'Roboto Mono', monospace;
|
|
background-color: #0a0a0a;
|
|
color: #00ff00;
|
|
}
|
|
|
|
.terminal-effect {
|
|
text-shadow: 0 0 5px rgba(0, 255, 0, 0.7);
|
|
}
|
|
|
|
.glow-border {
|
|
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
|
|
}
|
|
|
|
.data-row:hover {
|
|
background-color: rgba(0, 255, 0, 0.1);
|
|
}
|
|
|
|
/* Custom scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: #111;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #00ff00;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.tab-button {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.tab-button.active {
|
|
background-color: rgba(0, 255, 0, 0.2);
|
|
border-color: #00ff00;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen p-4">
|
|
<div class="max-w-6xl mx-auto">
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center mb-6 border-b border-green-500 pb-4">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-user-secret text-3xl text-green-500 mr-3"></i>
|
|
<h1 class="text-2xl font-bold terminal-effect">PERSONNEL DATABASE</h1>
|
|
</div>
|
|
<div class="text-xs text-green-400">
|
|
<span id="datetime" class="terminal-effect"></span>
|
|
<div class="mt-1">SECURITY CLEARANCE: <span class="font-bold">LEVEL 5</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Database Connection Panel -->
|
|
<div id="connectionPanel" class="bg-black p-6 rounded-lg glow-border mb-6">
|
|
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
|
|
<i class="fas fa-database mr-2"></i>DATABASE CONNECTION
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">CONNECTION TYPE</label>
|
|
<select id="connectionType" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500">
|
|
<option value="local">Local Browser Storage</option>
|
|
<option value="sqlite">SQLite Database</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="dbFileContainer" class="hidden">
|
|
<label class="block text-sm text-green-400 mb-1">DATABASE FILE</label>
|
|
<input type="file" id="dbFile" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500 file:mr-4 file:py-1 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-green-700 file:text-white hover:file:bg-green-600">
|
|
</div>
|
|
|
|
<div id="dbActionContainer" class="flex items-end space-x-2">
|
|
<button id="connectBtn" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded font-bold">
|
|
<i class="fas fa-plug mr-1"></i> CONNECT
|
|
</button>
|
|
<button id="newDbBtn" class="bg-blue-700 hover:bg-blue-600 text-white px-4 py-2 rounded font-bold">
|
|
<i class="fas fa-plus mr-1"></i> NEW DB
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="connectionStatus" class="text-sm p-3 rounded hidden">
|
|
<i class="fas fa-circle mr-2"></i> <span>Status message</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content Tabs -->
|
|
<div class="flex border-b border-green-700 mb-4">
|
|
<button class="tab-button active px-4 py-2 border-b-2 border-green-500 font-medium" data-tab="dataEntry">
|
|
<i class="fas fa-user-plus mr-2"></i> DATA ENTRY
|
|
</button>
|
|
<button class="tab-button px-4 py-2 border-b-2 border-transparent font-medium" data-tab="dataView">
|
|
<i class="fas fa-database mr-2"></i> RECORDS
|
|
</button>
|
|
<button class="tab-button px-4 py-2 border-b-2 border-transparent font-medium" data-tab="sqlQuery">
|
|
<i class="fas fa-terminal mr-2"></i> SQL QUERY
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div id="tabContent">
|
|
<!-- Data Entry Tab -->
|
|
<div id="dataEntry" class="tab-pane active">
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<!-- Input Form -->
|
|
<div class="lg:col-span-1 bg-black p-6 rounded-lg glow-border">
|
|
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
|
|
<i class="fas fa-user-plus mr-2"></i>ADD NEW SUBJECT
|
|
</h2>
|
|
|
|
<form id="personForm" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">IDENTIFICATION</label>
|
|
<input type="text" id="id" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="ID Number" required>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">FULL NAME</label>
|
|
<input type="text" id="name" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Last, First Middle" required>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">DATE OF BIRTH</label>
|
|
<input type="date" id="dob" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" required>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">NATIONALITY</label>
|
|
<input type="text" id="nationality" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Country of origin">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">KNOWN ASSOCIATES</label>
|
|
<input type="text" id="associates" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Separate with commas">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">STATUS</label>
|
|
<select id="status" class="w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500">
|
|
<option value="Active">Active</option>
|
|
<option value="Inactive">Inactive</option>
|
|
<option value="Deceased">Deceased</option>
|
|
<option value="Missing">Missing</option>
|
|
<option value="Classified">Classified</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="customFieldsContainer" class="space-y-2">
|
|
<!-- Dynamic fields will be added here -->
|
|
</div>
|
|
|
|
<div class="flex justify-between pt-2">
|
|
<button type="button" id="addFieldBtn" class="bg-green-900 hover:bg-green-800 text-green-100 px-3 py-1 rounded text-sm">
|
|
<i class="fas fa-plus mr-1"></i> Add Field
|
|
</button>
|
|
|
|
<button type="submit" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded font-bold">
|
|
<i class="fas fa-save mr-1"></i> RECORD SUBJECT
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Database Info -->
|
|
<div class="lg:col-span-2 bg-black p-6 rounded-lg glow-border">
|
|
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
|
|
<i class="fas fa-info-circle mr-2"></i>DATABASE INFORMATION
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<h3 class="text-lg font-medium text-green-400 mb-2">DATABASE STATISTICS</h3>
|
|
<div class="bg-gray-900 p-4 rounded">
|
|
<div class="flex justify-between mb-2">
|
|
<span>Records Count:</span>
|
|
<span id="recordCount" class="font-bold">0</span>
|
|
</div>
|
|
<div class="flex justify-between mb-2">
|
|
<span>Database Type:</span>
|
|
<span id="dbType" class="font-bold">Not connected</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span>Last Updated:</span>
|
|
<span id="lastUpdated" class="font-bold">Never</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 class="text-lg font-medium text-green-400 mb-2">QUICK ACTIONS</h3>
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<button id="exportDbBtn" class="bg-blue-700 hover:bg-blue-600 text-white px-3 py-2 rounded text-sm">
|
|
<i class="fas fa-download mr-1"></i> Export DB
|
|
</button>
|
|
<button id="importDbBtn" class="bg-purple-700 hover:bg-purple-600 text-white px-3 py-2 rounded text-sm">
|
|
<i class="fas fa-upload mr-1"></i> Import DB
|
|
</button>
|
|
<button id="backupDbBtn" class="bg-yellow-700 hover:bg-yellow-600 text-white px-3 py-2 rounded text-sm">
|
|
<i class="fas fa-copy mr-1"></i> Backup
|
|
</button>
|
|
<button id="clearDbBtn" class="bg-red-700 hover:bg-red-600 text-white px-3 py-2 rounded text-sm">
|
|
<i class="fas fa-trash mr-1"></i> Clear DB
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<h3 class="text-lg font-medium text-green-400 mb-2">SQLITE COMMANDS</h3>
|
|
<div class="bg-gray-900 p-3 rounded text-xs font-mono overflow-x-auto">
|
|
<p class="text-green-300">CREATE TABLE personnel (</p>
|
|
<p class="ml-4">id TEXT PRIMARY KEY,</p>
|
|
<p class="ml-4">name TEXT NOT NULL,</p>
|
|
<p class="ml-4">dob TEXT,</p>
|
|
<p class="ml-4">nationality TEXT,</p>
|
|
<p class="ml-4">associates TEXT,</p>
|
|
<p class="ml-4">status TEXT,</p>
|
|
<p class="ml-4">custom_fields TEXT</p>
|
|
<p class="text-green-300">);</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data View Tab -->
|
|
<div id="dataView" class="tab-pane hidden">
|
|
<div class="bg-black p-6 rounded-lg glow-border">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2 class="text-xl font-bold terminal-effect border-b border-green-500 pb-2">
|
|
<i class="fas fa-database mr-2"></i>SUBJECT RECORDS
|
|
</h2>
|
|
<div class="relative">
|
|
<input type="text" id="searchInput" placeholder="Search records..." class="bg-gray-900 border border-green-700 rounded px-3 py-1 pr-8 text-green-100 focus:outline-none focus:border-green-500 text-sm">
|
|
<i class="fas fa-search absolute right-2 top-2 text-green-500"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overflow-auto max-h-[600px]">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-green-700 text-green-400">
|
|
<th class="text-left py-2 px-3">ID</th>
|
|
<th class="text-left py-2 px-3">NAME</th>
|
|
<th class="text-left py-2 px-3">DOB</th>
|
|
<th class="text-left py-2 px-3">STATUS</th>
|
|
<th class="text-left py-2 px-3">ACTIONS</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="databaseTable" class="divide-y divide-green-900">
|
|
<!-- Database entries will be added here dynamically -->
|
|
<tr>
|
|
<td colspan="5" class="py-4 text-center text-green-500">
|
|
<i class="fas fa-exclamation-circle mr-2"></i> NO DATABASE CONNECTION
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SQL Query Tab -->
|
|
<div id="sqlQuery" class="tab-pane hidden">
|
|
<div class="bg-black p-6 rounded-lg glow-border">
|
|
<h2 class="text-xl font-bold mb-4 terminal-effect border-b border-green-500 pb-2">
|
|
<i class="fas fa-terminal mr-2"></i>SQL QUERY INTERFACE
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<div class="lg:col-span-2">
|
|
<div class="mb-4">
|
|
<label class="block text-sm text-green-400 mb-1">SQL COMMAND</label>
|
|
<textarea id="sqlCommand" class="w-full h-32 bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500 font-mono" placeholder="SELECT * FROM personnel;"></textarea>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<button id="executeQueryBtn" class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded font-bold">
|
|
<i class="fas fa-play mr-1"></i> EXECUTE
|
|
</button>
|
|
<button id="clearQueryBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded font-bold">
|
|
<i class="fas fa-eraser mr-1"></i> CLEAR
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm text-green-400 mb-1">QUERY HISTORY</label>
|
|
<div class="bg-gray-900 p-3 rounded max-h-48 overflow-y-auto">
|
|
<ul id="queryHistory" class="space-y-1 text-xs font-mono">
|
|
<li class="text-gray-500">No query history</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
<label class="block text-sm text-green-400 mb-1">RESULTS</label>
|
|
<div id="queryResults" class="bg-gray-900 p-3 rounded min-h-32 max-h-96 overflow-auto">
|
|
<p class="text-gray-500">Execute a query to see results</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal for viewing full record -->
|
|
<div id="recordModal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
|
|
<div class="bg-black p-6 rounded-lg glow-border max-w-2xl w-full max-h-[80vh] overflow-auto">
|
|
<div class="flex justify-between items-center mb-4 border-b border-green-500 pb-2">
|
|
<h3 class="text-lg font-bold terminal-effect">SUBJECT DETAILS</h3>
|
|
<button id="closeModalBtn" class="text-green-500 hover:text-green-300">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div id="modalContent" class="space-y-3">
|
|
<!-- Record details will be displayed here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal for SQLite initialization -->
|
|
<div id="initModal" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
|
|
<div class="bg-black p-6 rounded-lg glow-border max-w-md w-full">
|
|
<div class="flex justify-between items-center mb-4 border-b border-green-500 pb-2">
|
|
<h3 class="text-lg font-bold terminal-effect">INITIALIZING SQLITE</h3>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<p class="text-green-300">Loading SQLite database engine...</p>
|
|
<div class="w-full bg-gray-900 rounded-full h-2.5">
|
|
<div id="initProgress" class="bg-green-600 h-2.5 rounded-full" style="width: 0%"></div>
|
|
</div>
|
|
<p id="initStatus" class="text-sm text-green-400">Downloading SQLite WASM module</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Global variables
|
|
let db = null;
|
|
let sqliteInitialized = false;
|
|
let currentDatabase = 'none';
|
|
let database = []; // Fallback for localStorage mode
|
|
|
|
// DOM elements
|
|
const connectionPanel = document.getElementById('connectionPanel');
|
|
const connectionType = document.getElementById('connectionType');
|
|
const dbFileContainer = document.getElementById('dbFileContainer');
|
|
const dbFile = document.getElementById('dbFile');
|
|
const connectBtn = document.getElementById('connectBtn');
|
|
const newDbBtn = document.getElementById('newDbBtn');
|
|
const connectionStatus = document.getElementById('connectionStatus');
|
|
const recordCount = document.getElementById('recordCount');
|
|
const dbType = document.getElementById('dbType');
|
|
const lastUpdated = document.getElementById('lastUpdated');
|
|
const exportDbBtn = document.getElementById('exportDbBtn');
|
|
const importDbBtn = document.getElementById('importDbBtn');
|
|
const backupDbBtn = document.getElementById('backupDbBtn');
|
|
const clearDbBtn = document.getElementById('clearDbBtn');
|
|
const initModal = document.getElementById('initModal');
|
|
const initProgress = document.getElementById('initProgress');
|
|
const initStatus = document.getElementById('initStatus');
|
|
|
|
// Tab management
|
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
const tabPanes = document.querySelectorAll('.tab-pane');
|
|
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const tabId = button.getAttribute('data-tab');
|
|
|
|
// Update active tab button
|
|
tabButtons.forEach(btn => {
|
|
btn.classList.remove('active');
|
|
btn.classList.add('border-transparent');
|
|
});
|
|
button.classList.add('active');
|
|
button.classList.remove('border-transparent');
|
|
|
|
// Show corresponding tab pane
|
|
tabPanes.forEach(pane => {
|
|
pane.classList.add('hidden');
|
|
});
|
|
document.getElementById(tabId).classList.remove('hidden');
|
|
});
|
|
});
|
|
|
|
// Update datetime
|
|
function updateDateTime() {
|
|
const now = new Date();
|
|
document.getElementById('datetime').textContent = now.toLocaleString();
|
|
}
|
|
setInterval(updateDateTime, 1000);
|
|
updateDateTime();
|
|
|
|
// Connection type change handler
|
|
connectionType.addEventListener('change', (e) => {
|
|
if (e.target.value === 'sqlite') {
|
|
dbFileContainer.classList.remove('hidden');
|
|
} else {
|
|
dbFileContainer.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Initialize SQLite
|
|
async function initSQLite() {
|
|
if (sqliteInitialized) return true;
|
|
|
|
initModal.classList.remove('hidden');
|
|
initStatus.textContent = "Downloading SQLite WASM module...";
|
|
initProgress.style.width = "20%";
|
|
|
|
try {
|
|
// Load SQL.js
|
|
const SQL = await initSqlJs({
|
|
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
|
|
});
|
|
|
|
initStatus.textContent = "Initializing database engine...";
|
|
initProgress.style.width = "60%";
|
|
|
|
// Create a new database if none exists
|
|
db = new SQL.Database();
|
|
|
|
initStatus.textContent = "Creating schema...";
|
|
initProgress.style.width = "80%";
|
|
|
|
// Create tables if they don't exist
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS personnel (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
dob TEXT,
|
|
nationality TEXT,
|
|
associates TEXT,
|
|
status TEXT,
|
|
custom_fields TEXT
|
|
);
|
|
`);
|
|
|
|
initStatus.textContent = "Initialization complete!";
|
|
initProgress.style.width = "100%";
|
|
|
|
setTimeout(() => {
|
|
initModal.classList.add('hidden');
|
|
}, 500);
|
|
|
|
sqliteInitialized = true;
|
|
return true;
|
|
} catch (err) {
|
|
console.error("SQLite initialization error:", err);
|
|
initStatus.textContent = `Initialization failed: ${err.message}`;
|
|
initProgress.style.width = "0%";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Connect to database
|
|
connectBtn.addEventListener('click', async () => {
|
|
const type = connectionType.value;
|
|
|
|
if (type === 'local') {
|
|
// Fallback to localStorage
|
|
database = JSON.parse(localStorage.getItem('personnelDatabase')) || [];
|
|
currentDatabase = 'local';
|
|
|
|
// Update UI
|
|
connectionStatus.className = "text-sm p-3 rounded bg-green-900 text-green-100";
|
|
connectionStatus.innerHTML = '<i class="fas fa-circle mr-2"></i> <span>Connected to local browser storage</span>';
|
|
connectionStatus.classList.remove('hidden');
|
|
|
|
dbType.textContent = "Local Browser Storage";
|
|
recordCount.textContent = database.length;
|
|
lastUpdated.textContent = new Date().toLocaleString();
|
|
|
|
renderDatabase();
|
|
} else if (type === 'sqlite') {
|
|
if (!dbFile.files.length) {
|
|
showStatus("Please select a database file", "error");
|
|
return;
|
|
}
|
|
|
|
const file = dbFile.files[0];
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = async function() {
|
|
try {
|
|
// Initialize SQLite first if needed
|
|
const initialized = await initSQLite();
|
|
if (!initialized) {
|
|
showStatus("Failed to initialize SQLite", "error");
|
|
return;
|
|
}
|
|
|
|
// Load the database file
|
|
const uint8Array = new Uint8Array(reader.result);
|
|
db = new SQL.Database(uint8Array);
|
|
|
|
// Verify the database has our table
|
|
try {
|
|
db.exec("SELECT 1 FROM personnel LIMIT 1;");
|
|
} catch (err) {
|
|
// Table doesn't exist, create it
|
|
db.run(`
|
|
CREATE TABLE personnel (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
dob TEXT,
|
|
nationality TEXT,
|
|
associates TEXT,
|
|
status TEXT,
|
|
custom_fields TEXT
|
|
);
|
|
`);
|
|
}
|
|
|
|
currentDatabase = 'sqlite';
|
|
|
|
// Update UI
|
|
showStatus(`Connected to SQLite database: ${file.name}`, "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} catch (err) {
|
|
console.error("Database load error:", err);
|
|
showStatus(`Database error: ${err.message}`, "error");
|
|
}
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
});
|
|
|
|
// Create new database
|
|
newDbBtn.addEventListener('click', async () => {
|
|
const type = connectionType.value;
|
|
|
|
if (type === 'local') {
|
|
if (confirm("Create new local database? This will clear all existing data.")) {
|
|
database = [];
|
|
localStorage.setItem('personnelDatabase', JSON.stringify(database));
|
|
|
|
showStatus("New local database created", "success");
|
|
recordCount.textContent = "0";
|
|
lastUpdated.textContent = new Date().toLocaleString();
|
|
renderDatabase();
|
|
}
|
|
} else if (type === 'sqlite') {
|
|
if (confirm("Create new SQLite database? This will clear all existing data.")) {
|
|
try {
|
|
const initialized = await initSQLite();
|
|
if (!initialized) {
|
|
showStatus("Failed to initialize SQLite", "error");
|
|
return;
|
|
}
|
|
|
|
// Create a fresh database
|
|
db = new SQL.Database();
|
|
db.run(`
|
|
CREATE TABLE personnel (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
dob TEXT,
|
|
nationality TEXT,
|
|
associates TEXT,
|
|
status TEXT,
|
|
custom_fields TEXT
|
|
);
|
|
`);
|
|
|
|
currentDatabase = 'sqlite';
|
|
|
|
// Update UI
|
|
showStatus("New SQLite database created", "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} catch (err) {
|
|
console.error("Database creation error:", err);
|
|
showStatus(`Database error: ${err.message}`, "error");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Export database
|
|
exportDbBtn.addEventListener('click', () => {
|
|
if (currentDatabase === 'none') {
|
|
showStatus("No database connected", "error");
|
|
return;
|
|
}
|
|
|
|
if (currentDatabase === 'local') {
|
|
// Export localStorage data
|
|
const dataStr = JSON.stringify(database, null, 2);
|
|
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
|
|
|
const exportName = 'personnel-database-' + new Date().toISOString().slice(0,10) + '.json';
|
|
|
|
const linkElement = document.createElement('a');
|
|
linkElement.setAttribute('href', dataUri);
|
|
linkElement.setAttribute('download', exportName);
|
|
linkElement.click();
|
|
|
|
showStatus("Local database exported as JSON", "success");
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
// Export SQLite database
|
|
const data = db.export();
|
|
const blob = new Blob([data], {type: 'application/x-sqlite3'});
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const exportName = 'personnel-database-' + new Date().toISOString().slice(0,10) + '.sqlite';
|
|
|
|
const linkElement = document.createElement('a');
|
|
linkElement.setAttribute('href', url);
|
|
linkElement.setAttribute('download', exportName);
|
|
linkElement.click();
|
|
|
|
showStatus("SQLite database exported", "success");
|
|
}
|
|
});
|
|
|
|
// Import database
|
|
importDbBtn.addEventListener('click', () => {
|
|
if (currentDatabase === 'none') {
|
|
showStatus("Please connect to a database first", "error");
|
|
return;
|
|
}
|
|
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
|
|
if (currentDatabase === 'local') {
|
|
input.accept = '.json';
|
|
input.onchange = e => {
|
|
const file = e.target.files[0];
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = function() {
|
|
try {
|
|
const data = JSON.parse(reader.result);
|
|
if (Array.isArray(data)) {
|
|
if (confirm(`Import ${data.length} records? This will overwrite current data.`)) {
|
|
database = data;
|
|
localStorage.setItem('personnelDatabase', JSON.stringify(database));
|
|
|
|
showStatus(`Imported ${data.length} records`, "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
}
|
|
} else {
|
|
showStatus("Invalid data format", "error");
|
|
}
|
|
} catch (err) {
|
|
showStatus("Error parsing JSON file", "error");
|
|
}
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
};
|
|
} else if (currentDatabase === 'sqlite') {
|
|
input.accept = '.sqlite,.db';
|
|
input.onchange = e => {
|
|
const file = e.target.files[0];
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = async function() {
|
|
try {
|
|
const uint8Array = new Uint8Array(reader.result);
|
|
|
|
// Verify the database
|
|
const tempDb = new SQL.Database(uint8Array);
|
|
try {
|
|
tempDb.exec("SELECT 1 FROM personnel LIMIT 1;");
|
|
|
|
if (confirm("Import this SQLite database? This will overwrite current data.")) {
|
|
db = tempDb;
|
|
|
|
showStatus(`Imported SQLite database: ${file.name}`, "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
}
|
|
} catch (err) {
|
|
showStatus("The database doesn't have the required table structure", "error");
|
|
}
|
|
} catch (err) {
|
|
showStatus("Error reading SQLite database", "error");
|
|
}
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
};
|
|
}
|
|
|
|
input.click();
|
|
});
|
|
|
|
// Backup database
|
|
backupDbBtn.addEventListener('click', () => {
|
|
if (currentDatabase === 'none') {
|
|
showStatus("No database connected", "error");
|
|
return;
|
|
}
|
|
|
|
if (currentDatabase === 'local') {
|
|
// Simple clone for localStorage
|
|
const backup = JSON.parse(JSON.stringify(database));
|
|
localStorage.setItem('personnelDatabaseBackup', JSON.stringify(backup));
|
|
|
|
showStatus("Local database backup created", "success");
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
// Clone SQLite database
|
|
const backupData = db.export();
|
|
localStorage.setItem('sqliteDatabaseBackup', JSON.stringify(Array.from(backupData)));
|
|
|
|
showStatus("SQLite database backup created", "success");
|
|
}
|
|
});
|
|
|
|
// Clear database
|
|
clearDbBtn.addEventListener('click', () => {
|
|
if (currentDatabase === 'none') {
|
|
showStatus("No database connected", "error");
|
|
return;
|
|
}
|
|
|
|
if (confirm("WARNING: This will permanently delete all data. Continue?")) {
|
|
if (currentDatabase === 'local') {
|
|
database = [];
|
|
localStorage.setItem('personnelDatabase', JSON.stringify(database));
|
|
|
|
showStatus("Local database cleared", "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} else if (currentDatabase === 'sqlite') {
|
|
// Create a fresh database
|
|
db = new SQL.Database();
|
|
db.run(`
|
|
CREATE TABLE personnel (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
dob TEXT,
|
|
nationality TEXT,
|
|
associates TEXT,
|
|
status TEXT,
|
|
custom_fields TEXT
|
|
);
|
|
`);
|
|
|
|
showStatus("SQLite database cleared", "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Show status message
|
|
function showStatus(message, type) {
|
|
connectionStatus.className = "text-sm p-3 rounded";
|
|
|
|
if (type === 'success') {
|
|
connectionStatus.classList.add('bg-green-900', 'text-green-100');
|
|
} else if (type === 'error') {
|
|
connectionStatus.classList.add('bg-red-900', 'text-red-100');
|
|
} else {
|
|
connectionStatus.classList.add('bg-gray-900', 'text-gray-100');
|
|
}
|
|
|
|
connectionStatus.innerHTML = `<i class="fas fa-circle mr-2"></i> <span>${message}</span>`;
|
|
connectionStatus.classList.remove('hidden');
|
|
}
|
|
|
|
// Update database statistics
|
|
function updateDatabaseStats() {
|
|
if (currentDatabase === 'local') {
|
|
dbType.textContent = "Local Browser Storage";
|
|
recordCount.textContent = database.length;
|
|
lastUpdated.textContent = new Date().toLocaleString();
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
dbType.textContent = "SQLite Database";
|
|
|
|
try {
|
|
const stmt = db.prepare("SELECT COUNT(*) as count FROM personnel;");
|
|
stmt.step();
|
|
const count = stmt.getAsObject().count;
|
|
stmt.free();
|
|
|
|
recordCount.textContent = count;
|
|
lastUpdated.textContent = new Date().toLocaleString();
|
|
} catch (err) {
|
|
console.error("Error getting record count:", err);
|
|
recordCount.textContent = "Error";
|
|
}
|
|
} else {
|
|
dbType.textContent = "Not connected";
|
|
recordCount.textContent = "0";
|
|
lastUpdated.textContent = "Never";
|
|
}
|
|
}
|
|
|
|
// Render database table
|
|
function renderDatabase() {
|
|
const databaseTable = document.getElementById('databaseTable');
|
|
|
|
if (currentDatabase === 'none') {
|
|
databaseTable.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="py-4 text-center text-green-500">
|
|
<i class="fas fa-exclamation-circle mr-2"></i> NO DATABASE CONNECTION
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
if (currentDatabase === 'local') {
|
|
if (database.length === 0) {
|
|
databaseTable.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="py-4 text-center text-green-500">
|
|
<i class="fas fa-exclamation-circle mr-2"></i> NO RECORDS FOUND
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
databaseTable.innerHTML = '';
|
|
database.forEach((person, index) => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'data-row hover:bg-gray-900 cursor-pointer';
|
|
row.innerHTML = `
|
|
<td class="py-2 px-3 text-green-300">${person.id}</td>
|
|
<td class="py-2 px-3">${person.name}</td>
|
|
<td class="py-2 px-3">${person.dob}</td>
|
|
<td class="py-2 px-3">
|
|
<span class="inline-block px-2 py-0.5 rounded text-xs
|
|
${person.status === 'Active' ? 'bg-green-900 text-green-300' :
|
|
person.status === 'Deceased' ? 'bg-red-900 text-red-300' :
|
|
person.status === 'Missing' ? 'bg-yellow-900 text-yellow-300' :
|
|
'bg-gray-800 text-gray-300'}">
|
|
${person.status}
|
|
</span>
|
|
</td>
|
|
<td class="py-2 px-3">
|
|
<button class="view-btn mr-2 text-green-500 hover:text-green-300" data-id="${index}">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button class="delete-btn text-red-500 hover:text-red-300" data-id="${index}">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
databaseTable.appendChild(row);
|
|
});
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
try {
|
|
const stmt = db.prepare("SELECT * FROM personnel;");
|
|
|
|
let rows = [];
|
|
while (stmt.step()) {
|
|
rows.push(stmt.getAsObject());
|
|
}
|
|
stmt.free();
|
|
|
|
if (rows.length === 0) {
|
|
databaseTable.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="py-4 text-center text-green-500">
|
|
<i class="fas fa-exclamation-circle mr-2"></i> NO RECORDS FOUND
|
|
</td>
|
|
</tr>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
databaseTable.innerHTML = '';
|
|
rows.forEach((person, index) => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'data-row hover:bg-gray-900 cursor-pointer';
|
|
row.innerHTML = `
|
|
<td class="py-2 px-3 text-green-300">${person.id}</td>
|
|
<td class="py-2 px-3">${person.name}</td>
|
|
<td class="py-2 px-3">${person.dob}</td>
|
|
<td class="py-2 px-3">
|
|
<span class="inline-block px-2 py-0.5 rounded text-xs
|
|
${person.status === 'Active' ? 'bg-green-900 text-green-300' :
|
|
person.status === 'Deceased' ? 'bg-red-900 text-red-300' :
|
|
person.status === 'Missing' ? 'bg-yellow-900 text-yellow-300' :
|
|
'bg-gray-800 text-gray-300'}">
|
|
${person.status}
|
|
</span>
|
|
</td>
|
|
<td class="py-2 px-3">
|
|
<button class="view-btn mr-2 text-green-500 hover:text-green-300" data-id="${person.id}">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button class="delete-btn text-red-500 hover:text-red-300" data-id="${person.id}">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
databaseTable.appendChild(row);
|
|
});
|
|
} catch (err) {
|
|
console.error("Error rendering database:", err);
|
|
databaseTable.innerHTML = `
|
|
<tr>
|
|
<td colspan="5" class="py-4 text-center text-red-500">
|
|
<i class="fas fa-exclamation-circle mr-2"></i> ERROR LOADING DATA
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Add event listeners to view and delete buttons
|
|
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const id = btn.getAttribute('data-id');
|
|
viewRecord(id);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.delete-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const id = btn.getAttribute('data-id');
|
|
deleteRecord(id);
|
|
});
|
|
});
|
|
|
|
// Add click event to rows for viewing record
|
|
document.querySelectorAll('.data-row').forEach(row => {
|
|
row.addEventListener('click', () => {
|
|
const btn = row.querySelector('.view-btn');
|
|
const id = btn.getAttribute('data-id');
|
|
viewRecord(id);
|
|
});
|
|
});
|
|
}
|
|
|
|
// View full record
|
|
function viewRecord(id) {
|
|
const recordModal = document.getElementById('recordModal');
|
|
const modalContent = document.getElementById('modalContent');
|
|
|
|
if (currentDatabase === 'local') {
|
|
const person = database[parseInt(id)];
|
|
if (!person) {
|
|
modalContent.innerHTML = '<p class="text-red-500">Record not found</p>';
|
|
recordModal.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
let html = `
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">BASIC INFORMATION</h4>
|
|
<p><span class="text-green-400">ID:</span> ${person.id}</p>
|
|
<p><span class="text-green-400">Name:</span> ${person.name}</p>
|
|
<p><span class="text-green-400">DOB:</span> ${person.dob}</p>
|
|
<p><span class="text-green-400">Nationality:</span> ${person.nationality || 'N/A'}</p>
|
|
<p><span class="text-green-400">Status:</span> ${person.status}</p>
|
|
</div>
|
|
<div>
|
|
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ASSOCIATES</h4>
|
|
`;
|
|
|
|
if (person.associates && person.associates.length > 0) {
|
|
html += `<ul class="list-disc list-inside">`;
|
|
person.associates.split(',').forEach(associate => {
|
|
html += `<li>${associate.trim()}</li>`;
|
|
});
|
|
html += `</ul>`;
|
|
} else {
|
|
html += `<p>No known associates</p>`;
|
|
}
|
|
|
|
html += `</div></div>`;
|
|
|
|
// Add custom fields if they exist
|
|
if (person.customFields && Object.keys(person.customFields).length > 0) {
|
|
html += `
|
|
<div class="mt-4">
|
|
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ADDITIONAL INFORMATION</h4>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
`;
|
|
|
|
for (const [key, value] of Object.entries(person.customFields)) {
|
|
html += `
|
|
<div>
|
|
<p><span class="text-green-400">${key}:</span> ${value || 'N/A'}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += `</div></div>`;
|
|
}
|
|
|
|
modalContent.innerHTML = html;
|
|
recordModal.classList.remove('hidden');
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
try {
|
|
const stmt = db.prepare("SELECT * FROM personnel WHERE id = ?;");
|
|
stmt.bind([id]);
|
|
stmt.step();
|
|
const person = stmt.getAsObject();
|
|
stmt.free();
|
|
|
|
if (!person.id) {
|
|
modalContent.innerHTML = '<p class="text-red-500">Record not found</p>';
|
|
recordModal.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
let html = `
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">BASIC INFORMATION</h4>
|
|
<p><span class="text-green-400">ID:</span> ${person.id}</p>
|
|
<p><span class="text-green-400">Name:</span> ${person.name}</p>
|
|
<p><span class="text-green-400">DOB:</span> ${person.dob}</p>
|
|
<p><span class="text-green-400">Nationality:</span> ${person.nationality || 'N/A'}</p>
|
|
<p><span class="text-green-400">Status:</span> ${person.status}</p>
|
|
</div>
|
|
<div>
|
|
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ASSOCIATES</h4>
|
|
`;
|
|
|
|
if (person.associates && person.associates.length > 0) {
|
|
html += `<ul class="list-disc list-inside">`;
|
|
person.associates.split(',').forEach(associate => {
|
|
html += `<li>${associate.trim()}</li>`;
|
|
});
|
|
html += `</ul>`;
|
|
} else {
|
|
html += `<p>No known associates</p>`;
|
|
}
|
|
|
|
html += `</div></div>`;
|
|
|
|
// Add custom fields if they exist
|
|
if (person.custom_fields) {
|
|
try {
|
|
const customFields = JSON.parse(person.custom_fields);
|
|
if (Object.keys(customFields).length > 0) {
|
|
html += `
|
|
<div class="mt-4">
|
|
<h4 class="text-green-400 border-b border-green-700 pb-1 mb-2">ADDITIONAL INFORMATION</h4>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
`;
|
|
|
|
for (const [key, value] of Object.entries(customFields)) {
|
|
html += `
|
|
<div>
|
|
<p><span class="text-green-400">${key}:</span> ${value || 'N/A'}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += `</div></div>`;
|
|
}
|
|
} catch (e) {
|
|
console.error("Error parsing custom fields:", e);
|
|
}
|
|
}
|
|
|
|
modalContent.innerHTML = html;
|
|
recordModal.classList.remove('hidden');
|
|
} catch (err) {
|
|
console.error("Error viewing record:", err);
|
|
modalContent.innerHTML = '<p class="text-red-500">Error loading record</p>';
|
|
recordModal.classList.remove('hidden');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete record
|
|
function deleteRecord(id) {
|
|
if (!confirm('CONFIRM RECORD DELETION?\nThis action cannot be undone.')) {
|
|
return;
|
|
}
|
|
|
|
if (currentDatabase === 'local') {
|
|
database.splice(parseInt(id), 1);
|
|
localStorage.setItem('personnelDatabase', JSON.stringify(database));
|
|
|
|
showStatus("Record deleted", "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
try {
|
|
db.run("DELETE FROM personnel WHERE id = ?;", [id]);
|
|
|
|
showStatus("Record deleted", "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} catch (err) {
|
|
console.error("Error deleting record:", err);
|
|
showStatus("Error deleting record", "error");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Form submission
|
|
document.getElementById('personForm').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
|
|
// Get form values
|
|
const person = {
|
|
id: document.getElementById('id').value,
|
|
name: document.getElementById('name').value,
|
|
dob: document.getElementById('dob').value,
|
|
nationality: document.getElementById('nationality').value,
|
|
associates: document.getElementById('associates').value,
|
|
status: document.getElementById('status').value
|
|
};
|
|
|
|
// Get custom fields
|
|
const customFields = {};
|
|
document.querySelectorAll('.field-name').forEach(field => {
|
|
const name = field.value.trim();
|
|
if (name) {
|
|
const value = field.parentElement.nextElementSibling.querySelector('.field-value').value;
|
|
customFields[name] = value;
|
|
}
|
|
});
|
|
|
|
if (currentDatabase === 'local') {
|
|
// Add custom fields to person object
|
|
person.customFields = customFields;
|
|
|
|
// Check if ID already exists
|
|
const existingIndex = database.findIndex(p => p.id === person.id);
|
|
if (existingIndex !== -1) {
|
|
if (confirm('ID already exists. Update existing record?')) {
|
|
database[existingIndex] = person;
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
database.push(person);
|
|
}
|
|
|
|
localStorage.setItem('personnelDatabase', JSON.stringify(database));
|
|
|
|
showStatus("Record saved to local storage", "success");
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
try {
|
|
// Check if record exists
|
|
const checkStmt = db.prepare("SELECT 1 FROM personnel WHERE id = ?;");
|
|
checkStmt.bind([person.id]);
|
|
const exists = checkStmt.step();
|
|
checkStmt.free();
|
|
|
|
// Prepare custom fields as JSON
|
|
const customFieldsJson = JSON.stringify(customFields);
|
|
|
|
if (exists) {
|
|
// Update existing record
|
|
db.run(`
|
|
UPDATE personnel SET
|
|
name = ?,
|
|
dob = ?,
|
|
nationality = ?,
|
|
associates = ?,
|
|
status = ?,
|
|
custom_fields = ?
|
|
WHERE id = ?;
|
|
`, [
|
|
person.name,
|
|
person.dob,
|
|
person.nationality,
|
|
person.associates,
|
|
person.status,
|
|
customFieldsJson,
|
|
person.id
|
|
]);
|
|
|
|
showStatus("Record updated in SQLite database", "success");
|
|
} else {
|
|
// Insert new record
|
|
db.run(`
|
|
INSERT INTO personnel
|
|
(id, name, dob, nationality, associates, status, custom_fields)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?);
|
|
`, [
|
|
person.id,
|
|
person.name,
|
|
person.dob,
|
|
person.nationality,
|
|
person.associates,
|
|
person.status,
|
|
customFieldsJson
|
|
]);
|
|
|
|
showStatus("Record added to SQLite database", "success");
|
|
}
|
|
|
|
updateDatabaseStats();
|
|
renderDatabase();
|
|
} catch (err) {
|
|
console.error("Error saving record:", err);
|
|
showStatus("Error saving record to database", "error");
|
|
}
|
|
} else {
|
|
showStatus("No database connected", "error");
|
|
}
|
|
|
|
// Reset form
|
|
e.target.reset();
|
|
document.getElementById('customFieldsContainer').innerHTML = '';
|
|
});
|
|
|
|
// Add custom field
|
|
document.getElementById('addFieldBtn').addEventListener('click', () => {
|
|
const fieldId = Date.now();
|
|
const fieldDiv = document.createElement('div');
|
|
fieldDiv.className = 'flex items-end space-x-2';
|
|
fieldDiv.innerHTML = `
|
|
<div class="flex-grow">
|
|
<label class="block text-sm text-green-400 mb-1">FIELD NAME</label>
|
|
<input type="text" class="field-name w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Field name">
|
|
</div>
|
|
<div class="flex-grow">
|
|
<label class="block text-sm text-green-400 mb-1">VALUE</label>
|
|
<input type="text" class="field-value w-full bg-gray-900 border border-green-700 rounded px-3 py-2 text-green-100 focus:outline-none focus:border-green-500" placeholder="Field value">
|
|
</div>
|
|
<button type="button" class="remove-field-btn bg-red-900 hover:bg-red-800 text-red-100 px-3 py-2 rounded">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
|
|
document.getElementById('customFieldsContainer').appendChild(fieldDiv);
|
|
|
|
// Add event listener to remove button
|
|
fieldDiv.querySelector('.remove-field-btn').addEventListener('click', () => {
|
|
fieldDiv.remove();
|
|
});
|
|
});
|
|
|
|
// Close modal
|
|
document.getElementById('closeModalBtn').addEventListener('click', () => {
|
|
document.getElementById('recordModal').classList.add('hidden');
|
|
});
|
|
|
|
// Close modal when clicking outside
|
|
document.getElementById('recordModal').addEventListener('click', (e) => {
|
|
if (e.target === document.getElementById('recordModal')) {
|
|
document.getElementById('recordModal').classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// SQL Query Interface
|
|
const sqlCommand = document.getElementById('sqlCommand');
|
|
const executeQueryBtn = document.getElementById('executeQueryBtn');
|
|
const clearQueryBtn = document.getElementById('clearQueryBtn');
|
|
const queryResults = document.getElementById('queryResults');
|
|
const queryHistory = document.getElementById('queryHistory');
|
|
|
|
let queryHistoryList = JSON.parse(localStorage.getItem('queryHistory')) || [];
|
|
|
|
// Update query history display
|
|
function updateQueryHistory() {
|
|
queryHistory.innerHTML = '';
|
|
|
|
if (queryHistoryList.length === 0) {
|
|
queryHistory.innerHTML = '<li class="text-gray-500">No query history</li>';
|
|
return;
|
|
}
|
|
|
|
// Show last 5 queries
|
|
const recentQueries = queryHistoryList.slice(-5).reverse();
|
|
|
|
recentQueries.forEach((query, index) => {
|
|
const li = document.createElement('li');
|
|
li.className = 'cursor-pointer hover:text-green-300 truncate';
|
|
li.textContent = query;
|
|
li.addEventListener('click', () => {
|
|
sqlCommand.value = query;
|
|
});
|
|
queryHistory.appendChild(li);
|
|
});
|
|
}
|
|
|
|
// Execute SQL query
|
|
executeQueryBtn.addEventListener('click', () => {
|
|
if (currentDatabase !== 'sqlite' || !db) {
|
|
queryResults.innerHTML = '<p class="text-red-500">SQLite database not connected</p>';
|
|
return;
|
|
}
|
|
|
|
const query = sqlCommand.value.trim();
|
|
if (!query) {
|
|
queryResults.innerHTML = '<p class="text-yellow-500">Please enter a SQL query</p>';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Add to history
|
|
queryHistoryList.push(query);
|
|
localStorage.setItem('queryHistory', JSON.stringify(queryHistoryList));
|
|
updateQueryHistory();
|
|
|
|
// Execute query
|
|
const results = db.exec(query);
|
|
|
|
if (results.length === 0) {
|
|
queryResults.innerHTML = '<p class="text-green-500">Query executed successfully (no results)</p>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
results.forEach(result => {
|
|
html += '<div class="mb-4">';
|
|
html += `<h4 class="text-green-400 mb-2">${result.columns.join(' | ')}</h4>`;
|
|
html += '<div class="overflow-x-auto">';
|
|
html += '<table class="w-full border border-green-700">';
|
|
html += '<tbody>';
|
|
|
|
result.values.forEach(row => {
|
|
html += '<tr class="border-b border-green-800">';
|
|
row.forEach(cell => {
|
|
html += `<td class="px-3 py-1">${cell !== null ? cell : 'NULL'}</td>`;
|
|
});
|
|
html += '</tr>';
|
|
});
|
|
|
|
html += '</tbody>';
|
|
html += '</table>';
|
|
html += '</div>';
|
|
html += `<p class="text-xs text-green-500 mt-1">${result.values.length} rows returned</p>`;
|
|
html += '</div>';
|
|
});
|
|
|
|
queryResults.innerHTML = html;
|
|
} catch (err) {
|
|
console.error("SQL error:", err);
|
|
queryResults.innerHTML = `<p class="text-red-500">SQL Error: ${err.message}</p>`;
|
|
}
|
|
});
|
|
|
|
// Clear SQL query
|
|
clearQueryBtn.addEventListener('click', () => {
|
|
sqlCommand.value = '';
|
|
});
|
|
|
|
// Initialize query history
|
|
updateQueryHistory();
|
|
|
|
// Search functionality
|
|
document.getElementById('searchInput').addEventListener('input', (e) => {
|
|
const term = e.target.value.toLowerCase();
|
|
|
|
if (currentDatabase === 'local') {
|
|
const filtered = database.filter(person =>
|
|
person.id.toLowerCase().includes(term) ||
|
|
person.name.toLowerCase().includes(term) ||
|
|
(person.nationality && person.nationality.toLowerCase().includes(term)) ||
|
|
(person.associates && person.associates.toLowerCase().includes(term)) ||
|
|
person.status.toLowerCase().includes(term)
|
|
);
|
|
|
|
renderDatabase(filtered);
|
|
} else if (currentDatabase === 'sqlite' && db) {
|
|
try {
|
|
const stmt = db.prepare(`
|
|
SELECT * FROM personnel
|
|
WHERE
|
|
id LIKE ? OR
|
|
name LIKE ? OR
|
|
nationality LIKE ? OR
|
|
associates LIKE ? OR
|
|
status LIKE ?
|
|
`);
|
|
|
|
const searchTerm = `%${term}%`;
|
|
stmt.bind([searchTerm, searchTerm, searchTerm, searchTerm, searchTerm]);
|
|
|
|
let results = [];
|
|
while (stmt.step()) {
|
|
results.push(stmt.getAsObject());
|
|
}
|
|
stmt.free();
|
|
|
|
renderDatabase(results);
|
|
} catch (err) {
|
|
console.error("Search error:", err);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initial setup
|
|
updateDatabaseStats();
|
|
</script>
|
|
</body>
|
|
</html> |