import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; export interface PoliceCase { // Optional metadata used by your UI caseId?: string; dateTime?: string; status?: 'Open' | 'Under Investigation' | 'Closed' | 'Archived'; crime?: string; police?: { name?: string; station?: string; address?: string; pincode?: string; dutyPerson?: string; modeOfCrime?: string; information?: string; }; accused?: { name?: string; age?: string | number; gender?: string; address?: string; occupation?: string; }; lastUpdated?: string; nextAction?: string; casePriority?: 'High' | 'Medium' | 'Low'; reportedBy?: string; verifiedBy?: string; briefDescription?: string; caseCategory?: string; // Store raw form data from infopage so all entered fields can be displayed // Use array of key/value pairs for safe rendering formData?: Array<{ key: string; value: any }> | Record; selected?: boolean; // <-- Add this line for table selection } @Injectable({ providedIn: 'root' }) export class CaseStoreService { private readonly storageKey = 'py_detect_police_cases'; private cases: PoliceCase[] = []; // Observable subject to broadcast changes private casesSubject = new BehaviorSubject([]); /** Observable for components to subscribe to case list updates */ getCases$(): Observable { return this.casesSubject.asObservable(); } constructor() { this.load(); } /** Create (newest first) */ private getNextCaseId(): string { const max = this.cases .map(c => c.caseId) .map(id => parseInt((id || '').replace('CASE-', ''),10)) .filter(n => !isNaN(n)) .reduce((a, b) => Math.max(a, b),0); return `CASE-${(max +1).toString().padStart(3, '0')}`; } addPoliceCase(c: PoliceCase): void { if (!c.caseId) { c.caseId = this.getNextCaseId(); } c.lastUpdated = new Date().toISOString(); c.verifiedBy = c.verifiedBy || ''; this.cases.unshift(c); this.save(); this.debugLogCasesOperation('addPoliceCase', c); this.casesSubject.next(this.cases.slice()); } /** Read */ getPoliceCases(): PoliceCase[] { return this.cases; } /** Update by array index */ updatePoliceCaseAt(index: number, updated: PoliceCase): void { if (index >=0 && index < this.cases.length) { updated.lastUpdated = new Date().toISOString(); this.cases[index] = updated; this.save(); this.casesSubject.next(this.cases.slice()); } } /** Delete by array index */ deletePoliceCaseAt(index: number): void { if (index >=0 && index < this.cases.length) { this.cases.splice(index,1); this.save(); this.casesSubject.next(this.cases.slice()); } } /** Update by caseId */ updatePoliceCaseById(caseId: string, updated: PoliceCase): void { const idx = this.cases.findIndex(c => c.caseId === caseId); if (idx !== -1) { updated.lastUpdated = new Date().toISOString(); this.cases[idx] = updated; this.save(); this.casesSubject.next(this.cases.slice()); } } /** * Convenience: map Info page reactive-form value to PoliceCase and store it. * Call with the whole this.form.value from InfopageComponent. */ addFromInfoForm(formValue: any): void { const crime = (formValue && formValue.crime) || {}; const suspect = (formValue && formValue.suspect) || {}; const notes = (formValue && formValue.notes) || {}; // Merge raw inputs so we capture everything entered on the Info page const mergedRaw = { ...(formValue && formValue.formData ? formValue.formData : (formValue || {})), ...crime, ...suspect, ...notes }; const mapped: PoliceCase = { caseId: crime.caseId || '', dateTime: crime.dateTime || '', status: notes.status || 'Open', crime: crime.crimeType || 'Unknown', police: { name: notes.officerInCharge || '—', station: '—', // not captured on Info page address: crime.location || '—', pincode: '', // not captured on Info page dutyPerson: notes.officerInCharge || '—', modeOfCrime: crime.crimeType || '—', information: notes.initialFindings || '' }, accused: { name: suspect.fullName || '—', age: suspect.age || '—', gender: suspect.gender || '—', address: suspect.address || '—', occupation: suspect.alias || '' }, // store raw form data so UI can display all entered fields formData: this.convertToKeyValue(mergedRaw || {}) }; this.addPoliceCase(mapped); } /** Add or update from Info page form */ addOrUpdateFromInfoForm(formValue: any): void { const crime = (formValue && formValue.crime) || {}; const suspect = (formValue && formValue.suspect) || {}; const notes = (formValue && formValue.notes) || {}; const mapped: PoliceCase = { caseId: crime.caseId || (formValue && formValue['Case ID']) || '', dateTime: crime.dateTime || '' , // mark when infopage submission occurred lastUpdated: new Date().toISOString(), status: notes.status || 'Open', crime: crime.crimeType || 'Unknown', police: { name: notes.officerInCharge || '—', station: '—', address: crime.location || '—', pincode: '', dutyPerson: notes.officerInCharge || '—', modeOfCrime: crime.crimeType || '—', information: notes.initialFindings || '' }, accused: { name: suspect.fullName || '—', age: suspect.age || '—', gender: suspect.gender || '—', address: suspect.address || '—', occupation: suspect.alias || '' }, reportedBy: crime.reportedBy || '', verifiedBy: notes.verifiedBy || '', briefDescription: crime.briefDescription || '', // Save full raw form data. Merge wrapper fields so everything entered is preserved. formData: this.convertToKeyValue( { ...(formValue && formValue.formData ? formValue.formData : (formValue || {})), ...crime, ...suspect, ...notes, ...(formValue && formValue.legal ? formValue.legal : {}) } ) }; const idx = this.cases.findIndex(c => c.caseId === mapped.caseId); if (idx !== -1) { // Merge existing object to preserve other metadata where possible this.cases[idx] = { ...this.cases[idx], ...mapped }; // Ensure lastUpdated reflects this infopage update this.cases[idx].lastUpdated = mapped.lastUpdated || new Date().toISOString(); this.save(); this.debugLogCasesOperation('updatePoliceCase', mapped); this.casesSubject.next(this.cases.slice()); } else { this.addPoliceCase(mapped); } } /** * Convert an object (or already key/value array) into an array of {key, value} */ private convertToKeyValue(data: any): Array<{ key: string; value: any }> { if (!data) return []; if (Array.isArray(data)) { // assume already in the desired shape return data.map(item => { if (item && typeof item === 'object' && 'key' in item) return item; return { key: String(item), value: item }; }); } const result: Array<{ key: string; value: any }> = []; const isPlainObject = (v: any) => v && typeof v === 'object' && !(v instanceof Date) && !(v instanceof File) && !Array.isArray(v); const recurse = (obj: any, prefix = '') => { if (obj === null || obj === undefined) return; if (typeof obj !== 'object' || obj instanceof Date || obj instanceof File) { result.push({ key: prefix || 'value', value: obj }); return; } if (Array.isArray(obj)) { // store arrays as JSON string for readability result.push({ key: prefix || 'value', value: JSON.stringify(obj) }); return; } for (const k of Object.keys(obj)) { const v = obj[k]; const newKey = prefix ? `${prefix}.${k}` : k; if (isPlainObject(v)) { recurse(v, newKey); } else if (Array.isArray(v)) { // arrays -> stringify result.push({ key: newKey, value: JSON.stringify(v) }); } else { result.push({ key: newKey, value: v }); } } }; if (typeof data === 'object') { recurse(data, ''); return result; } // primitive return [{ key: 'value', value: data }]; } // Simple debug helper to log saved cases (can be removed later) private debugLogCasesOperation(label: string, mapped?: PoliceCase) { try { console.log(`[CaseStore] ${label}`, mapped || null); console.log('[CaseStore] current cases:', JSON.parse(localStorage.getItem(this.storageKey) || '[]')); } catch { } } /** Persist to localStorage (safe to keep; remove if not needed) */ private save(): void { try { localStorage.setItem(this.storageKey, JSON.stringify(this.cases)); } catch { } } /** Load from localStorage */ private load(): void { try { const raw = localStorage.getItem(this.storageKey); this.cases = raw ? (JSON.parse(raw) as PoliceCase[]) : []; } catch { this.cases = []; } // Seed a default case if none exist (for development/testing) if (this.cases.length ===0) { this.cases = [ { caseId: 'CASE-001', dateTime: new Date().toISOString(), status: 'Open', crime: 'Theft', police: { name: 'Officer John Doe', station: 'Central Station', address: '123 Main St, City', pincode: '123456', dutyPerson: 'Officer John Doe', modeOfCrime: 'Burglary', information: 'Initial investigation started.' }, accused: { name: 'Jane Smith', age:30, gender: 'Female', address: '456 Side Rd, City', occupation: 'Unemployed' }, formData: [] } ]; this.save(); } // Emit current cases to subscribers this.casesSubject.next(this.cases.slice()); } }