Files
Krow-workspace/frontend-web/src/pages/VendorDocumentReview.jsx
bwnyasse 554dc9f9e3 feat: Initialize monorepo structure and comprehensive documentation
This commit establishes the new monorepo architecture for the KROW Workforce platform.

Key changes include:
- Reorganized project into `frontend-web`, `mobile-apps`, `firebase`, `scripts`, and `secrets` directories.
- Updated `Makefile` to support the new monorepo layout and automate Base44 export integration.
- Fixed `scripts/prepare-export.js` for ES module compatibility and global component import resolution.
- Created and updated `CONTRIBUTING.md` for developer onboarding.
- Restructured, renamed, and translated all `docs/` files for clarity and consistency.
- Implemented an interactive internal launchpad with diagram viewing capabilities.
- Configured base Firebase project files (`firebase.json`, security rules).
- Updated `README.md` to reflect the new project structure and documentation overview.
2025-11-12 12:50:55 -05:00

511 lines
21 KiB
JavaScript

import React, { useState, useEffect } from "react"; // Added useEffect
import { base44 } from "@/api/base44Client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; // Added CardHeader, CardTitle
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { ArrowLeft, FileText, Shield, CheckCircle2, Clock, Eye } from "lucide-react";
import PageHeader from "../components/common/PageHeader";
import DocumentViewer from "../components/vendor/DocumentViewer";
import { useToast } from "@/components/ui/use-toast";
const ONBOARDING_DOCUMENTS = [
{
id: "nda",
name: "Confidentiality & Non-Disclosure Agreement",
type: "NDA",
url: "https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/99cd2ac5c_LegendaryEventStaffingFOODBUYVendorNDA.pdf",
description: "Confidential information protection agreement between Foodbuy and Legendary Event Staffing",
required: true,
icon: Shield,
color: "from-purple-500 to-purple-600"
},
{
id: "contract",
name: "Foodbuy Temporary Staffing Agreement",
type: "Contract",
url: "https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/2c22905a2_FoodbuyDraftContract.pdf",
description: "Standard temporary staffing service agreement with Foodbuy and Compass Group",
required: true,
icon: FileText,
color: "from-blue-500 to-blue-600"
},
{
id: "service-agreement",
name: "Vendor Service Standards Agreement",
type: "Service Agreement",
url: "https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/e57a799cf_image.png",
description: "Service standards including fill rate, response time, and compliance requirements",
required: true,
icon: Shield,
color: "from-emerald-500 to-emerald-600"
}
];
export default function VendorDocumentReview() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { toast } = useToast();
const [activeDoc, setActiveDoc] = useState("nda"); // Changed from "contract" to "nda"
const { data: user } = useQuery({
queryKey: ['current-user-doc-review'],
queryFn: () => base44.auth.me(),
});
const vendorName = user?.company_name || "Vendor";
const vendorId = user?.id;
const [attestations, setAttestations] = useState({
background_checks: false,
i9_verification: false,
wage_compliance: false,
general_compliance: false
});
// Effect to load initial attestations from user data once available
useEffect(() => {
if (user?.attestations) {
setAttestations(user.attestations);
}
}, [user]); // Run when user object changes
// Fetch existing document reviews
const { data: reviews = [] } = useQuery({
queryKey: ['vendor-document-reviews', vendorId],
queryFn: async () => {
if (!vendorId) return [];
return await base44.entities.VendorDocumentReview.filter({ vendor_id: vendorId });
},
enabled: !!vendorId,
initialData: [],
});
// Save/update review mutation
const saveReviewMutation = useMutation({
mutationFn: async ({ documentId, reviewData, acknowledged = false }) => {
const doc = ONBOARDING_DOCUMENTS.find(d => d.id === documentId);
if (!doc) return;
const existingReview = reviews.find(r =>
r.vendor_id === vendorId && r.document_type === doc.type
);
const fullReviewData = {
vendor_id: vendorId,
vendor_name: vendorName,
document_type: doc.type,
document_url: doc.url,
document_name: doc.name,
review_notes: reviewData.notes || "",
time_spent_minutes: reviewData.reviewTime || 0,
annotations: reviewData.annotations || [],
bookmarks: reviewData.bookmarks || [], // Added bookmarks to reviewData
reviewed: true,
reviewed_date: new Date().toISOString(),
acknowledged: acknowledged,
acknowledged_date: acknowledged ? new Date().toISOString() : existingReview?.acknowledged_date,
};
if (existingReview) {
return await base44.entities.VendorDocumentReview.update(existingReview.id, fullReviewData);
} else {
return await base44.entities.VendorDocumentReview.create(fullReviewData);
}
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: ['vendor-document-reviews'] });
// Show success toast
if (variables.acknowledged) {
toast({
title: "✅ Document Acknowledged",
description: `${ONBOARDING_DOCUMENTS.find(d => d.id === variables.documentId)?.name} has been acknowledged`,
});
} else {
const annotationCount = variables.reviewData?.annotations?.length || 0;
const bookmarkCount = variables.reviewData?.bookmarks?.length || 0;
toast({
title: "✅ Progress Saved",
description: `Saved ${annotationCount} annotations and ${bookmarkCount} bookmarks`,
});
}
},
onError: (error) => {
toast({
title: "❌ Save Failed",
description: "Failed to save. Please try again.",
variant: "destructive",
});
},
});
const handleSaveNotes = (reviewData) => {
saveReviewMutation.mutate({
documentId: activeDoc,
reviewData,
acknowledged: false
});
};
const handleAcknowledge = (reviewData) => {
saveReviewMutation.mutate({
documentId: activeDoc,
reviewData,
acknowledged: true
});
};
const getDocumentReview = (documentId) => {
const doc = ONBOARDING_DOCUMENTS.find(d => d.id === documentId);
return reviews.find(r => r.document_type === doc?.type);
};
const handleAttestationChange = (field, value) => {
setAttestations(prev => ({ ...prev, [field]: value }));
};
const handleSaveAttestations = async () => {
try {
await base44.auth.updateMe({
attestations: attestations,
attestation_date: new Date().toISOString()
});
// Invalidate the user query to refetch updated attestations
queryClient.invalidateQueries({ queryKey: ['current-user-doc-review'] });
toast({
title: "✅ Attestations Saved",
description: "Your compliance attestations have been recorded",
});
} catch (error) {
console.error("Failed to save attestations:", error);
toast({
title: "❌ Save Failed",
description: "Failed to save attestations. Please try again.",
variant: "destructive",
});
}
};
const allRequiredAcknowledged = ONBOARDING_DOCUMENTS
.filter(doc => doc.required)
.every(doc => {
const review = getDocumentReview(doc.id);
return review?.acknowledged;
});
const acknowledgedCount = ONBOARDING_DOCUMENTS.filter(doc => {
const review = getDocumentReview(doc.id);
return review?.acknowledged;
}).length;
const allAttestationsAcknowledged = Object.values(attestations).every(val => val === true);
return (
<div className="p-4 md:p-8 bg-slate-50 min-h-screen">
<div className="max-w-[1800px] mx-auto">
<div className="mb-6">
<Button
variant="ghost"
onClick={() => navigate(createPageUrl("VendorOnboarding"))}
className="mb-4 hover:bg-slate-100"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Onboarding
</Button>
<PageHeader
title="Document Review Center"
subtitle={`Review and acknowledge onboarding documents • ${acknowledgedCount}/${ONBOARDING_DOCUMENTS.length} completed`}
/>
</div>
{/* Progress Overview */}
<Card className="border-slate-200 mb-6">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-slate-900">Onboarding Documents Progress</h3>
<Badge className={allRequiredAcknowledged ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"}>
{acknowledgedCount}/{ONBOARDING_DOCUMENTS.length} Acknowledged
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{ONBOARDING_DOCUMENTS.map(doc => {
const review = getDocumentReview(doc.id);
const Icon = doc.icon;
return (
<div
key={doc.id}
onClick={() => setActiveDoc(doc.id)}
className={`p-4 border-2 rounded-lg cursor-pointer transition-all hover:shadow-md ${
activeDoc === doc.id
? 'border-[#0A39DF] bg-blue-50/50'
: 'border-slate-200 hover:border-300'
}`}
>
<div className="flex items-start gap-3">
<div className={`w-10 h-10 rounded-lg bg-gradient-to-br ${doc.color} flex items-center justify-center flex-shrink-0`}>
<Icon className="w-5 h-5 text-white" />
</div>
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-sm text-slate-900 mb-1">{doc.name}</h4>
<p className="text-xs text-slate-600 mb-2">{doc.description}</p>
<div className="flex items-center gap-2 flex-wrap">
{doc.required && (
<Badge variant="outline" className="text-[10px] bg-red-50 text-red-700 border-red-200">
Required
</Badge>
)}
{review?.acknowledged ? (
<Badge className="bg-green-100 text-green-700 text-[10px]">
<CheckCircle2 className="w-3 h-3 mr-1" />
Acknowledged
</Badge>
) : review?.reviewed ? (
<Badge className="bg-blue-100 text-blue-700 text-[10px]">
<Eye className="w-3 h-3 mr-1" />
Reviewed
</Badge>
) : (
<Badge variant="outline" className="text-[10px]">
Not Started
</Badge>
)}
{review && review.time_spent_minutes > 0 && (
<span className="text-[10px] text-slate-500 flex items-center gap-1">
<Clock className="w-3 h-3" />
{review.time_spent_minutes} min
</span>
)}
</div>
</div>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
{/* Compliance Attestations Section */}
<Card className="border-slate-200 mb-6">
<CardHeader className="bg-gradient-to-br from-green-50 to-white border-b border-slate-100">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-xl flex items-center gap-2">
<Shield className="w-6 h-6 text-green-600" />
Compliance Attestations
</CardTitle>
<p className="text-sm text-slate-500 mt-1">
Review and attest to compliance requirements
</p>
</div>
{allAttestationsAcknowledged && (
<Badge className="bg-green-100 text-green-700">
<CheckCircle2 className="w-4 h-4 mr-1" />
All Attested
</Badge>
)}
</div>
</CardHeader>
<CardContent className="p-6">
<div className="space-y-4">
{/* Background Checks Attestation */}
<div className="p-4 bg-slate-50 rounded-lg border-2 border-slate-200 hover:border-blue-300 transition-all">
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={attestations.background_checks}
onChange={(e) => handleAttestationChange('background_checks', e.target.checked)}
className="mt-1 w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
/>
<div className="flex-1">
<h4 className="font-semibold text-slate-900 mb-1">
Background Checks Required
</h4>
<p className="text-sm text-slate-600">
I attest that all workforce members assigned to Compass locations will have completed
background checks as required by the Service Contract Act and specified in Attachment "C".
Background checks will be current and meet all federal, state, and local requirements.
</p>
</div>
</label>
</div>
{/* I-9 Verification Attestation */}
<div className="p-4 bg-slate-50 rounded-lg border-2 border-slate-200 hover:border-blue-300 transition-all">
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={attestations.i9_verification}
onChange={(e) => handleAttestationChange('i9_verification', e.target.checked)}
className="mt-1 w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
/>
<div className="flex-1">
<h4 className="font-semibold text-slate-900 mb-1">
I-9 Employment Eligibility Verification
</h4>
<p className="text-sm text-slate-600">
I attest that we comply with the Immigration Reform and Control Act of 1986 (IRCA) by
examining specified documents to verify identity and work eligibility using Form I-9
for all personnel. We also use E-Verify to determine eligibility to work in the United States.
</p>
</div>
</label>
</div>
{/* Wage Compliance Attestation */}
<div className="p-4 bg-slate-50 rounded-lg border-2 border-slate-200 hover:border-blue-300 transition-all">
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={attestations.wage_compliance}
onChange={(e) => handleAttestationChange('wage_compliance', e.target.checked)}
className="mt-1 w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
/>
<div className="flex-1">
<h4 className="font-semibold text-slate-900 mb-1">
Wage & Hour Compliance
</h4>
<p className="text-sm text-slate-600">
I attest that all rates comply with local minimum wage laws and Service Contract Act (SCA)
wage determinations where applicable. All pricing is competitive within market standards.
We will pay Assigned Employees weekly for hours worked in accordance with all applicable laws,
including proper overtime calculation.
</p>
</div>
</label>
</div>
{/* General Compliance Attestation */}
<div className="p-4 bg-slate-50 rounded-lg border-2 border-slate-200 hover:border-blue-300 transition-all">
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={attestations.general_compliance}
onChange={(e) => handleAttestationChange('general_compliance', e.target.checked)}
className="mt-1 w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
/>
<div className="flex-1">
<h4 className="font-semibold text-slate-900 mb-1">
General Compliance & Service Standards
</h4>
<p className="text-sm text-slate-600">
I attest that we will maintain a 95% fill rate for all orders, respond to order requests
within 2 hours, ensure all staff arrive on-time and in proper uniform, maintain current
W-9 forms and Certificates of Insurance with minimum $1M general liability, and comply with
all laws, rules, regulations, and ordinances applicable to services provided.
</p>
</div>
</label>
</div>
</div>
{/* Save Attestations Button */}
<div className="mt-6 flex justify-end">
<Button
onClick={handleSaveAttestations}
disabled={!allAttestationsAcknowledged}
className="bg-green-600 hover:bg-green-700 text-white"
>
<CheckCircle2 className="w-4 h-4 mr-2" />
Save Attestations
</Button>
</div>
<div className="mt-4 p-4 bg-blue-50 rounded-lg border border-blue-200">
<p className="text-xs text-slate-700">
<span className="font-semibold text-blue-900">Legal Notice:</span> By checking these boxes,
you are legally attesting that your organization complies with all stated requirements.
False attestation may result in contract termination and legal action.
</p>
</div>
</CardContent>
</Card>
{/* Document Tabs */}
<Tabs value={activeDoc} onValueChange={setActiveDoc}>
<TabsList className="bg-white border border-slate-200 mb-6">
{ONBOARDING_DOCUMENTS.map(doc => {
const review = getDocumentReview(doc.id);
const annotationCount = review?.annotations?.length || 0;
return (
<TabsTrigger
key={doc.id}
value={doc.id}
className="data-[state=active]:bg-[#0A39DF] data-[state=active]:text-white relative"
>
<div className="flex items-center gap-2">
<span>{doc.name.split(' ')[0]}</span>
{review?.acknowledged && (
<CheckCircle2 className="w-4 h-4 text-green-600 data-[state=active]:text-white" />
)}
{annotationCount > 0 && (
<Badge
variant="outline"
className="ml-1 h-5 px-1.5 text-[10px] bg-purple-100 text-purple-700 border-purple-300"
>
{annotationCount}
</Badge>
)}
</div>
</TabsTrigger>
);
})}
</TabsList>
{ONBOARDING_DOCUMENTS.map(doc => {
const review = getDocumentReview(doc.id);
return (
<TabsContent key={doc.id} value={doc.id}>
<DocumentViewer
documentUrl={doc.url}
documentName={doc.name}
documentType={doc.type}
onSaveNotes={handleSaveNotes}
onAcknowledge={handleAcknowledge}
initialNotes={review?.review_notes || ""}
isAcknowledged={review?.acknowledged || false}
timeSpent={review?.time_spent_minutes || 0}
initialAnnotations={review?.annotations || []}
/>
</TabsContent>
);
})}
</Tabs>
{/* Action Footer */}
{allRequiredAcknowledged && allAttestationsAcknowledged && (
<Card className="border-green-200 bg-green-50">
<CardContent className="p-6 text-center">
<CheckCircle2 className="w-12 h-12 text-green-600 mx-auto mb-3" />
<h3 className="text-lg font-semibold text-green-900 mb-2">
All Onboarding Requirements Met!
</h3>
<p className="text-sm text-green-700 mb-4">
You've successfully reviewed all required documents and attested to compliance requirements.
</p>
<Button
onClick={() => navigate(createPageUrl("VendorOnboarding"))}
className="bg-green-600 hover:bg-green-700 text-white"
>
Continue Onboarding Process
</Button>
</CardContent>
</Card>
)}
</div>
</div>
);
}