Fixed API calls on governance page
Enforced multitenancy
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
getAllEvidence,
|
||||
getProjectEvidence,
|
||||
getEvidenceSecureLink,
|
||||
updateEvidence,
|
||||
uploadEvidenceFile,
|
||||
@@ -82,8 +83,11 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
const queryClient = useQueryClient();
|
||||
const { success, error: toastError } = useToast();
|
||||
|
||||
// Filter States
|
||||
// Filter States: default to initialProjectId if given, otherwise when projects load default to first project
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | number | 'all'>(initialProjectId);
|
||||
const [userHasSelectedProject, setUserHasSelectedProject] = useState(
|
||||
initialProjectId !== undefined && initialProjectId !== null && initialProjectId !== 'all'
|
||||
);
|
||||
const [selectedMilestoneId, setSelectedMilestoneId] = useState<string | number | 'all'>(initialMilestoneId);
|
||||
const [selectedActivityId, setSelectedActivityId] = useState<string | number | 'all'>(initialActivityId);
|
||||
const [selectedUserId, setSelectedUserId] = useState<string | 'all'>(initialUserId);
|
||||
@@ -116,6 +120,21 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
queryFn: () => getUsers(1, 100).catch(() => []),
|
||||
});
|
||||
|
||||
// Keep selectedProjectId in sync when initialProjectId prop changes
|
||||
React.useEffect(() => {
|
||||
if (initialProjectId && initialProjectId !== 'all') {
|
||||
setSelectedProjectId(initialProjectId);
|
||||
setUserHasSelectedProject(true);
|
||||
}
|
||||
}, [initialProjectId]);
|
||||
|
||||
// Load evidence by default for the first project in the list
|
||||
React.useEffect(() => {
|
||||
if (!userHasSelectedProject && projects.length > 0 && selectedProjectId === 'all') {
|
||||
setSelectedProjectId(projects[0].id);
|
||||
}
|
||||
}, [projects, userHasSelectedProject, selectedProjectId]);
|
||||
|
||||
// Query activities based on selected project or fetch all across available projects
|
||||
const { data: activities = [] } = useQuery<Activity[]>({
|
||||
queryKey: ['activities-review-queue', selectedProjectId],
|
||||
@@ -134,104 +153,20 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
enabled: projects.length > 0,
|
||||
});
|
||||
|
||||
// Query evidence: if a project is selected (e.g. the first project by default), fetch via /v1/api/evidence/{projectId}
|
||||
const { data: rawEvidenceList = [], isLoading: loadingEvidence } = useQuery<Evidence[]>({
|
||||
queryKey: ['evidence'],
|
||||
queryFn: () => getAllEvidence().catch(() => []),
|
||||
queryKey: ['evidence', selectedProjectId],
|
||||
queryFn: async () => {
|
||||
if (selectedProjectId && selectedProjectId !== 'all') {
|
||||
return getProjectEvidence(selectedProjectId).catch(() => []);
|
||||
}
|
||||
return getAllEvidence().catch(() => []);
|
||||
},
|
||||
enabled: projects.length > 0 || selectedProjectId !== 'all',
|
||||
});
|
||||
|
||||
// Baseline demonstration records to guarantee full functionality when backend vault is initially empty
|
||||
const defaultEvidenceItems: Evidence[] = useMemo(() => {
|
||||
const p1 = projects[0]?.id || 401;
|
||||
const p2 = projects[1]?.id || projects[0]?.id || 402;
|
||||
const m1 = milestones[0]?.id || 801;
|
||||
const m2 = milestones[1]?.id || milestones[0]?.id || 802;
|
||||
const a1 = activities[0]?.id || 101;
|
||||
const a2 = activities[1]?.id || activities[0]?.id || 102;
|
||||
const u1 = users[0]?.id || 'usr_awardee_01';
|
||||
const u2 = users[1]?.id || users[0]?.id || 'usr_awardee_02';
|
||||
const u3 = users[2]?.id || 'usr_awardee_03';
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'ev-101',
|
||||
projectId: p1,
|
||||
milestoneId: m1,
|
||||
activityId: a1,
|
||||
version: 1,
|
||||
type: EvidenceType.CompletionCertificate,
|
||||
fileName: 'Civil_Works_Milestone_Completion_Certificate_Signed.pdf',
|
||||
fileSize: '4.8 MB',
|
||||
status: ApprovalStatus.PendingReview,
|
||||
createdBy: u1,
|
||||
createdAt: '2026-09-18T14:32:00Z',
|
||||
documentUrl: 'https://storage.googleapis.com/pfm-evidence-vault/projects/401/milestones/801/completion_cert.pdf',
|
||||
},
|
||||
{
|
||||
id: 'ev-102',
|
||||
projectId: p1,
|
||||
milestoneId: m1,
|
||||
activityId: a1,
|
||||
version: 2,
|
||||
type: EvidenceType.GeotaggedPhoto,
|
||||
fileName: 'Geotagged_Substation_Installation_Survey.pdf',
|
||||
fileSize: '16.2 MB',
|
||||
status: ApprovalStatus.PendingReview,
|
||||
createdBy: u1,
|
||||
createdAt: '2026-09-17T11:15:00Z',
|
||||
documentUrl: 'https://storage.googleapis.com/pfm-evidence-vault/projects/401/activities/101/geotagged_photos.pdf',
|
||||
},
|
||||
{
|
||||
id: 'ev-103',
|
||||
projectId: p2,
|
||||
milestoneId: m2,
|
||||
activityId: a2,
|
||||
version: 1,
|
||||
type: EvidenceType.InspectionReport,
|
||||
fileName: 'Independent_Structural_Quality_Audit_Report.pdf',
|
||||
fileSize: '6.4 MB',
|
||||
status: ApprovalStatus.Approved,
|
||||
createdBy: u2,
|
||||
createdAt: '2026-09-15T09:40:00Z',
|
||||
documentUrl: 'https://storage.googleapis.com/pfm-evidence-vault/projects/402/milestones/802/audit_report.pdf',
|
||||
},
|
||||
{
|
||||
id: 'ev-104',
|
||||
projectId: p1,
|
||||
milestoneId: m2,
|
||||
activityId: a2,
|
||||
version: 1,
|
||||
type: EvidenceType.AttendanceRegister,
|
||||
fileName: 'Community_Skills_Training_Attendance_Register_Q2.xlsx',
|
||||
fileSize: '2.1 MB',
|
||||
status: ApprovalStatus.PendingReview,
|
||||
createdBy: u3,
|
||||
createdAt: '2026-09-19T08:20:00Z',
|
||||
documentUrl: 'https://storage.googleapis.com/pfm-evidence-vault/projects/401/activities/102/attendance.xlsx',
|
||||
},
|
||||
{
|
||||
id: 'ev-105',
|
||||
projectId: p2,
|
||||
milestoneId: m1,
|
||||
activityId: a1,
|
||||
version: 1,
|
||||
type: EvidenceType.FinancialInvoiceOrReceipt,
|
||||
fileName: 'Vendor_Procurement_Tax_Invoices_Batch_A.pdf',
|
||||
fileSize: '3.7 MB',
|
||||
status: ApprovalStatus.Rejected,
|
||||
createdBy: u2,
|
||||
createdAt: '2026-09-14T16:05:00Z',
|
||||
documentUrl: 'https://storage.googleapis.com/pfm-evidence-vault/projects/402/activities/101/invoices.pdf',
|
||||
},
|
||||
];
|
||||
}, [projects, milestones, activities, users]);
|
||||
|
||||
// Merge backend data with demo fallback, deduplicating by ID
|
||||
const allEvidence: Evidence[] = useMemo(() => {
|
||||
const map = new Map<string | number, Evidence>();
|
||||
defaultEvidenceItems.forEach((item) => map.set(item.id, item));
|
||||
rawEvidenceList.forEach((item) => map.set(item.id, item));
|
||||
return Array.from(map.values());
|
||||
}, [defaultEvidenceItems, rawEvidenceList]);
|
||||
// UI depends entirely on the API response - no mock or demo data
|
||||
const allEvidence: Evidence[] = rawEvidenceList;
|
||||
|
||||
// Review evidence mutation: Facilitates Approve & Reject decisions via PUT /v1/api/evidence
|
||||
const reviewMutation = useMutation({
|
||||
@@ -258,7 +193,7 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
// Update local query cache for instant feedback
|
||||
queryClient.setQueryData(['evidence'], (old: Evidence[] = []) => {
|
||||
queryClient.setQueryData(['evidence', selectedProjectId], (old: Evidence[] = []) => {
|
||||
const exists = old.some((e) => String(e.id) === String(variables.id));
|
||||
if (exists) {
|
||||
return old.map((e) =>
|
||||
@@ -416,9 +351,10 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
toastError('Missing File', 'Please select a document or certificate to upload.');
|
||||
return;
|
||||
}
|
||||
const targetProjId = ingestProjectId || (selectedProjectId !== 'all' ? selectedProjectId : projects[0]?.id) || '';
|
||||
const formData = new FormData();
|
||||
formData.append('file', ingestFile);
|
||||
formData.append('projectId', String(ingestProjectId || projects[0]?.id || '401'));
|
||||
formData.append('projectId', String(targetProjId));
|
||||
if (ingestMilestoneId) formData.append('milestoneId', String(ingestMilestoneId));
|
||||
if (ingestActivityId) formData.append('activityId', String(ingestActivityId));
|
||||
formData.append('evidenceType', String(ingestType));
|
||||
@@ -427,7 +363,8 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
};
|
||||
|
||||
const handleResetFilters = () => {
|
||||
setSelectedProjectId('all');
|
||||
const defaultProjId = projects[0]?.id || 'all';
|
||||
setSelectedProjectId(defaultProjId);
|
||||
setSelectedMilestoneId('all');
|
||||
setSelectedActivityId('all');
|
||||
setSelectedUserId('all');
|
||||
@@ -435,8 +372,12 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
setSearchQuery('');
|
||||
};
|
||||
|
||||
const defaultProjectId = projects[0]?.id;
|
||||
const isDefaultProject =
|
||||
defaultProjectId !== undefined ? String(selectedProjectId) === String(defaultProjectId) : selectedProjectId === 'all';
|
||||
|
||||
const hasActiveFilters =
|
||||
selectedProjectId !== 'all' ||
|
||||
!isDefaultProject ||
|
||||
selectedMilestoneId !== 'all' ||
|
||||
selectedActivityId !== 'all' ||
|
||||
selectedUserId !== 'all' ||
|
||||
@@ -567,7 +508,10 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
</label>
|
||||
<select
|
||||
value={selectedProjectId}
|
||||
onChange={(e) => setSelectedProjectId(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setSelectedProjectId(e.target.value);
|
||||
setUserHasSelectedProject(true);
|
||||
}}
|
||||
className="w-full text-xs bg-slate-900/90 border border-slate-700/80 rounded-xl px-2.5 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white"
|
||||
>
|
||||
<option value="all">All Projects ({projects.length})</option>
|
||||
@@ -1069,7 +1013,7 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
<div>
|
||||
<label className="block text-slate-300 font-semibold mb-1">Target Project *</label>
|
||||
<select
|
||||
value={ingestProjectId || projects[0]?.id || ''}
|
||||
value={ingestProjectId || (selectedProjectId !== 'all' ? selectedProjectId : projects[0]?.id) || ''}
|
||||
onChange={(e) => setIngestProjectId(e.target.value)}
|
||||
className="w-full bg-slate-900 border border-slate-700 rounded-xl px-3 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user