coming together

This commit is contained in:
HotSwapp
2025-08-13 18:53:35 -05:00
parent acc5155bf7
commit 5111079149
51 changed files with 14457 additions and 588 deletions

View File

@@ -3,8 +3,7 @@
{% block title %}File Cabinet - Delphi Database{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="space-y-6">
<div class="space-y-6">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex items-center gap-3">
@@ -13,7 +12,7 @@
</div>
<h1 class="text-2xl font-bold text-neutral-900 dark:text-neutral-100">File Cabinet</h1>
</div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-3">
<button id="addFileBtn" class="flex items-center gap-2 px-4 py-2 bg-success-600 text-white hover:bg-success-700 rounded-lg transition-colors duration-200">
<i class="fa-solid fa-circle-plus"></i>
<span>New File</span>
@@ -23,7 +22,7 @@
<i class="fa-solid fa-chart-line"></i>
<span>Statistics</span>
</button>
<button id="advancedSearchBtn" class="flex items-center gap-2 px-4 py-2 bg-secondary-600 text-white hover:bg-secondary-700 rounded-lg transition-colors duration-200">
<button id="advancedSearchBtn" class="flex items-center gap-2 px-4 py-2 bg-primary-600 text-white hover:bg-primary-700 rounded-lg transition-colors duration-200">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Advanced Search</span>
</button>
@@ -31,8 +30,15 @@
</div>
<!-- Search and Filter Panel -->
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6">
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-solid fa-filter"></i>
<span>Search & Filters</span>
</h5>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
<div class="md:col-span-3">
<label for="searchInput" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Search Files</label>
<div class="relative">
@@ -41,7 +47,8 @@
<button id="searchBtn" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-neutral-400 hover:text-primary-600 dark:text-neutral-500 dark:hover:text-primary-400 transition-colors">
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
</div>
</div>
<div>
<label for="statusFilter" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Status</label>
@@ -62,10 +69,10 @@
</select>
</div>
<div class="flex items-center gap-2">
<button id="clearFiltersBtn" class="px-3 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors duration-200">
<button id="clearFiltersBtn" class="px-3 py-2 bg-neutral-100 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-200 rounded-lg hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors duration-200">
<i class="fa-regular fa-circle-xmark"></i> Clear
</button>
<button id="refreshBtn" class="px-3 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors duration-200">
<button id="refreshBtn" class="px-3 py-2 bg-neutral-100 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-200 rounded-lg hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors duration-200">
<i class="fa-solid fa-rotate-right"></i> Refresh
</button>
</div>
@@ -73,32 +80,39 @@
</div>
<!-- File List -->
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6">
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden" id="filesTable">
<thead class="bg-neutral-100 dark:bg-neutral-700 text-neutral-800 dark:text-neutral-100">
<tr>
<th data-sort="file_no" class="px-4 py-2 uppercase text-xs tracking-wider">File #</th>
<th data-sort="client_name" class="px-4 py-2 uppercase text-xs tracking-wider">Client</th>
<th data-sort="regarding" class="px-4 py-2 uppercase text-xs tracking-wider">Matter</th>
<th data-sort="file_type" class="px-4 py-2 uppercase text-xs tracking-wider">Type</th>
<th data-sort="status" class="px-4 py-2 uppercase text-xs tracking-wider">Status</th>
<th data-sort="employee" class="px-4 py-2 uppercase text-xs tracking-wider">Attorney</th>
<th data-sort="opened" class="px-4 py-2 uppercase text-xs tracking-wider">Opened</th>
<th data-sort="amount_owing" class="px-4 py-2 text-right uppercase text-xs tracking-wider">Balance</th>
<th class="px-4 py-2 uppercase text-xs tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="filesTableBody">
<!-- File rows will be populated here -->
</tbody>
</table>
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-xl shadow-soft">
<div class="px-6 py-4 border-b border-neutral-200 dark:border-neutral-700">
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
<i class="fa-regular fa-folder-open"></i>
<span>File List</span>
</h5>
</div>
<div class="p-0">
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-neutral-900 dark:text-neutral-100" id="filesTable">
<thead class="bg-neutral-50 dark:bg-neutral-800/50">
<tr class="border-b border-neutral-200 dark:border-neutral-700">
<th data-sort="text" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">File #</th>
<th data-sort="text" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Client</th>
<th data-sort="text" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Matter</th>
<th data-sort="text" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Type</th>
<th data-sort="text" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Status</th>
<th data-sort="text" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Attorney</th>
<th data-sort="date" class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Opened</th>
<th data-sort="number" class="px-4 py-3 text-right text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Balance</th>
<th class="px-4 py-3 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="filesTableBody">
<!-- File rows will be populated here -->
</tbody>
</table>
</div>
<!-- Pagination -->
<nav aria-label="File pagination" class="px-6 py-4 border-t border-neutral-200 dark:border-neutral-700 bg-neutral-50 dark:bg-neutral-800/50 flex items-center justify-center" id="pagination"></nav>
</div>
<!-- Pagination -->
<nav aria-label="File pagination" class="mt-6 flex items-center justify-center" id="pagination"></nav>
</div>
</div>
</div>
<!-- File Modal -->
@@ -203,7 +217,7 @@
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6" id="financialSummaryCard" style="display: none;">
<div class="flex items-center justify-between pb-2 mb-3 border-b border-neutral-200 dark:border-neutral-700">
<h6 class="mb-0 font-semibold">Financial Summary</h6>
<button type="button" class="px-2 py-1 border border-info-600 text-info-600 rounded hover:bg-blue-100" id="viewFullFinancialBtn"><i class="fa-solid fa-calculator"></i> View Details</button>
<button type="button" class="px-3 py-2 bg-info-600 text-white hover:bg-info-700 rounded-lg transition-colors text-sm font-medium" id="viewFullFinancialBtn"><i class="fa-solid fa-calculator mr-2"></i> View Details</button>
</div>
<div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4" id="financialSummary">
@@ -215,22 +229,22 @@
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6 md:col-span-2" id="documentsCard" style="display: none;">
<div class="flex items-center justify-between pb-2 mb-3 border-b border-neutral-200 dark:border-neutral-700">
<h6 class="mb-0 font-semibold">Documents</h6>
<button type="button" class="px-2 py-1 border border-success-600 text-success-600 rounded hover:bg-green-100" id="uploadDocumentBtn"><i class="fa-solid fa-upload"></i> Upload</button>
<button type="button" class="px-3 py-2 bg-success-600 text-white hover:bg-success-700 rounded-lg transition-colors text-sm font-medium" id="uploadDocumentBtn"><i class="fa-solid fa-upload mr-2"></i> Upload</button>
</div>
<div>
<div class="mb-3">
<input type="file" id="documentFile" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg">
<input type="text" id="documentDescription" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg mt-2" placeholder="Description (optional)">
</div>
<div class="overflow-x-auto">
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden" id="documentsTable">
<thead>
<tr>
<th>Filename</th>
<th>Description</th>
<th>Uploaded</th>
<th>Size</th>
<th>Actions</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Filename</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Description</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Uploaded</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Size</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="documentsTableBody">
@@ -335,13 +349,13 @@
</div>
<div class="overflow-y-auto max-h-96">
<table class="w-full text-sm text-left text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>City, State</th>
<th>Group</th>
<th>Action</th>
<thead class="bg-neutral-50 dark:bg-neutral-800/50">
<tr class="border-b border-neutral-200 dark:border-neutral-700">
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">ID</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Name</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">City, State</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Group</th>
<th class="px-3 py-2 text-left text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody id="clientSelectionTableBody">
@@ -435,6 +449,35 @@ function setupEventListeners() {
document.getElementById('fileNo').addEventListener('blur', validateFileNumber);
}
// Highlight helpers
function _escapeHtml(text) {
try { if (window.htmlSanitizer && typeof window.htmlSanitizer.escape === 'function') { return window.htmlSanitizer.escape(text); } } catch (_) {}
const str = String(text == null ? '' : text);
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function _buildTokens(raw) {
return String(raw || '')
.trim()
.replace(/[,_;:]+/g, ' ')
.split(/\s+/)
.map(t => t.replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, ''))
.filter(Boolean);
}
function highlightText(text, tokens) {
if (!text) return '';
const unique = Array.from(new Set(tokens || []));
if (unique.length === 0) return _escapeHtml(text);
let safe = _escapeHtml(String(text));
try {
unique.forEach(tok => {
const esc = tok.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(`(${esc})`, 'ig');
safe = safe.replace(re, '<mark class="bg-yellow-200 text-neutral-900 rounded px-0.5">$1</mark>');
});
} catch (_) {}
return safe;
}
async function loadLookupData() {
try {
// Load all lookup data in parallel
@@ -514,6 +557,7 @@ async function loadFiles(page = 0, filters = {}) {
function displayFiles(files) {
const tbody = document.getElementById('filesTableBody');
tbody.innerHTML = '';
const tokens = _buildTokens(document.getElementById('searchInput') ? document.getElementById('searchInput').value : '');
if (files.length === 0) {
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-neutral-500">No files found</td></tr>';
@@ -523,19 +567,19 @@ function displayFiles(files) {
files.forEach(file => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-4 py-2"><strong>${file.file_no}</strong></td>
<td class="px-4 py-2"><strong>${highlightText(file.file_no, tokens)}</strong></td>
<td class="px-4 py-2">
<div>
<div>${file.client_name || 'Unknown Client'}</div>
<div class="text-xs text-neutral-500">${file.client_id}</div>
<div>${highlightText(file.client_name || 'Unknown Client', tokens)}</div>
<div class="text-xs text-neutral-500">${highlightText(file.client_id || '', tokens)}</div>
</div>
</td>
<td class="px-4 py-2">
${file.regarding ? `<div>${file.regarding.substring(0, 50)}${file.regarding.length > 50 ? '...' : ''}</div>` : '<em class="text-neutral-500">No description</em>'}
${file.regarding ? `<div>${highlightText(file.regarding.substring(0, 50) + (file.regarding.length > 50 ? '...' : ''), tokens)}</div>` : '<em class="text-neutral-500">No description</em>'}
</td>
<td class="px-4 py-2">${file.file_type}</td>
<td class="px-4 py-2"><span class="${getStatusBadgeClass(file.status)}">${file.status}</span></td>
<td class="px-4 py-2">${file.empl_num}</td>
<td class="px-4 py-2">${highlightText(file.file_type || '', tokens)}</td>
<td class="px-4 py-2"><span class="${getStatusBadgeClass(file.status)}">${highlightText(file.status || '', tokens)}</span></td>
<td class="px-4 py-2">${highlightText(file.empl_num || '', tokens)}</td>
<td class="px-4 py-2">${formatDate(file.opened)}</td>
<td class="px-4 py-2 text-right">
<strong class="${file.amount_owing > 0 ? 'text-success-600' : 'text-neutral-500'}">
@@ -544,8 +588,8 @@ function displayFiles(files) {
</td>
<td class="px-4 py-2">
<div class="flex items-center gap-2">
<button class="px-2 py-1 bg-primary-600 text-white rounded hover:bg-primary-700" onclick="editFile('${file.file_no}')"><i class="fa-solid fa-pencil"></i></button>
<button class="px-2 py-1 bg-info-600 text-white rounded hover:bg-info-700" onclick="viewFile('${file.file_no}')"><i class="fa-regular fa-eye"></i></button>
<button class="inline-flex items-center px-3 py-2 bg-primary-600 text-white hover:bg-primary-700 rounded-lg text-sm font-semibold transition-colors" onclick="editFile('${String(file.file_no).replace(/"/g, '&quot;')}')"><i class="fa-solid fa-pencil mr-1.5"></i> Edit</button>
<button class="inline-flex items-center px-3 py-2 bg-info-600 text-white hover:bg-info-700 rounded-lg text-sm font-semibold transition-colors" onclick="viewFile('${String(file.file_no).replace(/"/g, '&quot;')}')"><i class="fa-regular fa-eye mr-1.5"></i> View</button>
</div>
</td>
`;
@@ -868,16 +912,17 @@ async function searchClients() {
function displayClientOptions(clients) {
const tbody = document.getElementById('clientSelectionTableBody');
tbody.innerHTML = '';
const tokens = _buildTokens(document.getElementById('clientSearchInput') ? document.getElementById('clientSearchInput').value : '');
clients.forEach(client => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${client.id}</td>
<td>${client.first || ''} ${client.last}</td>
<td>${client.city || ''}, ${client.abrev || ''}</td>
<td>${client.group || ''}</td>
<td>${highlightText(client.id || '', tokens)}</td>
<td>${highlightText(`${client.first || ''} ${client.last || ''}`.trim(), tokens)}</td>
<td>${highlightText(`${client.city || ''}, ${client.abrev || ''}`.trim(), tokens)}</td>
<td>${highlightText(client.group || '', tokens)}</td>
<td>
<button class="px-2 py-1 bg-primary-600 text-white rounded hover:bg-primary-700 text-sm" onclick="selectClient('${client.id}', '${(client.first || '') + ' ' + client.last}', '${client.city || ''}, ${client.abrev || ''}')">Select</button>
<button class="px-2 py-1 bg-primary-600 text-white rounded hover:bg-primary-700 text-sm" onclick="selectClient('${String(client.id).replace(/"/g, '&quot;')}', '${((client.first || '') + ' ' + (client.last || '')).replace(/"/g, '&quot;')}', '${(`${client.city || ''}, ${client.abrev || ''}`).replace(/"/g, '&quot;')}')">Select</button>
</td>
`;
tbody.appendChild(row);