Replaces the in-memory mock (LOAN_APPLICATIONS array + seeded RNG, reset on every refresh) with real Fineract-backed persistence: a new LoanOriginationService for CRUD plus one API call per stage transition, a loan-fineract-bridge that creates a real loan account (create + approve + disburse) at disbursement for Existing Customers, and file uploads through Fineract's generic document store keyed by a stable checklist slot so "On file"/View controls survive a reload. Also fixes several gaps found while verifying full-cycle persistence: - CIC charge-account selection never saved (missing persist() call) - Co-borrower relationship/monthly income lived in local-only component state instead of on the application, so they reset on refresh - Field Investigation "View Full Details" showed local-session photos only; stored photos are now downloaded and shown as real thumbnails - "Assign Field Officer" was free text; now a dropdown of real CBS loan officers pulled from /staff - site photos and cheque/signature images now open in the same zoomable, pannable lightgallery viewer used elsewhere in the app Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
82 lines
2.9 KiB
HTML
82 lines
2.9 KiB
HTML
<!--
|
|
Copyright since 2025 Mifos Initiative
|
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
-->
|
|
|
|
<h2 mat-dialog-title>{{ data.label }} — Visit Details</h2>
|
|
<mat-dialog-content class="fvd-content">
|
|
<span [class]="statusBadgeClass">{{ data.check.status }}</span>
|
|
|
|
<dl class="fvd-kv-grid">
|
|
<div class="fvd-kv-field">
|
|
<dt>Officer Assigned</dt>
|
|
<dd>{{ data.check.officerAssigned }}</dd>
|
|
</div>
|
|
<div class="fvd-kv-field">
|
|
<dt>Assigned On</dt>
|
|
<dd>{{ data.check.assignedDate }}</dd>
|
|
</div>
|
|
<div class="fvd-kv-field">
|
|
<dt>GPS Coordinates</dt>
|
|
<dd>{{ data.check.latitude || '—' }}, {{ data.check.longitude || '—' }}</dd>
|
|
</div>
|
|
<div class="fvd-kv-field">
|
|
<dt>{{ data.check.status === 'FAILED' ? 'Rejected By' : 'Verified By' }}</dt>
|
|
<dd>{{ data.check.verifiedBy }}</dd>
|
|
</div>
|
|
<div class="fvd-kv-field">
|
|
<dt>{{ data.check.status === 'FAILED' ? 'Rejected On' : 'Verified On' }}</dt>
|
|
<dd>{{ data.check.verifiedDate }}</dd>
|
|
</div>
|
|
<div class="fvd-kv-field">
|
|
<dt>Remarks</dt>
|
|
<dd>{{ data.check.remarks }}</dd>
|
|
</div>
|
|
</dl>
|
|
|
|
<p class="fvd-photos-title">Site Photos ({{ data.check.photoCount }})</p>
|
|
@if (data.photos.length > 0 || data.storedPhotos.length > 0) {
|
|
<div class="fvd-photo-grid">
|
|
@for (photo of data.photos; track $index) {
|
|
<button
|
|
type="button"
|
|
class="fvd-photo"
|
|
[class.fvd-photo--zoomable]="!!photo.previewUrl"
|
|
title="{{ photo.previewUrl ? 'Click to zoom' : photo.file.name }}"
|
|
(click)="openPhotoZoom(photo.previewUrl)"
|
|
>
|
|
@if (photo.previewUrl) {
|
|
<img [src]="photo.previewUrl" alt="Site photo {{ $index + 1 }}" />
|
|
} @else {
|
|
<span class="fvd-photo-label">{{ photo.file.name }}</span>
|
|
}
|
|
</button>
|
|
}
|
|
@for (stored of data.storedPhotos; track stored.id) {
|
|
<button
|
|
type="button"
|
|
class="fvd-photo fvd-photo--stored"
|
|
[class.fvd-photo--zoomable]="!!stored.previewUrl"
|
|
title="{{ stored.previewUrl ? 'Click to zoom' : stored.fileName }}"
|
|
(click)="stored.previewUrl ? openPhotoZoom(stored.previewUrl) : data.onViewStored(stored)"
|
|
>
|
|
@if (stored.previewUrl) {
|
|
<img [src]="stored.previewUrl" alt="{{ stored.fileName }}" />
|
|
} @else {
|
|
<span class="fvd-photo-label">View file</span>
|
|
}
|
|
</button>
|
|
}
|
|
</div>
|
|
} @else {
|
|
<p class="fvd-no-photos">No photos on file for this visit.</p>
|
|
}
|
|
<div #lightboxRoot class="fvd-lightbox-root"></div>
|
|
</mat-dialog-content>
|
|
<mat-dialog-actions align="end">
|
|
<button type="button" mat-button mat-dialog-close>Close</button>
|
|
</mat-dialog-actions>
|