feat: backdated reported date for old active tickets
Wahab can now enter historical tickets that keep their true reported date: - Add nullable tickets.reported_at (DateTime) via alembic d5e0f2a1c3b4; backfill existing rows from created_at so nothing shows empty. - TicketCreate.reported_at (optional) is persisted by create_ticket and defaults to now when omitted, so existing create behavior is unchanged. - New-issue form gains a 'Reported Date' date picker (defaults to today, past dates allowed, future blocked) and sends reported_at in the payload. - List and detail pages show 'Reported' next to 'Created' (date-only, labeled) so backdated tickets are obvious; SLA deadline is unchanged and still runs from creation time so backfilling never instantly breaches. - Tests: backdated create persists + is exposed on list/detail; omitted reported_at defaults to now; SLA window unchanged; full datetime round trip.
This commit is contained in:
@@ -23,7 +23,11 @@
|
||||
<span class="px-3 py-1 rounded-full text-sm font-medium" :class="statusClass(ticket.status)" x-text="ticket.status"></span>
|
||||
<span class="px-3 py-1 rounded text-sm font-medium" :class="priorityClass(ticket.priority)" x-text="priorityBadge(ticket.priority)"></span>
|
||||
</div>
|
||||
<p class="text-gray-500 mt-1">Created <span x-text="formatDate(ticket.created_at)"></span></p>
|
||||
<p class="text-gray-500 mt-1">Created <span x-text="formatDate(ticket.created_at)"></span>
|
||||
<template x-if="ticket.reported_at && formatDateShort(ticket.reported_at) !== formatDateShort(ticket.created_at)">
|
||||
<span> · Reported <span class="font-medium text-gray-600" x-text="formatDateShort(ticket.reported_at)"></span></span>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="showStatusModal = true" class="px-4 py-2 bg-denya-600 text-white rounded-lg hover:bg-denya-700 transition text-sm font-medium">Update Status</button>
|
||||
@@ -129,6 +133,10 @@
|
||||
<dt class="text-sm text-gray-500">Reported Via</dt>
|
||||
<dd class="text-sm font-medium text-gray-900 capitalize" x-text="ticket.reported_via || '—'"></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm text-gray-500">Reported</dt>
|
||||
<dd class="text-sm font-medium text-gray-900" x-text="ticket.reported_at ? formatDateShort(ticket.reported_at) : formatDateShort(ticket.created_at)"></dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-sm text-gray-500">Priority</dt>
|
||||
<dd><span class="px-2 py-0.5 rounded text-xs font-medium" :class="priorityClass(ticket.priority)" x-text="priorityBadge(ticket.priority)"></span></dd>
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
<th class="px-5 py-3 text-left cursor-pointer hover:text-gray-900" @click="sortBy('created_at')">
|
||||
Created <span x-show="sortField === 'created_at'" x-text="sortDir === 'asc' ? '↑' : '↓'"></span>
|
||||
</th>
|
||||
<th class="px-5 py-3 text-left">Reported</th>
|
||||
<th class="px-5 py-3 text-left">SLA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -142,6 +143,7 @@
|
||||
<td class="px-5 py-3 text-gray-600 max-w-xs truncate" x-text="ticket.description || ''"></td>
|
||||
<td class="px-5 py-3 text-gray-500 text-xs" x-text="ticket.assigned_technician_name || '—'"></td>
|
||||
<td class="px-5 py-3 text-gray-500 text-xs" x-text="formatDate(ticket.created_at)"></td>
|
||||
<td class="px-5 py-3 text-gray-500 text-xs" x-text="formatDateShort(ticket.reported_at || ticket.created_at)"></td>
|
||||
<td class="px-5 py-3">
|
||||
<span x-show="ticket.sla_deadline" class="text-xs" :class="new Date(ticket.sla_deadline) < new Date() && !['Closed','Completed','Cancelled'].includes(ticket.status) ? 'text-red-600 font-medium' : 'text-gray-400'">
|
||||
<span x-text="formatDateShort(ticket.sla_deadline)"></span>
|
||||
@@ -151,7 +153,7 @@
|
||||
</tr>
|
||||
</template>
|
||||
<tr x-show="!tickets.length && !loading">
|
||||
<td colspan="7" class="px-5 py-16 text-center text-gray-400">
|
||||
<td colspan="8" class="px-5 py-16 text-center text-gray-400">
|
||||
<svg class="w-12 h-12 mx-auto text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
||||
No tickets match your filters
|
||||
</td>
|
||||
@@ -178,6 +180,7 @@
|
||||
<th class="px-5 py-3 text-left">Description</th>
|
||||
<th class="px-5 py-3 text-left">Assigned To</th>
|
||||
<th class="px-5 py-3 text-left">Created</th>
|
||||
<th class="px-5 py-3 text-left">Reported</th>
|
||||
<th class="px-5 py-3 text-left">SLA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -190,6 +193,7 @@
|
||||
<td class="px-5 py-3 text-gray-600 max-w-xs truncate" x-text="ticket.description || ''"></td>
|
||||
<td class="px-5 py-3 text-gray-500 text-xs" x-text="ticket.assigned_technician_name || '—'"></td>
|
||||
<td class="px-5 py-3 text-gray-500 text-xs" x-text="formatDate(ticket.created_at)"></td>
|
||||
<td class="px-5 py-3 text-gray-500 text-xs" x-text="formatDateShort(ticket.reported_at || ticket.created_at)"></td>
|
||||
<td class="px-5 py-3">
|
||||
<span x-show="ticket.sla_deadline" class="text-xs" :class="new Date(ticket.sla_deadline) < new Date() && !['Closed','Completed','Cancelled'].includes(ticket.status) ? 'text-red-600 font-medium' : 'text-gray-400'">
|
||||
<span x-text="formatDateShort(ticket.sla_deadline)"></span>
|
||||
|
||||
@@ -154,6 +154,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reported date (backdating support) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Reported Date</label>
|
||||
<input type="date" x-model="form.reported_date" :max="todayStr" class="w-full px-4 py-2.5 rounded-lg border border-gray-300 focus:ring-2 focus:ring-denya-500 focus:border-transparent outline-none">
|
||||
<p class="mt-1 text-xs text-gray-400">Defaults to today. Use a past date when entering an old/backlogged issue — it stays active in the normal workflow.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Photo Upload -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Photos (Before)</label>
|
||||
@@ -205,8 +214,10 @@
|
||||
priority: '',
|
||||
description: '',
|
||||
reporter: '',
|
||||
reported_via: ''
|
||||
reported_via: '',
|
||||
reported_date: ''
|
||||
},
|
||||
todayStr: '',
|
||||
priorityAuto: false,
|
||||
categories: [],
|
||||
subCategories: [],
|
||||
@@ -223,6 +234,14 @@
|
||||
async init() {
|
||||
await this.loadCategories();
|
||||
await this.loadUnits();
|
||||
// Default reported date to today (local), allow backdating via the date picker
|
||||
this.todayStr = this.localDateStr(new Date());
|
||||
if (!this.form.reported_date) this.form.reported_date = this.todayStr;
|
||||
},
|
||||
|
||||
localDateStr(d) {
|
||||
const offset = d.getTimezoneOffset();
|
||||
return new Date(d.getTime() - offset * 60000).toISOString().slice(0, 10);
|
||||
},
|
||||
|
||||
// ── Report mode ──────────────────────────────────────────
|
||||
@@ -384,6 +403,7 @@
|
||||
unit_id: this.form.unit.id,
|
||||
customer_name: this.form.customer_name || null,
|
||||
phone: this.form.phone || null,
|
||||
reported_at: this.form.reported_date || null,
|
||||
};
|
||||
|
||||
const ticket = await app().apiPost('/api/tickets', payload);
|
||||
|
||||
Reference in New Issue
Block a user