Sprint 3: Alpine.js Dashboards — Login, CS/FM/CEO Views, Ticket Management #3

Merged
abiba-bot merged 7 commits from fm/denya-onecare-s3 into main 2026-07-23 19:36:35 +00:00
3 changed files with 17 additions and 10 deletions
Showing only changes of commit a9c17d0703 - Show all commits
+14 -7
View File
@@ -218,15 +218,22 @@
}); });
this.charts.monthlyMax = Math.max(...this.charts.monthlyTrend.map(m => m.count), 1); this.charts.monthlyMax = Math.max(...this.charts.monthlyTrend.map(m => m.count), 1);
// By property // By property — load units once and compute from ticket data
const propData = { east: 0, west: 0 }; const propData = { east: 0, west: 0 };
all.forEach(t => { /* would need unit join — estimate from ticket IDs */ });
try { try {
const east = await app().apiGet('/api/tickets?property=East&page_size=1'); const units = await app().apiGet('/api/tickets/units');
const west = await app().apiGet('/api/tickets?property=West&page_size=1'); if (units && units.length > 0) {
propData.east = east?.total || 0; const unitPropertyMap = {};
propData.west = west?.total || 0; units.forEach(u => { unitPropertyMap[u.id] = u.property; });
} catch (e) { /* api may not support property filter directly */ } all.forEach(t => {
if (t.unit_id && unitPropertyMap[t.unit_id]) {
const p = unitPropertyMap[t.unit_id].toLowerCase();
if (p === 'east') propData.east++;
else if (p === 'west') propData.west++;
}
});
}
} catch (e) { console.error('Property stats error', e); }
this.charts.byProperty = { east: propData.east, west: propData.west, max: Math.max(propData.east, propData.west, 1) }; this.charts.byProperty = { east: propData.east, west: propData.west, max: Math.max(propData.east, propData.west, 1) };
// By category — use categories endpoint // By category — use categories endpoint
+2 -2
View File
@@ -115,7 +115,7 @@
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-sm text-gray-500">Assigned To</dt> <dt class="text-sm text-gray-500">Assigned To</dt>
<dd class="text-sm font-medium text-gray-900" x-text="ticket.assigned_technician?.full_name || 'Unassigned'"></dd> <dd class="text-sm font-medium text-gray-900" x-text="ticket.assigned_technician_name || 'Unassigned'"></dd>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-sm text-gray-500">Reporter</dt> <dt class="text-sm text-gray-500">Reporter</dt>
@@ -402,7 +402,7 @@
this.noteForm.note = ''; this.noteForm.note = '';
app().showToast('Note added', 'success'); app().showToast('Note added', 'success');
} catch (e) { } catch (e) {
app().showToast(e.message, 'error'); // No need to show error here — api() base method already shows it
} finally { } finally {
this.noteSubmitting = false; this.noteSubmitting = false;
} }
+1 -1
View File
@@ -104,7 +104,7 @@
<option value="walk-in">Walk-in</option> <option value="walk-in">Walk-in</option>
<option value="whatsapp">WhatsApp</option> <option value="whatsapp">WhatsApp</option>
<option value="agent">Agent</option> <option value="agent">Agent</option>
<option value="qr">QR Code</option> <!-- QR Code excluded per Sprint 3 scope -->
</select> </select>
</div> </div>
</div> </div>