This commit is contained in:
HotSwapp
2025-08-18 20:20:04 -05:00
parent 89b2bc0aa2
commit bac8cc4bd5
114 changed files with 30258 additions and 1341 deletions

View File

@@ -136,6 +136,75 @@
</div>
</div>
</div>
<div class="relative inline-block" id="phoneDirWrapper">
<div class="flex items-center gap-1">
<button id="phoneDirBtn" class="px-3 py-1.5 text-sm rounded-lg bg-neutral-100 dark:bg-neutral-700 text-neutral-700 dark:text-neutral-200 hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors" title="Generate printable phone directory">
<i class="fa-solid fa-address-book mr-1"></i>
Phone Directory
</button>
<div class="relative group">
<button type="button" class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 transition-colors" title="Phone Directory Help">
<i class="fa-solid fa-circle-question text-sm"></i>
</button>
<div class="hidden group-hover:block absolute right-0 mt-2 w-80 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow-lg p-3 z-30">
<div class="text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase mb-2">Phone Directory Help</div>
<div class="space-y-2 text-xs text-neutral-600 dark:text-neutral-300">
<p><strong>Grouping Options:</strong></p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li><strong>None:</strong> Alphabetical by last name, no sections</li>
<li><strong>By Letter:</strong> A-Z sections based on first letter of last name</li>
<li><strong>By Group:</strong> Sections by customer group (Client, Attorney, etc.)</li>
<li><strong>Group + Letter:</strong> Group sections, then letter subsections within each</li>
</ul>
<p class="mt-2"><strong>Letter Buckets:</strong> Names starting with numbers or symbols go into the "#" bucket.</p>
<p class="mt-2"><strong>Page Breaks:</strong> HTML format can insert page breaks between top-level groups for printing.</p>
</div>
</div>
</div>
</div>
<div id="phoneDirPopover" class="hidden absolute right-0 mt-2 w-80 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow-lg p-3 z-20">
<div class="text-xs font-semibold text-neutral-500 dark:text-neutral-400 uppercase mb-2">Phone Directory</div>
<div class="grid grid-cols-2 gap-3 text-sm">
<div class="col-span-2">
<label class="block text-xs text-neutral-600 dark:text-neutral-300 mb-1" for="phoneDirMode">Mode</label>
<select id="phoneDirMode" class="w-full px-2 py-1.5 rounded border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800">
<option value="numbers">Numbers</option>
<option value="addresses">Addresses</option>
<option value="full">Full</option>
</select>
</div>
<div>
<label class="block text-xs text-neutral-600 dark:text-neutral-300 mb-1" for="phoneDirFormat">Format</label>
<select id="phoneDirFormat" class="w-full px-2 py-1.5 rounded border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800">
<option value="html">HTML</option>
<option value="csv">CSV</option>
</select>
</div>
<div>
<label class="block text-xs text-neutral-600 dark:text-neutral-300 mb-1" for="phoneDirGrouping">Grouping</label>
<select id="phoneDirGrouping" class="w-full px-2 py-1.5 rounded border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800">
<option value="none">None</option>
<option value="letter">By Letter</option>
<option value="group">By Group</option>
<option value="group_letter">Group + Letter</option>
</select>
</div>
<div class="col-span-2">
<label class="inline-flex items-center gap-2 text-xs">
<input type="checkbox" id="phoneDirPageBreak">
<span>Page break per top-level group (HTML only)</span>
</label>
</div>
</div>
<div class="mt-3 text-xs text-neutral-500 dark:text-neutral-400">Uses current group filters and sort.</div>
<div class="mt-3 flex items-center justify-end gap-2">
<button id="downloadPhoneDirBtn" class="px-3 py-1.5 text-sm rounded-lg bg-primary-600 text-white hover:bg-primary-700 transition-colors">
<i class="fa-solid fa-download mr-1"></i>
Download
</button>
</div>
</div>
</div>
</div>
</div>
<div class="overflow-x-auto">
@@ -396,6 +465,56 @@ document.addEventListener('DOMContentLoaded', function() {
if (compactBtn && window.toggleCompactMode) {
compactBtn.addEventListener('click', window.toggleCompactMode);
}
// Support phone directory preconfiguration via URL
try {
const params = new URLSearchParams(window.location.search);
const wantsPhoneDir = params.has('phone_dir') && params.get('phone_dir') !== '0' && params.get('phone_dir') !== 'false';
const modeParam = params.get('mode');
const formatParam = params.get('format');
const groupingParam = params.get('grouping');
const pageBreakParam = params.get('page_break');
const namePrefixParam = params.get('name_prefix');
// If any params provided, set UI defaults
setTimeout(() => {
const fmt = document.getElementById('phoneDirFormat');
const grp = document.getElementById('phoneDirGrouping');
const pb = document.getElementById('phoneDirPageBreak');
const md = document.getElementById('phoneDirMode');
if (formatParam && fmt) fmt.value = formatParam;
if (groupingParam && grp) grp.value = groupingParam;
if (pageBreakParam != null && pb) pb.checked = (pageBreakParam === '1' || pageBreakParam === 'true');
if (modeParam && md) md.value = modeParam;
// If name_prefix provided and single char, also bind search field so our downloader logic includes it
if (typeof namePrefixParam === 'string' && namePrefixParam.length === 1) {
const s = document.getElementById('searchInput');
if (s) s.value = namePrefixParam;
}
}, 25);
// Auto-open popover from hash or when phone_dir=1
if (window.location.hash === '#phone-dir' || wantsPhoneDir) {
setTimeout(() => {
const btn = document.getElementById('phoneDirBtn');
if (btn) btn.click();
// If routing from hash without explicit params, set sensible defaults
if (!formatParam || !groupingParam) {
const fmt = document.getElementById('phoneDirFormat');
const grp = document.getElementById('phoneDirGrouping');
const pb = document.getElementById('phoneDirPageBreak');
if (fmt && !formatParam) fmt.value = 'html';
if (grp && !groupingParam) grp.value = 'letter';
if (pb && !pageBreakParam) pb.checked = true;
}
// Auto-trigger download when phone_dir=1
if (wantsPhoneDir) {
const dl = document.getElementById('downloadPhoneDirBtn');
if (dl) dl.click();
}
}, 60);
}
} catch (_) {}
// Initialize page size selector value
const sizeSel = document.getElementById('pageSizeSelect');
if (sizeSel) { sizeSel.value = String(window.customerPageSize); }
@@ -531,6 +650,51 @@ function setupEventListeners() {
e.stopPropagation();
columnsPopover.classList.toggle('hidden');
});
// Phone directory popover toggle
const phoneDirBtn = document.getElementById('phoneDirBtn');
const phoneDirPopover = document.getElementById('phoneDirPopover');
if (phoneDirBtn && phoneDirPopover) {
phoneDirBtn.addEventListener('click', function(e) {
e.stopPropagation();
phoneDirPopover.classList.toggle('hidden');
});
// Download action
const downloadBtn = document.getElementById('downloadPhoneDirBtn');
if (downloadBtn) {
downloadBtn.addEventListener('click', function(e) {
e.stopPropagation();
const mode = (document.getElementById('phoneDirMode')?.value || 'numbers');
const format = (document.getElementById('phoneDirFormat')?.value || 'html');
const grouping = (document.getElementById('phoneDirGrouping')?.value || 'letter');
const pageBreak = !!(document.getElementById('phoneDirPageBreak')?.checked);
const u = new URL(window.location.origin + '/api/customers/phone-book');
const p = u.searchParams;
p.set('mode', mode);
p.set('format', format);
p.set('grouping', grouping);
if (pageBreak) p.set('page_break', '1');
// Include filters and sort
const by = window.currentSortBy || 'name';
const dir = window.currentSortDir || 'asc';
p.set('sort_by', by);
p.set('sort_dir', dir);
(Array.isArray(window.currentGroupFilters) ? window.currentGroupFilters : []).forEach(v => p.append('groups', v));
// Optional name prefix: if user typed single letter quickly, offer faster slicing
const q = (document.getElementById('searchInput')?.value || '').trim();
if (q && q.length === 1) {
p.set('name_prefix', q);
}
// Trigger download
window.location.href = u.toString();
phoneDirPopover.classList.add('hidden');
});
}
// Clicking outside closes both popovers
document.addEventListener('click', function() {
phoneDirPopover.classList.add('hidden');
});
phoneDirPopover.addEventListener('click', function(e) { e.stopPropagation(); });
}
document.addEventListener('click', function() {
columnsPopover.classList.add('hidden');
});