Inspections: apply violations new-vs-old cue to the map + list

Match the violations changes on the Inspections map: add a green "New" badge for
violations recorded in the last 7 days, both in the map pin popups and the unit
list, so new vs old is distinguishable. (Newest-first ordering and
auto-advance-to-next-stage on re-recording a property already existed here.)
Also fixed a misleading stage fallback that showed "New" as a notice level.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 23:03:51 -04:00
parent b15ed4bff8
commit 86e8513694
+10 -1
View File
@@ -271,13 +271,20 @@ function InteractiveMap({ units, violationsByUnit, selectedUnit, onSelectUnit, o
if (days >= 30) lastColor = "#dc3545";
else if (days >= 14) lastColor = "#f59e0b";
}
// "New" badge for recently-recorded violations (last 7 days) — matches the
// violations list, so new vs old is distinguishable on the map too.
const createdDays = v.created_at ? Math.floor((Date.now() - new Date(v.created_at).getTime()) / 86400000) : 999;
const newBadge = createdDays < 7
? `<span style="font-size:9px;color:#047857;background:#d1fae5;border:1px solid #a7f3d0;border-radius:3px;padding:1px 5px;font-weight:700;white-space:nowrap">New</span>`
: "";
return `<div style="display:flex;align-items:center;gap:6px;padding:4px 0;font-size:12px;color:#343a40;border-bottom:1px solid #f0f0f0">
<span>${getIconForCategory(v.category || v.violation_type || "")}</span>
<span style="flex:1;display:flex;flex-direction:column;gap:1px">
<span>${v.category || v.violation_type || v.title || "Violation"}</span>
${lastLabel ? `<span style="font-size:9px;color:${lastColor};font-weight:600">${lastLabel}</span>` : ""}
</span>
<span style="font-size:9px;color:#6c757d;background:#f0f0f0;border-radius:3px;padding:1px 5px">${v.stage || v.notice_level || "New"}</span>
${newBadge}
<span style="font-size:9px;color:#6c757d;background:#f0f0f0;border-radius:3px;padding:1px 5px">${v.stage || v.notice_level || "First Notice"}</span>
${escalateBtn}
<button class="iw-resolve-btn" data-vid="${v.id}" style="background:#198754;color:white;border:none;border-radius:4px;padding:2px 8px;font-size:10px;font-weight:600;cursor:pointer;white-space:nowrap">✓ Resolve</button>
</div>`;
@@ -1023,6 +1030,7 @@ export default function InspectionsPage() {
const viols = violsByUnit[u.id] || [];
const open = viols.filter((v: any) => v.status === "open" || v.status === "Open" || v.status === "recommended_for_fining").length;
const hasPhotos = viols.some((v: any) => v.photo_url || (Array.isArray(v.photo_urls) && v.photo_urls.length > 0));
const hasNew = viols.some((v: any) => v.created_at && (Date.now() - new Date(v.created_at).getTime()) < 7 * 86400000);
const sel = selectedUnit?.id === u.id;
const isChecked = selectedUnitIds.includes(u.id);
@@ -1037,6 +1045,7 @@ export default function InspectionsPage() {
<div className={`w-5 h-5 rounded-full flex items-center justify-center text-[9px] font-bold text-white shrink-0 ${open && hasPhotos ? "bg-amber-500" : open ? "bg-red-500" : "bg-blue-600"}`}>{i + 1}</div>
<div className="text-[13px] font-bold text-slate-800 flex-1 truncate">{u.address || u.unit_number}</div>
{hasPhotos && <Camera className="w-3.5 h-3.5 text-amber-500 shrink-0" />}
{hasNew && <span className="text-[9px] font-bold text-emerald-700 bg-emerald-100 border border-emerald-200 rounded px-1.5 py-0.5 shrink-0">New</span>}
{open > 0 && <InspBadge count={open} color={B.red} />}
</div>
{u.owner_name && <div className="text-[11px] text-slate-500 pl-7 truncate">{u.owner_name}</div>}