|
|
import { Component, OnInit } from '@angular/core'; |
|
|
import { Router } from '@angular/router'; |
|
|
import { CaseStoreService, PoliceCase } from '../case-store.service'; |
|
|
import { InfopageComponent } from '../infopage/infopage.component'; |
|
|
|
|
|
@Component({ |
|
|
selector: 'app-recordpage', |
|
|
templateUrl: './recordpage.component.html', |
|
|
styleUrls: ['./recordpage.component.css'] |
|
|
}) |
|
|
export class RecordpageComponent implements OnInit { |
|
|
cases: PoliceCase[] = []; |
|
|
|
|
|
|
|
|
currentPage: number = 1; |
|
|
pageSize: number = 5; |
|
|
get totalPages(): number { |
|
|
return Math.ceil(this.filteredCases.length / this.pageSize) || 1; |
|
|
} |
|
|
get pagedCases(): PoliceCase[] { |
|
|
const start = (this.currentPage - 1) * this.pageSize; |
|
|
return this.filteredCases.slice(start, start + this.pageSize); |
|
|
} |
|
|
setPage(page: number) { |
|
|
if (page < 1 || page > this.totalPages) return; |
|
|
this.currentPage = page; |
|
|
} |
|
|
nextPage() { this.setPage(this.currentPage + 1); } |
|
|
prevPage() { this.setPage(this.currentPage - 1); } |
|
|
|
|
|
|
|
|
showDetails = false; |
|
|
selectedCase: PoliceCase | null = null; |
|
|
selectedIndex = -1; |
|
|
|
|
|
|
|
|
q = ''; |
|
|
|
|
|
|
|
|
sortKey: 'caseId' | 'crime' | 'dateTime' | 'location' | 'status' | 'Investigation Officer' = 'dateTime'; |
|
|
sortDir: 'asc' | 'desc' = 'desc'; |
|
|
|
|
|
objectKeys = Object.keys; |
|
|
|
|
|
|
|
|
selectedSubgroup: any = { |
|
|
crime: 'Identification & Timing', |
|
|
suspect: 'Identity', |
|
|
notes: 'Investigation Notes' |
|
|
}; |
|
|
|
|
|
|
|
|
sections = new InfopageComponent(null as any, null as any).sections; |
|
|
|
|
|
getSubgroups(sectionKey: string): string[] { |
|
|
return Object.keys(this.sections[sectionKey].subgroups); |
|
|
} |
|
|
|
|
|
getFieldsForSubgroup(sectionKey: string, subgroup: string): string[] { |
|
|
return this.sections[sectionKey].subgroups[subgroup] || []; |
|
|
} |
|
|
|
|
|
selectSubgroup(sectionKey: string, subgroup: string) { |
|
|
this.selectedSubgroup[sectionKey] = subgroup; |
|
|
} |
|
|
|
|
|
getFieldValue(sc: any, sectionKey: string, field: string): any { |
|
|
|
|
|
const fieldMap: { [key: string]: string | string[] } = { |
|
|
|
|
|
'Case ID': 'caseId', |
|
|
'FIR / Ref #': 'firRef', |
|
|
'Crime Type': 'crime', |
|
|
'Case Category': 'caseCategory', |
|
|
'Date & Time (Entry)': 'dateTime', |
|
|
'Occurred From': 'occurredFrom', |
|
|
'Occurred To': 'occurredTo', |
|
|
'Time Reported': 'timeReported', |
|
|
'Time Discovered': 'timeDiscovered', |
|
|
'Country': 'country', |
|
|
'State': 'state', |
|
|
'District': 'district', |
|
|
'Number of Victims': 'numberOfVictims', |
|
|
'Brief Description': 'briefDescription', |
|
|
'Location': ['police', 'address'], |
|
|
'Jurisdiction / PS': 'jurisdiction', |
|
|
'Scene Type': 'sceneType', |
|
|
'Reported By': 'reportedBy', |
|
|
'Reported Contact': 'reportedContact', |
|
|
'Witness Count': 'witnessCount', |
|
|
'Victim Name': 'victimName', |
|
|
'Victim Contact': 'victimContact', |
|
|
'Victim Summary': 'victimSummary', |
|
|
'Suspected Offender Known?': 'suspectedOffenderKnown', |
|
|
'Suspect Link': 'suspectLink', |
|
|
'Legal Sections / Charges': 'legalSections', |
|
|
'Offence Category': 'offenceCategory', |
|
|
'Offence Description': 'offenceDescription', |
|
|
'Suspected Motive': 'suspectedMotive', |
|
|
'Confirmed Motive': 'confirmedMotive', |
|
|
'Weapon Involved': 'weaponInvolved', |
|
|
'Property Loss / Damage': 'propertyLoss', |
|
|
'Evidence Collected': 'evidenceCollected', |
|
|
'Forensic Tests Required': 'forensicTestsRequired', |
|
|
'Scene Condition': 'sceneCondition', |
|
|
'Photos / Video?': 'photosVideo', |
|
|
'CCTV Present?': 'cctvPresent', |
|
|
'CCTV Sources / IDs': 'cctvSources', |
|
|
'Physical Evidence (list)': 'physicalEvidence', |
|
|
'Chain of Custody?': 'chainOfCustody', |
|
|
'Digital Evidence': 'digitalEvidence', |
|
|
'Evidence Storage Reference': 'evidenceStorageReference', |
|
|
'Investigating Officer': ['police', 'name'], |
|
|
'Duty Person': ['police', 'dutyPerson'], |
|
|
'Supervising Officer': ['police', 'supervisingOfficer'], |
|
|
'Patrol Notes': ['police', 'patrolNotes'], |
|
|
'Arrest Made': 'arrestMade', |
|
|
'Arrest Location': 'arrestLocation', |
|
|
'Initial Actions Taken': 'initialActionsTaken', |
|
|
'riskLevel': 'riskLevel', |
|
|
'Confidentiality': 'confidentiality', |
|
|
'Biometric / Forensic IDs': 'biometricIds', |
|
|
'DNA Ref ID': 'dnaRefId', |
|
|
'Fingerprint ID': 'fingerprintId', |
|
|
'Case Status': 'status', |
|
|
'Linked Cases': 'linkedCases', |
|
|
'arrestCount': 'arrestCount', |
|
|
'Case Priority': 'casePriority', |
|
|
'Follow-up Date': 'followUpDate', |
|
|
'Court Case ID': 'courtCaseId', |
|
|
'Next Hearing Date': 'nextHearingDate', |
|
|
'Final Summary': 'finalSummary', |
|
|
'Remark': 'remark', |
|
|
|
|
|
'Suspect ID': ['accused', 'suspectId'], |
|
|
'Suspect Name': ['accused', 'name'], |
|
|
'Alias / Nickname': ['accused', 'alias'], |
|
|
'Age': ['accused', 'age'], |
|
|
'Gender': ['accused', 'gender'], |
|
|
'Nationality': ['accused', 'nationality'], |
|
|
'Nationality ID / Passport Number': ['accused', 'passportNumber'], |
|
|
'Languages': ['accused', 'languages'], |
|
|
'Address': ['accused', 'address'], |
|
|
'Known Aliases': ['accused', 'knownAliases'], |
|
|
'Government ID': ['accused', 'governmentId'], |
|
|
'Height (cm)': ['accused', 'height'], |
|
|
'Weight (kg)': ['accused', 'weight'], |
|
|
'Build': ['accused', 'build'], |
|
|
'Hair Color': ['accused', 'hairColor'], |
|
|
'Eye Color': ['accused', 'eyeColor'], |
|
|
'Distinguishing Marks': ['accused', 'distinguishingMarks'], |
|
|
'Tattoo Details': ['accused', 'tattooDetails'], |
|
|
'Scar Details': ['accused', 'scarDetails'], |
|
|
'Photo Upload': ['accused', 'photoUpload'], |
|
|
'Employment': ['accused', 'employment'], |
|
|
'Education': ['accused', 'education'], |
|
|
'Occupation': ['accused', 'occupation'], |
|
|
'Company': ['accused', 'company'], |
|
|
'Workplace Address': ['accused', 'workplaceAddress'], |
|
|
'Marital Status': ['accused', 'maritalStatus'], |
|
|
'Known Habits': ['accused', 'knownHabits'], |
|
|
'Known Financial Details': ['accused', 'knownFinancialDetails'], |
|
|
'Associate Names': ['accused', 'associateNames'], |
|
|
'Gang Affiliation': ['accused', 'gangAffiliation'], |
|
|
'Family Connections': ['accused', 'familyConnections'], |
|
|
'Social Media Handles': ['accused', 'socialMediaHandles'], |
|
|
'Criminal History': ['accused', 'criminalHistory'], |
|
|
'Prior Arrests': ['accused', 'priorArrests'], |
|
|
'Probation/Parole Status': ['accused', 'probationStatus'], |
|
|
|
|
|
'Initial Findings': ['police', 'information'], |
|
|
'Detailed Notes': ['notes', 'detailedNotes'], |
|
|
'Status': 'status', |
|
|
'Version History / Updates': ['notes', 'versionHistory'], |
|
|
'Evidence Photos': ['legal', 'evidencePhotos'], |
|
|
'Evidence Videos': ['legal', 'evidenceVideos'], |
|
|
'Evidence Documents': ['legal', 'evidenceDocuments'], |
|
|
'Links to Evidence': ['legal', 'linksToEvidence'], |
|
|
'Final Recommendations': ['legal', 'finalRecommendations'], |
|
|
'Witness Statements': ['legal', 'witnessStatements'], |
|
|
'Confessions': ['legal', 'confessions'], |
|
|
|
|
|
'Created By': 'createdBy', |
|
|
'Creation Date': 'creationDate', |
|
|
'Last Updated': 'lastUpdated', |
|
|
'Verified By': 'verifiedBy' |
|
|
}; |
|
|
|
|
|
const path = fieldMap[field] || field; |
|
|
if (Array.isArray(path)) { |
|
|
let value = sc; |
|
|
for (const p of path) { |
|
|
if (value && value[p] !== undefined) value = value[p]; |
|
|
else return '—'; |
|
|
} |
|
|
return value !== undefined && value !== null && value !== '' ? value : '—'; |
|
|
} else { |
|
|
return sc[path] !== undefined && sc[path] !== null && sc[path] !== '' ? sc[path] : '—'; |
|
|
} |
|
|
} |
|
|
|
|
|
getValue(obj: any, key: string): any { |
|
|
return obj && obj[key] !== undefined && obj[key] !== null && obj[key] !== '' ? obj[key] : '—'; |
|
|
} |
|
|
|
|
|
constructor(private caseStore: CaseStoreService, private router: Router) { } |
|
|
|
|
|
|
|
|
filterCrimeType: string = ''; |
|
|
filterStatus: string = ''; |
|
|
filterLocation: string = ''; |
|
|
filterOfficer: string = ''; |
|
|
|
|
|
crimeTypes: string[] = []; |
|
|
statusTypes: string[] = []; |
|
|
locations: string[] = []; |
|
|
officers: string[] = []; |
|
|
|
|
|
filteredCases: PoliceCase[] = []; |
|
|
|
|
|
ngOnInit(): void { |
|
|
this.load(); |
|
|
this.populateFilterOptions(); |
|
|
this.applyFilters(); |
|
|
} |
|
|
|
|
|
load(): void { |
|
|
this.cases = this.caseStore.getPoliceCases(); |
|
|
this.populateFilterOptions(); |
|
|
this.applyFilters(); |
|
|
} |
|
|
|
|
|
populateFilterOptions() { |
|
|
this.crimeTypes = [...new Set(this.cases.map(c => c.crime).filter(Boolean))] as string[]; |
|
|
this.statusTypes = [...new Set(this.cases.map(c => c.status).filter(Boolean))] as string[]; |
|
|
this.locations = [...new Set(this.cases.map(c => c.police?.address).filter(Boolean))] as string[]; |
|
|
this.officers = [...new Set(this.cases.map(c => c.police?.name).filter(Boolean))] as string[]; |
|
|
} |
|
|
|
|
|
applyFilters() { |
|
|
this.filteredCases = this.cases.filter(c => |
|
|
(!this.filterCrimeType || c.crime === this.filterCrimeType) && |
|
|
(!this.filterStatus || c.status === this.filterStatus) && |
|
|
(!this.filterLocation || c.police?.address === this.filterLocation) && |
|
|
(!this.filterOfficer || c.police?.name === this.filterOfficer) |
|
|
); |
|
|
this.currentPage = 1; |
|
|
} |
|
|
|
|
|
resetFilters() { |
|
|
this.filterCrimeType = ''; |
|
|
this.filterStatus = ''; |
|
|
this.filterLocation = ''; |
|
|
this.filterOfficer = ''; |
|
|
this.applyFilters(); |
|
|
} |
|
|
|
|
|
|
|
|
get filtered(): PoliceCase[] { |
|
|
const s = (this.q || '').toLowerCase(); |
|
|
if (!s) return this.cases; |
|
|
return this.cases.filter(c => |
|
|
(c.caseId || '').toString().toLowerCase().includes(s) || |
|
|
(c.crime || '').toLowerCase().includes(s) || |
|
|
(c.police?.address || '').toLowerCase().includes(s) || |
|
|
(c.status || '').toLowerCase().includes(s) || |
|
|
(c.police?.name || '').toLowerCase().includes(s) |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
setSort(key: typeof this.sortKey) { |
|
|
if (this.sortKey === key) { |
|
|
this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc'; |
|
|
} else { |
|
|
this.sortKey = key; |
|
|
this.sortDir = key === 'dateTime' ? 'desc' : 'asc'; |
|
|
} |
|
|
} |
|
|
isAsc(key: typeof this.sortKey) { return this.sortKey === key && this.sortDir === 'asc'; } |
|
|
isDesc(key: typeof this.sortKey) { return this.sortKey === key && this.sortDir === 'desc'; } |
|
|
ariaSort(key: typeof this.sortKey) { |
|
|
return this.sortKey === key ? (this.sortDir === 'asc' ? 'ascending' : 'descending') : 'none'; |
|
|
} |
|
|
|
|
|
private cell(c: any, key: typeof this.sortKey): string | number { |
|
|
switch (key) { |
|
|
case 'caseId': return (c.caseId ?? '').toString(); |
|
|
case 'crime': return (c.crime ?? '').toString(); |
|
|
case 'dateTime': return c.dateTime ? new Date(c.dateTime).getTime() : 0; |
|
|
case 'location': return (c.police?.address ?? '').toString(); |
|
|
case 'status': return (c.status ?? '').toString(); |
|
|
case 'Investigation Officer': return (c.police?.name ?? '').toString(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
get rows(): PoliceCase[] { |
|
|
return this.pagedCases; |
|
|
} |
|
|
|
|
|
openDetails(c: PoliceCase, i: number): void { |
|
|
this.selectedCase = c; |
|
|
this.selectedIndex = i; |
|
|
this.showDetails = true; |
|
|
document.body.classList.add('modal-open'); |
|
|
} |
|
|
|
|
|
closeDetails(): void { |
|
|
this.showDetails = false; |
|
|
this.selectedCase = null; |
|
|
this.selectedIndex = -1; |
|
|
document.body.classList.remove('modal-open'); |
|
|
} |
|
|
|
|
|
editCase(c: PoliceCase, i: number): void { |
|
|
|
|
|
this.router.navigate(['/infopage', c.caseId]); |
|
|
} |
|
|
|
|
|
onModernSearch() { |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
navigateHome(): void { |
|
|
this.router.navigate(['/']); |
|
|
} |
|
|
|
|
|
onNewRecord(): void { |
|
|
|
|
|
alert('New record functionality coming soon!'); |
|
|
} |
|
|
|
|
|
deleteCase(index: number): void { |
|
|
if (confirm('Are you sure you want to delete this case?')) { |
|
|
this.caseStore.deletePoliceCaseAt(index); |
|
|
this.load(); |
|
|
} |
|
|
} |
|
|
|
|
|
verifyCase(index: number): void { |
|
|
const adminName = 'Admin'; |
|
|
const updated = { ...this.cases[index], verifiedBy: adminName, lastUpdated: new Date().toISOString() }; |
|
|
this.caseStore.updatePoliceCaseAt(index, updated); |
|
|
this.load(); |
|
|
} |
|
|
|
|
|
get totalCases(): number { |
|
|
return this.cases.length; |
|
|
} |
|
|
get openCases(): number { |
|
|
return this.cases.filter(c => c.status === 'Open').length; |
|
|
} |
|
|
get closedCases(): number { |
|
|
return this.cases.filter(c => c.status === 'Closed').length; |
|
|
} |
|
|
|
|
|
getPagination(): (number | string)[] { |
|
|
const pages: (number | string)[] = []; |
|
|
const total = this.totalPages; |
|
|
if (total <= 7) { |
|
|
for (let i = 1; i <= total; i++) pages.push(i); |
|
|
} else { |
|
|
if (this.currentPage <= 4) { |
|
|
for (let i = 1; i <= 5; i++) pages.push(i); |
|
|
pages.push('...'); |
|
|
pages.push(total); |
|
|
} else if (this.currentPage >= total - 3) { |
|
|
pages.push(1); |
|
|
pages.push('...'); |
|
|
for (let i = total - 4; i <= total; i++) pages.push(i); |
|
|
} else { |
|
|
pages.push(1); |
|
|
pages.push('...'); |
|
|
for (let i = this.currentPage - 1; i <= this.currentPage + 1; i++) pages.push(i); |
|
|
pages.push('...'); |
|
|
pages.push(total); |
|
|
} |
|
|
} |
|
|
return pages; |
|
|
} |
|
|
|
|
|
goToPage(page: string | number) { |
|
|
if (typeof page === 'number') { |
|
|
this.setPage(page); |
|
|
} |
|
|
} |
|
|
|
|
|
pageSizeOptions: number[] = [5, 10, 20, 50]; |
|
|
onPageSizeChange(size: number) { |
|
|
this.pageSize = size; |
|
|
this.currentPage = 1; |
|
|
} |
|
|
get resultsStart(): number { |
|
|
return this.filteredCases.length === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1; |
|
|
} |
|
|
get resultsEnd(): number { |
|
|
return Math.min(this.currentPage * this.pageSize, this.filteredCases.length); |
|
|
} |
|
|
get resultsTotal(): number { |
|
|
return this.filteredCases.length; |
|
|
} |
|
|
|
|
|
goToDetect(caseId: string): void { |
|
|
this.router.navigate(['/py-detect'], { state: { caseId } }); |
|
|
} |
|
|
} |
|
|
|