|
|
|
|
|
<div class="site-header"> |
|
|
<div class="header-inner"> |
|
|
<div class="logo-cluster"> |
|
|
<span (click)="navigateHome()" style="cursor:pointer;display:flex;align-items:center;"> |
|
|
<img src="/assets/pykara-logo.png" alt="PyDetect Logo" class="logo-img-header" /> |
|
|
</span> |
|
|
<div class="py-detect-title-header"> |
|
|
<span class="py-letter p">P</span> |
|
|
<span class="py-letter y">Y</span> |
|
|
<span class="py-shape"></span> |
|
|
<span class="py-letter d">D</span> |
|
|
<span class="py-letter e">E</span> |
|
|
<span class="py-letter t">T</span> |
|
|
<span class="py-letter e2">E</span> |
|
|
<span class="py-letter c">C</span> |
|
|
<span class="py-letter t2">T</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="record-card"> |
|
|
<div class="record-header"> |
|
|
<div class="record-title-group"> |
|
|
<span class="record-title">Police Investigation Records</span> |
|
|
<select class="record-dropdown"> |
|
|
<option>Recently Viewed</option> |
|
|
<option>All Records</option> |
|
|
</select> |
|
|
</div> |
|
|
<div class="record-header-actions"> |
|
|
<input class="record-search" type="text" [(ngModel)]="q" placeholder="Search this list..." /> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="analytics-summary"> |
|
|
<div class="summary-card total"> |
|
|
<div class="summary-label">Total Cases</div> |
|
|
<div class="summary-value">{{ totalCases }}</div> |
|
|
</div> |
|
|
<div class="summary-card open"> |
|
|
<div class="summary-label">Open</div> |
|
|
<div class="summary-value">{{ openCases }}</div> |
|
|
</div> |
|
|
<div class="summary-card closed"> |
|
|
<div class="summary-label">Closed</div> |
|
|
<div class="summary-value">{{ closedCases }}</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="record-meta" style="padding: 8px 24px 0 24px; color: #6b7280; font-size: 0.98em;"> |
|
|
{{ rows.length }} items • Updated a few seconds ago |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="filter-bar"> |
|
|
<select [(ngModel)]="filterCrimeType"> |
|
|
<option value="">Crime Type</option> |
|
|
<option *ngFor="let type of crimeTypes">{{ type }}</option> |
|
|
</select> |
|
|
<select [(ngModel)]="filterStatus"> |
|
|
<option value="">Status</option> |
|
|
<option *ngFor="let status of statusTypes">{{ status }}</option> |
|
|
</select> |
|
|
<select [(ngModel)]="filterLocation"> |
|
|
<option value="">Location</option> |
|
|
<option *ngFor="let loc of locations">{{ loc }}</option> |
|
|
</select> |
|
|
<select [(ngModel)]="filterOfficer"> |
|
|
<option value="">Officer</option> |
|
|
<option *ngFor="let officer of officers">{{ officer }}</option> |
|
|
</select> |
|
|
<button (click)="applyFilters()">Apply</button> |
|
|
<button (click)="resetFilters()">Reset</button> |
|
|
</div> |
|
|
|
|
|
<table class="record-table"> |
|
|
<thead> |
|
|
<tr> |
|
|
<th>#</th> |
|
|
<th>Case ID</th> |
|
|
<th>Status</th> |
|
|
<th>Crime Type</th> |
|
|
<th>Date & Time</th> |
|
|
<th>Location</th> |
|
|
<th>Investigation Officer</th> |
|
|
<th>Suspect Name</th> |
|
|
<th>Reported By</th> |
|
|
<th>Last Updated</th> |
|
|
<th>Verified By</th> |
|
|
<th>Actions</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody> |
|
|
<tr *ngFor="let c of rows; let i = index"> |
|
|
<td>{{ (currentPage - 1) * pageSize + i + 1 }}</td> |
|
|
<td><a (click)="openDetails(c, i)">{{ c.caseId || '—' }}</a></td> |
|
|
<td> |
|
|
<span class="status-label" |
|
|
[ngClass]="{ |
|
|
'status-open': c.status === 'Open', |
|
|
'status-under': c.status === 'Under Investigation', |
|
|
'status-closed': c.status === 'Closed' |
|
|
}"> |
|
|
{{ c.status || '—' }} |
|
|
</span> |
|
|
</td> |
|
|
<td>{{ c.crime || '—' }}</td> |
|
|
<td>{{ c.dateTime ? (c.dateTime | date:'M/d/yyyy HH:mm') : '—' }}</td> |
|
|
<td>{{ c.police.address || '—' }}</td> |
|
|
<td>{{ c.police.name || '—' }}</td> |
|
|
<td>{{ c.accused.name || '—' }}</td> |
|
|
<td>{{ c.reportedBy || '—' }}</td> |
|
|
<td>{{ c.lastUpdated ? (c.lastUpdated | date:'M/d/yyyy HH:mm') : '—' }}</td> |
|
|
<td>{{ c.verifiedBy || '—' }}</td> |
|
|
<td> |
|
|
<button class="icon-btn verify" (click)="verifyCase((currentPage - 1) * pageSize + i)" title="Verify"> |
|
|
<i class="fas fa-user-check"></i> |
|
|
</button> |
|
|
<button class="icon-btn view" (click)="openDetails(c, (currentPage - 1) * pageSize + i)" title="View"> |
|
|
<i class="fas fa-eye"></i> |
|
|
</button> |
|
|
<button class="icon-btn edit" (click)="editCase(c, (currentPage - 1) * pageSize + i)" title="Edit"> |
|
|
<i class="fas fa-edit"></i> |
|
|
</button> |
|
|
<button class="icon-btn delete" (click)="deleteCase((currentPage - 1) * pageSize + i)" title="Delete"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</td> |
|
|
</tr> |
|
|
<tr *ngIf="rows.length === 0"> |
|
|
<td colspan="12" class="empty">No records found.</td> |
|
|
</tr> |
|
|
</tbody> |
|
|
</table> |
|
|
|
|
|
|
|
|
<div class="pagination-controls" style="display:flex;justify-content:center;align-items:center;margin:20px 0;gap:10px;"> |
|
|
<style> |
|
|
.pagination-controls button { |
|
|
border: none; |
|
|
background: #f3f4f6; |
|
|
color: #333; |
|
|
border-radius: 8px; |
|
|
padding: 0 16px; |
|
|
min-width: 40px; |
|
|
min-height: 40px; |
|
|
font-size: 1.1em; |
|
|
font-weight: 500; |
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.04); |
|
|
transition: background 0.2s, color 0.2s, transform 0.2s; |
|
|
cursor: pointer; |
|
|
outline: none; |
|
|
} |
|
|
|
|
|
.pagination-controls button:hover:not(:disabled), |
|
|
.pagination-controls button:focus:not(:disabled) { |
|
|
background: #e3eafe; |
|
|
color: #1976d2; |
|
|
transform: scale(1.08); |
|
|
} |
|
|
|
|
|
.pagination-controls button.active { |
|
|
background: #1976d2; |
|
|
color: #fff; |
|
|
font-weight: bold; |
|
|
box-shadow: 0 0 0 2px #90caf9; |
|
|
animation: pulseActive 1s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulseActive { |
|
|
0% { |
|
|
box-shadow: 0 0 0 2px #90caf9; |
|
|
} |
|
|
|
|
|
50% { |
|
|
box-shadow: 0 0 0 6px #90caf9; |
|
|
} |
|
|
|
|
|
100% { |
|
|
box-shadow: 0 0 0 2px #90caf9; |
|
|
} |
|
|
} |
|
|
|
|
|
.pagination-controls span { |
|
|
font-size: 1.2em; |
|
|
color: #888; |
|
|
padding: 0 8px; |
|
|
} |
|
|
</style> |
|
|
<button (click)="prevPage()" [disabled]="currentPage === 1">«</button> |
|
|
<ng-container *ngFor="let page of getPagination()"> |
|
|
<button *ngIf="page !== '...'" (click)="goToPage(page)" [class.active]="currentPage === page">{{ page }}</button> |
|
|
<span *ngIf="page === '...'">...</span> |
|
|
</ng-container> |
|
|
<button (click)="nextPage()" [disabled]="currentPage === totalPages">»</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div style="display:flex;align-items:center;justify-content:flex-start;gap:24px;margin-bottom:16px;"> |
|
|
<span style="font-size:1.1em;">Results: {{ resultsStart }} - {{ resultsEnd }} of {{ resultsTotal }}</span> |
|
|
<select [(ngModel)]="pageSize" (change)="onPageSizeChange(pageSize)" style="padding:4px 12px;border-radius:8px;font-size:1em;"> |
|
|
<option *ngFor="let size of pageSizeOptions" [value]="size">{{ size }}</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="modal-blur-overlay" *ngIf="showDetails"></div> |
|
|
|
|
|
|
|
|
<div class="modal-backdrop" *ngIf="showDetails" (click)="closeDetails()"></div> |
|
|
<div class="modal" *ngIf="showDetails" role="dialog" aria-modal="true" aria-labelledby="detailsTitle"> |
|
|
<div class="modal-header"> |
|
|
<h2 id="detailsTitle">Case Details</h2> |
|
|
</div> |
|
|
|
|
|
<div class="modal-body" *ngIf="selectedCase as sc"> |
|
|
<div class="modal-sections-grid"> |
|
|
<ng-container *ngFor="let sectionKey of ['crime', 'suspect', 'notes']"> |
|
|
<div class="section-block"> |
|
|
<div class="section-title">{{ sectionKey === 'crime' ? 'Crime Details' : sectionKey === 'suspect' ? 'Suspect Details' : 'Evidence and Documents' }}</div> |
|
|
<ng-container *ngFor="let subgroup of getSubgroups(sectionKey)"> |
|
|
<div class="subgroup-title" style="margin:10px 0 4px 0;font-weight:600;color:#1976d2;">{{ subgroup }}</div> |
|
|
<div class="fields-grid"> |
|
|
<ng-container *ngFor="let field of getFieldsForSubgroup(sectionKey, subgroup)"> |
|
|
<div class="field-card" style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f3f4f6;"> |
|
|
<span style="font-weight:500;color:#333;">{{ field }}</span> |
|
|
<span style="color:#444;">{{ getFieldValue(sc, sectionKey, field) }}</span> |
|
|
</div> |
|
|
</ng-container> |
|
|
</div> |
|
|
</ng-container> |
|
|
</div> |
|
|
</ng-container> |
|
|
|
|
|
<div class="section-block"> |
|
|
<div class="section-title">All Entered Information</div> |
|
|
<div class="fields-grid"> |
|
|
<div class="field-card" *ngFor="let key of objectKeys(sc)" style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f3f4f6;"> |
|
|
<span style="font-weight:500;color:#333;">{{ key }}</span> |
|
|
<span style="color:#444;">{{ getValue(sc, key) }}</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="modal-footer"> |
|
|
<button type="button" class="btn" (click)="closeDetails()">Close</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<footer> |
|
|
<p>© 2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p> |
|
|
</footer> |
|
|
|