|
|
|
|
|
<div class="site-header"> |
|
|
<div class="header-inner"> |
|
|
<div class="logo-cluster"> |
|
|
<span [routerLink]="'/'" 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 class="header-progress"> |
|
|
<span class="pykara-analysis-label">Progress:</span> |
|
|
<div class="pykara-progress-bar"> |
|
|
<div class="pykara-progress-bar-inner" [style.width.%]="progressPercentage"></div> |
|
|
</div> |
|
|
<span class="pykara-progress-percentage">{{ progressPercentage }}%</span> |
|
|
</div> |
|
|
<div class="header-actions autosave-right"> |
|
|
<div class="autosave-indicator" [class.saving]="isAutoSaving"> |
|
|
<i class="fas fa-save"></i> |
|
|
<span>{{ autoSaveStatus }}</span> |
|
|
</div> |
|
|
|
|
|
<div #profileDisplay class="profile-display" [class.open]="showProfileMenu" style="margin-left:12px; display: flex; align-items: center; cursor: pointer; color: #fff; position: relative; margin-bottom:15px;" (click)="toggleProfileMenu($event)"> |
|
|
<div class="profile-avatar" style="width: 2.2em; height: 2.2em; border-radius: 50%; background: linear-gradient(135deg, #1E3A8A, #2563eb); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.95em; overflow: hidden; margin-top: 17px;"> |
|
|
<img *ngIf="currentUser?.avatarUrl" [src]="currentUser?.avatarUrl" alt="avatar" style="width:100%;height:100%;object-fit:cover;" /> |
|
|
<span *ngIf="!currentUser?.avatarUrl">{{ getUserInitials() }}</span> |
|
|
</div> |
|
|
|
|
|
<div *ngIf="showProfileMenu" class="profile-menu" [ngStyle]="profileMenuStyle" style="background:#222;color:#fff;border-radius:8px;padding:8px 10px;min-width:220px;box-shadow:0 6px 18px rgba(0,0,0,0.25);"> |
|
|
<div style="display:flex;align-items:center;gap:10px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:8px;"> |
|
|
<div style="width:42px;height:42px;border-radius:50%;background:#2b6ea6;display:flex;align-items:center;justify-content:center;font-weight:700;color:#fff;overflow:hidden;"> |
|
|
<img *ngIf="currentUser?.avatarUrl" [src]="currentUser?.avatarUrl" alt="avatar" style="width:100%;height:100%;object-fit:cover;" /> |
|
|
<span *ngIf="!currentUser?.avatarUrl">{{ getUserInitials() }}</span> |
|
|
</div> |
|
|
<div style="font-size:0.95em;"> |
|
|
<div>{{ currentUser?.name }}</div> |
|
|
<div style="font-size:0.8em;opacity:0.9;">{{ currentUser?.email }}</div> |
|
|
</div> |
|
|
</div> |
|
|
<button (click)="logout()" style="width:100%;display:flex;align-items:center;gap:8px;padding:8px 10px;border:none;border-radius:6px;background:transparent;color:#fff;cursor:pointer;"> |
|
|
<i class="fas fa-sign-out-alt" style="width:18px;text-align:center;"></i> |
|
|
Logout |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="view-records-btn" style="margin-left: 16px; vertical-align: middle; color: #fff; font-size: 1.5em; width: 2em; height: 2em; display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; background: linear-gradient(135deg, #1E3A8A, #2563eb);" |
|
|
type="button" |
|
|
(click)="goToRecords()" |
|
|
(mouseenter)="showViewRecordsTooltip = true" |
|
|
(mouseleave)="showViewRecordsTooltip = false"> |
|
|
<i class="fas fa-folder-open"></i> |
|
|
<span *ngIf="showViewRecordsTooltip" style="position:absolute;top:100%;left:0%;transform:translateX(-50%);background:#222;color:#fff;padding:4px 12px;border-radius:6px;font-size:0.6em;white-space:nowrap;z-index:10;box-shadow:0 2px 8px rgba(0,0,0,0.12);">View Records</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="section-navigation" [@fadeIn]> |
|
|
<div class="ai-neural-bg"></div> |
|
|
<div class="section-pills"> |
|
|
<button class="section-pill main-section-pill" |
|
|
*ngFor="let section of sectionKeys; let i = index" |
|
|
[class.active]="currentSection === section" |
|
|
[class.completed]="isSectionCompleted(section)" |
|
|
(click)="showSection(section)" |
|
|
[attr.tabindex]="0"> |
|
|
<i [class]="sectionIcons[section]"></i> |
|
|
<span>{{ sections[section].title }}</span> |
|
|
<i class="fas fa-chevron-right nav-chevron" |
|
|
[class.rotated]="currentSection === section"></i> |
|
|
<div class="ai-completion-orb" *ngIf="isSectionCompleted(section)"> |
|
|
<div class="orb-pulse"></div> |
|
|
<i class="fas fa-check"></i> |
|
|
</div> |
|
|
</button> |
|
|
|
|
|
</div> |
|
|
<div class="section-ai-grid"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="subgroup-pills" |
|
|
[class.crime-section]="currentSection === 'crime'" |
|
|
[class.suspect-section]="currentSection === 'suspect'" |
|
|
[class.notes-section]="currentSection === 'notes'" |
|
|
[@fadeIn]> |
|
|
<div class="neural-mesh"></div> |
|
|
<div class="pill ai-subgroup-pill" |
|
|
*ngFor="let key of getSubgroups(); let i = index" |
|
|
[class.active]="key === currentSubgroup" |
|
|
[class.completed]="isSubgroupCompleted(key)" |
|
|
(click)="setSubgroup(key)" |
|
|
[attr.tabindex]="0"> |
|
|
<div class="pill-ai-core"></div> |
|
|
<i [class]="subgroupIcons[key] || 'fas fa-circle'"></i> |
|
|
<span>{{ key }}</span> |
|
|
<div class="ai-completion-badge" *ngIf="isSubgroupCompleted(key)"> |
|
|
<div class="badge-glow"></div> |
|
|
<i class="fas fa-check-circle"></i> |
|
|
</div> |
|
|
<div class="pill-ai-scanner"></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="investigation-container"> |
|
|
|
|
|
|
|
|
<div class="form-card primary-card" [class.is-physical-description]="isPhysicalDescriptionPage()" [@cardSlide] #formCard1> |
|
|
<div class="card-header compact-card-header"> |
|
|
<div class="card-header-main"> |
|
|
<h2 class="compact-title"> |
|
|
<i [class]="subgroupIcons[currentSubgroup] || 'fas fa-circle'"></i> |
|
|
{{ currentSubgroup }} |
|
|
<span class="field-counter compact-field-counter">{{ getSelectedFieldCount() }} of {{ getTotalAvailableFieldsCount() }}</span> |
|
|
</h2> |
|
|
</div> |
|
|
<div class="modern-field-selector-btn compact-selector-btn" style="position:relative;"> |
|
|
<button class="field-selector-btn modern-selector-animated compact-selector-btn-inner" |
|
|
[class.active]="showFieldSelector === (currentSection + '-' + currentSubgroup)" |
|
|
(click)="toggleFieldSelector($event)" |
|
|
type="button" |
|
|
title="Select fields to display"> |
|
|
<i class="fas fa-list-check"></i> |
|
|
<span class="field-selector-counter">{{ getSelectedFieldCount() }}/{{ getDynamicMaxSelectable() }}</span> |
|
|
</button> |
|
|
|
|
|
<div class="modern-field-selector-popup" |
|
|
*ngIf="showFieldSelector === (currentSection + '-' + currentSubgroup)" |
|
|
(click)="$event.stopPropagation()"> |
|
|
<div class="popup-header"> |
|
|
<span><i class="fas fa-list-check"></i> Select Fields to Display</span> |
|
|
<button class="popup-close-btn" (click)="closeFieldSelector()" type="button"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div style="font-size:0.85rem;color:#9fc9de;margin:8px 12px 0 12px;">{{ getSelectedFieldCount() }} of {{ getTotalAvailableFieldsCount() }} fields selected</div> |
|
|
<div class="popup-fields-list"> |
|
|
<div *ngFor="let field of getAvailableFields(); trackBy: trackByField" class="popup-field-row"> |
|
|
<label class="popup-field-label"> |
|
|
<input type="checkbox" |
|
|
[checked]="isFieldSelected(field)" |
|
|
(change)="toggleFieldSelection(field, $event)" /> |
|
|
<span class="popup-field-text">{{ field }}</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
<div class="popup-actions"> |
|
|
<button class="popup-action-btn clear-btn" (click)="resetFieldSelection($event)" type="button" |
|
|
style="min-width:80px;padding:8px 10px;border:2px solid #e74c3c;background:transparent;color:#e74c3c;font-weight:600;border-radius:10px;display:inline-flex;align-items:center;gap:6px;"> |
|
|
<i class="fas fa-times-circle" style="color:#e74c3c;"></i> Clear All |
|
|
</button> |
|
|
<button class="popup-action-btn selectall-btn" (click)="selectAllFields($event)" type="button" |
|
|
style="min-width:96px;padding:8px 12px;border:2px solid #5fa8d4;background:transparent;color:#2b6ea6.font-weight:600;border-radius:10px;display:inline-flex;align-items:center;gap:6px;"> |
|
|
<i class="fas fa-check-double" style="color:#2b6ea6;"></i> Select All |
|
|
</button> |
|
|
<button class="popup-action-btn save-btn" (click)="closeFieldSelector()" type="button" |
|
|
style="min-width:110px;padding:8px 14px;border:none;border-radius:12px;background:linear-gradient(90deg,#38bdf80%, #23272b100%);color:#fff;font-weight:700;display:inline-flex;align-items:center;gap:8px;box-shadow:0 2px 12px rgba(56,189,248,0.55);"> |
|
|
<i class="fas fa-save" style="color:#fff;"></i> Save |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="card-content" [class.minimized]="isCardMinimized.primary"> |
|
|
<div class="section-description" [@fadeIn]> |
|
|
<strong>{{ sections[currentSection].title }} - {{ currentSubgroup }}:</strong> {{ getSectionDescription(currentSection) }} |
|
|
</div> |
|
|
|
|
|
<div class="fields-grid" [class.evidence-scene-grid]="currentSubgroup === 'Evidence & Scene'"> |
|
|
<div *ngFor="let field of getPrimaryFields(); let i = index; trackBy: trackByField" |
|
|
class="field-container" [class.photo-field]="field === 'Photo Upload' || field === 'Digital Evidence'" |
|
|
[class.filled]="formData[field] && formData[field].toString().trim() !== ''" |
|
|
[attr.data-field]="field" |
|
|
[@fieldAnimation]> |
|
|
|
|
|
<label class="field-label"> |
|
|
{{ field }} |
|
|
<span class="required-indicator" *ngIf="isFieldRequired(field)">*</span> |
|
|
<button type="button" |
|
|
class="info-btn glossy-info-btn" |
|
|
(click)="toggleFieldInfo(field, $event)" |
|
|
[attr.aria-label]="'Info for ' + field"> |
|
|
<i class="fas fa-info"></i> |
|
|
</button> |
|
|
|
|
|
|
|
|
<span *ngIf="!fileFields.has(field)" style="display:inline-flex;align-items:center;position:relative;" |
|
|
(mouseenter)="showMicPopupField = field" |
|
|
(mouseleave)="showMicPopupField = null"> |
|
|
<button type="button" |
|
|
(click)="toggleRecording(field)" |
|
|
style="background:none;border:none;outline:none;cursor:pointer;padding:0;margin-left:6px;"> |
|
|
<i class="fas fa-microphone" |
|
|
[ngStyle]="{ |
|
|
color: recordingField === field ? '#e74c3c' : '#3498db', |
|
|
animation: recordingField === field ? 'micPulse1s infinite' : 'none', |
|
|
fontSize: '1.2em' |
|
|
}"></i> |
|
|
</button> |
|
|
<div *ngIf="showMicPopupField === field" |
|
|
(mouseenter)="showMicPopupField = field" |
|
|
(mouseleave)="showMicPopupField = null" |
|
|
style="position:absolute;bottom:120%;left:0;z-index:5000;background:#fff;border:1px solid #ccc;padding:8px 12px;border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,0.12);white-space:nowrap;min-width:180px;pointer-events:auto;"> |
|
|
Click to record your input for "{{ field }}".<br>Click again to stop.<br>Recording is {{ recordingField === field ? 'ON' : 'OFF' }}. |
|
|
</div> |
|
|
</span> |
|
|
</label> |
|
|
|
|
|
<div class="input-container"> |
|
|
|
|
|
|
|
|
<ng-container *ngIf="fileFields.has(field)"> |
|
|
<div class="file-upload-block"> |
|
|
<div class="file-drop-zone" title="Click to upload files or drag and drop here" (dragover)="onDragOver($event)" (dragleave)="onDragLeave($event)" (drop)="onFileDrop(field, $event)" [class.drag-over]="isDragOver"> |
|
|
<input #fileInput type="file" (change)="onFileChange(field, $event)" multiple [accept]="getAcceptedFileTypes(field)" class="file-input" style="display:none;" /> |
|
|
<div class="drop-zone-content" (click)="fileInput.click()" style="cursor:pointer;"> |
|
|
<i [class]="getUploadZoneIcon(field)"></i> |
|
|
<p style="margin:0;">Drop files here or click to browse</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="file-accepts" style="font-size:0.85rem;color:#9fb6c8;margin-top:6px;"> |
|
|
Accepts: {{ getAcceptedDisplay(field) }} |
|
|
</div> |
|
|
<div class="file-list" *ngIf="uploadedFiles[field]?.length"> |
|
|
<div *ngFor="let f of uploadedFiles[field]" class="file-chip"> |
|
|
<i [class]="getFileIcon(f.name)"></i> |
|
|
<span [title]="f.name">{{ f.name | slice:0:15 }}{{ f.name.length >15 ? '...' : '' }}</span> |
|
|
<button class="remove-file" (click)="removeFile(field, f)"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div *ngIf="uploadConfirmations[field]" class="upload-confirmation" style="color:#2b7a2b;margin-top:6px;font-size:0.95rem;"> |
|
|
{{ uploadConfirmations[field] }} |
|
|
</div> |
|
|
</div> |
|
|
</ng-container> |
|
|
|
|
|
<ng-container *ngIf="!fileFields.has(field)"> |
|
|
|
|
|
<ng-container *ngIf="dateTimeFields.has(field)"> |
|
|
<input type="datetime-local" |
|
|
class="field-input" |
|
|
[class.compact]="isCompactField(field)" |
|
|
[(ngModel)]="formData[field]" |
|
|
(input)="onFieldChange(field)" /> |
|
|
</ng-container> |
|
|
|
|
|
|
|
|
<ng-container *ngIf="!dateTimeFields.has(field) && dateFields.has(field)"> |
|
|
<input type="date" |
|
|
class="field-input" |
|
|
[class.compact]="isCompactField(field)" |
|
|
[(ngModel)]="formData[field]" |
|
|
(input)="onFieldChange(field)" /> |
|
|
</ng-container> |
|
|
|
|
|
|
|
|
<ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && (field === 'Country' || field === 'State' || field === 'District')"> |
|
|
<select class="field-input" |
|
|
[class.compact]="isCompactField(field)" |
|
|
[(ngModel)]="formData[field]" |
|
|
(change)="onSelectChange(field, $event)" |
|
|
[class.empty-select]="!formData[field]"> |
|
|
<option [value]="''" [selected]="!formData[field]">-- Select {{ field }} --</option> |
|
|
<option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option> |
|
|
</select> |
|
|
</ng-container> |
|
|
|
|
|
|
|
|
<ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && !(field === 'Country' || field === 'State' || field === 'District') && getOptions(field)?.length"> |
|
|
<select class="field-input" |
|
|
[class.compact]="isCompactField(field)" |
|
|
[(ngModel)]="formData[field]" |
|
|
(change)="onSelectChange(field, $event)" |
|
|
[class.empty-select]="!formData[field]"> |
|
|
<option [value]="''" [selected]="!formData[field]">-- Select {{ field }} --</option> |
|
|
<option *ngFor="let opt of getOptions(field)" [value]="opt">{{ opt }}</option> |
|
|
</select> |
|
|
</ng-container> |
|
|
|
|
|
|
|
|
<ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && !(field === 'Country' || field === 'State' || field === 'District') && !getOptions(field)?.length && (field.toLowerCase().includes('description') || field === 'Remark')"> |
|
|
<textarea class="field-input auto-scroll-textarea" |
|
|
[class.compact]="isCompactField(field)" |
|
|
[(ngModel)]="formData[field]" |
|
|
(input)="onFieldChange(field)" |
|
|
[placeholder]="getFieldPlaceholder(field)" |
|
|
[attr.maxlength]="field === 'Brief Description' ? null : getMaxLength(field)" |
|
|
rows="3"></textarea> |
|
|
|
|
|
<div *ngIf="recordingField === field" style="margin-top:4px;color:#e74c3c;font-size:0.95em;"> |
|
|
Recording... Speak now. |
|
|
</div> |
|
|
</ng-container> |
|
|
|
|
|
|
|
|
<ng-container *ngIf="!dateTimeFields.has(field) && !dateFields.has(field) && !(field === 'Country' || field === 'State' || field === 'District') && !getOptions(field)?.length && !(field.toLowerCase().includes('description') || field === 'Remark')"> |
|
|
<input [type]="getInputType(field)" |
|
|
class="field-input" |
|
|
[class.compact]="isCompactField(field)" |
|
|
[(ngModel)]="formData[field]" |
|
|
(input)="onFieldChange(field)" |
|
|
[placeholder]="getFieldPlaceholder(field)" |
|
|
[maxlength]="getMaxLength(field)" /> |
|
|
</ng-container> |
|
|
</ng-container> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="field-help" *ngIf="showHelpFor === field" [@helpAnimation]> |
|
|
<strong>Field Information:</strong><br> |
|
|
{{ fieldDescriptions[field] || 'Additional details for this field are being prepared.' }} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="modern-floating-nav-btn"> |
|
|
<div class="modern-nav-row"> |
|
|
|
|
|
<button *ngIf="canPrevSubgroup()" type="button" class="modern-round-btn" (click)="previousSubgroup()" [disabled]="!canPrevSubgroup()" title="Previous"> |
|
|
<i class="fas fa-arrow-left"></i> |
|
|
</button> |
|
|
|
|
|
|
|
|
<button *ngIf="isLastSubgroup() && currentSection === 'notes' && currentSubgroup === 'Remark'" type="button" class="modern-round-btn submit-btn-animated" (click)="submitCurrentSection()" title="Submit & View Records"> |
|
|
<i class="fas fa-paper-plane"></i> |
|
|
</button> |
|
|
|
|
|
|
|
|
<button *ngIf="!(isLastSubgroup() && currentSection === 'notes' && currentSubgroup === 'Remark')" type="button" class="modern-round-btn next-btn-animated" (click)="nextSubgroup()" [disabled]="!canNextSubgroup()" title="Next"> |
|
|
<i class="fas fa-arrow-right"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="showSubmitPopup" class="submit-popup-backdrop"> |
|
|
<div class="submit-popup-modal"> |
|
|
<div class="submit-popup-content" (click)="onSubmitPopupClose()" style="cursor:pointer;"> |
|
|
<i class="fas fa-check-circle submit-popup-icon"></i> |
|
|
<h3>Submission Successful!</h3> |
|
|
<p>Your information has been submitted.</p> |
|
|
<button class="submit-popup-btn">OK</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="showCaseIdExistsPopup" class="submit-popup-backdrop" (click)="closeCaseIdPopup()"> |
|
|
<div class="submit-popup-modal" (click)="$event.stopPropagation()"> |
|
|
<div class="submit-popup-content"> |
|
|
<i class="fas fa-exclamation-triangle submit-popup-icon" style="color:#ef4444;"></i> |
|
|
<h3>Duplicate Case ID</h3> |
|
|
<p>{{ caseIdExistsMessage }}</p> |
|
|
<button class="submit-popup-btn" (click)="closeCaseIdPopup()">OK</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="showNoDataPopup" class="submit-popup-backdrop" (click)="closeNoDataPopup()"> |
|
|
<div class="submit-popup-modal" (click)="$event.stopPropagation()"> |
|
|
<div class="submit-popup-content"> |
|
|
<i class="fas fa-exclamation-circle submit-popup-icon" style="color:#f59e0b;"></i> |
|
|
<h3>No Data Entered</h3> |
|
|
<p>Please enter at least one field or upload a file before submitting. No case will be created.</p> |
|
|
<button class="submit-popup-btn" (click)="closeNoDataPopup()">OK</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div *ngIf="isSubmitting" class="submit-loader-backdrop"> |
|
|
<div class="submit-loader-modal"> |
|
|
<div class="submit-loader-content"> |
|
|
<div class="spinner-container"> |
|
|
<div class="spinner"></div> |
|
|
<div class="spinner-ring"></div> |
|
|
</div> |
|
|
<h3>Submitting...</h3> |
|
|
<p>Please wait while we save your case information.</p> |
|
|
<div class="loader-progress"> |
|
|
<div class="loader-progress-bar" [style.width.%]="submitProgress"></div> |
|
|
</div> |
|
|
<p class="loader-subtext">Processing {{ submitProgress }}% complete</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<footer> |
|
|
<p>©2025 Pykara Technologies Pvt. Ltd. All rights reserved.</p> |
|
|
</footer> |
|
|
|
|
|
|