Py-detect / src /app /recordpage /recordpage.component.ts
RajalashmiNagarajan
adminpage update
86b4aeb
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { CaseStoreService, PoliceCase } from '../shared/case-store.service';
import { INFOPAGE_SECTIONS } from '../shared/infopage-sections';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-recordpage',
templateUrl: './recordpage.component.html',
styleUrls: ['./recordpage.component.css']
})
export class RecordpageComponent implements OnInit, OnDestroy {
cases: PoliceCase[] = [];
private casesSub?: Subscription;
// Date field groups for formatting
dateTimeFields = new Set<string>(['Date & Time (Entry)', 'Occurred From', 'Occurred To', 'Time Reported', 'Time Discovered']);
dateFields = new Set<string>(['Follow-up Date', 'Next Hearing Date']);
// Pagination
currentPage: number = 1;
pageSize: number = 5;
pageSizeOptions: number[] = [5, 10, 20, 50];
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);
}
// Modal state
showDetails = false;
selectedCase: PoliceCase | null = null;
selectedIndex = -1;
// Search query
q = '';
// Sorting
sortKey: 'caseId' | 'crime' | 'dateTime' | 'status' | 'Investigation Officer' = 'dateTime';
sortDir: 'asc' | 'desc' = 'desc';
// Filter state
filterCrimeType: string = '';
filterStatus: string = '';
filterLocation: string = '';
filterOfficer: string = '';
crimeTypes: string[] = [];
statusTypes: string[] = [];
locations: string[] = [];
officers: string[] = [];
filteredCases: PoliceCase[] = [];
// Selection state
allSelected: boolean = false;
// For modal subgroup pills
selectedSubgroup: any = {
crime: 'Identification & Timing',
suspect: 'Identity',
notes: 'Investigation Notes'
};
// Reference infopage section/subgroup/fields structure
sections = INFOPAGE_SECTIONS;
constructor(private caseStore: CaseStoreService, private router: Router) { }
ngOnInit(): void {
if (typeof this.caseStore.getCases$ === 'function') {
this.casesSub = this.caseStore.getCases$().subscribe((cases: PoliceCase[]) => {
this.cases = cases || [];
this.cases.forEach((c: any) => { if (c.selected === undefined) c.selected = false; });
this.populateFilterOptions();
this.applyFilters();
this.applySort();
});
} else {
this.load();
this.populateFilterOptions();
this.applyFilters();
this.applySort();
}
}
ngOnDestroy(): void {
if (this.casesSub) this.casesSub.unsubscribe();
}
// Helper methods
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;
}
// Case statistics
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;
}
// Table rows getter
get rows(): PoliceCase[] {
return this.pagedCases;
}
// Pagination results
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;
}
// Navigation methods
navigateHome(): void {
this.router.navigate(['/']);
}
navigateBackToInfoPage(): void {
this.router.navigate(['/infopage']);
}
logout(): void {
// Implement proper logout logic
localStorage.clear();
sessionStorage.clear();
this.router.navigate(['/login']);
}
// Data loading and filtering
load(): void {
this.cases = this.caseStore.getPoliceCases();
this.cases.forEach((c: any) => { if (c.selected === undefined) c.selected = false; });
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() {
// Filter by dropdowns
let filtered = 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)
);
// Filter by search query
const s = (this.q || '').toLowerCase();
if (s) {
filtered = filtered.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)
);
}
this.filteredCases = filtered;
this.currentPage = 1;
this.applySort();
}
resetFilters() {
this.filterCrimeType = '';
this.filterStatus = '';
this.filterLocation = '';
this.filterOfficer = '';
this.q = '';
this.applyFilters();
}
// Sorting methods
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';
}
this.applySort();
}
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';
}
applySort() {
const key = this.sortKey;
const dir = this.sortDir;
this.filteredCases.sort((a, b) => {
let aVal: any, bVal: any;
switch (key) {
case 'caseId':
aVal = a.caseId || '';
bVal = b.caseId || '';
break;
case 'crime':
aVal = a.crime || '';
bVal = b.crime || '';
break;
case 'dateTime':
aVal = a.dateTime ? new Date(a.dateTime).getTime() : 0;
bVal = b.dateTime ? new Date(b.dateTime).getTime() : 0;
break;
case 'status':
aVal = a.status || '';
bVal = b.status || '';
break;
case 'Investigation Officer':
aVal = a.police?.name || '';
bVal = b.police?.name || '';
break;
default:
aVal = '';
bVal = '';
}
if (aVal < bVal) return dir === 'asc' ? -1 : 1;
if (aVal > bVal) return dir === 'asc' ? 1 : -1;
return 0;
});
}
// Pagination methods
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);
}
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);
}
}
onPageSizeChange(size: number) {
this.pageSize = size;
this.currentPage = 1;
}
// Modal methods
openDetails(c: PoliceCase, i: number): void {
this.selectedCase = c;
this.selectedIndex = i;
this.showDetails = true;
document.body.style.overflow = 'hidden';
}
closeDetails(): void {
this.showDetails = false;
this.selectedCase = null;
this.selectedIndex = -1;
document.body.style.overflow = 'auto';
}
// Case actions
editCase(c: PoliceCase, i: number): void {
const prefill: Record<string, any> = {};
try {
const fd = (c as any).formData;
if (fd && Array.isArray(fd)) {
(fd as Array<any>).forEach(kv => { if (kv && kv.key) prefill[kv.key] = kv.value; });
} else if (fd && typeof fd === 'object') {
Object.assign(prefill, fd as Record<string, any>);
}
// Include mapped top-level properties
if (c.caseId) prefill['Case ID'] = c.caseId;
if (c.crime) prefill['Crime Type'] = c.crime;
if (c.dateTime) prefill['Date & Time (Entry)'] = c.dateTime;
if (c.police?.address) prefill['Location'] = c.police.address;
if (c.police?.name) prefill['Investigating Officer'] = c.police.name;
if (c.accused?.name) prefill['Suspect Name'] = c.accused.name;
} catch (e) {
console.error('Error preparing edit data:', e);
}
this.router.navigate(['/infopage', c.caseId], {
state: {
from: 'record',
returnId: c.caseId,
prefillFormData: prefill,
case: c
}
});
}
deleteCase(index: number): void {
if (confirm('Are you sure you want to delete this case?')) {
this.caseStore.deletePoliceCaseAt(index);
this.load();
}
}
navigateToCaseDetails(c: PoliceCase): void {
if (!c || !c.caseId) return;
const origin = this.router.url.includes('/case-details') ? 'case-details' : 'record';
this.router.navigate(['/case-details-summary-page', c.caseId], {
queryParams: {
from: origin,
returnId: c.caseId
},
state: {
case: c,
from: origin,
returnId: c.caseId
}
});
}
// Field value getters for modal
objectKeys = Object.keys;
getFieldValue(sc: any, sectionKey: string, field: string): any {
const fieldMap: { [key: string]: string | string[] } = {
// Crime Details
'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 Details
'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'],
// Notes/Evidence
'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'],
// Audit Fields
'Created By': 'createdBy',
'Creation Date': 'creationDate',
'Last Updated': 'lastUpdated',
'Verified By': 'verifiedBy'
};
const path = fieldMap[field] || field;
let value: any = undefined;
if (Array.isArray(path)) {
let v = sc;
for (const p of path) {
if (v && v[p] !== undefined) v = v[p];
else { v = undefined; break; }
}
value = v;
} else {
value = sc && sc[path] !== undefined ? sc[path] : undefined;
}
// Try to find in formData
if (value === null || value === undefined || value === '') {
try {
const fd = this.getFormDataArray(sc);
const norm = (s: any) => {
if (s === null || s === undefined) return '';
let t = String(s).toLowerCase();
t = t.replace(/&/g, '');
t = t.replace(/and/g, '');
t = t.replace(/entry/g, '');
t = t.replace(/\s+/g, '');
return t.replace(/[^a-z0-9]/g, '');
};
if (fd && fd.length) {
let kv = fd.find(k => k && String(k.key).toLowerCase() === String(field).toLowerCase());
if (kv) value = kv.value;
if (value === null || value === undefined || value === '') {
const fieldNorm = norm(field);
const pathName = Array.isArray(path) ? path[path.length - 1] : String(path);
const pathNorm = norm(pathName);
kv = fd.find(k => k && (norm(k.key) === fieldNorm || norm(k.key) === pathNorm));
if (kv) value = kv.value;
}
}
if ((value === null || value === undefined || value === '') && sc && sc.formData && typeof sc.formData === 'object') {
if (sc.formData[field] !== undefined) value = sc.formData[field];
}
} catch (e) {
// ignore
}
}
// Normalize and format
if (value === null || value === undefined || value === '') return 'Not Assigned';
// Date formatting
if (this.dateTimeFields.has(field) || this.dateFields.has(field)) {
const d = new Date(value);
if (!isNaN(d.getTime())) {
if (this.dateFields.has(field)) return d.toISOString().slice(0, 10);
return d.toLocaleString();
}
}
if (typeof value === 'object') return this.formatFormValue(value);
return value;
}
getValue(obj: any, key: string): any {
const v = obj && obj[key] !== undefined ? obj[key] : undefined;
if (v === null || v === undefined || v === '') return 'Not Assigned';
if (typeof v === 'object') return this.formatFormValue(v);
if (this.dateTimeFields.has(key) || this.dateFields.has(key)) {
const d = new Date(v);
if (!isNaN(d.getTime())) {
if (this.dateFields.has(key)) return d.toISOString().slice(0, 10);
return d.toLocaleString();
}
}
return v;
}
// Form data helpers
isFormDataArray(fd: any): boolean {
return Array.isArray(fd) && fd.length > 0 && fd.every((item: any) => item && Object.prototype.hasOwnProperty.call(item, 'key'));
}
getFormDataArray(caseObj: any): Array<{ key: string; value: any }> {
if (!caseObj || !caseObj.formData) return [];
const fd = caseObj.formData;
if (this.isFormDataArray(fd)) return fd as Array<{ key: string; value: any }>;
if (typeof fd === 'object') return Object.keys(fd).map(k => ({ key: k, value: fd[k] }));
return [{ key: 'value', value: fd }];
}
formatFormValue(value: any): string {
if (value === null || value === undefined || value === '') return 'Not Assigned';
if (typeof value === 'object') {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}
return String(value);
}
// Other methods
toggleSelectAll(event: Event): void {
const checked = (event.target as HTMLInputElement).checked;
this.allSelected = checked;
this.rows.forEach((c: any) => c.selected = checked);
}
onModernSearch() {
return false;
}
goToDetect(caseId: string): void {
this.router.navigate(['/py-detect'], { state: { caseId } });
}
}