Integrate lt.time.ago() for dashboard timestamps, update README

- Add data-ts attributes to table and card view date cells so JS can
  convert them to relative time ("2h ago") while keeping the full date
  in the title attribute for hover tooltips
- Add initRelativeTimes() in dashboard.js using lt.time.ago(); runs on
  DOMContentLoaded and refreshes every 60s so times stay current
- Fix table sort for date columns to read data-ts attribute instead of
  text content (which is now relative and not sortable as a date)
- Update README: add base.css/base.js/utils.js to project structure,
  fix ascii-banner.js description, expand keyboard shortcuts table,
  add developer notes for lt.time and boot sequence behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 10:52:59 -04:00
parent 1046537429
commit 3c3b9d0a61
3 changed files with 39 additions and 9 deletions

View File

@@ -126,6 +126,11 @@ Access all admin pages via the **Admin dropdown** in the dashboard header.
| `Ctrl/Cmd + E` | Toggle edit mode (ticket page) |
| `Ctrl/Cmd + S` | Save changes (ticket page) |
| `Ctrl/Cmd + K` | Focus search box (dashboard) |
| `N` | New ticket (dashboard) |
| `J` / `K` | Next / previous row (dashboard table) |
| `Enter` | Open selected ticket (dashboard) |
| `G` then `D` | Go to dashboard |
| `1``4` | Quick status change (ticket page) |
| `ESC` | Cancel edit / close modal |
| `?` | Show keyboard shortcuts help |
@@ -242,17 +247,20 @@ tinker_tickets/
│ └── upload_attachment.php # GET/POST: List or upload attachments
├── assets/
│ ├── css/
│ │ ├── base.css # LotusGuild Terminal Design System (symlinked from web_template)
│ │ ├── dashboard.css # Dashboard + terminal styling
│ │ └── ticket.css # Ticket view styling
│ ├── js/
│ │ ├── advanced-search.js # Advanced search modal
│ │ ├── ascii-banner.js # ASCII art banner
│ │ ├── ascii-banner.js # ASCII art banner (rendered in boot overlay on first visit)
│ │ ├── base.js # LotusGuild JS utilities — window.lt (symlinked from web_template)
│ │ ├── dashboard.js # Dashboard + bulk actions + kanban + sidebar
│ │ ├── keyboard-shortcuts.js # Keyboard shortcuts
│ │ ├── keyboard-shortcuts.js # Keyboard shortcuts (uses lt.keys)
│ │ ├── markdown.js # Markdown rendering + ticket linking (XSS-safe)
│ │ ├── settings.js # User preferences
│ │ ├── ticket.js # Ticket + comments + visibility
│ │ ── toast.js # Toast notifications
│ │ ── toast.js # Backwards-compat shim → delegates to lt.toast
│ │ └── utils.js # escapeHtml (→ lt.escHtml) + getTicketIdFromUrl
│ └── images/
│ └── favicon.png
├── config/
@@ -387,6 +395,8 @@ Key conventions and gotchas for working with this codebase:
15. **CSP nonces**: All inline `<script>` tags require `nonce="<?php echo $nonce; ?>"`
16. **Visibility validation**: Internal visibility requires at least one group — validated server-side
17. **Rate limiting**: Both session-based AND IP-based limits are enforced
18. **Relative timestamps**: Dashboard dates use `lt.time.ago()` and refresh every 60s; full date is always in the `title` attribute for hover
19. **Boot sequence**: Shows ASCII banner then boot messages on first page visit per session (`sessionStorage.getItem('booted')`); removed on subsequent loads
## File Reference

View File

@@ -366,11 +366,13 @@ function sortTable(table, column) {
const aValue = a.children[column].textContent.trim();
const bValue = b.children[column].textContent.trim();
// Check if this is a date column
// Check if this is a date column — prefer data-ts attribute over text (which may be relative)
const headerText = headers[column].textContent.toLowerCase();
if (headerText === 'created' || headerText === 'updated') {
const dateA = new Date(aValue);
const dateB = new Date(bValue);
const cellA = a.children[column];
const cellB = b.children[column];
const dateA = new Date(cellA.dataset.ts || aValue);
const dateB = new Date(cellB.dataset.ts || bValue);
return currentDirection === 'asc' ? dateA - dateB : dateB - dateA;
}
@@ -1817,6 +1819,24 @@ function hideLoadingOverlay(element) {
}
}
// ========================================
// RELATIVE TIMESTAMPS
// ========================================
/**
* Convert all .ts-cell[data-ts] elements to relative time using lt.time.ago().
* Runs once on DOMContentLoaded and refreshes every 60s so "2m ago" stays current.
* The original full timestamp is preserved in the title attribute for hover.
*/
function initRelativeTimes() {
document.querySelectorAll('.ts-cell[data-ts]').forEach(el => {
el.textContent = lt.time.ago(el.dataset.ts);
});
}
document.addEventListener('DOMContentLoaded', initRelativeTimes);
setInterval(initRelativeTimes, 60000);
// Export for use in other scripts
window.generateSkeletonRows = generateSkeletonRows;
window.generateSkeletonComments = generateSkeletonComments;

View File

@@ -445,8 +445,8 @@ $nonce = SecurityHeadersMiddleware::getNonce();
echo "<td><span class='status-" . str_replace(' ', '-', $row['status']) . "'>{$row['status']}</span></td>";
echo "<td>" . htmlspecialchars($creator) . "</td>";
echo "<td>" . htmlspecialchars($assignedTo) . "</td>";
echo "<td>" . date('Y-m-d H:i', strtotime($row['created_at'])) . "</td>";
echo "<td>" . date('Y-m-d H:i', strtotime($row['updated_at'])) . "</td>";
echo "<td class='ts-cell' data-ts='" . htmlspecialchars($row['created_at'], ENT_QUOTES, 'UTF-8') . "' title='" . date('Y-m-d H:i T', strtotime($row['created_at'])) . "'>" . date('Y-m-d H:i', strtotime($row['created_at'])) . "</td>";
echo "<td class='ts-cell' data-ts='" . htmlspecialchars($row['updated_at'], ENT_QUOTES, 'UTF-8') . "' title='" . date('Y-m-d H:i T', strtotime($row['updated_at'])) . "'>" . date('Y-m-d H:i', strtotime($row['updated_at'])) . "</td>";
// Quick actions column
echo "<td class='quick-actions-cell'>";
echo "<div class='quick-actions'>";
@@ -493,7 +493,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
<div class="ticket-card-meta">
<span><?php echo htmlspecialchars($row['category']); ?></span>
<span>@ <?php echo htmlspecialchars($assignedTo); ?></span>
<span><?php echo date('M j', strtotime($row['updated_at'])); ?></span>
<span class="ts-cell" data-ts="<?php echo htmlspecialchars($row['updated_at'], ENT_QUOTES, 'UTF-8'); ?>" title="<?php echo date('Y-m-d H:i', strtotime($row['updated_at'])); ?>"><?php echo date('M j', strtotime($row['updated_at'])); ?></span>
</div>
</div>
<div class="ticket-card-status <?php echo $statusClass; ?>">