coming together
This commit is contained in:
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
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, '"')}')"><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, '"')}')"><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, '"')}', '${((client.first || '') + ' ' + (client.last || '')).replace(/"/g, '"')}', '${(`${client.city || ''}, ${client.abrev || ''}`).replace(/"/g, '"')}')">Select</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
|
||||
Reference in New Issue
Block a user