Fix up a ton of pages, settings/help/wiki need improvements, report generation needs slight updates. Otherwise completed

This commit is contained in:
Ra
2025-08-25 02:08:21 -07:00
parent 1ed3e16ff6
commit df7020cdd4
29 changed files with 3671 additions and 4684 deletions

9
.gitignore vendored
View File

@@ -58,4 +58,11 @@ dist-ssr
/deprecated /deprecated
/figma-code /figma-code
/*ignore.* /*ignore.*
/document.svg /document.svg
/AUTH_AGENT_MK2_SUMMARY.md
/ASSESSMENT_REPORT.md
/.tool-versions
/deploy-security.sh
/EMPLOYEE_FORMS_FIGMA_README.md
/TODOS.md
/SECURITY_MIGRATION.md

View File

@@ -1 +0,0 @@
elixir 1.18.4-otp-28

View File

@@ -1,178 +0,0 @@
# Comprehensive Assessment Report: Figma vs Current Implementation
**Assessment Date:** August 24, 2025
**Assessor:** GitHub Copilot Assessing Agent Purple Elephant
**Total Figma Pages Analyzed:** 130 pages
## Executive Summary
The Auditly application has achieved **excellent implementation coverage** of the core Figma designs. The major workflow areas (Login, Onboarding, Employee Forms, Reports, Chat) have been successfully implemented with proper Figma styling and functionality. The remaining gaps are primarily around UI polish, theme variants, and specific Company Wiki states.
**Overall Implementation Status: 85% Complete**
## Detailed Analysis
### ✅ FULLY IMPLEMENTED (Major Areas)
#### 1. Login System (4 Figma Pages → Login.tsx)
- **Figma Pages:** `Login-Empty-State.jsx`, `Login-Filled-State.jsx`, `Login-Verification-Empty.jsx`, `Login-Verification-Filling.jsx`
- **Current Implementation:** `/src/pages/Login.tsx`
- **Status:** ✅ Complete - OTP verification is built into the main login component
- **Notes:** Includes email input, OTP verification, Google sign-in, and proper Figma styling
#### 2. Employee Forms System (40 Figma Pages → EmployeeQuestionnaireNew.tsx)
- **Figma Pages:** `Employee-Forms-Step-1.jsx` through `Employee-Forms-Step-38.jsx` (40 total)
- **Current Implementation:** `/src/pages/EmployeeQuestionnaireNew.tsx`
- **Status:** ✅ Complete per TODOS.md - Invite-based system implemented
- **Notes:** No-auth employee questionnaire with cloud function integration
#### 3. Onboarding System (62 Figma Pages → Onboarding.tsx)
- **Figma Pages:** `Onboarding-Step-1.jsx` through `Onboarding-Step-63.jsx` (note: Step 24 has typo "Onbaording")
- **Current Implementation:** `/src/pages/Onboarding.tsx`
- **Status:** ✅ Complete per TODOS.md - All 63 steps implemented
- **Notes:** Comprehensive step-by-step onboarding with proper API integration
#### 4. Reports System (2 Figma Pages → Reports.tsx)
- **Figma Pages:** `Company-Report.jsx`, `Employee-Report.jsx`
- **Current Implementation:** `/src/pages/Reports.tsx`
- **Status:** ✅ Complete per TODOS.md - Three-column layout with exact Figma styling
- **Notes:** Company report prioritized, employee reports alphabetical, PDF export
#### 5. Chat System (8 Figma Pages → Chat.tsx)
- **Figma Pages:** `Chat-AI-Response.jsx`, `Chat-File-Upload.jsx`, `Chat-Image-Upload.jsx`, `Chat-Image-And-File-Upload.jsx`, `Chat-Dark.jsx`, `Chat-Light.jsx`, `Chat-Mention-Employee-Menu.jsx`, `Chat-Mention-Employee-Text.jsx`, `CHat-Text-Area-Selected.jsx`
- **Current Implementation:** `/src/pages/Chat.tsx`
- **Status:** ✅ Complete - Uses Figma sidebar component
- **Notes:** Includes file upload, mentions, AI responses
### 🟡 PARTIALLY IMPLEMENTED (Needs Review)
#### 1. Company Wiki System (6 Figma Pages vs CompanyWiki.tsx)
- **Figma Pages:** `Company-Wiki-Empty-State-Dark.jsx`, `Company-Wiki-Empty-State-Light.jsx`, `Company-Wiki-Completed-State-Dark.jsx`, `Company-WIki-Completed-State-Light.jsx`, `Company-Wiki-Invite-Employees.jsx`, `Company-Wiki-Invite-Employee-Step-2-MultiSelect.jsx`
- **Current Implementation:** `/src/pages/CompanyWiki.tsx`
- **Status:** 🟡 Functional but needs Figma layout compliance review
- **Gap Analysis:**
- Missing exact three-column Figma layout
- Missing empty state designs
- Missing invite employee workflows
- Missing dark/light theme variants
#### 2. Settings System (1 Figma Page vs SettingsNew.tsx)
- **Figma Pages:** `Settings.jsx`
- **Current Implementation:** `/src/pages/SettingsNew.tsx`
- **Status:** 🟡 Implemented but needs Figma compliance verification
- **Gap Analysis:**
- Need to verify exact Figma layout matching
- Theme selection functionality present
#### 3. Help System (1 Figma Page vs HelpNew.tsx)
- **Figma Pages:** `Help.jsx`
- **Current Implementation:** `/src/pages/HelpNew.tsx`
- **Status:** 🟡 Implemented but needs Figma compliance verification
- **Gap Analysis:**
- Need to verify exact Figma layout matching
### ❌ IMPLEMENTATION GAPS
#### 1. Theme System (Dark/Light Variants)
- **Gap:** Many Figma pages have Dark/Light variants but theme switching is not fully implemented
- **Affected Pages:** All pages with `-Dark.jsx` and `-Light.jsx` variants
- **Recommendation:** Implement comprehensive dark theme with ThemeContext
#### 2. Submissions Pages Distinction
- **Figma Pages:** `Submissions-Dark.jsx`, `Submissions-Light.jsx`
- **Current Implementation:** `/submissions` route uses `EmployeeReport` in submissions mode
- **Status:** ❌ May need dedicated submissions page layout
- **Gap Analysis:** Need to determine if submissions should have distinct UI from reports
#### 3. OTP Verification as Standalone Page
- **Figma Pages:** `OTP-Verification.jsx`
- **Current Implementation:** Built into Login.tsx
- **Status:** ❌ Missing standalone OTP page
- **Note:** Functionality exists but not as separate route
## Deprecated/Unused Pages Analysis
### 🗑️ DEPRECATED PAGES (Can be removed)
The following pages in `/deprecated/pages/` are no longer needed:
1. **`deprecated/pages/Chat.tsx`** - Replaced by new `/src/pages/Chat.tsx`
2. **`deprecated/pages/EmployeeFormNew.tsx`** - Replaced by `/src/pages/EmployeeQuestionnaireNew.tsx`
3. **`deprecated/pages/OTPVerification.tsx`** - Functionality merged into `/src/pages/Login.tsx`
4. **`deprecated/pages/EmployeeFormsController.tsx`** - Replaced by new form system
5. **`deprecated/pages/OnboardingController.tsx`** - Replaced by `/src/pages/Onboarding.tsx`
6. **`deprecated/pages/EmployeeQuestionnaireMerged.tsx`** - Replaced by new questionnaire
7. **`deprecated/pages/DebugEmployee.tsx`** - Debug component, no longer needed
### 🤔 CURRENT PAGES NEEDING REVIEW
1. **`/src/pages/EmployeeQuestionnaire.tsx`** - Legacy version, kept for backwards compatibility
2. **`/src/pages/EmployeeQuestionnaireSteps.tsx`** - May be redundant
3. **`/src/pages/HelpAndSettings.tsx`** - Old combined version vs new separate pages
4. **`/src/pages/QuestionTypesDemo.tsx`** - Debug/demo page
5. **`/src/pages/FormsDashboard.tsx`** - Debug page
## Missing Figma Implementations
### High Priority Missing Features
1. **Company Wiki Complete Implementation**
- Empty state layouts (Dark/Light)
- Completed state layouts (Dark/Light)
- Employee invitation workflow
- Multi-select employee invitation
2. **Standalone OTP Verification Page**
- Dedicated route for OTP verification
- Email resend functionality
- Proper error states
3. **Dedicated Submissions Page**
- Verify if submissions needs distinct UI from reports
- Implement Dark/Light variants if needed
### Medium Priority Missing Features
1. **Dark Theme Implementation**
- Complete dark theme for all pages
- Theme toggle functionality
- Persistence across sessions
2. **Settings/Help Figma Compliance**
- Verify exact layout matching
- Implement missing UI elements
## Recommendations
### Immediate Actions (High Priority)
1. **Review and enhance Company Wiki** to match exact Figma layouts
2. **Implement standalone OTP verification page** as separate route
3. **Clean up deprecated pages** from `/deprecated/` folder
4. **Verify submissions page requirements** - determine if distinct from reports
### Medium Term Actions
1. **Implement comprehensive dark theme** with proper toggle
2. **Review Settings and Help pages** for Figma compliance
3. **Remove or consolidate redundant pages** (legacy questionnaire versions)
### Long Term Actions
1. **Create component library documentation** for Figma components
2. **Implement automated Figma-to-code validation**
3. **Add theme variant testing** to ensure all pages work in both themes
## Conclusion
The Auditly application demonstrates **excellent implementation of core Figma designs**. The major user workflows are fully functional and styled according to Figma specifications. The remaining work is primarily around UI polish, theme variants, and specific edge cases.
**Key Achievements:**
- ✅ 4 major workflow areas fully implemented
- ✅ Proper Figma component library usage
- ✅ Invite-based employee system working
- ✅ 63-step onboarding completed
- ✅ Comprehensive reports system
**Next Steps:**
Focus on Company Wiki Figma compliance and dark theme implementation to achieve 95%+ coverage of Figma designs.

View File

@@ -1,245 +0,0 @@
# Updated Employee Forms - Figma Design Implementation
This document describes the complete redesign and enhancement of the employee questionnaire system to match the exact Figma designs and implement the requirements from TODOS.md.
## 🎨 Design System Implementation
### Exact Figma Styling
- **Component Library**: Created precise React components matching Figma designs
- **Color System**: Updated CSS variables and Tailwind config with exact Figma colors
- **Typography**: Implemented Neue Montreal and Inter font families per Figma specs
- **Layout**: Pixel-perfect responsive layouts matching the 1440px design width
### Key Design Components
#### `/src/components/figma/FigmaEmployeeForms.tsx`
Complete component library including:
- `WelcomeScreen` - Landing page with Auditly branding
- `SectionIntro` - Section introduction pages with progress indicators
- `PersonalInfoForm` - Form inputs with exact Figma styling
- `TextAreaQuestion` - Multi-line text input components
- `RatingScaleQuestion` - Interactive 1-10 rating scales
- `YesNoChoice` - Binary choice components
- `ThankYouPage` - Completion confirmation
#### Color System Updates
```css
/* New Figma Design System Colors */
--Neutrals-NeutralSlate0 : #FFFFFF;
--Neutrals-NeutralSlate100 : #F5F5F5;
--Neutrals-NeutralSlate300 : #D5D7DA;
--Neutrals-NeutralSlate500 : #717680;
--Neutrals-NeutralSlate800 : #0A0D12;
--Neutrals-NeutralSlate950 : #0A0D12;
--Brand-Orange: #FF6B35;
--Other-White : #FFFFFF;
```
## 🚀 Enhanced Functionality
### Invite-Based System (No Authentication Required)
- **Direct Access**: Employees access forms via unique invite codes
- **Metadata Attachment**: Invite codes contain employee information
- **Pre-populated Data**: Forms auto-fill with invite metadata
- **Security**: One-time use invites with validation
### Company Owner Workflow
1. **Create Employee Invites**: Generate invites with employee metadata
2. **Send Invitations**: Share unique URLs with employees
3. **Track Progress**: Monitor form completion status
4. **View Reports**: Access AI-generated insights
### Employee Workflow
1. **Click Invite Link**: Access form directly (no account needed)
2. **Complete Assessment**: Step-by-step questionnaire
3. **Submit Responses**: Automatic processing via cloud functions
4. **Report Generation**: AI analysis with company context
## 🔗 Integration Points
### Cloud Functions Processing
```typescript
// Enhanced submission with company context
const submitResponse = await fetch(`${API_URL}/submitEmployeeAnswers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inviteCode: inviteCode,
answers: answers,
orgId: orgId,
includeCompanyContext: true // Include company Q&A for LLM
})
});
```
### LLM Enhancement
- **Company Context**: Include company onboarding questions and answers
- **Alignment Analysis**: Compare employee responses to company values
- **Contextual Reports**: Generate reports with company-specific insights
- **Firestore Storage**: Save reports for dashboard display
## 📱 User Experience
### Progressive Disclosure
- **Welcome Screen**: Friendly introduction with branding
- **Section Intros**: Clear context for each question category
- **Progress Indicators**: Visual progress bars and step counters
- **Skip Options**: Allow users to skip non-critical questions
### Responsive Design
- **Mobile-First**: Optimized for all device sizes
- **Touch-Friendly**: Large buttons and touch targets
- **Accessibility**: Proper focus states and keyboard navigation
### Error Handling
- **Invite Validation**: Clear error messages for invalid/expired invites
- **Form Validation**: Real-time validation with helpful feedback
- **Network Issues**: Graceful handling of connectivity problems
- **Progress Saving**: Automatic form state preservation
## 🛠 Technical Implementation
### Route Structure
```typescript
// Invite-based (no auth)
/employee-form/:inviteCode
/questionnaire/:inviteCode
// Authenticated (legacy support)
/employee-questionnaire
/employee-questionnaire-legacy
```
### Component Architecture
```
EmployeeQuestionnaireNew.tsx
├── WelcomeScreen
├── PersonalInfoForm
├── SectionIntro (6 sections)
├── Question Components
│ ├── TextAreaQuestion
│ ├── RatingScaleQuestion
│ └── YesNoChoice
└── ThankYouPage
```
### State Management
- **Form Data**: Centralized state with TypeScript interfaces
- **Progress Tracking**: Step-by-step navigation
- **Error Handling**: Comprehensive error state management
- **Loading States**: User-friendly loading indicators
## 📊 Question Categories
### 1. Personal Information
- Name, email, company details
- Pre-populated from invite metadata
### 2. Role & Responsibilities
- Current title and department
- Daily responsibilities
- Role clarity assessment
### 3. Output & Accountability
- Weekly output rating
- Key deliverables
- KPIs and reporting structure
### 4. Team & Collaboration
- Close collaborators
- Team communication rating
- Support assessment
### 5. Tools & Resources
- Current tools and software
- Tool effectiveness rating
- Missing resources
### 6. Skills & Development
- Key skills and strengths
- Development goals
- Training awareness
- Career aspirations
### 7. Feedback & Improvement
- Company improvement suggestions
- Job satisfaction rating
- Additional feedback
## 🔐 Security & Privacy
### Invite Code Security
- **Unique Generation**: Cryptographically secure invite codes
- **One-Time Use**: Codes invalidated after submission
- **Expiration**: Time-based code expiration
- **Validation**: Server-side invite verification
### Data Protection
- **Encryption**: All data encrypted in transit and at rest
- **Access Control**: Role-based access to reports
- **Audit Trail**: Complete submission logging
- **Compliance**: GDPR and privacy regulation adherence
## 🚀 Deployment & Testing
### Development Testing
```bash
# Start development server
bun run dev
# Test invite flow
http://localhost:5173/#/employee-form/TEST_INVITE_CODE
# Test authenticated flow
http://localhost:5173/#/employee-questionnaire
```
### Production Deployment
- **Environment Variables**: Secure API endpoint configuration
- **CDN Integration**: Optimized asset delivery
- **Error Monitoring**: Comprehensive error tracking
- **Performance Monitoring**: Real-time performance metrics
## 📈 Analytics & Reporting
### Completion Metrics
- **Response Rates**: Track invitation to completion rates
- **Drop-off Analysis**: Identify form abandonment points
- **Time Analysis**: Monitor completion times
- **Quality Scores**: Assess response completeness
### Report Generation
- **AI Processing**: Cloud function LLM analysis
- **Company Alignment**: Compare with organizational values
- **Actionable Insights**: Specific improvement recommendations
- **Dashboard Integration**: Seamless report display
## 🔄 Migration Strategy
### Backwards Compatibility
- **Legacy Routes**: Maintain existing functionality
- **Gradual Migration**: Phased rollout approach
- **Data Consistency**: Ensure data format compatibility
- **Feature Parity**: All existing features preserved
### Rollout Plan
1. **Phase 1**: Deploy new components alongside existing
2. **Phase 2**: Update invite generation to use new routes
3. **Phase 3**: Migrate existing authenticated users
4. **Phase 4**: Deprecate legacy routes
## 🛡 Error Handling & Recovery
### Common Error Scenarios
- **Invalid Invite**: Clear messaging with support contact
- **Network Issues**: Retry mechanisms with user feedback
- **Form Validation**: Real-time validation with helpful hints
- **Submission Failures**: Automatic retry with progress preservation
### Recovery Mechanisms
- **Auto-save**: Periodic form state saving
- **Session Recovery**: Resume from last saved state
- **Support Integration**: Direct access to help resources
- **Manual Backup**: Export form data for recovery
This comprehensive redesign ensures the employee forms system meets all requirements while providing an exceptional user experience that matches the Figma designs exactly.

View File

@@ -1,209 +0,0 @@
# Security Migration: Frontend to Cloud Functions
## Overview
This migration addresses critical security vulnerabilities by moving all Firestore interactions from the frontend to secure cloud functions. This prevents unauthorized data access and protects your database structure from being exposed to users.
## Security Issues Addressed
### Before (Vulnerable)
- ❌ Direct Firestore access from frontend
- ❌ Database schema exposed to users
- ❌ API keys visible in browser
- ❌ Firestore rules can be analyzed by attackers
- ❌ Users can potentially bypass frontend logic
### After (Secure)
- ✅ All data operations go through authenticated cloud functions
- ✅ Database structure hidden from frontend
- ✅ User authorization verified on every request
- ✅ No sensitive data exposed to client
- ✅ Complete audit trail of data access
## Migration Changes
### 1. Cloud Functions (Backend)
**File: `functions/index.js`**
Added secure endpoints:
- `getOrgData` - Get organization data with auth
- `updateOrgData` - Update organization data with auth
- `getEmployees` - Get employees with auth
- `getSubmissions` - Get submissions with auth
- `getReports` - Get reports with auth
- `upsertEmployee` - Create/update employees with auth
- `saveReport` - Save reports with auth
- `getCompanyReports` - Get company reports with auth
Each endpoint:
- Verifies user authentication
- Checks user authorization for the organization
- Validates all inputs
- Returns appropriate error messages
### 2. Secure API Service (Frontend)
**File: `src/services/secureApi.ts`**
New service that:
- Handles all communication with cloud functions
- Provides type-safe methods for data operations
- Manages authentication tokens
- Handles errors gracefully
### 3. Updated Context (Frontend)
**File: `src/contexts/OrgContext.tsx`**
Completely rewritten to:
- Use secure API instead of direct Firestore
- Load data on component mount instead of real-time listeners
- Provide loading states for better UX
- Handle authentication properly
### 4. Firestore Rules (Security)
**File: `firestore.rules`**
Updated rules to:
```javascript
// DENY ALL direct client access
allow read, write: if false;
```
## Deployment Steps
1. **Deploy Cloud Functions**
```bash
cd functions
npm install
cd ..
firebase deploy --only functions
```
2. **Deploy Firestore Rules**
```bash
firebase deploy --only firestore:rules
```
3. **Use Deployment Script**
```bash
./deploy-security.sh
```
## Frontend Usage
### Before (Direct Firestore)
```typescript
// DON'T DO THIS ANYMORE
import { collection, doc, getDoc } from 'firebase/firestore';
const orgDoc = await getDoc(doc(db, 'orgs', orgId));
```
### After (Secure API)
```typescript
// DO THIS INSTEAD
import { secureApi } from '../services/secureApi';
const orgData = await secureApi.getOrgData(orgId, userId);
```
## API Methods Available
### Organization Data
- `secureApi.getOrgData(orgId, userId)`
- `secureApi.updateOrgData(orgId, userId, data)`
### Employees
- `secureApi.getEmployees(orgId, userId)`
- `secureApi.upsertEmployee(orgId, userId, employeeData)`
### Submissions & Reports
- `secureApi.getSubmissions(orgId, userId)`
- `secureApi.getReports(orgId, userId)`
- `secureApi.saveReport(orgId, userId, employeeId, reportData)`
### Company Reports
- `secureApi.getCompanyReports(orgId, userId)`
- `secureApi.saveCompanyReport(orgId, report)`
### Existing API (Already Secure)
- `secureApi.sendOTP(email, inviteCode?)`
- `secureApi.verifyOTP(email, otp)`
- `secureApi.createInvitation(...)`
- `secureApi.generateEmployeeReport(...)`
- `secureApi.generateCompanyWiki(...)`
- `secureApi.chat(...)`
## Authentication Flow
1. User logs in via OTP (cloud function)
2. Cloud function returns user data and token
3. Frontend stores user data in AuthContext
4. All API calls include user ID for authorization
5. Cloud functions verify user access to organization data
## Error Handling
The secure API provides consistent error handling:
```typescript
try {
const data = await secureApi.getOrgData(orgId, userId);
// Handle success
} catch (error) {
// Handle error - could be auth, network, or data error
console.error('Failed to load organization:', error.message);
}
```
## Performance Considerations
- **Loading States**: UI shows loading while data fetches
- **Caching**: Local state caching reduces API calls
- **Batch Operations**: Multiple related operations in single calls
- **Error Recovery**: Graceful fallbacks for network issues
## Security Benefits
1. **Zero Trust**: Every request is authenticated and authorized
2. **Data Hiding**: Database schema not exposed to frontend
3. **Audit Trail**: All access logged in cloud functions
4. **Input Validation**: All data validated server-side
5. **Rate Limiting**: Can be added to cloud functions
6. **IP Filtering**: Can be implemented at cloud function level
## Testing
After migration, verify:
1. ✅ Users can only access their organization's data
2. ✅ Unauthenticated requests are rejected
3. ✅ Invalid organization IDs are rejected
4. ✅ All CRUD operations work through secure API
5. ✅ Error messages don't leak sensitive information
## Monitoring
Monitor these metrics:
- API response times
- Authentication failures
- Authorization failures
- Data access patterns
- Error rates
## Future Enhancements
This secure foundation enables:
- Role-based access control (RBAC)
- Data encryption at rest
- Advanced audit logging
- API rate limiting
- IP whitelisting
- Multi-factor authentication
## Support
If you encounter issues:
1. Check browser console for detailed error messages
2. Verify Firebase project configuration
3. Ensure cloud functions are deployed successfully
4. Check Firestore rules are updated
The secure API provides comprehensive error messages to help debug issues during development.

View File

@@ -1,73 +0,0 @@
# Agents
- The following is true for all agents:
- The style from the figma should be preserved, the figma style takes highest priority on style, our opinions and preferences are not relevant. However, functionality is just as important, and if functionality is already implemnted, preseve it, as well as if the task says; update the functionality.
---
## Login Agent
- Copy the login steps style and scaffolding available in /figma-code/pages/Login-*.jsx into our current login process
## Employee Forms Agent ✅ COMPLETED
- ✅ Copy the employee forms style and scaffolding available in /figma-code/pages/Employee-Forms-*.jsx into our current employee forms process.
- ✅ The current employee forms process may not be correct, we need to have it so that the employees do not require any account to fill out the forms.
- ✅ The company owner should just be able to invite their employees, and each employee gets their own invite.
- ✅ The invite will have the employee metadata attached to it, and this invite is all that is needed for verification.
- ✅ Upon an employee submitting their form, we must run it through our LLM via sending the form to cloud functions.
- ✅ When we are generating the employee report, we also need to supply the LLM with the company's onboarding questions and answers, so that the LLM has knowledge of what the company aligns with, and how the employee aligns with the company.
- ✅ We need to store this report in firestore, so that it may be displayed on our reports page.
**Implementation Details:**
- Created exact Figma component library in `/src/components/figma/FigmaEmployeeForms.tsx`
- Implemented invite-based employee questionnaire in `/src/pages/EmployeeQuestionnaireNew.tsx`
- Updated color system in `index.css` and `tailwind.config.js` with exact Figma tokens
- Configured routing for invite-based access: `/employee-form/:inviteCode`
- Maintained cloud function integration for LLM processing
- Included company context in submission for alignment analysis
- See `EMPLOYEE_FORMS_FIGMA_README.md` for complete documentation
## Reports Agent ✅ COMPLETED
- ✅ The company report is always the report found at the top-most of the reports, and following are all employees alphabetically.
- ✅ You must copy the style AND information sections from /figma-code/pages/Company-Report.jsx and /figma-code/pages/Employee-Report.jsx into the application.
- ✅ You must dynamically fill out all of the correlating sections with truthy information from the Firestore documents.
**Implementation Details:**
- Created comprehensive Reports.tsx component with exact three-column Figma layout
- Left sidebar: Complete navigation with company logo, nav items, settings, and CTA card
- Middle sidebar: Employee list with search functionality and alphabetical sorting
- Main content: Dynamic report display with company report prioritized at top
- Implemented all major company report sections: Weaknesses, Personnel Changes, Hiring Needs, Forward Plan, Strengths, and Grading Overview
- Updated App.tsx routing to use new Reports component instead of legacy EmployeeReport
- Added comprehensive CSS color system with all Figma design tokens
- Maintained existing functionality: data loading, report generation, PDF export
- Component shows company report by default for owners, with employee reports listed alphabetically
- See `/src/pages/Reports.tsx` for complete implementation
## Onboarding Agent ✅ COMPLETED
- ✅ Currently, the design for the frames is pretty much there, however there 63 steps for the onboarding, currently only 8 of them are implmented.
- ✅ Be sure to implement every step listed in /figma-code/Onboarding-Step-*.jsx
- ✅ Then, when submitting the onboarding questions, we should make a request to submitting this data via `/home/ra/auditly/src/services/secureApi.ts`
**Implementation Details:**
- Created comprehensive 63-step onboarding system in `/src/data/onboardingSteps.ts`
- Built exact Figma component library in `/src/components/onboarding/FigmaOnboardingComponents.tsx`
- Updated main Onboarding.tsx component to use new 63-step structure with proper Figma styling
- Organized steps into 7 logical sections: Company Overview & Mission, Leadership & Organizational Structure, Operations & Execution, Culture & Team Health, Sales Marketing & Growth, Innovation & Product/Service Strategy, Personal Leadership & Risk
- Implemented all step types: intro (section introductions), question (open-ended text), multiple_choice (option selection), form (company details)
- Added proper API integration using `secureApi.ts` with `onboarding/complete` endpoint
- Maintained exact Figma styling with progress indicators, section navigation, and responsive layouts
- Enhanced CSS with all necessary Figma color tokens and variables
## White Screen Agent
- Currently, when the app is started, it immediately goes to a all white page, we never make it anywhere else. We need to fix this.
## Assessing Agent
- You are to go through all of the figmas, and then go through all of the current pages.
- You will note down which pages are deprecated / unused due to incomplete implementation or redundancy.
- You will create a detailed report on which pages need to be implemented still, and what we currently do not have that the figma has.

View File

@@ -1,22 +0,0 @@
#!/bin/bash
# Deploy Firestore security rules
echo "🔒 Deploying secure Firestore rules..."
firebase deploy --only firestore:rules
# Deploy cloud functions with new secure endpoints
echo "☁️ Deploying cloud functions..."
firebase deploy --only functions
echo "✅ Security migration complete!"
echo ""
echo "🔒 Security improvements implemented:"
echo " - All direct Firestore client access is now blocked"
echo " - Data operations go through authenticated cloud functions"
echo " - User authorization is verified on every request"
echo " - Database structure is hidden from clients"
echo ""
echo "⚠️ Important: Make sure to update your frontend to use the secure API"
echo " - Replace all direct Firestore calls with secureApi methods"
echo " - Update components to use the new OrgContext implementation"
echo ""

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ import { UserOrganizationsProvider, useUserOrganizations } from './contexts/User
import { OrgProvider, useOrg } from './contexts/OrgContext'; import { OrgProvider, useOrg } from './contexts/OrgContext';
import { Layout } from './components/UiKit'; import { Layout } from './components/UiKit';
import CompanyWiki from './pages/CompanyWiki'; import CompanyWiki from './pages/CompanyWiki';
import EmployeeReport from './pages/EmployeeData'; // import Report from '../deprecated/pages/EmployeeData';
import Reports from './pages/Reports'; import Reports from './pages/Reports';
import Chat from './pages/Chat'; import Chat from './pages/Chat';
import HelpNew from './pages/HelpNew'; import HelpNew from './pages/HelpNew';
@@ -113,7 +113,7 @@ function App() {
{/* Employee questionnaire - no auth needed, uses invite code */} {/* Employee questionnaire - no auth needed, uses invite code */}
<Route path="/employee-form/:inviteCode" element={<EmployeeQuestionnaireNew />} /> <Route path="/employee-form/:inviteCode" element={<EmployeeQuestionnaireNew />} />
<Route path="/questionnaire/:inviteCode" element={<EmployeeQuestionnaireNew />} /> <Route path="/questionnaire/:inviteCode" element={<EmployeeQuestionnaireNew />} />
{/* Legacy employee questionnaire route for backwards compatibility */} {/* Legacy employee questionnaire route for backwards compatibility */}
<Route path="/employee-form-legacy/:inviteCode" element={<EmployeeQuestionnaire />} /> <Route path="/employee-form-legacy/:inviteCode" element={<EmployeeQuestionnaire />} />
@@ -150,7 +150,7 @@ function App() {
</RequireAuth> </RequireAuth>
} }
/> />
{/* Legacy employee questionnaire route for backwards compatibility */} {/* Legacy employee questionnaire route for backwards compatibility */}
<Route <Route
path="/employee-questionnaire-legacy" path="/employee-questionnaire-legacy"
@@ -257,7 +257,10 @@ function App() {
> >
<Route path="/" element={<Navigate to="/reports" replace />} /> <Route path="/" element={<Navigate to="/reports" replace />} />
<Route path="/company-wiki" element={<CompanyWiki />} /> <Route path="/company-wiki" element={<CompanyWiki />} />
<Route path="/submissions" element={<EmployeeReport mode="submissions" />} /> {/*
This is the old reports page
<Route path="/submissions" element={<Report mode="submissions" />} />
*/}
<Route path="/reports" element={<Reports />} /> <Route path="/reports" element={<Reports />} />
<Route path="/help" element={<HelpAndSettings />} /> <Route path="/help" element={<HelpAndSettings />} />
<Route path="/settings" element={<HelpAndSettings />} /> <Route path="/settings" element={<HelpAndSettings />} />

View File

@@ -72,12 +72,12 @@ export const CompanyWikiCompletedState: React.FC<CompanyWikiCompletedStateProps>
onClick={() => onSectionClick?.(sectionNumber)} onClick={() => onSectionClick?.(sectionNumber)}
className={`self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden cursor-pointer hover:bg-Main-BG-Gray-50 dark:hover:bg-[--Neutrals-NeutralSlate700] ${isActive ? 'bg-Main-BG-Gray-100 dark:bg-[--Neutrals-NeutralSlate700] shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)]' : ''}`} className={`self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden cursor-pointer hover:bg-Main-BG-Gray-50 dark:hover:bg-[--Neutrals-NeutralSlate700] ${isActive ? 'bg-Main-BG-Gray-100 dark:bg-[--Neutrals-NeutralSlate700] shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)]' : ''}`}
> >
<div className={`h-5 p-0.5 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden ${isActive ? 'bg-[--Brand-Orange]' : 'bg-Text-Gray-100 dark:bg-Neutrals-NeutralSlate600'}`}> <div className={`h-5 p-0.5 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden ${isActive ? 'bg-[--Brand-Orange]' : 'bg-Text-Gray-100 dark:bg-[--Neutrals-NeutralSlate600]'}`}>
<div className={`w-4 text-center justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-Neutrals-NeutralSlate0' : 'text-Text-Dark-950 dark:text-Neutrals-NeutralSlate200'}`}> <div className={`w-4 text-center justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-[--Neutrals-NeutralSlate0]' : 'text-Text-Dark-950 dark:text-[--Neutrals-NeutralSlate0]'}`}>
{sectionNumber} {sectionNumber}
</div> </div>
</div> </div>
<div className={`flex-1 justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-[--Neutrals-NeutralSlate800] dark:text-Neutrals-NeutralSlate100' : 'text-Text-Gray-500 dark:text-Neutrals-NeutralSlate400'}`}> <div className={`flex-1 justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-[--Neutrals-NeutralSlate800] dark:text-[--Neutrals-NeutralSlate100]' : 'text-Text-Gray-500 dark:text-[--Neutrals-NeutralSlate100]'}`}>
{section} {section}
</div> </div>
</div> </div>

View File

@@ -39,14 +39,14 @@ export const CompanyWikiEmptyState: React.FC<CompanyWikiEmptyStateProps> = ({
return ( return (
<div <div
key={index} key={index}
className={`self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden ${isActive ? 'bg-Main-BG-Gray-100 dark:bg-[--Neutrals-NeutralSlate800] shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)]' : 'hover:bg-Main-BG-Gray-50 dark:hover:bg-Neutrals-NeutralSlate700'}`} className={`self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden ${isActive ? 'bg-Main-BG-Gray-100 dark:bg-[--Neutrals-NeutralSlate800] shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)]' : 'hover:bg-Main-BG-Gray-50 dark:hover:bg-[--Neutrals-NeutralSlate700]'}`}
> >
<div className={`h-5 p-0.5 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden ${isActive ? 'bg-[--Brand-Orange]' : 'bg-Text-Gray-100 dark:bg-Neutrals-NeutralSlate600'}`}> <div className={`h-5 p-0.5 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden ${isActive ? 'bg-[--Brand-Orange]' : 'bg-Text-Gray-100 dark:bg-[--Neutrals-NeutralSlate600]'}`}>
<div className={`w-4 text-center justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-Neutrals-NeutralSlate0' : 'text-Text-Dark-950 dark:text-Neutrals-NeutralSlate200'}`}> <div className={`w-4 text-center justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-[--Neutrals-NeutralSlate0]' : 'text-Text-Dark-950 dark:text-[--Neutrals-NeutralSlate0]'}`}>
{sectionNumber} {sectionNumber}
</div> </div>
</div> </div>
<div className={`flex-1 justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-[--Neutrals-NeutralSlate800] dark:text-Neutrals-NeutralSlate100' : 'text-Text-Gray-500 dark:text-Neutrals-NeutralSlate400'}`}> <div className={`flex-1 justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-[--Neutrals-NeutralSlate800] dark:text-[--Neutrals-NeutralSlate100]' : 'text-Text-Gray-500 dark:text-[--Neutrals-NeutralSlate100]'}`}>
{section} {section}
</div> </div>
</div> </div>

View File

@@ -66,7 +66,7 @@ export const InviteEmployeesModal: React.FC<InviteEmployeesModalProps> = ({
<div className="self-stretch p-6 inline-flex justify-between items-center"> <div className="self-stretch p-6 inline-flex justify-between items-center">
<button <button
onClick={onMultipleInvite} onClick={onMultipleInvite}
className="text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight hover:underline" className="text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight hover:underline"
> >
Invite multiple employees Invite multiple employees
</button> </button>

View File

@@ -19,14 +19,14 @@ export const AuditlyIcon: React.FC = () => (
); );
// Progress Bar Component for Section Headers // Progress Bar Component for Section Headers
export const SectionProgressBar: React.FC<{ currentSection: number; totalSections: number; sectionName: string }> = ({ export const SectionProgressBar: React.FC<{ currentSection: number; totalSections: number; sectionName: string }> = ({
currentSection, currentSection,
totalSections, totalSections,
sectionName sectionName
}) => { }) => {
return ( return (
<div className="w-[464px] max-w-[464px] min-w-[464px] absolute top-[24px] left-1/2 transform -translate-x-1/2 flex flex-col justify-start items-center gap-4"> <div className="w-[464px] max-w-[464px] min-w-[464px] absolute top-[24px] left-1/2 transform -translate-x-1/2 flex flex-col justify-start items-center gap-4">
<div className="p-4 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="p-4 bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
{Array.from({ length: 7 }, (_, index) => { {Array.from({ length: 7 }, (_, index) => {
const isActive = index === currentSection - 1; const isActive = index === currentSection - 1;
return ( return (
@@ -44,7 +44,7 @@ export const SectionProgressBar: React.FC<{ currentSection: number; totalSection
); );
})} })}
</div> </div>
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate500 text-base font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate500] text-base font-medium font-['Neue_Montreal'] leading-normal">
{sectionName} {sectionName}
</div> </div>
</div> </div>
@@ -58,27 +58,27 @@ export const WelcomeScreen: React.FC<{
}> = ({ onStart, imageUrl = "https://placehold.co/560x682" }) => { }> = ({ onStart, imageUrl = "https://placehold.co/560x682" }) => {
return ( return (
<div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden"> <div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-[810px] px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden"> <div className="flex-1 h-[810px] px-32 py-48 bg-[--Neutrals-NeutralSlate0] flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12"> <div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden"> <div className="w-12 h-12 relative bg-[--Brand-Orange] rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" /> <div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute"> <div className="left-[12px] top-[9.33px] absolute">
<AuditlyIcon /> <AuditlyIcon />
</div> </div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-4"> <div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-5xl font-medium font-['Neue_Montreal'] leading-[48px]"> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate800] text-5xl font-medium font-['Neue_Montreal'] leading-[48px]">
Welcome to the Internal Staff Survey Welcome to the Internal Staff Survey
</div> </div>
<div className="self-stretch justify-center text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal"> <div className="self-stretch justify-center text-[--Neutrals-NeutralSlate500] text-base font-normal font-['Inter'] leading-normal">
The survey takes around 15 minutes to complete. The survey takes around 15 minutes to complete.
</div> </div>
</div> </div>
</div> </div>
<button <button
onClick={onStart} onClick={onStart}
className="self-stretch px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors" className="self-stretch px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start</div>
@@ -105,32 +105,32 @@ export const SectionIntro: React.FC<{
}> = ({ sectionNumber, title, description, onStart, imageUrl = "https://placehold.co/560x682" }) => { }> = ({ sectionNumber, title, description, onStart, imageUrl = "https://placehold.co/560x682" }) => {
return ( return (
<div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden"> <div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-[810px] px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden"> <div className="flex-1 h-[810px] px-32 py-48 bg-[--Neutrals-NeutralSlate0] flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12"> <div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden"> <div className="w-12 h-12 relative bg-[--Brand-Orange] rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" /> <div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute"> <div className="left-[12px] top-[9.33px] absolute">
<AuditlyIcon /> <AuditlyIcon />
</div> </div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-4"> <div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="px-3 py-1.5 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{sectionNumber} {sectionNumber}
</div> </div>
</div> </div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-5xl font-medium font-['Neue_Montreal'] leading-[48px]"> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate800] text-5xl font-medium font-['Neue_Montreal'] leading-[48px]">
{title} {title}
</div> </div>
<div className="self-stretch justify-center text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal"> <div className="self-stretch justify-center text-[--Neutrals-NeutralSlate500] text-base font-normal font-['Inter'] leading-normal">
{description} {description}
</div> </div>
</div> </div>
</div> </div>
<button <button
onClick={onStart} onClick={onStart}
className="self-stretch px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors" className="self-stretch px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start</div>
@@ -159,20 +159,20 @@ export const FormField: React.FC<{
return ( return (
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight"> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">
{label} {label}
</div> </div>
{required && ( {required && (
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
)} )}
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input <input
type={type} type={type}
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none" className="flex-1 bg-transparent text-[--Neutrals-NeutralSlate950] text-sm font-normal font-['Inter'] leading-tight placeholder:text-[--Neutrals-NeutralSlate950] outline-none"
placeholder={placeholder} placeholder={placeholder}
/> />
</div> </div>
@@ -190,10 +190,10 @@ export const PersonalInfoForm: React.FC<{
const isValid = formData.email && formData.name && formData.company; const isValid = formData.email && formData.name && formData.company;
return ( return (
<div className="w-[1440px] h-[810px] px-[488px] py-32 bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9"> <div className="w-[1440px] h-[810px] px-[488px] py-32 bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8"> <div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
Personal Information Personal Information
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
@@ -224,7 +224,7 @@ export const PersonalInfoForm: React.FC<{
<button <button
onClick={onNext} onClick={onNext}
disabled={!isValid} disabled={!isValid}
className="self-stretch h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-orange-600 transition-colors" className="self-stretch h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-orange-600 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
@@ -249,17 +249,17 @@ export const TextAreaQuestion: React.FC<{
placeholder?: string; placeholder?: string;
}> = ({ question, value, onChange, onBack, onNext, onSkip, currentStep, totalSteps, sectionName, placeholder = "Type your answer...." }) => { }> = ({ question, value, onChange, onBack, onNext, onSkip, currentStep, totalSteps, sectionName, placeholder = "Type your answer...." }) => {
return ( return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9"> <div className="w-[1440px] h-[810px] py-6 relative bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8"> <div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question} {question}
</div> </div>
<div className="self-stretch min-h-40 p-5 relative bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-start gap-2.5"> <div className="self-stretch min-h-40 p-5 relative bg-[--Neutrals-NeutralSlate100] rounded-xl inline-flex justify-start items-start gap-2.5">
<textarea <textarea
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-base font-normal font-['Inter'] leading-normal placeholder:text-Neutrals-NeutralSlate500 outline-none resize-none" className="flex-1 bg-transparent text-[--Neutrals-NeutralSlate950] text-base font-normal font-['Inter'] leading-normal placeholder:text-[--Neutrals-NeutralSlate950] outline-none resize-none"
placeholder={placeholder} placeholder={placeholder}
rows={6} rows={6}
/> />
@@ -273,16 +273,16 @@ export const TextAreaQuestion: React.FC<{
{onBack && ( {onBack && (
<button <button
onClick={onBack} onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-neutral-200 transition-colors" className="h-12 px-8 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-neutral-200 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div> </div>
</button> </button>
)} )}
<button <button
onClick={onNext} onClick={onNext}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors" className="flex-1 h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
@@ -293,8 +293,8 @@ export const TextAreaQuestion: React.FC<{
{/* Step indicator */} {/* Step indicator */}
{currentStep && totalSteps && ( {currentStep && totalSteps && (
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{currentStep} of {totalSteps} {currentStep} of {totalSteps}
</div> </div>
</div> </div>
@@ -304,17 +304,17 @@ export const TextAreaQuestion: React.FC<{
{onSkip && ( {onSkip && (
<button <button
onClick={onSkip} onClick={onSkip}
className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden hover:bg-neutral-200 transition-colors" className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden hover:bg-neutral-200 transition-colors"
> >
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-none">Skip</div>
</button> </button>
)} )}
{/* Progress bar */} {/* Progress bar */}
{currentStep && totalSteps && sectionName && ( {currentStep && totalSteps && sectionName && (
<SectionProgressBar <SectionProgressBar
currentSection={currentStep} currentSection={currentStep}
totalSections={totalSteps} totalSections={totalSteps}
sectionName={sectionName} sectionName={sectionName}
/> />
)} )}
@@ -338,14 +338,14 @@ export const RatingScaleQuestion: React.FC<{
scale?: number; scale?: number;
}> = ({ question, leftLabel, rightLabel, value, onChange, onBack, onNext, onSkip, currentStep, totalSteps, sectionName, scale = 10 }) => { }> = ({ question, leftLabel, rightLabel, value, onChange, onBack, onNext, onSkip, currentStep, totalSteps, sectionName, scale = 10 }) => {
return ( return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9"> <div className="w-[1440px] h-[810px] py-6 relative bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-center items-center gap-8"> <div className="self-stretch flex flex-col justify-center items-center gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question} {question}
</div> </div>
<div className="inline-flex justify-center items-center gap-3"> <div className="inline-flex justify-center items-center gap-3">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight"> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">
{leftLabel} {leftLabel}
</div> </div>
{Array.from({ length: scale }, (_, index) => { {Array.from({ length: scale }, (_, index) => {
@@ -355,19 +355,17 @@ export const RatingScaleQuestion: React.FC<{
<button <button
key={ratingValue} key={ratingValue}
onClick={() => onChange(ratingValue)} onClick={() => onChange(ratingValue)}
className={`w-12 h-12 relative rounded-[576.35px] overflow-hidden transition-colors ${ className={`w-12 h-12 relative rounded-[576.35px] overflow-hidden transition-colors ${isSelected ? 'bg-[--Neutrals-NeutralSlate800]' : 'bg-[--Neutrals-NeutralSlate800] hover:bg-neutral-200'
isSelected ? 'bg-Neutrals-NeutralSlate800' : 'bg-Neutrals-NeutralSlate100 hover:bg-neutral-200' }`}
}`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-xl font-medium font-['Inter'] leading-7 ${ <div className={`absolute inset-0 flex items-center justify-center text-xl font-medium font-['Inter'] leading-7 ${isSelected ? 'text-[--Neutrals-NeutralSlate0]' : 'text-[--Neutrals-NeutralSlate0]'
isSelected ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950' }`}>
}`}>
{ratingValue} {ratingValue}
</div> </div>
</button> </button>
); );
})} })}
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight"> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">
{rightLabel} {rightLabel}
</div> </div>
</div> </div>
@@ -376,17 +374,17 @@ export const RatingScaleQuestion: React.FC<{
{onBack && ( {onBack && (
<button <button
onClick={onBack} onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-neutral-200 transition-colors" className="h-12 px-8 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-neutral-200 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div> </div>
</button> </button>
)} )}
<button <button
onClick={onNext} onClick={onNext}
disabled={!value} disabled={!value}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-orange-600 transition-colors" className="flex-1 h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-orange-600 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
@@ -397,8 +395,8 @@ export const RatingScaleQuestion: React.FC<{
{/* Step indicator */} {/* Step indicator */}
{currentStep && totalSteps && ( {currentStep && totalSteps && (
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{currentStep} of {totalSteps} {currentStep} of {totalSteps}
</div> </div>
</div> </div>
@@ -408,17 +406,17 @@ export const RatingScaleQuestion: React.FC<{
{onSkip && ( {onSkip && (
<button <button
onClick={onSkip} onClick={onSkip}
className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden hover:bg-neutral-200 transition-colors" className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden hover:bg-neutral-200 transition-colors"
> >
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-none">Skip</div>
</button> </button>
)} )}
{/* Progress bar */} {/* Progress bar */}
{currentStep && totalSteps && sectionName && ( {currentStep && totalSteps && sectionName && (
<SectionProgressBar <SectionProgressBar
currentSection={currentStep} currentSection={currentStep}
totalSections={totalSteps} totalSections={totalSteps}
sectionName={sectionName} sectionName={sectionName}
/> />
)} )}
@@ -439,34 +437,30 @@ export const YesNoChoice: React.FC<{
sectionName?: string; sectionName?: string;
}> = ({ question, value, onChange, onBack, onNext, onSkip, currentStep, totalSteps, sectionName }) => { }> = ({ question, value, onChange, onBack, onNext, onSkip, currentStep, totalSteps, sectionName }) => {
return ( return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9"> <div className="w-[1440px] h-[810px] py-6 relative bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8"> <div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question} {question}
</div> </div>
<div className="self-stretch inline-flex justify-center items-center gap-3"> <div className="self-stretch inline-flex justify-center items-center gap-3">
<button <button
onClick={() => onChange('No')} onClick={() => onChange('No')}
className={`w-20 h-20 relative rounded-[999px] overflow-hidden transition-colors ${ className={`w-20 h-20 relative rounded-[999px] overflow-hidden transition-colors ${value === 'No' ? 'bg-[--Neutrals-NeutralSlate800]' : 'bg-[--Neutrals-NeutralSlate800] hover:bg-neutral-200'
value === 'No' ? 'bg-Neutrals-NeutralSlate800' : 'bg-Neutrals-NeutralSlate100 hover:bg-neutral-200' }`}
}`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-base font-normal font-['Inter'] leading-normal ${ <div className={`absolute inset-0 flex items-center justify-center text-base font-normal font-['Inter'] leading-normal ${value === 'No' ? 'text-[--Neutrals-NeutralSlate0]' : 'text-[--Neutrals-NeutralSlate0]'
value === 'No' ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950' }`}>
}`}>
No No
</div> </div>
</button> </button>
<button <button
onClick={() => onChange('Yes')} onClick={() => onChange('Yes')}
className={`w-20 h-20 relative rounded-[999px] overflow-hidden transition-colors ${ className={`w-20 h-20 relative rounded-[999px] overflow-hidden transition-colors ${value === 'Yes' ? 'bg-[--Neutrals-NeutralSlate800]' : 'bg-[--Neutrals-NeutralSlate800] hover:bg-neutral-200'
value === 'Yes' ? 'bg-Neutrals-NeutralSlate800' : 'bg-Neutrals-NeutralSlate100 hover:bg-neutral-200' }`}
}`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-base font-normal font-['Inter'] leading-normal ${ <div className={`absolute inset-0 flex items-center justify-center text-base font-normal font-['Inter'] leading-normal ${value === 'Yes' ? 'text-[--Neutrals-NeutralSlate0]' : 'text-[--Neutrals-NeutralSlate0]'
value === 'Yes' ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950' }`}>
}`}>
Yes Yes
</div> </div>
</button> </button>
@@ -476,17 +470,17 @@ export const YesNoChoice: React.FC<{
{onBack && ( {onBack && (
<button <button
onClick={onBack} onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-neutral-200 transition-colors" className="h-12 px-8 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-neutral-200 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div> </div>
</button> </button>
)} )}
<button <button
onClick={onNext} onClick={onNext}
disabled={!value} disabled={!value}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-orange-600 transition-colors" className="flex-1 h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-orange-600 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
@@ -497,8 +491,8 @@ export const YesNoChoice: React.FC<{
{/* Step indicator */} {/* Step indicator */}
{currentStep && totalSteps && ( {currentStep && totalSteps && (
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{currentStep} of {totalSteps} {currentStep} of {totalSteps}
</div> </div>
</div> </div>
@@ -508,17 +502,17 @@ export const YesNoChoice: React.FC<{
{onSkip && ( {onSkip && (
<button <button
onClick={onSkip} onClick={onSkip}
className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden hover:bg-neutral-200 transition-colors" className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden hover:bg-neutral-200 transition-colors"
> >
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-none">Skip</div>
</button> </button>
)} )}
{/* Progress bar */} {/* Progress bar */}
{currentStep && totalSteps && sectionName && ( {currentStep && totalSteps && sectionName && (
<SectionProgressBar <SectionProgressBar
currentSection={currentStep} currentSection={currentStep}
totalSections={totalSteps} totalSections={totalSteps}
sectionName={sectionName} sectionName={sectionName}
/> />
)} )}
@@ -530,20 +524,20 @@ export const YesNoChoice: React.FC<{
export const ThankYouPage: React.FC = () => { export const ThankYouPage: React.FC = () => {
return ( return (
<div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden"> <div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-[810px] px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden"> <div className="flex-1 h-[810px] px-32 py-48 bg-[--Neutrals-NeutralSlate0] flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12"> <div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden"> <div className="w-12 h-12 relative bg-[--Brand-Orange] rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" /> <div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute"> <div className="left-[12px] top-[9.33px] absolute">
<AuditlyIcon /> <AuditlyIcon />
</div> </div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-4"> <div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-5xl font-medium font-['Neue_Montreal'] leading-[48px]"> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate800] text-5xl font-medium font-['Neue_Montreal'] leading-[48px]">
Thank you your form has been submitted! Thank you your form has been submitted!
</div> </div>
<div className="self-stretch justify-center text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal"> <div className="self-stretch justify-center text-[--Neutrals-NeutralSlate500] text-base font-normal font-['Inter'] leading-normal">
Your responses have been recorded and your AI-powered performance report will be generated shortly. Your responses have been recorded and your AI-powered performance report will be generated shortly.
</div> </div>
</div> </div>

View File

@@ -167,10 +167,10 @@ export const FigmaRatingScale: React.FC<{
<div <div
key={number} key={number}
onClick={() => onChange(number)} onClick={() => onChange(number)}
className={`w-12 h-12 relative rounded-[576.35px] overflow-hidden cursor-pointer transition-colors ${isSelected ? 'bg-[--Brand-Orange]' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-Neutrals-NeutralSlate200' className={`w-12 h-12 relative rounded-[576.35px] overflow-hidden cursor-pointer transition-colors ${isSelected ? 'bg-[--Brand-Orange]' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-[--Neutrals-NeutralSlate200]'
}`} }`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-xl font-medium font-['Inter'] leading-7 ${isSelected ? 'text-white' : 'text-Neutrals-NeutralSlate950' <div className={`absolute inset-0 flex items-center justify-center text-xl font-medium font-['Inter'] leading-7 ${isSelected ? 'text-white' : 'text-[--Neutrals-NeutralSlate950]'
}`}> }`}>
{number} {number}
</div> </div>

View File

@@ -16,46 +16,81 @@ export const FigmaOnboardingIntro: React.FC<FigmaOnboardingIntroProps> = ({
onStart onStart
}) => { }) => {
return ( return (
<div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden"> <div className="w-[1440px] bg-transparent inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-[810px] px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden"> <div className="flex-1 h-[810px] px-32 py-48 bg-[--Neutrals-NeutralSlate0] flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12"> <div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden"> <div className="w-12 h-12 relative bg-[--Brand-Orange] rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" /> <div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute"> <div data-svg-wrapper className="left-[12px] top-[9.33px] absolute">
<svg width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M2.57408 17.8128C3.11835 18.3639 3.11834 19.2575 2.57406 19.8087L2.54619 19.8369C2.00191 20.3881 1.11946 20.3881 0.57519 19.8369C0.030919 19.2857 0.0309274 18.3921 0.575208 17.841L0.603083 17.8128C1.14736 17.2616 2.02981 17.2616 2.57408 17.8128Z" fill="url(#paint0_linear_755_3218)" /> <path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M2.57408 17.8128C3.11835 18.3639 3.11834 19.2575 2.57406 19.8087L2.54619 19.8369C2.00191 20.3881 1.11946 20.3881 0.57519 19.8369C0.030919 19.2857 0.0309274 18.3921 0.575208 17.841L0.603083 17.8128C1.14736 17.2616 2.02981 17.2616 2.57408 17.8128Z" fill="url(#paint0_linear_759_16243)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M9.12583 18.2379C9.66912 18.7901 9.66752 19.6837 9.12226 20.2338L5.2617 24.1291C4.71644 24.6792 3.83399 24.6776 3.2907 24.1255C2.74741 23.5733 2.74901 22.6797 3.29427 22.1296L7.15483 18.2343C7.70009 17.6842 8.58254 17.6858 9.12583 18.2379Z" fill="url(#paint1_linear_755_3218)" /> <path opacity="0.7" fill-rule="evenodd" clip-rule="evenodd" d="M9.12583 18.2379C9.66912 18.7901 9.66752 19.6837 9.12226 20.2338L5.2617 24.1291C4.71644 24.6792 3.83399 24.6776 3.2907 24.1255C2.74741 23.5733 2.74901 22.6797 3.29427 22.1296L7.15483 18.2343C7.70009 17.6842 8.58254 17.6858 9.12583 18.2379Z" fill="url(#paint1_linear_759_16243)" />
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M14.3656 24.9431C14.7925 24.2945 15.6578 24.1193 16.2983 24.5516L16.3819 24.6081C17.0224 25.0404 17.1954 25.9167 16.7685 26.5652C16.3415 27.2138 15.4762 27.389 14.8357 26.9567L14.7521 26.9002C14.1117 26.4678 13.9386 25.5916 14.3656 24.9431Z" fill="url(#paint2_linear_759_16243)" />
<path opacity="0.7" fill-rule="evenodd" clip-rule="evenodd" d="M23.5637 17.7278C24.108 18.279 24.108 19.1726 23.5637 19.7237L21.6683 21.6431C21.124 22.1943 20.2415 22.1943 19.6973 21.6431C19.153 21.092 19.153 20.1984 19.6973 19.6472L21.5927 17.7278C22.137 17.1767 23.0194 17.1767 23.5637 17.7278Z" fill="url(#paint3_linear_759_16243)" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.592 10.6292C24.1363 11.1803 24.1363 12.0739 23.592 12.6251L9.58526 26.8089C9.04098 27.36 8.15854 27.36 7.61426 26.8089C7.06999 26.2577 7.06999 25.3641 7.61426 24.8129L21.621 10.6292C22.1653 10.078 23.0477 10.078 23.592 10.6292Z" fill="url(#paint4_linear_759_16243)" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.9426 6.26835C21.4869 6.8195 21.4869 7.7131 20.9426 8.26425L12.887 16.4217C12.3427 16.9728 11.4603 16.9728 10.916 16.4217C10.3717 15.8705 10.3717 14.9769 10.916 14.4258L18.9716 6.26835C19.5159 5.71719 20.3984 5.71719 20.9426 6.26835Z" fill="url(#paint5_linear_759_16243)" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6918 3.50156C17.2364 4.05235 17.237 4.94594 16.6931 5.49747L6.33787 15.9977C5.79396 16.5492 4.91152 16.5498 4.36688 15.999C3.82224 15.4482 3.82164 14.5546 4.36555 14.0031L14.7208 3.5029C15.2647 2.95138 16.1471 2.95078 16.6918 3.50156Z" fill="url(#paint6_linear_759_16243)" />
<path opacity="0.7" fill-rule="evenodd" clip-rule="evenodd" d="M6.43658 6.86284C6.97992 7.41494 6.9784 8.30854 6.43319 8.85874L2.37751 12.9516C1.83229 13.5018 0.94985 13.5002 0.406512 12.9481C-0.136826 12.396 -0.135307 11.5024 0.409905 10.9522L4.46559 6.8594C5.0108 6.3092 5.89324 6.31074 6.43658 6.86284Z" fill="url(#paint7_linear_759_16243)" />
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M8.92007 2.77832C9.6898 2.77832 10.3138 3.41019 10.3138 4.18964V4.33077C10.3138 5.11022 9.6898 5.74209 8.92007 5.74209C8.15035 5.74209 7.52637 5.11022 7.52637 4.33077V4.18964C7.52637 3.41019 8.15035 2.77832 8.92007 2.77832Z" fill="url(#paint8_linear_759_16243)" />
<defs> <defs>
<linearGradient id="paint0_linear_755_3218" x1="1.57463" y1="17.3994" x2="1.57463" y2="20.2503" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_759_16243" x1="1.57463" y1="17.3994" x2="1.57463" y2="20.2503" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" /> <stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" /> <stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient> </linearGradient>
<linearGradient id="paint1_linear_755_3218" x1="6.20827" y1="17.8228" x2="6.20827" y2="24.5406" gradientUnits="userSpaceOnUse"> <linearGradient id="paint1_linear_759_16243" x1="6.20827" y1="17.8228" x2="6.20827" y2="24.5406" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" /> <stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" /> <stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint2_linear_759_16243" x1="15.567" y1="24.3145" x2="15.567" y2="27.1938" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint3_linear_759_16243" x1="21.6305" y1="17.3145" x2="21.6305" y2="22.0565" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint4_linear_759_16243" x1="15.6031" y1="10.2158" x2="15.6031" y2="27.2222" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint5_linear_759_16243" x1="15.9293" y1="5.85498" x2="15.9293" y2="16.835" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint6_linear_759_16243" x1="10.5293" y1="3.08887" x2="10.5293" y2="16.4117" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint7_linear_759_16243" x1="3.42155" y1="6.44775" x2="3.42155" y2="13.3632" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient>
<linearGradient id="paint8_linear_759_16243" x1="8.92007" y1="2.77832" x2="8.92007" y2="5.74209" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.8" />
<stop offset="1" stop-color="white" stop-opacity="0.5" />
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>
</div> </div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-4"> <div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="px-3 py-1.5 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{section} of {totalSections} {section} of {totalSections}
</div> </div>
</div> </div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-5xl font-medium font-['Neue_Montreal'] leading-[48px]"> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate800] text-5xl font-medium font-['Neue_Montreal'] leading-[48px]">
{title} {title}
</div> </div>
<div className="self-stretch justify-center text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal"> <div className="self-stretch justify-center text-[--Neutrals-NeutralSlate500] text-base font-normal font-['Inter'] leading-normal">
{description} {description}
</div> </div>
</div> </div>
</div> </div>
<button <button
onClick={onStart} onClick={onStart}
className="self-stretch px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden hover:bg-Brand-Orange/90 transition-colors" className="self-stretch px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden hover:bg-[--Brand-Orange]/90 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start</div>
@@ -63,9 +98,9 @@ export const FigmaOnboardingIntro: React.FC<FigmaOnboardingIntroProps> = ({
</button> </button>
</div> </div>
</div> </div>
<div className="flex-1 h-[810px] px-20 py-16 flex justify-center items-center gap-2.5 overflow-hidden"> <div className="self-stretch h-[810px] px-20 py-16 inline-flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 self-stretch origin-top-left rotate-180 rounded-3xl inline-flex flex-col justify-center items-center gap-2.5 overflow-hidden"> <div className="flex-1 self-stretch origin-top-left rotate-180 rounded-3xl inline-flex flex-col justify-center items-center gap-2.5 overflow-hidden">
<img className="self-stretch flex-1 object-cover" src="https://placehold.co/560x682" alt="Onboarding illustration" /> <img className="self-stretch flex-1" src="/public/image/onboarding-robot.png" />
</div> </div>
</div> </div>
</div> </div>
@@ -123,17 +158,17 @@ export const FigmaOnboardingQuestion: React.FC<FigmaOnboardingQuestionProps> = (
}; };
return ( return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-36"> <div className="w-[1440px] h-[810px] py-6 relative bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-36">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8"> <div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question} {question}
</div> </div>
<div className="self-stretch min-h-40 p-5 relative bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-start gap-2.5"> <div className="self-stretch min-h-40 p-5 relative bg-[--Neutrals-NeutralSlate100] rounded-xl inline-flex justify-start items-start gap-2.5">
<textarea <textarea
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-base font-normal font-['Inter'] leading-normal placeholder:text-Neutrals-NeutralSlate500 outline-none resize-none" className="flex-1 bg-transparent text-[--Neutrals-NeutralSlate950] text-base font-normal font-['Inter'] leading-normal placeholder:text-[--Neutrals-NeutralSlate950] outline-none resize-none"
placeholder={placeholder} placeholder={placeholder}
rows={rows} rows={rows}
/> />
@@ -146,16 +181,16 @@ export const FigmaOnboardingQuestion: React.FC<FigmaOnboardingQuestionProps> = (
<div className="self-stretch inline-flex justify-start items-start gap-2"> <div className="self-stretch inline-flex justify-start items-start gap-2">
<button <button
onClick={onBack} onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-Neutrals-NeutralSlate200 transition-colors" className="h-12 px-8 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-[--Neutrals-NeutralSlate100] transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div> </div>
</button> </button>
<button <button
onClick={onNext} onClick={onNext}
disabled={!canProceed} disabled={!canProceed}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-Brand-Orange/90 transition-colors" className="flex-1 h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-[--Brand-Orange]/90 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
@@ -163,24 +198,24 @@ export const FigmaOnboardingQuestion: React.FC<FigmaOnboardingQuestionProps> = (
</button> </button>
</div> </div>
</div> </div>
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{sectionPosition} of {totalInSection} {sectionPosition} of {totalInSection}
</div> </div>
</div> </div>
{canSkip && ( {canSkip && (
<div className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200 transition-colors"> <div className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-[--Neutrals-NeutralSlate100] transition-colors">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-none">Skip</div>
</div> </div>
)} )}
<div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4"> <div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4">
<div className="p-4 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="p-4 bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
{renderProgressDots()} {renderProgressDots()}
</div> </div>
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate500 text-base font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate500] text-base font-medium font-['Neue_Montreal'] leading-normal">
{sectionName} {sectionName}
</div> </div>
</div> </div>
@@ -219,7 +254,7 @@ export const FigmaOnboardingMultipleChoice: React.FC<FigmaOnboardingMultipleChoi
for (let i = 1; i <= 7; i++) { for (let i = 1; i <= 7; i++) {
if (i === sectionPosition) { if (i === sectionPosition) {
dots.push( dots.push(
<div key={i} className="w-6 h-1 bg-Brand-Orange rounded-3xl" /> <div key={i} className="w-6 h-1 bg-[--Brand-Orange] rounded-3xl" />
); );
} else { } else {
dots.push( dots.push(
@@ -233,10 +268,10 @@ export const FigmaOnboardingMultipleChoice: React.FC<FigmaOnboardingMultipleChoi
}; };
return ( return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9"> <div className="w-[1440px] h-[810px] py-6 relative bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8"> <div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question} {question}
</div> </div>
<div className="self-stretch inline-flex justify-center items-center gap-3"> <div className="self-stretch inline-flex justify-center items-center gap-3">
@@ -244,17 +279,15 @@ export const FigmaOnboardingMultipleChoice: React.FC<FigmaOnboardingMultipleChoi
<button <button
key={option} key={option}
onClick={() => onSelect(option)} onClick={() => onSelect(option)}
className={`flex-1 h-20 relative rounded-[999px] overflow-hidden transition-colors ${ className={`flex-1 h-20 relative rounded-[999px] overflow-hidden transition-colors ${selectedValue === option
selectedValue === option ? 'bg-[--Neutrals-NeutralSlate800]'
? 'bg-Neutrals-NeutralSlate800' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-[--Neutrals-NeutralSlate50]'
: 'bg-Neutrals-NeutralSlate100 hover:bg-Neutrals-NeutralSlate200' }`}
}`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${ <div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${selectedValue === option
selectedValue === option ? 'text-[--Neutrals-NeutralSlate0]'
? 'text-Neutrals-NeutralSlate0' : 'text-[--Neutrals-NeutralSlate950]'
: 'text-Neutrals-NeutralSlate950' }`}>
}`}>
{option} {option}
</div> </div>
</button> </button>
@@ -264,16 +297,16 @@ export const FigmaOnboardingMultipleChoice: React.FC<FigmaOnboardingMultipleChoi
<div className="self-stretch inline-flex justify-start items-start gap-2"> <div className="self-stretch inline-flex justify-start items-start gap-2">
<button <button
onClick={onBack} onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-Neutrals-NeutralSlate200 transition-colors" className="h-12 px-8 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-[--Neutrals-NeutralSlate100] transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div> </div>
</button> </button>
<button <button
onClick={onNext} onClick={onNext}
disabled={!selectedValue} disabled={!selectedValue}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-Brand-Orange/90 transition-colors" className="flex-1 h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-[--Brand-Orange]/90 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
@@ -281,24 +314,24 @@ export const FigmaOnboardingMultipleChoice: React.FC<FigmaOnboardingMultipleChoi
</button> </button>
</div> </div>
</div> </div>
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none"> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] uppercase leading-none">
{sectionPosition} of {totalInSection} {sectionPosition} of {totalInSection}
</div> </div>
</div> </div>
{canSkip && ( {canSkip && (
<div className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200 transition-colors"> <div className="px-3 py-1.5 right-[24px] top-[24px] absolute bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-[--Neutrals-NeutralSlate100] transition-colors">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-none">Skip</div>
</div> </div>
)} )}
<div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4"> <div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4">
<div className="p-4 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden"> <div className="p-4 bg-[--Neutrals-NeutralSlate100] rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
{renderProgressDots()} {renderProgressDots()}
</div> </div>
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate500 text-base font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate500] text-base font-medium font-['Neue_Montreal'] leading-normal">
{sectionName} {sectionName}
</div> </div>
</div> </div>
@@ -335,16 +368,16 @@ export const FigmaOnboardingForm: React.FC<FigmaOnboardingFormProps> = ({
}; };
return ( return (
<div className="w-[1440px] h-[810px] px-[488px] py-32 bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9"> <div className="w-[1440px] h-[810px] px-[488px] py-32 bg-[--Neutrals-NeutralSlate0] inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12"> <div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8"> <div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal"> <div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate950] text-2xl font-medium font-['Neue_Montreal'] leading-normal">
Company Details Company Details
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight">Company Logo</div> <div className="justify-start text-[--Neutrals-NeutralSlate950] text-sm font-normal font-['Inter'] leading-tight">Company Logo</div>
</div> </div>
<div className="self-stretch p-4 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-start items-center gap-4"> <div className="self-stretch p-4 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-start items-center gap-4">
<div className="w-16 h-16 relative bg-gray-200 rounded-[250px]"> <div className="w-16 h-16 relative bg-gray-200 rounded-[250px]">
@@ -361,7 +394,7 @@ export const FigmaOnboardingForm: React.FC<FigmaOnboardingFormProps> = ({
<path d="M14 2H2M12 8.66667L8 4.66667M8 4.66667L4 8.66667M8 4.66667V14" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M14 2H2M12 8.66667L8 4.66667M8 4.66667L4 8.66667M8 4.66667V14" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Upload image</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Upload image</div>
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
@@ -375,16 +408,16 @@ export const FigmaOnboardingForm: React.FC<FigmaOnboardingFormProps> = ({
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Your Name</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Your Name</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input <input
type="text" type="text"
value={yourName} value={yourName}
onChange={(e) => onYourNameChange(e.target.value)} onChange={(e) => onYourNameChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none" className="flex-1 bg-transparent text-[--Neutrals-NeutralSlate950] text-sm font-normal font-['Inter'] leading-tight placeholder:text-[--Neutrals-NeutralSlate950] outline-none"
placeholder="John Doe" placeholder="John Doe"
/> />
</div> </div>
@@ -392,16 +425,16 @@ export const FigmaOnboardingForm: React.FC<FigmaOnboardingFormProps> = ({
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Company Name</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Company Name</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input <input
type="text" type="text"
value={companyName} value={companyName}
onChange={(e) => onCompanyNameChange(e.target.value)} onChange={(e) => onCompanyNameChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none" className="flex-1 bg-transparent text-[--Neutrals-NeutralSlate950] text-sm font-normal font-['Inter'] leading-tight placeholder:text-[--Neutrals-NeutralSlate950] outline-none"
placeholder="Doe Enterprises" placeholder="Doe Enterprises"
/> />
</div> </div>
@@ -412,7 +445,7 @@ export const FigmaOnboardingForm: React.FC<FigmaOnboardingFormProps> = ({
<button <button
onClick={onNext} onClick={onNext}
disabled={!canProceed} disabled={!canProceed}
className="self-stretch h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-Brand-Orange/90 transition-colors" className="self-stretch h-12 px-4 py-3.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed hover:bg-[--Brand-Orange]/90 transition-colors"
> >
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div> <div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>

View File

@@ -164,7 +164,7 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
{dragOver && ( {dragOver && (
<div className="absolute inset-0 bg-[--Brand-Orange] bg-opacity-20 flex items-center justify-center"> <div className="absolute inset-0 bg-[--Brand-Orange] bg-opacity-20 flex items-center justify-center">
<span className="text-Brand-Orange text-xs font-medium">Drop image</span> <span className="text-[--Brand-Orange] text-xs font-medium">Drop image</span>
</div> </div>
)} )}
</div> </div>

View File

@@ -1,4 +1,4 @@
import { Employee, Report, Submission, FaqItem, CompanyReport } from './types'; import { Employee, EmployeeReport, Submission, FaqItem, CompanyReport } from './types';
// URL Configuration - reads from environment variables with fallbacks // URL Configuration - reads from environment variables with fallbacks
export const SITE_URL = import.meta.env.VITE_SITE_URL || 'http://localhost:5173'; export const SITE_URL = import.meta.env.VITE_SITE_URL || 'http://localhost:5173';
@@ -24,7 +24,7 @@ if (import.meta.env.DEV) {
export const EMPLOYEES: Employee[] = []; export const EMPLOYEES: Employee[] = [];
// DEPRECATED: Sample report data - real reports should be generated via /generateEmployeeReport API // DEPRECATED: Sample report data - real reports should be generated via /generateEmployeeReport API
export const REPORT_DATA: Report = { export const REPORT_DATA: EmployeeReport = {
employeeId: 'sample', employeeId: 'sample',
department: 'Sample Department', department: 'Sample Department',
role: 'Sample Role', role: 'Sample Role',
@@ -45,11 +45,14 @@ export const REPORT_DATA: Report = {
weaknesses: [ weaknesses: [
{ isCritical: false, description: 'Sample data - use AI-generated reports' } { isCritical: false, description: 'Sample data - use AI-generated reports' }
], ],
opportunities: { opportunities: [
roleAdjustment: 'Sample data', {
accountabilitySupport: 'Sample data', roleAdjustment: 'Sample data',
}, accountabilitySupport: 'Sample data',
}
],
risks: ['Sample data - use AI-generated reports'], risks: ['Sample data - use AI-generated reports'],
recommendations: ['Sample recommendation - use AI-generated reports'],
recommendation: { recommendation: {
action: 'Keep', action: 'Keep',
details: ['Sample data - use AI-generated reports'], details: ['Sample data - use AI-generated reports'],
@@ -109,22 +112,159 @@ export const SAMPLE_COMPANY_REPORT: CompanyReport = {
id: 'placeholder-report', id: 'placeholder-report',
createdAt: Date.now(), createdAt: Date.now(),
overview: { overview: {
totalEmployees: 0, totalEmployees: 8,
departmentBreakdown: [], departmentBreakdown: [
submissionRate: 0, { department: 'Campaigns', count: 3 },
{ department: 'Social Media', count: 2 },
{ department: 'Creative', count: 2 },
{ department: 'Tech', count: 1 }
],
submissionRate: 87.5,
lastUpdated: Date.now(), lastUpdated: Date.now(),
averagePerformanceScore: 0, averagePerformanceScore: 8.2,
riskLevel: 'Low' riskLevel: 'Low'
}, },
gradingBreakdown: [], weaknesses: [
{ title: 'Lack of Structure', description: 'People are confused about their roles. No one knows who\'s responsible.' },
{ title: 'Communication Gaps', description: 'Information doesn\'t flow effectively between departments.' }
],
gradingBreakdown: {
departmentNameShort: 'Campaigns',
departmentName: 'Campaigns Department',
lead: 'Sarah Johnson',
support: 'Mike Chen',
departmentGrade: 'A',
executiveSummary: 'Campaigns team shows exceptional performance with strong collaboration and innovative approaches to client work.',
teamScores: [
{
employeeName: 'Sarah Johnson',
grade: 'A+',
reliability: 9,
roleFit: 10,
scalability: 8,
output: 9,
initiative: 9
},
{
employeeName: 'Mike Chen',
grade: 'A',
reliability: 8,
roleFit: 9,
scalability: 7,
output: 8,
initiative: 8
},
{
employeeName: 'Lisa Wong',
grade: 'B+',
reliability: 7,
roleFit: 8,
scalability: 6,
output: 7,
initiative: 7
},
{
employeeName: 'Alex Rivera',
grade: 'A',
reliability: 8,
roleFit: 9,
scalability: 7,
output: 8,
initiative: 8
},
{
employeeName: 'Jamie Park',
grade: 'B',
reliability: 6,
roleFit: 7,
scalability: 6,
output: 6,
initiative: 7
},
{
employeeName: 'Taylor Smith',
grade: 'A',
reliability: 9,
roleFit: 9,
scalability: 8,
output: 9,
initiative: 8
},
{
employeeName: 'Jordan Lee',
grade: 'B+',
reliability: 7,
roleFit: 8,
scalability: 7,
output: 8,
initiative: 7
},
{
employeeName: 'Casey Johnson',
grade: 'A+',
reliability: 10,
roleFit: 9,
scalability: 9,
output: 10,
initiative: 9
}
]
},
operatingPlan: { nextQuarterGoals: [], keyInitiatives: [], resourceNeeds: [], riskMitigation: [] }, operatingPlan: { nextQuarterGoals: [], keyInitiatives: [], resourceNeeds: [], riskMitigation: [] },
personnelChanges: { newHires: [], promotions: [], departures: [] }, personnelChanges: {
newHires: [
{ name: 'Emma Wilson', department: 'Creative', role: 'Graphic Designer', impact: 'Will strengthen visual design capabilities' }
],
promotions: [
{ name: 'Sarah Johnson', fromRole: 'Campaign Manager', toRole: 'Senior Campaign Manager', impact: 'Will lead larger client accounts' }
],
departures: []
},
keyPersonnelChanges: [], keyPersonnelChanges: [],
immediateHiringNeeds: [], immediateHiringNeeds: [
forwardOperatingPlan: { { department: 'Tech', role: 'Frontend Developer', priority: 'High', reasoning: 'Need to expand web development capacity for growing client demands' },
quarterlyGoals: [], { department: 'Social Media', role: 'Content Creator', priority: 'Medium', reasoning: 'Additional support needed for video content production' }
resourceNeeds: [], ],
riskMitigation: [] recommendations: [
'Hire additional frontend developer for tech team expansion',
'Implement cross-departmental collaboration sessions',
'Develop leadership training program for senior staff',
'Establish mentorship program for junior employees'
],
forwardOperatingPlan: [
{
title: 'Q1 Client Growth Initiative',
details: ['Expand campaign team by 2 members', 'Implement new project management system', 'Launch client referral program']
},
{
title: 'Technology Infrastructure',
details: ['Upgrade development tools', 'Implement automated testing', 'Enhance security protocols']
}
],
strengths: [
'Strong creative vision and execution across all projects',
'Excellent client satisfaction ratings (4.8/5)',
'Highly collaborative team culture',
'Innovative approach to campaign development',
'Reliable project delivery within deadlines'
],
organizationalImpactSummary: {
missionCritical: [
{ employeeName: 'Sarah Johnson', impact: 'Lead Campaign Manager', description: 'Manages top-tier client relationships and drives 40% of company revenue' },
{ employeeName: 'Casey Johnson', impact: 'Tech Infrastructure', description: 'Maintains all technical systems and client delivery platforms' }
],
highlyValuable: [
{ employeeName: 'Alex Rivera', impact: 'Social Media Strategy', description: 'Develops social strategies that increase client engagement by 150%' },
{ employeeName: 'Taylor Smith', impact: 'Creative Director', description: 'Ensures brand consistency and award-winning creative output' }
],
coreSupport: [
{ employeeName: 'Mike Chen', impact: 'Campaign Support', description: 'Reliable execution of campaign tasks and client communication' },
{ employeeName: 'Jordan Lee', impact: 'Design Support', description: 'Produces high-quality visual assets and maintains design standards' }
],
lowCriticality: [
{ employeeName: 'Jamie Park', impact: 'Content Assistant', description: 'Supports content creation with room for skill development' },
{ employeeName: 'Morgan Davis', impact: 'Administrative Support', description: 'Handles routine operations and documentation' }
]
}, },
organizationalStrengths: [], organizationalStrengths: [],
organizationalRisks: [], organizationalRisks: [],

View File

@@ -1,6 +1,6 @@
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useState } from 'react';
import { useAuth } from './AuthContext'; import { useAuth } from './AuthContext';
import { Employee, Report, Submission, CompanyReport } from '../types'; import { Employee, EmployeeReport, Submission, CompanyReport } from '../types';
import { SAMPLE_COMPANY_REPORT, API_URL } from '../constants'; import { SAMPLE_COMPANY_REPORT, API_URL } from '../constants';
import { apiPost, apiPut } from '../services/api'; import { apiPost, apiPut } from '../services/api';
import { User } from 'firebase/auth'; import { User } from 'firebase/auth';
@@ -9,29 +9,10 @@ import { secureApi } from '../services/secureApi';
interface OrgData { interface OrgData {
orgId: string; orgId: string;
name: string; companyName?: string;
industry?: string; onboardingData?: Record<string, any>;
size?: string; companyLogo?: string;
description?: string; updatedAt?: number;
mission?: string;
vision?: string;
values?: string;
foundingYear?: string;
evolution?: string;
majorMilestones?: string;
advantages?: string;
vulnerabilities?: string;
competitors?: string;
marketPosition?: string;
currentChallenges?: string;
shortTermGoals?: string;
longTermGoals?: string;
keyMetrics?: string;
cultureDescription?: string;
workEnvironment?: string;
leadershipStyle?: string;
communicationStyle?: string;
additionalContext?: string;
onboardingCompleted?: boolean; onboardingCompleted?: boolean;
} }
@@ -41,16 +22,16 @@ interface OrgContextType {
orgId: string; orgId: string;
employees: Employee[]; employees: Employee[];
submissions: Record<string, Submission>; submissions: Record<string, Submission>;
reports: Record<string, Report>; reports: Record<string, EmployeeReport>;
loading: boolean; loading: boolean;
upsertOrg: (data: Partial<OrgData>) => Promise<void>; upsertOrg: (data: Partial<OrgData>) => Promise<void>;
saveReport: (employeeId: string, report: Report) => Promise<void>; saveReport: (employeeId: string, report: EmployeeReport) => Promise<void>;
inviteEmployee: (args: { name: string; email: string }) => Promise<{ employeeId: string; inviteLink: string }>; inviteEmployee: (args: { name: string; email: string }) => Promise<{ employeeId: string; inviteLink: string }>;
issueInviteViaApi: (args: { name: string; email: string; role?: string; department?: string }) => Promise<{ code: string; inviteLink: string; emailLink: string; employee: any }>; issueInviteViaApi: (args: { name: string; email: string; role?: string; department?: string }) => Promise<{ code: string; inviteLink: string; emailLink: string; employee: any }>;
getInviteStatus: (code: string) => Promise<{ used: boolean; employee: any } | null>; getInviteStatus: (code: string) => Promise<{ used: boolean; employee: any } | null>;
consumeInvite: (code: string) => Promise<{ employee: any; orgId?: string } | null>; consumeInvite: (code: string) => Promise<{ employee: any; orgId?: string } | null>;
getReportVersions: (employeeId: string) => Promise<Array<{ id: string; createdAt: number; report: Report }>>; getReportVersions: (employeeId: string) => Promise<Array<{ id: string; createdAt: number; report: EmployeeReport }>>;
saveReportVersion: (employeeId: string, report: Report) => Promise<void>; saveReportVersion: (employeeId: string, report: EmployeeReport) => Promise<void>;
acceptInvite: (code: string) => Promise<void>; acceptInvite: (code: string) => Promise<void>;
saveCompanyReport: (summary: string) => Promise<void>; saveCompanyReport: (summary: string) => Promise<void>;
getCompanyReportHistory: () => Promise<Array<{ id: string; createdAt: number; summary: string }>>; getCompanyReportHistory: () => Promise<Array<{ id: string; createdAt: number; summary: string }>>;
@@ -61,9 +42,9 @@ interface OrgContextType {
seedInitialData: () => Promise<void>; seedInitialData: () => Promise<void>;
isOwner: (employeeId?: string) => boolean; isOwner: (employeeId?: string) => boolean;
submitEmployeeAnswers: (employeeId: string, answers: Record<string, string>) => Promise<any>; submitEmployeeAnswers: (employeeId: string, answers: Record<string, string>) => Promise<any>;
generateEmployeeReport: (employee: Employee) => Promise<Report | null>; generateEmployeeReport: (employee: Employee) => Promise<EmployeeReport | null>;
getEmployeeReport: (employeeId: string) => Promise<{ success: boolean; report?: Report; error?: string }>; getEmployeeReport: (employeeId: string) => Promise<{ success: boolean; report?: EmployeeReport; error?: string }>;
getEmployeeReports: () => Promise<{ success: boolean; reports?: Report[]; error?: string }>; getEmployeeReports: () => Promise<{ success: boolean; reports?: EmployeeReport[]; error?: string }>;
} }
const OrgContext = createContext<OrgContextType | undefined>(undefined); const OrgContext = createContext<OrgContextType | undefined>(undefined);
@@ -73,8 +54,8 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
const [org, setOrg] = useState<OrgData | null>(null); const [org, setOrg] = useState<OrgData | null>(null);
const [employees, setEmployees] = useState<Employee[]>([]); const [employees, setEmployees] = useState<Employee[]>([]);
const [submissions, setSubmissions] = useState<Record<string, Submission>>({}); const [submissions, setSubmissions] = useState<Record<string, Submission>>({});
const [reports, setReports] = useState<Record<string, Report>>({}); const [reports, setReports] = useState<Record<string, EmployeeReport>>({});
const [reportVersions, setReportVersions] = useState<Record<string, Array<{ id: string; createdAt: number; report: Report }>>>({}); const [reportVersions, setReportVersions] = useState<Record<string, Array<{ id: string; createdAt: number; report: EmployeeReport }>>>({});
const [companyReports, setCompanyReports] = useState<Array<{ id: string; createdAt: number; summary: string }>>([]); const [companyReports, setCompanyReports] = useState<Array<{ id: string; createdAt: number; summary: string }>>([]);
const [fullCompanyReports, setFullCompanyReports] = useState<CompanyReport[]>([]); const [fullCompanyReports, setFullCompanyReports] = useState<CompanyReport[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -97,19 +78,19 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
// Load organization data // Load organization data
try { try {
const orgData = await secureApi.getOrgData(orgId, user.uid); const orgData = await secureApi.getOrgData();
setOrg({ orgId, ...orgData }); setOrg({ orgId, ...orgData });
} catch (error) { } catch (error) {
console.warn('Could not load org data, creating default:', error); console.warn('Could not load org data, creating default:', error);
// Create default org if not found // Create default org if not found
const defaultOrg = { name: 'Your Company', onboardingCompleted: false }; const defaultOrg = { name: 'Your Company', onboardingCompleted: false };
await secureApi.updateOrgData(orgId, user.uid, defaultOrg); await secureApi.updateOrgData(defaultOrg);
setOrg({ orgId, ...defaultOrg }); setOrg({ orgId, ...defaultOrg });
} }
// Load employees // Load employees
try { try {
const employeesData = await secureApi.getEmployees(orgId, user.uid); const employeesData = await secureApi.getEmployees();
setEmployees(employeesData.map(emp => ({ setEmployees(employeesData.map(emp => ({
...emp, ...emp,
initials: emp.name ? emp.name.split(' ').map(n => n[0]).join('').toUpperCase() : emp.email?.substring(0, 2).toUpperCase() || 'U' initials: emp.name ? emp.name.split(' ').map(n => n[0]).join('').toUpperCase() : emp.email?.substring(0, 2).toUpperCase() || 'U'
@@ -121,7 +102,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
// Load submissions // Load submissions
try { try {
const submissionsData = await secureApi.getSubmissions(orgId, user.uid); const submissionsData = await secureApi.getSubmissions();
setSubmissions(submissionsData); setSubmissions(submissionsData);
} catch (error) { } catch (error) {
console.warn('Could not load submissions:', error); console.warn('Could not load submissions:', error);
@@ -130,8 +111,8 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
// Load reports // Load reports
try { try {
const reportsData = await secureApi.getReports(orgId, user.uid); const reportsData = await secureApi.getReports();
setReports(reportsData as Record<string, Report>); setReports(reportsData as Record<string, EmployeeReport>);
} catch (error) { } catch (error) {
console.warn('Could not load reports:', error); console.warn('Could not load reports:', error);
setReports({}); setReports({});
@@ -139,7 +120,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
// Load company reports // Load company reports
try { try {
const companyReportsData = await secureApi.getCompanyReports(orgId, user.uid); const companyReportsData = await secureApi.getCompanyReports();
setFullCompanyReports(companyReportsData); setFullCompanyReports(companyReportsData);
} catch (error) { } catch (error) {
console.warn('Could not load company reports:', error); console.warn('Could not load company reports:', error);
@@ -162,7 +143,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
try { try {
await secureApi.updateOrgData(orgId, user.uid, data); await secureApi.updateOrgData(data);
// Update local state // Update local state
const updatedOrg = { ...(org || { orgId, name: 'Your Company' }), ...data } as OrgData; const updatedOrg = { ...(org || { orgId, name: 'Your Company' }), ...data } as OrgData;
@@ -185,13 +166,13 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
}; };
const saveReport = async (employeeId: string, report: Report) => { const saveReport = async (employeeId: string, report: EmployeeReport) => {
if (!user?.uid) { if (!user?.uid) {
throw new Error('User authentication required'); throw new Error('User authentication required');
} }
try { try {
const savedReport = await secureApi.saveReport(orgId, user.uid, employeeId, report); const savedReport = await secureApi.saveReport(employeeId, report);
// Update local state // Update local state
setReports(prev => ({ ...prev, [employeeId]: savedReport })); setReports(prev => ({ ...prev, [employeeId]: savedReport }));
@@ -206,7 +187,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
try { try {
// Use secure API for invites // Use secure API for invites
const data = await secureApi.createInvitation(orgId, name, email); const data = await secureApi.createInvitation(name, email);
console.log('Invite created successfully:', { code: data.code, employee: data.employee.name, inviteLink: data.inviteLink }); console.log('Invite created successfully:', { code: data.code, employee: data.employee.name, inviteLink: data.inviteLink });
@@ -241,7 +222,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
return []; return [];
}; };
const saveReportVersion = async (employeeId: string, report: Report) => { const saveReportVersion = async (employeeId: string, report: EmployeeReport) => {
// This feature is not yet implemented in secure API // This feature is not yet implemented in secure API
console.warn('Report versions not yet supported in secure API'); console.warn('Report versions not yet supported in secure API');
const version = { id: Date.now().toString(), createdAt: Date.now(), report }; const version = { id: Date.now().toString(), createdAt: Date.now(), report };
@@ -271,7 +252,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
summary summary
}; };
await secureApi.saveCompanyReport(orgId, report); await secureApi.saveCompanyReport(report);
// Update local state // Update local state
setCompanyReports(prev => [{ id: report.id, createdAt: report.createdAt, summary }, ...prev]); setCompanyReports(prev => [{ id: report.id, createdAt: report.createdAt, summary }, ...prev]);
@@ -287,11 +268,11 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
try { try {
const reports = await secureApi.getCompanyReports(orgId, user.uid); const reports = await secureApi.getCompanyReports();
return reports.map(report => ({ return reports.map(report => ({
id: report.id, id: report.id,
createdAt: report.createdAt || 0, createdAt: report.createdAt || 0,
summary: report.summary || '' summary: report.executiveSummary || ''
})).sort((a, b) => b.createdAt - a.createdAt); })).sort((a, b) => b.createdAt - a.createdAt);
} catch (error) { } catch (error) {
console.error('Failed to get company report history:', error); console.error('Failed to get company report history:', error);
@@ -318,7 +299,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
try { try {
await secureApi.saveCompanyReport(orgId, report); await secureApi.saveCompanyReport(report);
// Update local state after successful save // Update local state after successful save
setFullCompanyReports(prev => [report, ...prev]); setFullCompanyReports(prev => [report, ...prev]);
@@ -334,7 +315,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
try { try {
const reports = await secureApi.getCompanyReports(orgId, user.uid); const reports = await secureApi.getCompanyReports();
return reports.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); return reports.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
} catch (error) { } catch (error) {
console.error('Failed to get full company report history:', error); console.error('Failed to get full company report history:', error);
@@ -477,7 +458,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
// Use secure API for all employee report operations // Use secure API for all employee report operations
const report = await secureApi.getReports(orgId, user.uid); const report = await secureApi.getReports();
const employeeReport = report[employeeId]; const employeeReport = report[employeeId];
if (employeeReport) { if (employeeReport) {
@@ -497,7 +478,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
// Use secure API for all employee report operations // Use secure API for all employee report operations
const reportsData = await secureApi.getReports(orgId, user.uid); const reportsData = await secureApi.getReports();
const reports = Object.values(reportsData); const reports = Object.values(reportsData);
return { success: true, reports }; return { success: true, reports };
} catch (error) { } catch (error) {
@@ -533,7 +514,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
throw new Error('User authentication required'); throw new Error('User authentication required');
} }
const data = await secureApi.createInvitation(orgId, name, email, role, department); const data = await secureApi.createInvitation(name, email, role, department);
// Optimistically add employee shell (not yet active until consume) // Optimistically add employee shell (not yet active until consume)
setEmployees(prev => prev.find(e => e.id === data.employee.id) ? prev : [...prev, { setEmployees(prev => prev.find(e => e.id === data.employee.id) ? prev : [...prev, {
@@ -580,7 +561,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
} }
// Use secure API for submission // Use secure API for submission
await secureApi.submitEmployeeAnswers(orgId, employeeId, answers); await secureApi.submitEmployeeAnswers(employeeId, answers);
// Update local state for immediate UI feedback // Update local state for immediate UI feedback
const convertedSubmission: Submission = { const convertedSubmission: Submission = {
@@ -647,7 +628,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
if ((data as any).report) { if ((data as any).report) {
console.log('Employee report generated successfully'); console.log('Employee report generated successfully');
const report = (data as any).report as Report; const report = (data as any).report as EmployeeReport;
setReports(prev => ({ ...prev, [employee.id]: report })); setReports(prev => ({ ...prev, [employee.id]: report }));
return report; return report;
} else { } else {

View File

@@ -1,303 +1,258 @@
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useState } from 'react';
import { useAuth } from './AuthContext'; import { useAuth } from './AuthContext';
import { isFirebaseConfigured } from '../services/firebase'; import { isFirebaseConfigured } from '../services/firebase';
import { API_URL } from '../constants'; import { API_URL } from '../constants';
import { demoStorage } from '../services/demoStorage'; import { secureApi } from '../services/secureApi';
interface UserOrganization { interface UserOrganization {
orgId: string; orgId: string;
name: string; name: string;
role: 'owner' | 'admin' | 'employee'; role: 'owner' | 'admin' | 'employee';
onboardingCompleted: boolean; onboardingCompleted: boolean;
joinedAt: number; joinedAt: number;
} }
interface UserOrganizationsContextType { interface UserOrganizationsContextType {
organizations: UserOrganization[]; organizations: UserOrganization[];
selectedOrgId: string | null; selectedOrgId: string | null;
loading: boolean; loading: boolean;
selectOrganization: (orgId: string) => void; selectOrganization: (orgId: string) => void;
createOrganization: (name: string) => Promise<{ orgId: string; requiresSubscription?: boolean }>; createOrganization: (name: string) => Promise<{ orgId: string; requiresSubscription?: boolean }>;
joinOrganization: (inviteCode: string) => Promise<string>; joinOrganization: (inviteCode: string) => Promise<string>;
refreshOrganizations: () => Promise<void>; refreshOrganizations: () => Promise<void>;
createCheckoutSession: (orgId: string, userEmail: string) => Promise<{ sessionUrl: string; sessionId: string }>; createCheckoutSession: (userEmail: string) => Promise<{ sessionUrl: string; sessionId: string }>;
getSubscriptionStatus: (orgId: string) => Promise<any>; getSubscriptionStatus: () => Promise<any>;
} }
const UserOrganizationsContext = createContext<UserOrganizationsContextType | undefined>(undefined); const UserOrganizationsContext = createContext<UserOrganizationsContextType | undefined>(undefined);
export const UserOrganizationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const UserOrganizationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user } = useAuth(); const { user } = useAuth();
const [organizations, setOrganizations] = useState<UserOrganization[]>([]); const [organizations, setOrganizations] = useState<UserOrganization[]>([]);
const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null); const [selectedOrgId, setSelectedOrgId] = useState<string | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
// Load user's organizations // Load user's organizations
const loadOrganizations = async () => { const loadOrganizations = async () => {
if (!user) { if (!user) {
setOrganizations([]); setOrganizations([]);
setLoading(false); setLoading(false);
return; return;
} }
try { try {
// Firebase mode - fetch from Cloud Functions // Use secure API for organizations
const response = await fetch(`${API_URL}/getUserOrganizations?userId=${user.uid}`); const data = await secureApi.getUserOrganizations();
if (response.ok) { setOrganizations(data.organizations || []);
const data = await response.json(); } catch (error) {
setOrganizations(data.organizations || []); console.error('Failed to load organizations:', error);
} else { setOrganizations([]);
console.error('Failed to load organizations:', response.status); } finally {
setOrganizations([]); setLoading(false);
} }
} catch (error) { };
console.error('Failed to load organizations:', error);
setOrganizations([]); // Initialize selected org from localStorage (persistent across sessions)
} finally { useEffect(() => {
setLoading(false); const savedOrgId = localStorage.getItem('auditly_selected_org');
} if (savedOrgId) {
}; setSelectedOrgId(savedOrgId);
}
// Initialize selected org from localStorage (persistent across sessions) }, []);
useEffect(() => {
const savedOrgId = localStorage.getItem('auditly_selected_org'); // Load organizations when user changes
if (savedOrgId) { useEffect(() => {
setSelectedOrgId(savedOrgId); loadOrganizations();
} }, [user]);
}, []);
// Listen for organization updates (e.g., onboarding completion)
// Load organizations when user changes useEffect(() => {
useEffect(() => { const handleOrgUpdate = (event: CustomEvent) => {
loadOrganizations(); const { orgId, onboardingCompleted } = event.detail;
}, [user]); console.log('UserOrganizationsContext received org update:', { orgId, onboardingCompleted });
// Listen for organization updates (e.g., onboarding completion) if (onboardingCompleted && orgId) {
useEffect(() => { // Update the specific organization in the list to reflect onboarding completion
const handleOrgUpdate = (event: CustomEvent) => { setOrganizations(prev => {
const { orgId, onboardingCompleted } = event.detail; const updated = prev.map(org =>
console.log('UserOrganizationsContext received org update:', { orgId, onboardingCompleted }); org.orgId === orgId
? { ...org, onboardingCompleted: true }
if (onboardingCompleted && orgId) { : org
// Update the specific organization in the list to reflect onboarding completion );
setOrganizations(prev => { console.log('Updated organizations after onboarding completion:', updated);
const updated = prev.map(org => return updated;
org.orgId === orgId });
? { ...org, onboardingCompleted: true } }
: org };
);
console.log('Updated organizations after onboarding completion:', updated); window.addEventListener('organizationUpdated', handleOrgUpdate as EventListener);
return updated;
}); return () => {
} window.removeEventListener('organizationUpdated', handleOrgUpdate as EventListener);
}; };
}, []);
window.addEventListener('organizationUpdated', handleOrgUpdate as EventListener);
const selectOrganization = (orgId: string) => {
return () => { console.log('Switching to organization:', orgId);
window.removeEventListener('organizationUpdated', handleOrgUpdate as EventListener);
}; // Clear any cached data when switching organizations for security
}, []); sessionStorage.removeItem('auditly_cached_employees');
sessionStorage.removeItem('auditly_cached_submissions');
const selectOrganization = (orgId: string) => { sessionStorage.removeItem('auditly_cached_reports');
console.log('Switching to organization:', orgId);
setSelectedOrgId(orgId);
// Clear any cached data when switching organizations for security localStorage.setItem('auditly_selected_org', orgId);
sessionStorage.removeItem('auditly_cached_employees');
sessionStorage.removeItem('auditly_cached_submissions'); // Dispatch event to notify other contexts about the org switch
sessionStorage.removeItem('auditly_cached_reports'); window.dispatchEvent(new CustomEvent('organizationChanged', {
detail: { newOrgId: orgId }
setSelectedOrgId(orgId); }));
localStorage.setItem('auditly_selected_org', orgId); };
// Dispatch event to notify other contexts about the org switch const createOrganization = async (name: string): Promise<{ orgId: string; requiresSubscription?: boolean }> => {
window.dispatchEvent(new CustomEvent('organizationChanged', { if (!user) throw new Error('User not authenticated');
detail: { newOrgId: orgId }
})); try {
}; let newOrg: UserOrganization;
let requiresSubscription = false;
const createOrganization = async (name: string): Promise<{ orgId: string; requiresSubscription?: boolean }> => {
if (!user) throw new Error('User not authenticated'); // Use secure API for organization creation
const data = await secureApi.createOrganization(name);
try {
let newOrg: UserOrganization; newOrg = {
let requiresSubscription = false; orgId: data.orgId,
name: data.name,
// Firebase mode - use Cloud Function role: data.role,
const response = await fetch(`${API_URL}/createOrganization`, { onboardingCompleted: data.onboardingCompleted,
method: 'POST', joinedAt: data.joinedAt
headers: { 'Content-Type': 'application/json' }, };
body: JSON.stringify({ name, userId: user.uid })
}); requiresSubscription = data.requiresSubscription || false;
setOrganizations(prev => [...prev, newOrg]);
if (!response.ok) {
throw new Error(`Failed to create organization: ${response.status}`); return { orgId: newOrg.orgId, requiresSubscription };
} } catch (error) {
console.error('Failed to create organization:', error);
const data = await response.json(); throw error;
newOrg = { }
orgId: data.orgId, };
name: data.name,
role: data.role, const joinOrganization = async (inviteCode: string): Promise<string> => {
onboardingCompleted: data.onboardingCompleted, if (!user) throw new Error('User not authenticated');
joinedAt: data.joinedAt
}; try {
// if (!isFirebaseConfigured) {
requiresSubscription = data.requiresSubscription || false; // // Demo mode - use server API to get and consume invite
setOrganizations(prev => [...prev, newOrg]); // const inviteStatusRes = await fetch(`/api/invitations/${inviteCode}`);
// if (!inviteStatusRes.ok) {
return { orgId: newOrg.orgId, requiresSubscription }; // throw new Error('Invalid or expired invite code');
} catch (error) { // }
console.error('Failed to create organization:', error);
throw error; // const inviteData = await inviteStatusRes.json();
} // if (inviteData.used) {
}; // throw new Error('Invite code has already been used');
// }
const joinOrganization = async (inviteCode: string): Promise<string> => {
if (!user) throw new Error('User not authenticated'); // // Consume the invite
// const consumeRes = await fetch(`/api/invitations/${inviteCode}/consume`, {
try { // method: 'POST'
// if (!isFirebaseConfigured) { // });
// // Demo mode - use server API to get and consume invite // if (!consumeRes.ok) {
// const inviteStatusRes = await fetch(`/api/invitations/${inviteCode}`); // throw new Error('Failed to consume invite');
// if (!inviteStatusRes.ok) { // }
// throw new Error('Invalid or expired invite code');
// } // const consumedData = await consumeRes.json();
// const orgId = consumedData.orgId;
// const inviteData = await inviteStatusRes.json();
// if (inviteData.used) { // // Get organization data (this might be from localStorage for demo mode)
// throw new Error('Invite code has already been used'); // const orgData = demoStorage.getOrganization(orgId);
// } // if (!orgData) {
// throw new Error('Organization not found');
// // Consume the invite // }
// const consumeRes = await fetch(`/api/invitations/${inviteCode}/consume`, {
// method: 'POST' // const userOrg: UserOrganization = {
// }); // orgId: orgId,
// if (!consumeRes.ok) { // name: orgData.name,
// throw new Error('Failed to consume invite'); // role: 'employee',
// } // onboardingCompleted: orgData.onboardingCompleted || false,
// joinedAt: Date.now()
// const consumedData = await consumeRes.json(); // };
// const orgId = consumedData.orgId;
// setOrganizations(prev => [...prev, userOrg]);
// // Get organization data (this might be from localStorage for demo mode) // return orgId;
// const orgData = demoStorage.getOrganization(orgId); // } else {
// if (!orgData) { // Firebase mode - use Cloud Function
// throw new Error('Organization not found'); // Use secure API for joining organization
// } const data = await secureApi.joinOrganization(inviteCode);
// const userOrg: UserOrganization = { const userOrg: UserOrganization = {
// orgId: orgId, orgId: data.orgId,
// name: orgData.name, name: data.name,
// role: 'employee', role: data.role,
// onboardingCompleted: orgData.onboardingCompleted || false, onboardingCompleted: data.onboardingCompleted,
// joinedAt: Date.now() joinedAt: data.joinedAt
// }; };
// setOrganizations(prev => [...prev, userOrg]); setOrganizations(prev => [...prev, userOrg]);
// return orgId; return data.orgId;
// } else { // }
// Firebase mode - use Cloud Function } catch (error) {
const response = await fetch(`${API_URL}/joinOrganization`, { console.error('Failed to join organization:', error);
method: 'POST', throw error;
headers: { 'Content-Type': 'application/json' }, }
body: JSON.stringify({ userId: user.uid, inviteCode }) };
});
const refreshOrganizations = async () => {
if (!response.ok) { setLoading(true);
const errorData = await response.json(); await loadOrganizations();
throw new Error(errorData.error || 'Failed to join organization'); };
}
const createCheckoutSession = async (userEmail: string): Promise<{ sessionUrl: string; sessionId: string }> => {
const data = await response.json(); if (!user) throw new Error('User not authenticated');
const userOrg: UserOrganization = {
orgId: data.orgId, try {
name: data.name, const data = await secureApi.createCheckoutSession(userEmail);
role: data.role, return {
onboardingCompleted: data.onboardingCompleted, sessionUrl: data.sessionUrl,
joinedAt: data.joinedAt sessionId: data.sessionId
}; };
} catch (error) {
setOrganizations(prev => [...prev, userOrg]); console.error('Failed to create checkout session:', error);
return data.orgId; throw error;
// } }
} catch (error) { };
console.error('Failed to join organization:', error);
throw error; const getSubscriptionStatus = async () => {
} try {
}; const data = await secureApi.getSubscriptionStatus();
return data;
const refreshOrganizations = async () => { } catch (error) {
setLoading(true); console.error('Failed to get subscription status:', error);
await loadOrganizations(); throw error;
}; }
};
const createCheckoutSession = async (orgId: string, userEmail: string): Promise<{ sessionUrl: string; sessionId: string }> => {
if (!user) throw new Error('User not authenticated'); return (
<UserOrganizationsContext.Provider value={{
try { organizations,
const response = await fetch(`${API_URL}/createCheckoutSession`, { selectedOrgId,
method: 'POST', loading,
headers: { 'Content-Type': 'application/json' }, selectOrganization,
body: JSON.stringify({ createOrganization,
orgId, joinOrganization,
userId: user.uid, refreshOrganizations,
userEmail createCheckoutSession,
}) getSubscriptionStatus
}); }}>
{children}
if (!response.ok) { </UserOrganizationsContext.Provider>
const errorData = await response.json(); );
throw new Error(errorData.error || 'Failed to create checkout session'); };
}
export const useUserOrganizations = () => {
const data = await response.json(); const context = useContext(UserOrganizationsContext);
return { if (!context) {
sessionUrl: data.sessionUrl, throw new Error('useUserOrganizations must be used within UserOrganizationsProvider');
sessionId: data.sessionId }
}; return context;
} catch (error) { };
console.error('Failed to create checkout session:', error);
throw error;
}
};
const getSubscriptionStatus = async (orgId: string) => {
try {
const response = await fetch(`${API_URL}/getSubscriptionStatus?orgId=${orgId}`);
if (!response.ok) {
throw new Error('Failed to get subscription status');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to get subscription status:', error);
throw error;
}
};
return (
<UserOrganizationsContext.Provider value={{
organizations,
selectedOrgId,
loading,
selectOrganization,
createOrganization,
joinOrganization,
refreshOrganizations,
createCheckoutSession,
getSubscriptionStatus
}}>
{children}
</UserOrganizationsContext.Provider>
);
};
export const useUserOrganizations = () => {
const context = useContext(UserOrganizationsContext);
if (!context) {
throw new Error('useUserOrganizations must be used within UserOrganizationsProvider');
}
return context;
};

View File

@@ -19,13 +19,18 @@ export interface OnboardingStep {
} }
export interface OnboardingData { export interface OnboardingData {
companyName: string;
yourName: string;
companyLogo: string;
[key: string]: string | string[]; [key: string]: string | string[];
} }
export const initializeOnboardingData = (): OnboardingData => { export const initializeOnboardingData = (): OnboardingData => {
const data: OnboardingData = { const data: OnboardingData = {
// Ensure required form fields are initialized // Ensure required form fields are initialized
companyDetails: '', companyName: '',
yourName: '',
companyLogo: '',
}; };
onboardingSteps.forEach(step => { onboardingSteps.forEach(step => {
if (step.field) { if (step.field) {

View File

@@ -1,526 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useOrg } from '../contexts/OrgContext';
import { Card, Button } from '../components/UiKit';
import { CompanyReport, Employee, Report } from '../types';
import RadarPerformanceChart from '../components/charts/RadarPerformanceChart';
import ScoreBarList from '../components/charts/ScoreBarList';
import { SAMPLE_COMPANY_REPORT } from '../constants';
import ReportDetail from './ReportDetail';
interface EmployeeReportProps {
mode: 'submissions' | 'reports';
}
const CompanyReportCard: React.FC<{ report: CompanyReport }> = ({ report }) => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<Card className="mb-6 border-l-4 border-blue-500">
<div className="flex justify-between items-start mb-4">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-blue-600 rounded-full flex items-center justify-center text-white font-bold text-sm">
ZM
</div>
<div>
<h2 className="text-xl font-bold text-[--text-primary]">Company Report</h2>
<p className="text-sm text-[--text-secondary]">
Last updated: {new Date(report.createdAt).toLocaleDateString()}
</p>
</div>
</div>
<div className="flex space-x-2">
<Button
size="sm"
variant="secondary"
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? 'Collapse' : 'View Details'}
</Button>
<Button size="sm">Download as PDF</Button>
</div>
</div>
{/* Overview Section - Always Visible */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<div className="bg-[--background-tertiary] p-4 rounded-lg">
<h3 className="text-sm font-medium text-[--text-secondary]">Total Employees</h3>
<p className="text-2xl font-bold text-[--text-primary]">{report.overview.totalEmployees}</p>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg">
<h3 className="text-sm font-medium text-[--text-secondary]">Departments</h3>
<p className="text-2xl font-bold text-[--text-primary]">{report.overview.departmentBreakdown.length}</p>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg">
<h3 className="text-sm font-medium text-[--text-secondary]">Avg Performance</h3>
<p className="text-2xl font-bold text-[--text-primary]">{report.overview.averagePerformanceScore}/5</p>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg">
<h3 className="text-sm font-medium text-[--text-secondary]">Risk Level</h3>
<p className="text-2xl font-bold text-[--text-primary]">{report.overview.riskLevel}</p>
</div>
</div>
{isExpanded && (
<div className="mt-6 space-y-6">
{/* Key Personnel Changes */}
{report.keyPersonnelChanges && report.keyPersonnelChanges.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3 flex items-center">
<span className="w-2 h-2 bg-orange-500 rounded-full mr-2"></span>
Key Personnel Changes
</h3>
<div className="space-y-2">
{report.keyPersonnelChanges.map((change, idx) => (
<div key={idx} className="p-3 bg-[--background-tertiary] rounded-lg">
<div className="flex justify-between items-start">
<div>
<p className="font-medium text-[--text-primary]">{change.employeeName}</p>
<p className="text-sm text-[--text-secondary]">{change.role} - {change.department}</p>
</div>
<span className={`px-2 py-1 text-xs rounded-full ${change.changeType === 'departure' ? 'bg-red-100 text-red-800' :
change.changeType === 'promotion' ? 'bg-green-100 text-green-800' :
'bg-blue-100 text-blue-800'
}`}>
{change.changeType}
</span>
</div>
<p className="text-sm text-[--text-secondary] mt-2">{change.impact}</p>
</div>
))}
</div>
</div>
)}
{/* Immediate Hiring Needs */}
{report.immediateHiringNeeds && report.immediateHiringNeeds.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3 flex items-center">
<span className="w-2 h-2 bg-red-500 rounded-full mr-2"></span>
Immediate Hiring Needs
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{report.immediateHiringNeeds.map((need, idx) => (
<div key={idx} className="p-4 bg-[--background-tertiary] rounded-lg">
<div className="flex justify-between items-start mb-2">
<h4 className="font-medium text-[--text-primary]">{need.role}</h4>
<span className={`px-2 py-1 text-xs rounded-full ${need.urgency === 'high' ? 'bg-red-100 text-red-800' :
need.urgency === 'medium' ? 'bg-yellow-100 text-yellow-800' :
'bg-green-100 text-green-800'
}`}>
{need.urgency} priority
</span>
</div>
<p className="text-sm text-[--text-secondary] mb-2">{need.department}</p>
<p className="text-sm text-[--text-secondary]">{need.reasoning}</p>
</div>
))}
</div>
</div>
)}
{/* Forward Operating Plan */}
{report.forwardOperatingPlan && (
<div>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3 flex items-center">
<span className="w-2 h-2 bg-blue-500 rounded-full mr-2"></span>
Forward Operating Plan
</h3>
<div className="space-y-4">
<div className="p-4 bg-[--background-tertiary] rounded-lg">
<h4 className="font-medium text-[--text-primary] mb-2">Next Quarter Goals</h4>
<ul className="space-y-1">
{report.forwardOperatingPlan.quarterlyGoals.map((goal, idx) => (
<li key={idx} className="text-sm text-[--text-secondary] flex items-start">
<span className="w-1.5 h-1.5 bg-blue-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
{goal}
</li>
))}
</ul>
</div>
<div className="p-4 bg-[--background-tertiary] rounded-lg">
<h4 className="font-medium text-[--text-primary] mb-2">Risk Mitigation</h4>
<ul className="space-y-1">
{report.forwardOperatingPlan.riskMitigation.map((initiative, idx) => (
<li key={idx} className="text-sm text-[--text-secondary] flex items-start">
<span className="w-1.5 h-1.5 bg-green-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
{initiative}
</li>
))}
</ul>
</div>
</div>
</div>
)}
{/* Organizational Strengths */}
{report.organizationalStrengths && report.organizationalStrengths.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3 flex items-center">
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
Organizational Strengths
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{report.organizationalStrengths.map((strength, idx) => (
<div key={idx} className="p-3 bg-[--background-tertiary] rounded-lg">
<div className="flex items-start space-x-3">
<span className="text-2xl">{strength.icon}</span>
<div>
<h4 className="font-medium text-[--text-primary]">{strength.area}</h4>
<p className="text-sm text-[--text-secondary]">{strength.description}</p>
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Organizational Impact Summary */}
{report.organizationalImpactSummary && (
<div>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3 flex items-center">
<span className="w-2 h-2 bg-purple-500 rounded-full mr-2"></span>
Organizational Impact Summary
</h3>
<div className="p-4 bg-[--background-tertiary] rounded-lg">
<p className="text-[--text-secondary] text-sm leading-relaxed">
{report.organizationalImpactSummary}
</p>
</div>
</div>
)}
{/* Grading Overview */}
{report.gradingOverview && (
<div>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3 flex items-center">
<span className="w-2 h-2 bg-indigo-500 rounded-full mr-2"></span>
Grading Overview
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{Object.entries(report.gradingOverview).map(([category, score], idx) => (
<div key={idx} className="text-center p-4 bg-[--background-tertiary] rounded-lg">
<div className="text-2xl font-bold text-[--text-primary] mb-1">{score}/5</div>
<div className="text-sm text-[--text-secondary] capitalize">{category.replace(/([A-Z])/g, ' $1').trim()}</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</Card>
);
};
const EmployeeCard: React.FC<{
employee: Employee;
report?: Report;
mode: 'submissions' | 'reports';
isOwner: boolean;
onGenerateReport?: (employee: Employee) => void;
isGeneratingReport?: boolean;
onViewReport?: (report: Report, employeeName: string) => void;
}> = ({ employee, report, mode, isOwner, onGenerateReport, isGeneratingReport, onViewReport }) => {
const [isExpanded, setIsExpanded] = useState(false);
return (
<Card className="hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full flex items-center justify-center text-white font-bold text-sm">
{employee.initials}
</div>
<div>
<h3 className="font-semibold text-[--text-primary]">{employee.name}</h3>
<p className="text-sm text-[--text-secondary]">
{employee.role} {employee.department && `${employee.department}`}
</p>
</div>
{employee.isOwner && (
<span className="px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full">
Owner
</span>
)}
</div>
<div className="flex space-x-2">
{report && (
<>
<Button
size="sm"
variant="secondary"
onClick={() => onViewReport?.(report, employee.name)}
>
View Full Report
</Button>
<Button
size="sm"
variant="secondary"
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? 'Hide' : 'View'} Summary
</Button>
</>
)}
{isOwner && mode === 'reports' && (
<Button
size="sm"
onClick={() => onGenerateReport?.(employee)}
disabled={isGeneratingReport}
>
{isGeneratingReport ? 'Generating...' : report ? 'Regenerate Report' : 'Generate Report'}
</Button>
)}
</div>
</div>
{isExpanded && report && (
<div className="mt-4 pt-4 border-t border-[--border-color] space-y-4">
<div>
<h4 className="font-medium text-[--text-primary] mb-2">Role & Output</h4>
<p className="text-sm text-[--text-secondary]">{report.roleAndOutput.responsibilities}</p>
</div>
{report.grading?.[0]?.scores && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-[--background-tertiary] rounded-lg p-4">
<RadarPerformanceChart
title="Performance Profile"
data={report.grading[0].scores.map(s => ({ label: s.subject, value: (s.value / s.fullMark) * 100 }))}
/>
</div>
<div className="bg-[--background-tertiary] rounded-lg p-4">
<ScoreBarList
title="Score Breakdown"
items={report.grading[0].scores.map(s => ({ label: s.subject, value: s.value, max: s.fullMark }))}
/>
</div>
</div>
)}
<div>
<h4 className="font-medium text-[--text-primary] mb-2">Key Strengths</h4>
<div className="flex flex-wrap gap-2">
{report.insights.strengths.map((strength, idx) => (
<span key={idx} className="px-2 py-1 bg-green-100 text-green-800 text-xs rounded-full">
{strength}
</span>
))}
</div>
</div>
<div>
<h4 className="font-medium text-[--text-primary] mb-2">Development Areas</h4>
<div className="flex flex-wrap gap-2">
{report.insights.weaknesses.map((weakness, idx) => (
<span key={idx} className="px-2 py-1 bg-orange-100 text-orange-800 text-xs rounded-full">
{weakness}
</span>
))}
</div>
</div>
<div>
<h4 className="font-medium text-[--text-primary] mb-2">Recommendations</h4>
<ul className="space-y-1">
{report.recommendations.map((rec, idx) => (
<li key={idx} className="text-sm text-[--text-secondary] flex items-start">
<span className="w-1.5 h-1.5 bg-blue-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
{rec}
</li>
))}
</ul>
</div>
</div>
)}
</Card>
);
};
const EmployeeReport: React.FC<EmployeeReportProps> = ({ mode }) => {
const { employees, reports, user, isOwner, getFullCompanyReportHistory, generateEmployeeReport, generateCompanyReport, saveReport, orgId } = useOrg();
const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null);
const [generatingReports, setGeneratingReports] = useState<Set<string>>(new Set());
const [generatingCompanyReport, setGeneratingCompanyReport] = useState(false);
const [selectedReport, setSelectedReport] = useState<{ report: CompanyReport | Report; type: 'company' | 'employee'; employeeName?: string } | null>(null);
useEffect(() => {
// Load company report for owners
const loadCompanyReport = async () => {
if (isOwner(user?.uid || '') && mode === 'reports') {
try {
const history = await getFullCompanyReportHistory();
if (history.length > 0) {
setCompanyReport(history[0]);
} else {
// Fallback to sample report if no real report exists
setCompanyReport(SAMPLE_COMPANY_REPORT);
}
} catch (error) {
console.error('Failed to load company report:', error);
setCompanyReport(SAMPLE_COMPANY_REPORT);
}
}
};
loadCompanyReport();
}, [isOwner, user?.uid, mode, getFullCompanyReportHistory]);
const handleGenerateReport = async (employee: Employee) => {
setGeneratingReports(prev => new Set(prev).add(employee.id));
try {
console.log('Generating report for employee:', employee.name, 'in org:', orgId);
// Use the OrgContext method instead of direct API call
const report = await generateEmployeeReport(employee);
if (report) {
console.log('Report generated and saved successfully for:', employee.name);
} else {
console.error('Report generation failed for:', employee.name);
// Show user-friendly error
alert(`Failed to generate report for ${employee.name}. Please try again.`);
}
} catch (error) {
console.error('Error generating report:', error);
alert(`Error generating report for ${employee.name}: ${error.message}`);
} finally {
setGeneratingReports(prev => {
const newSet = new Set(prev);
newSet.delete(employee.id);
return newSet;
});
}
};
const handleGenerateCompanyReport = async () => {
setGeneratingCompanyReport(true);
try {
console.log('Generating company report for org:', orgId);
const newReport = await generateCompanyReport();
console.log('Received new company report:', newReport);
setCompanyReport(newReport);
console.log('Company report generated and state updated successfully');
} catch (error) {
console.error('Error generating company report:', error);
alert(`Error generating company report: ${error.message}`);
} finally {
setGeneratingCompanyReport(false);
}
};
const currentUserIsOwner = isOwner(user?.uid || '');
// Filter employees based on user access
const visibleEmployees = currentUserIsOwner
? employees
: employees.filter(emp => emp.id === user?.uid);
return (
<div className="p-6 max-w-6xl mx-auto">
<div className="mb-6">
<h1 className="text-3xl font-bold text-[--text-primary]">
{mode === 'submissions' ? 'Employee Submissions' : 'Employee Reports'}
</h1>
<p className="text-[--text-secondary] mt-1">
{mode === 'submissions'
? 'Manage employee data and submissions'
: 'View AI-generated insights and reports'}
</p>
</div>
{/* Company Report - Only visible to owners in reports mode */}
{currentUserIsOwner && mode === 'reports' && (
<div className="mb-6">
{companyReport ? (
<div>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-[--text-primary]">Company Report</h2>
<div className="flex space-x-2">
<Button
size="sm"
variant="secondary"
onClick={() => setSelectedReport({ report: companyReport, type: 'company' })}
>
View Full Report
</Button>
<Button
size="sm"
onClick={handleGenerateCompanyReport}
disabled={generatingCompanyReport}
>
{generatingCompanyReport ? 'Regenerating...' : 'Regenerate Report'}
</Button>
</div>
</div>
<CompanyReportCard report={companyReport} />
</div>
) : (
<Card>
<div className="text-center py-8">
<h3 className="text-lg font-semibold text-[--text-primary] mb-2">
Generate Company Report
</h3>
<p className="text-[--text-secondary] mb-4">
Create a comprehensive AI-powered report analyzing your organization's performance,
strengths, and recommendations based on employee data.
</p>
<Button
onClick={handleGenerateCompanyReport}
disabled={generatingCompanyReport}
>
{generatingCompanyReport ? 'Generating Report...' : 'Generate Company Report'}
</Button>
</div>
</Card>
)}
</div>
)}
{/* Employee Cards */}
<div className="space-y-4">
<h2 className="text-xl font-semibold text-[--text-primary]">
{currentUserIsOwner ? 'All Employees' : 'Your Information'}
</h2>
{visibleEmployees.length === 0 ? (
<Card>
<div className="text-center py-8">
<p className="text-[--text-secondary]">No employees found.</p>
{currentUserIsOwner && (
<Button className="mt-4" size="sm">
Invite First Employee
</Button>
)}
</div>
</Card>
) : (
visibleEmployees.map(employee => (
<EmployeeCard
key={employee.id}
employee={employee}
report={reports[employee.id]}
mode={mode}
isOwner={currentUserIsOwner}
onGenerateReport={handleGenerateReport}
isGeneratingReport={generatingReports.has(employee.id)}
onViewReport={(report, employeeName) => setSelectedReport({ report, type: 'employee', employeeName })}
/>
))
)}
</div>
{/* Report Detail Modal */}
{selectedReport && (
<ReportDetail
report={selectedReport.report}
type={selectedReport.type}
employeeName={selectedReport.employeeName}
onClose={() => setSelectedReport(null)}
/>
)}
</div>
);
};
export default EmployeeReport;

View File

@@ -66,17 +66,17 @@ const YesNoChoice: React.FC<{
<div className="self-stretch inline-flex justify-center items-center gap-3"> <div className="self-stretch inline-flex justify-center items-center gap-3">
<div <div
onClick={() => onChange('No')} onClick={() => onChange('No')}
className={`w-20 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${value === 'No' ? 'bg-Neutrals-NeutralSlate800' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-Neutrals-NeutralSlate200'}`} className={`w-20 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${value === 'No' ? 'bg-[--Neutrals-NeutralSlate800]' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-[--Neutrals-NeutralSlate800]'}`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${value === 'No' ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950'}`}> <div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${value === 'No' ? 'text-[--Neutrals-NeutralSlate0]' : 'text-[--Neutrals-NeutralSlate0]'}`}>
No No
</div> </div>
</div> </div>
<div <div
onClick={() => onChange('Yes')} onClick={() => onChange('Yes')}
className={`w-20 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${value === 'Yes' ? 'bg-Neutrals-NeutralSlate800' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-Neutrals-NeutralSlate200'}`} className={`w-20 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${value === 'Yes' ? 'bg-[--Neutrals-NeutralSlate800]' : 'bg-[--Neutrals-NeutralSlate100] hover:bg-[--Neutrals-NeutralSlate800]'}`}
> >
<div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${value === 'Yes' ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950'}`}> <div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${value === 'Yes' ? 'text-[--Neutrals-NeutralSlate0]' : 'text-[--Neutrals-NeutralSlate0]'}`}>
Yes Yes
</div> </div>
</div> </div>
@@ -217,7 +217,7 @@ const EmployeeFormStep1: React.FC<{ onNext: (data: any) => void }> = ({ onNext }
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Your Name</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Your Name</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
@@ -234,7 +234,7 @@ const EmployeeFormStep1: React.FC<{ onNext: (data: any) => void }> = ({ onNext }
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">What is your role at the company?</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">What is your role at the company?</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
@@ -251,7 +251,7 @@ const EmployeeFormStep1: React.FC<{ onNext: (data: any) => void }> = ({ onNext }
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">What department do you work in?</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">What department do you work in?</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
@@ -300,7 +300,7 @@ const EmployeeFormStep2: React.FC<{ onNext: (data: any) => void; onBack: () => v
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Email</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Email</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
@@ -317,7 +317,7 @@ const EmployeeFormStep2: React.FC<{ onNext: (data: any) => void; onBack: () => v
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Your Name</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">Your Name</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
@@ -334,7 +334,7 @@ const EmployeeFormStep2: React.FC<{ onNext: (data: any) => void; onBack: () => v
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">What is the name of your Company and department?</div> <div className="justify-start text-[--Neutrals-NeutralSlate900] text-sm font-normal font-['Inter'] leading-tight">What is the name of your Company and department?</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div> <div className="justify-start text-[--Brand-Orange] text-sm font-medium font-['Inter'] leading-tight">*</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">

View File

@@ -16,7 +16,7 @@ import {
/** /**
* Enhanced Employee Questionnaire with Exact Figma Design Implementation * Enhanced Employee Questionnaire with Exact Figma Design Implementation
* *
* Features: * Features:
* - Exact Figma design system styling * - Exact Figma design system styling
* - Invite-based flow (no authentication required) * - Invite-based flow (no authentication required)
@@ -264,13 +264,13 @@ const EmployeeQuestionnaire: React.FC = () => {
// Early return for invite flow loading state // Early return for invite flow loading state
if (isInviteFlow && isLoadingInvite) { if (isInviteFlow && isLoadingInvite) {
return ( return (
<div className="min-h-screen bg-Neutrals-NeutralSlate0 py-8 px-4 flex items-center justify-center"> <div className="min-h-screen bg-[--Neutrals-NeutralSlate0] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center"> <div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-Brand-Orange rounded-full flex items-center justify-center font-bold text-Other-White text-2xl mx-auto mb-4"> <div className="w-16 h-16 bg-[--Brand-Orange] rounded-full flex items-center justify-center font-bold text-Other-White text-2xl mx-auto mb-4">
A A
</div> </div>
<h1 className="text-3xl font-bold text-Neutrals-NeutralSlate950 mb-4">Loading Your Invitation...</h1> <h1 className="text-3xl font-bold text-[--Neutrals-NeutralSlate950] mb-4">Loading Your Invitation...</h1>
<p className="text-Neutrals-NeutralSlate500">Please wait while we verify your invitation.</p> <p className="text-[--Neutrals-NeutralSlate500]">Please wait while we verify your invitation.</p>
</div> </div>
</div> </div>
); );
@@ -279,16 +279,16 @@ const EmployeeQuestionnaire: React.FC = () => {
// Early return for invite flow error state // Early return for invite flow error state
if (isInviteFlow && error && currentStep === 1) { if (isInviteFlow && error && currentStep === 1) {
return ( return (
<div className="min-h-screen bg-Neutrals-NeutralSlate0 py-8 px-4 flex items-center justify-center"> <div className="min-h-screen bg-[--Neutrals-NeutralSlate0] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center"> <div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-red-500 rounded-full flex items-center justify-center font-bold text-Other-White text-2xl mx-auto mb-4"> <div className="w-16 h-16 bg-red-500 rounded-full flex items-center justify-center font-bold text-Other-White text-2xl mx-auto mb-4">
! !
</div> </div>
<h1 className="text-3xl font-bold text-Neutrals-NeutralSlate950 mb-4">Invitation Error</h1> <h1 className="text-3xl font-bold text-[--Neutrals-NeutralSlate950] mb-4">Invitation Error</h1>
<p className="text-Neutrals-NeutralSlate500 mb-6">{error}</p> <p className="text-[--Neutrals-NeutralSlate500] mb-6">{error}</p>
<button <button
onClick={() => window.location.href = '/'} onClick={() => window.location.href = '/'}
className="px-6 py-3 bg-Brand-Orange text-Other-White rounded-lg hover:bg-orange-600" className="px-6 py-3 bg-[--Brand-Orange] text-Other-White rounded-lg hover:bg-orange-600"
> >
Return to Homepage Return to Homepage
</button> </button>
@@ -301,7 +301,7 @@ const EmployeeQuestionnaire: React.FC = () => {
switch (currentStep) { switch (currentStep) {
case 1: case 1:
return ( return (
<WelcomeScreen <WelcomeScreen
onStart={() => handleNext()} onStart={() => handleNext()}
/> />
); );
@@ -680,13 +680,13 @@ const EmployeeQuestionnaire: React.FC = () => {
if (isSubmitting) { if (isSubmitting) {
return ( return (
<div className="min-h-screen bg-Neutrals-NeutralSlate0 py-8 px-4 flex items-center justify-center"> <div className="min-h-screen bg-[--Neutrals-NeutralSlate0] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center"> <div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-Brand-Orange rounded-full flex items-center justify-center font-bold text-Other-White text-2xl mx-auto mb-4 animate-pulse"> <div className="w-16 h-16 bg-[--Brand-Orange] rounded-full flex items-center justify-center font-bold text-Other-White text-2xl mx-auto mb-4 animate-pulse">
A A
</div> </div>
<h1 className="text-3xl font-bold text-Neutrals-NeutralSlate950 mb-4">Submitting Your Responses...</h1> <h1 className="text-3xl font-bold text-[--Neutrals-NeutralSlate950] mb-4">Submitting Your Responses...</h1>
<p className="text-Neutrals-NeutralSlate500">Please wait while we process your assessment and generate your report.</p> <p className="text-[--Neutrals-NeutralSlate500]">Please wait while we process your assessment and generate your report.</p>
</div> </div>
</div> </div>
); );

View File

@@ -1,17 +1,19 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { useOrg } from '../contexts/OrgContext'; import { useOrg } from '../contexts/OrgContext';
import { onboardingSteps, OnboardingData, initializeOnboardingData } from '../data/onboardingSteps'; import { onboardingSteps, OnboardingData, initializeOnboardingData } from '../data/onboardingSteps';
import { secureApiPOST } from '../services/secureApi'; import { secureApiPOST, secureApi } from '../services/secureApi';
import { import {
FigmaOnboardingIntro, FigmaOnboardingIntro,
FigmaOnboardingQuestion, FigmaOnboardingQuestion,
FigmaOnboardingMultipleChoice, FigmaOnboardingMultipleChoice,
FigmaOnboardingForm FigmaOnboardingForm
} from '../components/onboarding/FigmaOnboardingComponents'; } from '../components/onboarding/FigmaOnboardingComponents';
const Onboarding: React.FC = () => { const Onboarding: React.FC = () => {
const { org, upsertOrg, generateCompanyWiki } = useOrg(); const { org, upsertOrg, generateCompanyWiki } = useOrg();
const { user } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
@@ -36,10 +38,12 @@ const Onboarding: React.FC = () => {
switch (currentStep.type) { switch (currentStep.type) {
case 'form': case 'form':
// Check required fields for form step - using companyDetails field // Check required fields for form step - company name and user name
const companyDetails = formData.companyDetails; const companyName = formData.companyName;
return typeof companyDetails === 'string' && companyDetails.trim().length > 0; const yourName = formData.yourName;
return typeof companyName === 'string' && companyName.trim().length > 0 &&
typeof yourName === 'string' && yourName.trim().length > 0;
case 'question': case 'question':
// Check if field is filled // Check if field is filled
if (currentStep.field) { if (currentStep.field) {
@@ -47,7 +51,7 @@ const Onboarding: React.FC = () => {
return Array.isArray(fieldValue) ? fieldValue.length > 0 : String(fieldValue || '').trim().length > 0; return Array.isArray(fieldValue) ? fieldValue.length > 0 : String(fieldValue || '').trim().length > 0;
} }
return false; return false;
case 'multiple_choice': case 'multiple_choice':
// Check if option is selected // Check if option is selected
if (currentStep.field) { if (currentStep.field) {
@@ -55,10 +59,10 @@ const Onboarding: React.FC = () => {
return String(fieldValue || '').trim().length > 0; return String(fieldValue || '').trim().length > 0;
} }
return false; return false;
case 'intro': case 'intro':
return true; return true;
default: default:
return false; return false;
} }
@@ -75,25 +79,15 @@ const Onboarding: React.FC = () => {
// Final step: submit all data and complete onboarding // Final step: submit all data and complete onboarding
setIsGeneratingReport(true); setIsGeneratingReport(true);
try { try {
// Submit the complete onboarding data await upsertOrg({
const response = await secureApiPOST('onboarding/complete', formData); ...org,
companyName: formData.companyName,
if (response.success) { companyLogo: formData.companyLogo,
// Update org with completion status onboardingData: formData,
const updatedOrg = { onboardingCompleted: true,
...org, updatedAt: Date.now(),
name: formData.companyName, });
onboardingCompleted: true, navigate('/reports', { replace: true });
updatedAt: Date.now()
};
await upsertOrg(updatedOrg);
// Navigate to reports
navigate('/reports', { replace: true });
} else {
throw new Error(response.error || 'Failed to complete onboarding');
}
} catch (error) { } catch (error) {
console.error('Error completing onboarding:', error); console.error('Error completing onboarding:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
@@ -119,7 +113,7 @@ const Onboarding: React.FC = () => {
const sectionSteps = onboardingSteps.filter(step => step.section === currentStep.section); const sectionSteps = onboardingSteps.filter(step => step.section === currentStep.section);
const currentSectionIndex = sectionSteps.findIndex(step => step.id === currentStep.id); const currentSectionIndex = sectionSteps.findIndex(step => step.id === currentStep.id);
const sectionName = currentStep.sectionName || `Section ${currentStep.section}`; const sectionName = currentStep.sectionName || `Section ${currentStep.section}`;
return { return {
sectionPosition: currentStep.section, sectionPosition: currentStep.section,
totalInSection: sectionSteps.length, totalInSection: sectionSteps.length,
@@ -130,7 +124,7 @@ const Onboarding: React.FC = () => {
const renderStepContent = () => { const renderStepContent = () => {
if (!currentStep) return null; if (!currentStep) return null;
const sectionInfo = getSectionInfo(); const sectionInfo = getSectionInfo();
switch (currentStep.type) { switch (currentStep.type) {
@@ -160,18 +154,18 @@ const Onboarding: React.FC = () => {
); );
case 'question': case 'question':
const questionValue = currentStep.fieldMapping const questionValue = currentStep.field
? String(formData[currentStep.fieldMapping as keyof OnboardingData] || '') ? String(formData[currentStep.field as keyof OnboardingData] || '')
: ''; : '';
return ( return (
<FigmaOnboardingQuestion <FigmaOnboardingQuestion
question={currentStep.question || ''} question={currentStep.title || ''}
placeholder={currentStep.placeholder} placeholder={currentStep.placeholder}
value={questionValue} value={questionValue}
onChange={(value) => { onChange={(value) => {
if (currentStep.fieldMapping) { if (currentStep.field) {
updateFormData(currentStep.fieldMapping as keyof OnboardingData, value); updateFormData(currentStep.field as keyof OnboardingData, value);
} }
}} }}
onBack={handleBack} onBack={handleBack}
@@ -180,24 +174,24 @@ const Onboarding: React.FC = () => {
totalInSection={sectionInfo.totalInSection} totalInSection={sectionInfo.totalInSection}
sectionName={sectionInfo.sectionName} sectionName={sectionInfo.sectionName}
canProceed={canProceed()} canProceed={canProceed()}
canSkip={currentStep.optional} canSkip={!currentStep.required}
rows={6} rows={6}
/> />
); );
case 'multiple_choice': case 'multiple_choice':
const multipleChoiceValue = currentStep.fieldMapping const multipleChoiceValue = currentStep.field
? String(formData[currentStep.fieldMapping as keyof OnboardingData] || '') ? String(formData[currentStep.field as keyof OnboardingData] || '')
: ''; : '';
return ( return (
<FigmaOnboardingMultipleChoice <FigmaOnboardingMultipleChoice
question={currentStep.question || ''} question={currentStep.title || ''}
options={currentStep.options || []} options={currentStep.options || []}
selectedValue={multipleChoiceValue} selectedValue={multipleChoiceValue}
onSelect={(value) => { onSelect={(value) => {
if (currentStep.fieldMapping) { if (currentStep.field) {
updateFormData(currentStep.fieldMapping as keyof OnboardingData, value); updateFormData(currentStep.field as keyof OnboardingData, value);
} }
}} }}
onBack={handleBack} onBack={handleBack}
@@ -205,7 +199,7 @@ const Onboarding: React.FC = () => {
sectionPosition={sectionInfo.sectionPosition} sectionPosition={sectionInfo.sectionPosition}
totalInSection={sectionInfo.totalInSection} totalInSection={sectionInfo.totalInSection}
sectionName={sectionInfo.sectionName} sectionName={sectionInfo.sectionName}
canSkip={currentStep.optional} canSkip={!currentStep.required}
/> />
); );

View File

@@ -1,411 +0,0 @@
import React from 'react';
import { Card, Button } from '../components/UiKit';
import { CompanyReport, Report } from '../types';
import RadarPerformanceChart from '../components/charts/RadarPerformanceChart';
import ScoreBarList from '../components/charts/ScoreBarList';
interface ReportDetailProps {
report: CompanyReport | Report;
type: 'company' | 'employee';
employeeName?: string;
onClose: () => void;
}
const ReportDetail: React.FC<ReportDetailProps> = ({ report, type, employeeName, onClose }) => {
if (type === 'company') {
const companyReport = report as CompanyReport;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-[--background-primary] rounded-lg shadow-xl max-w-6xl w-full max-h-[90vh] overflow-y-auto">
<div className="sticky top-0 bg-[--background-primary] border-b border-[--border-color] p-6 flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold text-[--text-primary]">Company Report</h1>
<p className="text-[--text-secondary]">Last updated: {new Date(companyReport.createdAt).toLocaleDateString()}</p>
</div>
<div className="flex space-x-2">
<Button size="sm">Download as PDF</Button>
<Button size="sm" variant="secondary" onClick={onClose}>Close</Button>
</div>
</div>
<div className="p-6 space-y-6">
{/* Executive Summary */}
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4">Executive Summary</h2>
<p className="text-[--text-secondary] whitespace-pre-line leading-relaxed">
{companyReport.executiveSummary}
</p>
</Card>
{/* Overview Stats */}
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4">Company Overview</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-[--background-tertiary] p-4 rounded-lg text-center">
<div className="text-3xl font-bold text-blue-500 mb-1">{companyReport.overview.totalEmployees}</div>
<div className="text-sm text-[--text-secondary]">Total Employees</div>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg text-center">
<div className="text-3xl font-bold text-green-500 mb-1">{companyReport.overview.departmentBreakdown?.length || 0}</div>
<div className="text-sm text-[--text-secondary]">Departments</div>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg text-center">
<div className="text-3xl font-bold text-purple-500 mb-1">{companyReport.overview.averagePerformanceScore || 'N/A'}</div>
<div className="text-sm text-[--text-secondary]">Avg Performance</div>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg text-center">
<div className="text-3xl font-bold text-orange-500 mb-1">{companyReport.overview.riskLevel || 'Low'}</div>
<div className="text-sm text-[--text-secondary]">Risk Level</div>
</div>
</div>
</Card>
{/* Key Personnel Changes */}
{companyReport.keyPersonnelChanges && companyReport.keyPersonnelChanges.length > 0 && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-orange-500 rounded-full mr-3"></span>
Key Personnel Changes
</h2>
<div className="space-y-3">
{companyReport.keyPersonnelChanges.map((change, idx) => (
<div key={idx} className="p-4 bg-[--background-tertiary] rounded-lg">
<div className="flex justify-between items-start mb-2">
<div>
<h3 className="font-semibold text-[--text-primary]">{change.employeeName}</h3>
<p className="text-sm text-[--text-secondary]">{change.role} {change.department}</p>
</div>
<span className={`px-3 py-1 text-xs rounded-full font-medium ${change.changeType === 'departure' ? 'bg-red-100 text-red-800' :
change.changeType === 'promotion' ? 'bg-green-100 text-green-800' :
'bg-blue-100 text-blue-800'
}`}>
{change.changeType}
</span>
</div>
<p className="text-[--text-secondary] text-sm">{change.impact}</p>
</div>
))}
</div>
</Card>
)}
{/* Immediate Hiring Needs */}
{companyReport.immediateHiringNeeds && companyReport.immediateHiringNeeds.length > 0 && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-red-500 rounded-full mr-3"></span>
Immediate Hiring Needs
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{companyReport.immediateHiringNeeds.map((need, idx) => (
<div key={idx} className="p-4 bg-[--background-tertiary] rounded-lg">
<div className="flex justify-between items-start mb-2">
<h3 className="font-semibold text-[--text-primary]">{need.role}</h3>
<span className={`px-2 py-1 text-xs rounded-full font-medium ${need.urgency === 'high' ? 'bg-red-100 text-red-800' :
need.urgency === 'medium' ? 'bg-yellow-100 text-yellow-800' :
'bg-green-100 text-green-800'
}`}>
{need.urgency}
</span>
</div>
<p className="text-sm text-[--text-secondary] mb-2">{need.department}</p>
<p className="text-sm text-[--text-secondary]">{need.reasoning}</p>
</div>
))}
</div>
</Card>
)}
{/* Forward Operating Plan */}
{companyReport.forwardOperatingPlan && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-blue-500 rounded-full mr-3"></span>
Forward Operating Plan
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="p-4 bg-[--background-tertiary] rounded-lg">
<h3 className="font-semibold text-[--text-primary] mb-3">Next Quarter Goals</h3>
<ul className="space-y-2">
{companyReport.forwardOperatingPlan.quarterlyGoals?.map((goal, idx) => (
<li key={idx} className="text-sm text-[--text-secondary] flex items-start">
<span className="w-2 h-2 bg-blue-500 rounded-full mt-2 mr-3 flex-shrink-0"></span>
{goal}
</li>
))}
</ul>
</div>
<div className="p-4 bg-[--background-tertiary] rounded-lg">
<h3 className="font-semibold text-[--text-primary] mb-3">Key Initiatives</h3>
<ul className="space-y-2">
{companyReport.forwardOperatingPlan.riskMitigation?.map((initiative, idx) => (
<li key={idx} className="text-sm text-[--text-secondary] flex items-start">
<span className="w-2 h-2 bg-green-500 rounded-full mt-2 mr-3 flex-shrink-0"></span>
{initiative}
</li>
))}
</ul>
</div>
</div>
</Card>
)}
{/* Organizational Strengths */}
{companyReport.organizationalStrengths && companyReport.organizationalStrengths.length > 0 && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-green-500 rounded-full mr-3"></span>
Organizational Strengths
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{companyReport.organizationalStrengths.map((strength, idx) => (
<div key={idx} className="p-4 bg-[--background-tertiary] rounded-lg">
<div className="flex items-start space-x-3">
<span className="text-3xl">{strength.icon || '💪'}</span>
<div>
<h3 className="font-semibold text-[--text-primary] mb-1">{strength.area || strength.description}</h3>
<p className="text-sm text-[--text-secondary]">{strength.description}</p>
</div>
</div>
</div>
))}
</div>
</Card>
)}
{/* Grading Overview */}
{companyReport.gradingOverview && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-indigo-500 rounded-full mr-3"></span>
Grading Overview
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
{Object.entries(companyReport.gradingOverview).map(([category, score], idx) => (
<div key={idx} className="text-center p-4 bg-[--background-tertiary] rounded-lg">
<div className="text-3xl font-bold text-[--text-primary] mb-2">{score}/5</div>
<div className="text-sm text-[--text-secondary] capitalize">
{category.replace(/([A-Z])/g, ' $1').trim()}
</div>
</div>
))}
</div>
</Card>
)}
{/* Organizational Impact Summary */}
{companyReport.organizationalImpactSummary && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-purple-500 rounded-full mr-3"></span>
Organizational Impact Summary
</h2>
<div className="p-4 bg-[--background-tertiary] rounded-lg">
<p className="text-[--text-secondary] leading-relaxed">
{companyReport.organizationalImpactSummary}
</p>
</div>
</Card>
)}
</div>
</div>
</div>
);
} else {
const employeeReport = report as Report;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-[--background-primary] rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="sticky top-0 bg-[--background-primary] border-b border-[--border-color] p-6 flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold text-[--text-primary]">{employeeName}'s Performance Report</h1>
<p className="text-[--text-secondary]">{employeeReport.role} • {employeeReport.department}</p>
</div>
<div className="flex space-x-2">
<Button size="sm">Download as PDF</Button>
<Button size="sm" variant="secondary" onClick={onClose}>Close</Button>
</div>
</div>
<div className="p-6 space-y-6">
{/* Self-Reported Role & Output */}
{employeeReport.roleAndOutput && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4">Self-Reported Role & Output</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="font-medium text-[--text-primary] mb-2">Responsibilities</h3>
<p className="text-[--text-secondary] text-sm leading-relaxed">{employeeReport.roleAndOutput.responsibilities}</p>
</div>
<div className="space-y-3">
<div>
<h3 className="font-medium text-[--text-primary] mb-1">Clarity on Role</h3>
<p className="text-[--text-secondary] text-sm">{employeeReport.roleAndOutput.clarityOnRole}</p>
</div>
<div>
<h3 className="font-medium text-[--text-primary] mb-1">Self-Rated Output</h3>
<p className="text-[--text-secondary] text-sm">{employeeReport.roleAndOutput.selfRatedOutput}</p>
</div>
</div>
</div>
</Card>
)}
{/* Performance Charts */}
{employeeReport.grading?.[0]?.scores && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4">Performance Analysis</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-[--background-tertiary] rounded-lg p-6">
<RadarPerformanceChart
title="Performance Profile"
data={employeeReport.grading[0].scores.map(s => ({
label: s.subject,
value: (s.value / s.fullMark) * 100
}))}
/>
</div>
<div className="bg-[--background-tertiary] rounded-lg p-6">
<ScoreBarList
title="Score Breakdown"
items={employeeReport.grading[0].scores.map(s => ({
label: s.subject,
value: s.value,
max: s.fullMark
}))}
/>
</div>
</div>
</Card>
)}
{/* Behavioral & Psychological Insights */}
{employeeReport.insights && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4">Behavioral & Psychological Insights</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 className="font-medium text-[--text-primary] mb-3">Personality Traits</h3>
<p className="text-[--text-secondary] text-sm leading-relaxed mb-4">
{employeeReport.insights.personalityTraits || 'No personality traits data available.'}
</p>
<h3 className="font-medium text-[--text-primary] mb-3">Self-awareness</h3>
<p className="text-[--text-secondary] text-sm leading-relaxed">
{employeeReport.insights.selfAwareness || 'No self-awareness data available.'}
</p>
</div>
<div>
<h3 className="font-medium text-[--text-primary] mb-3">Psychological Indicators</h3>
<ul className="space-y-2 mb-4">
{employeeReport.insights.psychologicalIndicators?.map((indicator, idx) => (
<li key={idx} className="text-sm text-[--text-secondary] flex items-start">
<span className="w-1.5 h-1.5 bg-blue-500 rounded-full mt-2 mr-2 flex-shrink-0"></span>
{indicator}
</li>
)) || <li className="text-sm text-[--text-secondary]">No psychological indicators available.</li>}
</ul>
<h3 className="font-medium text-[--text-primary] mb-3">Growth Desire</h3>
<p className="text-[--text-secondary] text-sm leading-relaxed">
{employeeReport.insights.growthDesire || 'No growth desire data available.'}
</p>
</div>
</div>
</Card>
)}
{/* Strengths & Weaknesses */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-green-500 rounded-full mr-3"></span>
Strengths
</h2>
<div className="space-y-2">
{employeeReport.insights?.strengths?.map((strength, idx) => (
<div key={idx} className="flex items-center space-x-2">
<span className="text-green-500">✓</span>
<span className="text-sm text-[--text-secondary]">{strength}</span>
</div>
))}
</div>
</Card>
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-orange-500 rounded-full mr-3"></span>
Development Areas
</h2>
<div className="space-y-2">
{employeeReport.insights?.weaknesses?.map((weakness, idx) => (
<div key={idx} className="flex items-center space-x-2">
<span className="text-orange-500">!</span>
<span className="text-sm text-[--text-secondary]">{weakness}</span>
</div>
))}
</div>
</Card>
</div>
{/* Opportunities */}
{employeeReport.opportunities && employeeReport.opportunities?.length > 0 && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-blue-500 rounded-full mr-3"></span>
Opportunities
</h2>
<div className="space-y-4">
{employeeReport.opportunities.map((opp, idx) => (
<div key={idx} className="p-4 bg-[--background-tertiary] rounded-lg">
<h3 className="font-medium text-[--text-primary] mb-2">{opp.roleAdjustment || 'Opportunity'}</h3>
<p className="text-sm text-[--text-secondary]">{opp.accountabilitySupport || opp.description }</p>
</div>
))}
</div>
</Card>
)}
{/* Risks */}
{employeeReport.risks && employeeReport.risks.length > 0 && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-yellow-500 rounded-full mr-3"></span>
Risks
</h2>
<div className="space-y-2">
{employeeReport.risks.map((risk, idx) => (
<div key={idx} className="flex items-start space-x-2 p-3 bg-yellow-50 rounded-lg">
<span className="text-yellow-500 mt-0.5"></span>
<span className="text-sm text-gray-700">{risk}</span>
</div>
))}
</div>
</Card>
)}
{/* Recommendations */}
{employeeReport.recommendations && employeeReport.recommendations.length > 0 && (
<Card>
<h2 className="text-xl font-semibold text-[--text-primary] mb-4 flex items-center">
<span className="w-3 h-3 bg-purple-500 rounded-full mr-3"></span>
Recommendations
</h2>
<div className="space-y-3">
{employeeReport.recommendations.map((rec, idx) => (
<div key={idx} className="flex items-start space-x-3 p-3 bg-[--background-tertiary] rounded-lg">
<span className="w-2 h-2 bg-purple-500 rounded-full mt-2 flex-shrink-0"></span>
<span className="text-sm text-[--text-secondary]">{rec}</span>
</div>
))}
</div>
</Card>
)}
</div>
</div>
</div>
);
}
};
export default ReportDetail;

View File

@@ -1,12 +1,13 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useOrg } from '../contexts/OrgContext'; import { useOrg } from '../contexts/OrgContext';
import { CompanyReport, Employee, Report } from '../types'; import { CompanyReport, Employee, EmployeeReport } from '../types';
import { SAMPLE_COMPANY_REPORT } from '../constants'; import { SAMPLE_COMPANY_REPORT } from '../constants';
import RadarPerformanceChart from '../components/charts/RadarPerformanceChart';
const Reports: React.FC = () => { const Reports: React.FC = () => {
const { employees, reports, user, isOwner, getFullCompanyReportHistory, generateEmployeeReport, generateCompanyReport, orgId } = useOrg(); const { employees, reports, user, isOwner, getFullCompanyReportHistory, generateEmployeeReport, generateCompanyReport, orgId } = useOrg();
const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null); const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null);
const [selectedReport, setSelectedReport] = useState<{ report: CompanyReport | Report; type: 'company' | 'employee'; employeeName?: string } | null>(null); const [selectedReport, setSelectedReport] = useState<{ report: CompanyReport | EmployeeReport; type: 'company' | 'employee'; employeeName?: string } | null>(null);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [generatingReports, setGeneratingReports] = useState<Set<string>>(new Set()); const [generatingReports, setGeneratingReports] = useState<Set<string>>(new Set());
const [generatingCompanyReport, setGeneratingCompanyReport] = useState(false); const [generatingCompanyReport, setGeneratingCompanyReport] = useState(false);
@@ -75,22 +76,22 @@ const Reports: React.FC = () => {
}; };
return ( return (
<div className="w-[1440px] h-[4558px] p-4 bg-Neutrals-NeutralSlate200 inline-flex justify-start items-start overflow-hidden"> <div className="w-[1440px] h-[4558px] p-4 bg-[--Neutrals-NeutralSlate200] inline-flex justify-start items-start overflow-hidden">
<div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden"> <div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden">
{/* Left Sidebar - Navigation */} {/* Left Sidebar - Navigation */}
<div className="w-64 self-stretch max-w-64 min-w-64 px-3 pt-4 pb-3 bg-Neutrals-NeutralSlate0 border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden"> <div className="w-64 self-stretch max-w-64 min-w-64 px-3 pt-4 pb-3 bg-[--Neutrals-NeutralSlate0] border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden">
<div className="self-stretch flex flex-col justify-start items-start gap-5"> <div className="self-stretch flex flex-col justify-start items-start gap-5">
{/* Company Logo */} {/* Company Logo */}
<div className="w-60 pl-2 pr-4 py-2 bg-Neutrals-NeutralSlate0 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden"> <div className="w-60 pl-2 pr-4 py-2 bg-[--Neutrals-NeutralSlate0] rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden">
<div className="flex-1 flex justify-start items-center gap-2"> <div className="flex-1 flex justify-start items-center gap-2">
<div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5"> <div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5">
<div className="w-8 h-8 relative bg-Brand-Orange rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden"> <div className="w-8 h-8 relative bg-[--Brand-Orange] rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden">
<div className="w-8 h-8 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" /> <div className="w-8 h-8 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
</div> </div>
</div> </div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5"> <div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">Auditly Company</div> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate950] text-base font-medium font-['Inter'] leading-normal">Auditly Company</div>
</div> </div>
</div> </div>
<div className="relative"> <div className="relative">
@@ -109,7 +110,7 @@ const Reports: React.FC = () => {
<path d="M7.5 17.4996V11.333C7.5 10.8662 7.5 10.6329 7.59083 10.4546C7.67072 10.2978 7.79821 10.1703 7.95501 10.0904C8.13327 9.99962 8.36662 9.99962 8.83333 9.99962H11.1667C11.6334 9.99962 11.8667 9.99962 12.045 10.0904C12.2018 10.1703 12.3293 10.2978 12.4092 10.4546C12.5 10.6329 12.5 10.8662 12.5 11.333V17.4996M9.18141 2.30297L3.52949 6.6989C3.15168 6.99275 2.96278 7.13968 2.82669 7.32368C2.70614 7.48667 2.61633 7.67029 2.56169 7.86551C2.5 8.0859 2.5 8.32521 2.5 8.80384V14.833C2.5 15.7664 2.5 16.2331 2.68166 16.5896C2.84144 16.9032 3.09641 17.1582 3.41002 17.318C3.76654 17.4996 4.23325 17.4996 5.16667 17.4996H14.8333C15.7668 17.4996 16.2335 17.4996 16.59 17.318C16.9036 17.1582 17.1586 16.9032 17.3183 16.5896C17.5 16.2331 17.5 15.7664 17.5 14.833V8.80384C17.5 8.32521 17.5 8.0859 17.4383 7.86551C17.3837 7.67029 17.2939 7.48667 17.1733 7.32368C17.0372 7.13968 16.8483 6.99275 16.4705 6.69891L10.8186 2.30297C10.5258 2.07526 10.3794 1.9614 10.2178 1.91763C10.0752 1.87902 9.92484 1.87902 9.78221 1.91763C9.62057 1.9614 9.47418 2.07526 9.18141 2.30297Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M7.5 17.4996V11.333C7.5 10.8662 7.5 10.6329 7.59083 10.4546C7.67072 10.2978 7.79821 10.1703 7.95501 10.0904C8.13327 9.99962 8.36662 9.99962 8.83333 9.99962H11.1667C11.6334 9.99962 11.8667 9.99962 12.045 10.0904C12.2018 10.1703 12.3293 10.2978 12.4092 10.4546C12.5 10.6329 12.5 10.8662 12.5 11.333V17.4996M9.18141 2.30297L3.52949 6.6989C3.15168 6.99275 2.96278 7.13968 2.82669 7.32368C2.70614 7.48667 2.61633 7.67029 2.56169 7.86551C2.5 8.0859 2.5 8.32521 2.5 8.80384V14.833C2.5 15.7664 2.5 16.2331 2.68166 16.5896C2.84144 16.9032 3.09641 17.1582 3.41002 17.318C3.76654 17.4996 4.23325 17.4996 5.16667 17.4996H14.8333C15.7668 17.4996 16.2335 17.4996 16.59 17.318C16.9036 17.1582 17.1586 16.9032 17.3183 16.5896C17.5 16.2331 17.5 15.7664 17.5 14.833V8.80384C17.5 8.32521 17.5 8.0859 17.4383 7.86551C17.3837 7.67029 17.2939 7.48667 17.1733 7.32368C17.0372 7.13968 16.8483 6.99275 16.4705 6.69891L10.8186 2.30297C10.5258 2.07526 10.3794 1.9614 10.2178 1.91763C10.0752 1.87902 9.92484 1.87902 9.78221 1.91763C9.62057 1.9614 9.47418 2.07526 9.18141 2.30297Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Company Wiki</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-tight">Company Wiki</div>
</div> </div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2"> <div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative"> <div className="relative">
@@ -117,9 +118,9 @@ const Reports: React.FC = () => {
<path d="M11.6667 9.16602H6.66667M8.33333 12.4993H6.66667M13.3333 5.83268H6.66667M16.6667 5.66602V14.3327C16.6667 15.7328 16.6667 16.4329 16.3942 16.9677C16.1545 17.4381 15.772 17.8205 15.3016 18.0602C14.7669 18.3327 14.0668 18.3327 12.6667 18.3327H7.33333C5.9332 18.3327 5.23314 18.3327 4.69836 18.0602C4.22795 17.8205 3.8455 17.4381 3.60582 16.9677C3.33333 16.4329 3.33333 15.7328 3.33333 14.3327V5.66602C3.33333 4.26588 3.33333 3.56582 3.60582 3.03104C3.8455 2.56063 4.22795 2.17818 4.69836 1.9385C5.23314 1.66602 5.9332 1.66602 7.33333 1.66602H12.6667C14.0668 1.66602 14.7669 1.66602 15.3016 1.9385C15.772 2.17818 16.1545 2.56063 16.3942 3.03104C16.6667 3.56582 16.6667 4.26588 16.6667 5.66602Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M11.6667 9.16602H6.66667M8.33333 12.4993H6.66667M13.3333 5.83268H6.66667M16.6667 5.66602V14.3327C16.6667 15.7328 16.6667 16.4329 16.3942 16.9677C16.1545 17.4381 15.772 17.8205 15.3016 18.0602C14.7669 18.3327 14.0668 18.3327 12.6667 18.3327H7.33333C5.9332 18.3327 5.23314 18.3327 4.69836 18.0602C4.22795 17.8205 3.8455 17.4381 3.60582 16.9677C3.33333 16.4329 3.33333 15.7328 3.33333 14.3327V5.66602C3.33333 4.26588 3.33333 3.56582 3.60582 3.03104C3.8455 2.56063 4.22795 2.17818 4.69836 1.9385C5.23314 1.66602 5.9332 1.66602 7.33333 1.66602H12.6667C14.0668 1.66602 14.7669 1.66602 15.3016 1.9385C15.772 2.17818 16.1545 2.56063 16.3942 3.03104C16.6667 3.56582 16.6667 4.26588 16.6667 5.66602Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Submissions</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-tight">Submissions</div>
</div> </div>
<div className="w-60 px-4 py-2.5 bg-Neutrals-NeutralSlate100 rounded-[34px] inline-flex justify-start items-center gap-2"> <div className="w-60 px-4 py-2.5 bg-[--Neutrals-NeutralSlate100] rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative"> <div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_5096)"> <g clipPath="url(#clip0_1042_5096)">
@@ -132,7 +133,7 @@ const Reports: React.FC = () => {
</defs> </defs>
</svg> </svg>
</div> </div>
<div className="justify-start text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Reports</div> <div className="justify-start text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Reports</div>
</div> </div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2"> <div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative"> <div className="relative">
@@ -140,7 +141,7 @@ const Reports: React.FC = () => {
<path d="M17.4997 9.58333C17.4997 13.4953 14.3284 16.6667 10.4164 16.6667C9.51904 16.6667 8.66069 16.4998 7.87065 16.1954C7.7262 16.1398 7.65398 16.112 7.59655 16.0987C7.54006 16.0857 7.49917 16.0803 7.44124 16.0781C7.38234 16.0758 7.31772 16.0825 7.18849 16.0958L2.92097 16.537C2.5141 16.579 2.31067 16.6001 2.19067 16.5269C2.08614 16.4631 2.01495 16.3566 1.996 16.2356C1.97425 16.0968 2.07146 15.9168 2.26588 15.557L3.62893 13.034C3.74118 12.8262 3.79731 12.7223 3.82273 12.6225C3.84784 12.5238 3.85391 12.4527 3.84588 12.3512C3.83775 12.2484 3.79266 12.1147 3.7025 11.8472C3.46289 11.1363 3.33302 10.375 3.33302 9.58333C3.33302 5.67132 6.50434 2.5 10.4164 2.5C14.3284 2.5 17.4997 5.67132 17.4997 9.58333Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M17.4997 9.58333C17.4997 13.4953 14.3284 16.6667 10.4164 16.6667C9.51904 16.6667 8.66069 16.4998 7.87065 16.1954C7.7262 16.1398 7.65398 16.112 7.59655 16.0987C7.54006 16.0857 7.49917 16.0803 7.44124 16.0781C7.38234 16.0758 7.31772 16.0825 7.18849 16.0958L2.92097 16.537C2.5141 16.579 2.31067 16.6001 2.19067 16.5269C2.08614 16.4631 2.01495 16.3566 1.996 16.2356C1.97425 16.0968 2.07146 15.9168 2.26588 15.557L3.62893 13.034C3.74118 12.8262 3.79731 12.7223 3.82273 12.6225C3.84784 12.5238 3.85391 12.4527 3.84588 12.3512C3.83775 12.2484 3.79266 12.1147 3.7025 11.8472C3.46289 11.1363 3.33302 10.375 3.33302 9.58333C3.33302 5.67132 6.50434 2.5 10.4164 2.5C14.3284 2.5 17.4997 5.67132 17.4997 9.58333Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Chat</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-tight">Chat</div>
</div> </div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2"> <div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative"> <div className="relative">
@@ -155,7 +156,7 @@ const Reports: React.FC = () => {
</defs> </defs>
</svg> </svg>
</div> </div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Help</div> <div className="justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-tight">Help</div>
</div> </div>
</div> </div>
</div> </div>
@@ -171,23 +172,23 @@ const Reports: React.FC = () => {
</g> </g>
</svg> </svg>
</div> </div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Settings</div> <div className="flex-1 justify-start text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-tight">Settings</div>
</div> </div>
{/* CTA Card */} {/* CTA Card */}
<div className="self-stretch bg-Neutrals-NeutralSlate0 rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden"> <div className="self-stretch bg-[--Neutrals-NeutralSlate0] rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden">
<div className="self-stretch h-24 relative"> <div className="self-stretch h-24 relative">
<div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" /> <div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" />
</div> </div>
<div className="self-stretch p-3 flex flex-col justify-start items-start gap-1"> <div className="self-stretch p-3 flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-sm font-semibold font-['Inter'] leading-tight">Build [Company]'s Report</div> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate800] text-sm font-semibold font-['Inter'] leading-tight">Build [Company]'s Report</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-xs font-normal font-['Inter'] leading-none">Share this form with your team members to capture valuable info about your company to train Auditly.</div> <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate500] text-xs font-normal font-['Inter'] leading-none">Share this form with your team members to capture valuable info about your company to train Auditly.</div>
</div> </div>
<div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8"> <div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8">
<div className="self-stretch inline-flex justify-start items-start gap-2"> <div className="self-stretch inline-flex justify-start items-start gap-2">
<div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden"> <div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden">
<div className="px-1 flex justify-center items-center"> <div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Invite</div> <div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">Invite</div>
</div> </div>
<div className="relative"> <div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -195,7 +196,7 @@ const Reports: React.FC = () => {
</svg> </svg>
</div> </div>
</div> </div>
<div className="flex-1 px-3 py-1.5 bg-Brand-Orange rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden"> <div className="flex-1 px-3 py-1.5 bg-[--Brand-Orange] rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden">
<div className="relative"> <div className="relative">
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.97171 12.2442L8.0289 13.1871C6.72716 14.4888 4.61661 14.4888 3.31486 13.1871C2.01311 11.8853 2.01311 9.77476 3.31486 8.47301L4.25767 7.5302M12.7429 8.47301L13.6858 7.5302C14.9875 6.22845 14.9875 4.1179 13.6858 2.81615C12.384 1.51441 10.2735 1.51441 8.97171 2.81615L8.0289 3.75896M6.16697 10.3349L10.8336 5.66826" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M8.97171 12.2442L8.0289 13.1871C6.72716 14.4888 4.61661 14.4888 3.31486 13.1871C2.01311 11.8853 2.01311 9.77476 3.31486 8.47301L4.25767 7.5302M12.7429 8.47301L13.6858 7.5302C14.9875 6.22845 14.9875 4.1179 13.6858 2.81615C12.384 1.51441 10.2735 1.51441 8.97171 2.81615L8.0289 3.75896M6.16697 10.3349L10.8336 5.66826" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
@@ -212,16 +213,16 @@ const Reports: React.FC = () => {
</div> </div>
{/* Middle Section - Employee List */} {/* Middle Section - Employee List */}
<div className="flex-1 self-stretch bg-Neutrals-NeutralSlate0 flex justify-start items-center"> <div className="flex-1 self-stretch bg-[--Neutrals-NeutralSlate0] flex justify-start items-center">
<div className="flex-1 self-stretch max-w-64 min-w-64 border-r border-Outline-Outline-Gray-200 inline-flex flex-col justify-start items-start"> <div className="flex-1 self-stretch max-w-64 min-w-64 border-r border-Outline-Outline-Gray-200 inline-flex flex-col justify-start items-start">
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5"> <div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">Employees</div> <div className="flex-1 justify-start text-[--Neutrals-NeutralSlate950] text-base font-medium font-['Inter'] leading-normal">Employees</div>
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-2">
{/* Search */} {/* Search */}
<div className="self-stretch px-2.5 flex flex-col justify-start items-start gap-2"> <div className="self-stretch px-2.5 flex flex-col justify-start items-start gap-2">
<div className="self-stretch flex flex-col justify-start items-start gap-1"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="relative"> <div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5 17.5L14.5834 14.5833M16.6667 9.58333C16.6667 13.4954 13.4954 16.6667 9.58333 16.6667C5.67132 16.6667 2.5 13.4954 2.5 9.58333C2.5 5.67132 5.67132 2.5 9.58333 2.5C13.4954 2.5 16.6667 5.67132 16.6667 9.58333Z" stroke="var(--Neutrals-NeutralSlate500, #717680)" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" /> <path d="M17.5 17.5L14.5834 14.5833M16.6667 9.58333C16.6667 13.4954 13.4954 16.6667 9.58333 16.6667C5.67132 16.6667 2.5 13.4954 2.5 9.58333C2.5 5.67132 5.67132 2.5 9.58333 2.5C13.4954 2.5 16.6667 5.67132 16.6667 9.58333Z" stroke="var(--Neutrals-NeutralSlate500, #717680)" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
@@ -232,7 +233,7 @@ const Reports: React.FC = () => {
placeholder="Search" placeholder="Search"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate500 text-sm font-normal font-['Inter'] leading-tight outline-none" className="flex-1 bg-transparent text-[--Neutrals-NeutralSlate500] text-sm font-normal font-['Inter'] leading-tight outline-none"
/> />
</div> </div>
</div> </div>
@@ -247,12 +248,12 @@ const Reports: React.FC = () => {
}`} }`}
onClick={handleCompanyReportSelect} onClick={handleCompanyReportSelect}
> >
<div className="w-7 h-7 p-1 bg-Brand-Orange rounded-[666.67px] flex justify-center items-center"> <div className="w-7 h-7 p-1 bg-[--Brand-Orange] rounded-[666.67px] flex justify-center items-center">
<div className="text-center justify-start text-white text-xs font-medium font-['Inter'] leading-none"> <div className="text-center justify-start text-white text-xs font-medium font-['Inter'] leading-none">
C C
</div> </div>
</div> </div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight">Company Report</div> <div className="flex-1 justify-start text-[--Neutrals-NeutralSlate950] text-sm font-normal font-['Inter'] leading-tight">Company Report</div>
</div> </div>
)} )}
@@ -265,11 +266,11 @@ const Reports: React.FC = () => {
onClick={() => handleEmployeeSelect(employee)} onClick={() => handleEmployeeSelect(employee)}
> >
<div className="w-7 h-7 p-1 bg-Main-BG-Gray-100 rounded-[666.67px] flex justify-center items-center"> <div className="w-7 h-7 p-1 bg-Main-BG-Gray-100 rounded-[666.67px] flex justify-center items-center">
<div className="text-center justify-start text-Neutrals-NeutralSlate500 text-xs font-medium font-['Inter'] leading-none"> <div className="text-center justify-start text-[--Neutrals-NeutralSlate500] text-xs font-medium font-['Inter'] leading-none">
{employee.initials} {employee.initials}
</div> </div>
</div> </div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 text-sm font-normal font-['Inter'] leading-tight"> <div className="flex-1 justify-start text-[--Neutrals-NeutralSlate800] text-sm font-normal font-['Inter'] leading-tight">
{employee.name} {employee.name}
</div> </div>
</div> </div>
@@ -289,17 +290,17 @@ const Reports: React.FC = () => {
/> />
) : ( ) : (
<EmployeeReportContent <EmployeeReportContent
report={selectedReport.report as Report} report={selectedReport.report as EmployeeReport}
employeeName={selectedReport.employeeName!} employeeName={selectedReport.employeeName!}
/> />
) )
) : ( ) : (
<div className="flex-1 flex items-center justify-center"> <div className="flex-1 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<h3 className="text-lg font-semibold text-Neutrals-NeutralSlate950 mb-2"> <h3 className="text-lg font-semibold text-[--Neutrals-NeutralSlate950] mb-2">
Select a Report Select a Report
</h3> </h3>
<p className="text-Neutrals-NeutralSlate500"> <p className="text-[--Neutrals-NeutralSlate500]">
Choose a company or employee report from the list to view details. Choose a company or employee report from the list to view details.
</p> </p>
</div> </div>
@@ -318,14 +319,16 @@ const CompanyReportContent: React.FC<{
onRegenerate: () => void; onRegenerate: () => void;
isGenerating: boolean; isGenerating: boolean;
}> = ({ report, onRegenerate, isGenerating }) => { }> = ({ report, onRegenerate, isGenerating }) => {
const [activeDepartmentTab, setActiveDepartmentTab] = useState('Campaigns');
return ( return (
<> <>
{/* Header */} {/* Header */}
<div className="self-stretch px-5 py-3 inline-flex justify-start items-center gap-2.5"> <div className="self-stretch px-5 py-3 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 text-base font-medium font-['Inter'] leading-normal"> <div className="flex-1 justify-start text-[--Neutrals-NeutralSlate800] text-base font-medium font-['Inter'] leading-normal">
Company Report Company Report
</div> </div>
<div className="px-3 py-2.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden"> <div className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden">
<div className="relative"> <div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M14 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
@@ -374,10 +377,218 @@ const CompanyReportContent: React.FC<{
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2"> <div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Personnel Changes</div> <div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Personnel Changes</div>
</div> </div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-6">
{/* New Hires */}
{report.personnelChanges.newHires && report.personnelChanges.newHires.length > 0 && (
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">New Hires</div>
{report.personnelChanges.newHires.map((hire, index) => (
<div key={index} className="self-stretch p-3 bg-green-50 rounded-lg border border-green-200">
<div className="font-medium text-green-800">{hire.name} - {hire.role}</div>
<div className="text-sm text-green-600">Department: {hire.department}</div>
{hire.impact && <div className="text-sm text-green-600 mt-1">{hire.impact}</div>}
</div>
))}
</div>
)}
{/* Promotions */}
{report.personnelChanges.promotions && report.personnelChanges.promotions.length > 0 && (
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Promotions</div>
{report.personnelChanges.promotions.map((promotion, index) => (
<div key={index} className="self-stretch p-3 bg-blue-50 rounded-lg border border-blue-200">
<div className="font-medium text-blue-800">{promotion.name}</div>
<div className="text-sm text-blue-600">{promotion.fromRole} {promotion.toRole}</div>
{promotion.impact && <div className="text-sm text-blue-600 mt-1">{promotion.impact}</div>}
</div>
))}
</div>
)}
{/* Departures */}
{report.personnelChanges.departures && report.personnelChanges.departures.length > 0 && (
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Departures</div>
{report.personnelChanges.departures.map((departure, index) => (
<div key={index} className="self-stretch p-3 bg-red-50 rounded-lg border border-red-200">
<div className="font-medium text-red-800">{departure.name}</div>
<div className="text-sm text-red-600">Department: {departure.department}</div>
<div className="text-sm text-red-600">Reason: {departure.reason}</div>
{departure.impact && <div className="text-sm text-red-600 mt-1">{departure.impact}</div>}
</div>
))}
</div>
)}
{/* No changes message */}
{(!report.personnelChanges.newHires || report.personnelChanges.newHires.length === 0) &&
(!report.personnelChanges.promotions || report.personnelChanges.promotions.length === 0) &&
(!report.personnelChanges.departures || report.personnelChanges.departures.length === 0) && (
<div className="self-stretch text-Text-Gray-500 text-base font-normal font-['Inter'] leading-normal">
No personnel changes to report.
</div>
)}
</div>
</div>
)}
{/* Hiring Needs */}
{report?.immediateHiringNeeds && report.immediateHiringNeeds.length > 0 && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Immediate Hiring Needs</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4"> <div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> {report.immediateHiringNeeds.map((need, index) => (
{report.personnelChanges} <div key={index} className="self-stretch p-4 bg-white rounded-lg border border-Text-Gray-200 flex flex-col justify-start items-start gap-2">
{/* Copilot, we need to implement this so that it works with the defined type, it's not currently implemented correctly. */} <div className="flex justify-between items-start w-full">
<div className="flex flex-col gap-1">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">
{need.role} - {need.department}
</div>
<div className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${need.priority === 'High' ? 'bg-red-100 text-red-800' :
need.priority === 'Medium' ? 'bg-yellow-100 text-yellow-800' :
'bg-green-100 text-green-800'
}`}>
{need.priority} Priority
</div>
</div>
</div>
<div className="text-Text-Gray-600 text-sm font-normal font-['Inter'] leading-normal">
{need.reasoning}
</div>
</div>
))}
</div>
</div>
)}
{/* Forward Plan */}
{report?.forwardOperatingPlan && (
<div className="w-full self-stretch p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Forward Plan</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
{
report.forwardOperatingPlan.map((plan, index) => (
<div key={index} className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="justify-start text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">{plan.title}</div>
<div className="self-stretch h-12 justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{plan.details.map((detail, idx) => (
<p key={idx}>{detail}</p>
))}
</div>
</div>
<div className="self-stretch h-0 outline outline-1 outline-offset-[-0.50px] outline-Text-Gray-200" />
</div>
))
}
</div>
</div>
)}
{/*
Strengths goes here
*/}
{report?.strengths && (
<div className="self-stretch p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] inline-flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Strengths</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
{report.strengths.map((strength, idx) => (
<div key={idx} className="self-stretch inline-flex justify-start items-start gap-4">
<div className="w-6 h-6 relative rounded-[999px] overflow-hidden">
<div className="w-6 h-6 left-0 top-0 absolute bg-Other-Green rounded-full" />
<div data-svg-wrapper className="left-[5px] top-[5px] absolute">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 3.5L5.24998 9.91667L2.33331 7" stroke="var(--Neutrals-NeutralSlate0, #FDFDFD)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">{strength}</div>
</div>
))}
</div>
</div>
)}
{/*
Organizational Impact Summary
*/}
{report?.organizationalImpactSummary && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Organizational Impact Summary</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-6">
{/* Mission Critical */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="flex justify-start items-center gap-2">
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Mission Critical</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
{report.organizationalImpactSummary.missionCritical.map((employee, index) => (
<div key={index} className="self-stretch flex justify-between items-start">
<div className="w-48 text-Text-Gray-800 text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
<div className="flex-1 text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
</div>
))}
</div>
</div>
{/* Highly Valuable */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="flex justify-start items-center gap-2">
<div className="w-3 h-3 bg-orange-500 rounded-full"></div>
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Highly Valuable</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
{report.organizationalImpactSummary.highlyValuable.map((employee, index) => (
<div key={index} className="self-stretch flex justify-between items-start">
<div className="w-48 text-Text-Gray-800 text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
<div className="flex-1 text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
</div>
))}
</div>
</div>
{/* Core Support */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="flex justify-start items-center gap-2">
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Core Support</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
{report.organizationalImpactSummary.coreSupport.map((employee, index) => (
<div key={index} className="self-stretch flex justify-between items-start">
<div className="w-48 text-Text-Gray-800 text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
<div className="flex-1 text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
</div>
))}
</div>
</div>
{/* Low Criticality */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="flex justify-start items-center gap-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Low Criticality</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
{report.organizationalImpactSummary.lowCriticality.map((employee, index) => (
<div key={index} className="self-stretch flex justify-between items-start">
<div className="w-48 text-Text-Gray-800 text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
<div className="flex-1 text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
</div>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -390,33 +601,27 @@ const CompanyReportContent: React.FC<{
</div> </div>
<div className="self-stretch flex flex-col justify-start items-start gap-3"> <div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="p-1 bg-Text-Gray-200 rounded-full outline outline-1 outline-offset-[-1px] outline-Outline-Outline-Gray-200 inline-flex justify-start items-center gap-1"> <div className="p-1 bg-Text-Gray-200 rounded-full outline outline-1 outline-offset-[-1px] outline-Outline-Outline-Gray-200 inline-flex justify-start items-center gap-1">
<div className="px-3 py-1.5 bg-Main-BG-White-0 rounded-full flex justify-center items-center gap-1 overflow-hidden"> {/* Department Tabs */}
<div className="px-0.5 flex justify-center items-center"> {['Campaigns', 'Social Media', 'Creative', 'Tech', 'Admin/OPS'].map((dept) => (
<div className="justify-start text-Text-Dark-950 text-xs font-medium font-['Inter'] leading-none">Campaigns</div> <div
key={dept}
className={`px-3 py-1.5 rounded-full flex justify-center items-center gap-1 overflow-hidden cursor-pointer ${activeDepartmentTab === dept
? 'bg-Main-BG-White-0'
: 'shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)]'
}`}
onClick={() => setActiveDepartmentTab(dept)}
>
<div className="px-0.5 flex justify-center items-center">
<div className={`justify-start text-xs font-medium font-['Inter'] leading-none ${activeDepartmentTab === dept ? 'text-Text-Dark-950' : 'text-zinc-600'
}`}>
{dept}
</div>
</div>
</div> </div>
</div> ))}
<div className="px-3 py-1.5 rounded-full shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-zinc-600 text-xs font-medium font-['Inter'] leading-none">Social Media</div>
</div>
</div>
<div className="px-3 py-1.5 rounded-full shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-zinc-600 text-xs font-medium font-['Inter'] leading-none">Creative</div>
</div>
</div>
<div className="px-3 py-1.5 rounded-full shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-zinc-600 text-xs font-medium font-['Inter'] leading-none">Tech</div>
</div>
</div>
<div className="px-3 py-1.5 rounded-full shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-zinc-600 text-xs font-medium font-['Inter'] leading-none">Admin/OPS</div>
</div>
</div>
</div> </div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4"> <div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
{/* Department Overview Section */}
<div className="self-stretch p-5 bg-Text-Gray-100 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-3 overflow-hidden"> <div className="self-stretch p-5 bg-Text-Gray-100 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-3 overflow-hidden">
<div className="self-stretch inline-flex justify-between items-center"> <div className="self-stretch inline-flex justify-between items-center">
<div className="w-48 justify-start text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Department Overview</div> <div className="w-48 justify-start text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Department Overview</div>
@@ -436,42 +641,63 @@ const CompanyReportContent: React.FC<{
Overall company performance shows strong collaboration and delivery with opportunities for process improvement. Overall company performance shows strong collaboration and delivery with opportunities for process improvement.
</div> </div>
</div> </div>
{/* Employee Radar Charts Section */}
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Team Performance Analysis</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full">
{report?.gradingBreakdown?.teamScores
?.filter(teamScore => {
// Filter employees based on active department tab
// For now, showing all employees as we don't have department info per employee
// In a real implementation, you'd filter by employee department
return true;
})
?.map((teamScore, index) => {
const radarData = [
{ label: 'Reliability', value: teamScore.reliability * 10 },
{ label: 'Role Fit', value: teamScore.roleFit * 10 },
{ label: 'Scalability', value: teamScore.scalability * 10 },
{ label: 'Output', value: teamScore.output * 10 },
{ label: 'Initiative', value: teamScore.initiative * 10 }
];
return (
<div key={index} className="p-4 bg-white rounded-lg shadow-sm border border-Text-Gray-200 flex flex-col justify-start items-center gap-3">
<div className="flex flex-col items-center gap-1">
<h3 className="text-base font-semibold text-Text-Gray-800">{teamScore.employeeName}</h3>
<div className={`px-2 py-1 rounded-full text-xs font-medium ${teamScore.grade.startsWith('A') ? 'bg-green-100 text-green-800' :
teamScore.grade.startsWith('B') ? 'bg-blue-100 text-blue-800' :
teamScore.grade.startsWith('C') ? 'bg-yellow-100 text-yellow-800' :
'bg-red-100 text-red-800'
}`}>
Grade: {teamScore.grade}
</div>
</div>
<div className="w-full h-64">
<RadarPerformanceChart
data={radarData}
height={240}
color={
teamScore.grade.startsWith('A') ? '#22c55e' :
teamScore.grade.startsWith('B') ? '#3b82f6' :
teamScore.grade.startsWith('C') ? '#f59e0b' :
'#ef4444'
}
/>
</div>
</div>
);
})
}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{/* Hiring Needs */}
{report?.immediateHiringNeeds && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Hiring Needs</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{report.immediateHiringNeeds}
{/* Copilot, we need to implement this so that it works with the defined type, it's not currently implemented correctly. */}
</div>
</div>
</div>
)}
{/* Forward Plan */}
{report?.forwardOperatingPlan && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Forward Plan</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{report.forwardOperatingPlan}
{/* Copilot, we need to implement this so that it works with the defined type, it's not currently implemented correctly. */}
</div>
</div>
</div>
)}
{/* Company Strengths */} {/* Company Strengths */}
{report?.organizationalStrengths && ( {/* {report?.organizationalStrengths && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden"> <div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2"> <div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Company Strengths</div> <div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Company Strengths</div>
@@ -494,7 +720,7 @@ const CompanyReportContent: React.FC<{
))} ))}
</div> </div>
</div> </div>
)} )} */}
</div> </div>
</> </>
); );
@@ -502,17 +728,17 @@ const CompanyReportContent: React.FC<{
// Component for displaying employee report content // Component for displaying employee report content
const EmployeeReportContent: React.FC<{ const EmployeeReportContent: React.FC<{
report: Report; report: EmployeeReport;
employeeName: string; employeeName: string;
}> = ({ report, employeeName }) => { }> = ({ report, employeeName }) => {
return ( return (
<> <>
{/* Header */} {/* Header */}
<div className="self-stretch px-5 py-3 inline-flex justify-start items-center gap-2.5"> <div className="self-stretch px-5 py-3 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 text-base font-medium font-['Inter'] leading-normal"> <div className="flex-1 justify-start text-[--Neutrals-NeutralSlate800] text-base font-medium font-['Inter'] leading-normal">
{employeeName}'s Answers {employeeName}'s Answers
</div> </div>
<div className="px-3 py-2.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden"> <div className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden">
<div className="relative"> <div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M14 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
@@ -526,7 +752,7 @@ const EmployeeReportContent: React.FC<{
{/* Content */} {/* Content */}
<div className="self-stretch flex flex-col justify-start items-start gap-4 px-5 pb-6 overflow-y-auto"> <div className="self-stretch flex flex-col justify-start items-start gap-4 px-5 pb-6 overflow-y-auto">
{/* Performance Overview */} {/* Role & Responsibilities */}
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden"> <div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2"> <div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Role & Responsibilities</div> <div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Role & Responsibilities</div>
@@ -538,44 +764,91 @@ const EmployeeReportContent: React.FC<{
</div> </div>
</div> </div>
{/* Work Output */} {/* Self-Rated Output */}
{report.roleAndOutput?.output && ( {report.roleAndOutput?.selfRatedOutput && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden"> <div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2"> <div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Work Output</div> <div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Self-Rated Output</div>
</div> </div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4"> <div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
<p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> <p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{report.roleAndOutput.output} {report.roleAndOutput.selfRatedOutput}
</p> </p>
</div> </div>
</div> </div>
)} )}
{/* Skills & Experience */} {/* Insights */}
{report.skillsAndExperience && ( {report.insights && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden"> <div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2"> <div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Skills & Experience</div> <div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Insights & Traits</div>
</div> </div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4"> <div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
<p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> {report.insights.personalityTraits && (
{report.skillsAndExperience} <div className="self-stretch">
</p> <div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal mb-2">Personality Traits</div>
<p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{report.insights.personalityTraits}
</p>
</div>
)}
{report.insights.selfAwareness && (
<div className="self-stretch">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal mb-2">Self Awareness</div>
<p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{report.insights.selfAwareness}
</p>
</div>
)}
{report.insights.growthDesire && (
<div className="self-stretch">
<div className="text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal mb-2">Growth Desire</div>
<p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{report.insights.growthDesire}
</p>
</div>
)}
</div> </div>
</div> </div>
)} )}
{/* Communication Style */} {/* Strengths */}
{report.communicationStyle && ( {report.strengths && report.strengths.length > 0 && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden"> <div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2"> <div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Communication Style</div> <div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Strengths</div>
</div> </div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4"> <div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
<p className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> {report.strengths.map((strength, index) => (
{report.communicationStyle} <div key={index} className="self-stretch flex justify-start items-start gap-3">
</p> <div className="w-6 h-6 relative rounded-[999px] overflow-hidden">
<div className="w-6 h-6 left-0 top-0 absolute bg-Other-Green rounded-full" />
<div className="left-[5px] top-[5px] absolute">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 3.5L5.24998 9.91667L2.33331 7" stroke="var(--Neutrals-NeutralSlate0, #FDFDFD)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">{strength}</div>
</div>
))}
</div>
</div>
)}
{/* Recommendations */}
{report.recommendations && report.recommendations.length > 0 && (
<div className="w-full p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
<div className="justify-start text-Text-Dark-950 text-xl font-medium font-['Neue_Montreal'] leading-normal">Recommendations</div>
</div>
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
{report.recommendations.map((recommendation, index) => (
<div key={index} className="self-stretch text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
{recommendation}
</div>
))}
</div> </div>
</div> </div>
)} )}

View File

@@ -36,7 +36,7 @@ const SubscriptionSetup: React.FC = () => {
try { try {
setLoading(true); setLoading(true);
// Get updated subscription status // Get updated subscription status
await getSubscriptionStatus(orgId); await getSubscriptionStatus();
// Redirect to onboarding to complete organization setup // Redirect to onboarding to complete organization setup
setTimeout(() => { setTimeout(() => {
@@ -57,7 +57,7 @@ const SubscriptionSetup: React.FC = () => {
setLoading(true); setLoading(true);
setError(null); setError(null);
const { sessionUrl } = await createCheckoutSession(orgId, user.email!); const { sessionUrl } = await createCheckoutSession(user.email!);
// Redirect to Stripe Checkout // Redirect to Stripe Checkout
window.location.href = sessionUrl; window.location.href = sessionUrl;

View File

@@ -3,7 +3,7 @@
* All data operations go through authenticated cloud functions * All data operations go through authenticated cloud functions
*/ */
import { Employee, Report, Submission, CompanyReport } from '../types'; import { Employee, EmployeeReport, Submission, CompanyReport, UserOrganization, Organization } from '../types';
import { API_URL } from '../constants'; import { API_URL } from '../constants';
interface ApiResponse<T> { interface ApiResponse<T> {
@@ -42,19 +42,62 @@ interface OrgData {
[key: string]: any; [key: string]: any;
} }
interface GetUserOrganizations {
organizations: UserOrganization[];
}
interface CreateOrganization {
success: boolean;
orgId: string;
name: string;
role: "owner" | "admin" | "employee";
onboardingCompleted: boolean;
joinedAt: number;
subscription: any;
requiresSubscription: boolean;
}
interface JoinOrganization {
orgId: string;
name: string;
role: "owner" | "admin" | "employee";
onboardingCompleted: boolean;
joinedAt: number;
success: boolean;
}
interface CreateCheckoutSession {
success: true;
sessionId: string;
sessionUrl: string;
customerId: string;
}
class SecureApiService { class SecureApiService {
private async makeRequest<T>( private async makeRequest<T>(
endpoint: string, endpoint: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
data?: any data?: any,
requireAuth: boolean = true
): Promise<T> { ): Promise<T> {
const url = `${API_URL}/${endpoint}`; const url = `${API_URL}/${endpoint}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
// Add auth token for authenticated requests
if (requireAuth) {
const authToken = localStorage.getItem('auditly_auth_token');
if (!authToken) {
throw new Error('Authentication token not found. Please log in again.');
}
headers['Authorization'] = `Bearer ${authToken}`;
}
const config: RequestInit = { const config: RequestInit = {
method, method,
headers: { headers,
'Content-Type': 'application/json',
},
}; };
if (data && method !== 'GET') { if (data && method !== 'GET') {
@@ -65,6 +108,14 @@ class SecureApiService {
const response = await fetch(url, config); const response = await fetch(url, config);
if (!response.ok) { if (!response.ok) {
// Handle auth errors specifically
if (requireAuth && (response.status === 401 || response.status === 403)) {
// Clear invalid token and redirect to login
localStorage.removeItem('auditly_auth_token');
localStorage.removeItem('auditly_demo_session');
throw new Error('Authentication failed. Please log in again.');
}
const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
throw new Error(errorData.error || `HTTP ${response.status}`); throw new Error(errorData.error || `HTTP ${response.status}`);
} }
@@ -83,90 +134,90 @@ class SecureApiService {
} }
// Organization Data Methods // Organization Data Methods
async getOrgData(orgId: string, userId: string): Promise<OrgData> { async getOrgData(): Promise<OrgData> {
const response = await this.makeRequest<{ org: OrgData }>( const response = await this.makeRequest<{ org: OrgData }>(
`getOrgData?orgId=${orgId}&userId=${userId}` 'getOrgData', 'GET', null, true
); );
return response.org; return response.org;
} }
async updateOrgData(orgId: string, userId: string, data: Partial<OrgData>): Promise<void> { async updateOrgData(data: Partial<OrgData>): Promise<void> {
await this.makeRequest( await this.makeRequest(
'updateOrgData', 'updateOrgData',
'PUT', 'PUT',
{ orgId, userId, data } { data }
); );
} }
// Employee Methods // Employee Methods
async getEmployees(orgId: string, userId: string): Promise<Employee[]> { async getEmployees(): Promise<Employee[]> {
const response = await this.makeRequest<{ employees: Employee[] }>( const response = await this.makeRequest<{ employees: Employee[] }>(
`getEmployees?orgId=${orgId}&userId=${userId}` 'getEmployees'
); );
return response.employees; return response.employees;
} }
async upsertEmployee(orgId: string, userId: string, employeeData: Partial<Employee>): Promise<Employee> { async upsertEmployee(employeeData: Partial<Employee>): Promise<Employee> {
const response = await this.makeRequest<{ employee: Employee }>( const response = await this.makeRequest<{ employee: Employee }>(
'upsertEmployee', 'upsertEmployee',
'POST', 'POST',
{ orgId, userId, employeeData } { employeeData }
); );
return response.employee; return response.employee;
} }
// Submission Methods // Submission Methods
async getSubmissions(orgId: string, userId: string): Promise<Record<string, Submission>> { async getSubmissions(): Promise<Record<string, Submission>> {
const response = await this.makeRequest<{ submissions: Record<string, Submission> }>( const response = await this.makeRequest<{ submissions: Record<string, Submission> }>(
`getSubmissions?orgId=${orgId}&userId=${userId}` 'getSubmissions'
); );
return response.submissions; return response.submissions;
} }
// Report Methods // Report Methods
async getReports(orgId: string, userId: string): Promise<Record<string, Report>> { async getReports(): Promise<Record<string, EmployeeReport>> {
const response = await this.makeRequest<{ reports: Record<string, Report> }>( const response = await this.makeRequest<{ reports: Record<string, EmployeeReport> }>(
`getReports?orgId=${orgId}&userId=${userId}` 'getReports'
); );
return response.reports; return response.reports;
} }
async saveReport(orgId: string, userId: string, employeeId: string, reportData: Partial<Report>): Promise<Report> { async saveReport(employeeId: string, reportData: Partial<EmployeeReport>): Promise<EmployeeReport> {
const response = await this.makeRequest<{ report: Report }>( const response = await this.makeRequest<{ report: EmployeeReport }>(
'saveReport', 'saveReport',
'POST', 'POST',
{ orgId, userId, employeeId, reportData } { employeeId, reportData }
); );
return response.report; return response.report;
} }
// Company Report Methods // Company Report Methods
async getCompanyReports(orgId: string, userId: string): Promise<CompanyReport[]> { async getCompanyReports(): Promise<CompanyReport[]> {
const response = await this.makeRequest<{ reports: CompanyReport[] }>( const response = await this.makeRequest<{ reports: CompanyReport[] }>(
`getCompanyReports?orgId=${orgId}&userId=${userId}` 'getCompanyReports'
); );
return response.reports; return response.reports;
} }
async saveCompanyReport(orgId: string, report: any): Promise<string> { async saveCompanyReport(report: any): Promise<string> {
const response = await this.makeRequest<{ reportId: string }>( const response = await this.makeRequest<{ reportId: string }>(
'saveCompanyReport', 'saveCompanyReport',
'POST', 'POST',
{ orgId, report } { report }
); );
return response.reportId; return response.reportId;
} }
// Existing API methods (these already use cloud functions) // Existing API methods (these already use cloud functions)
async sendOTP(email: string, inviteCode?: string) { async sendOTP(email: string, inviteCode?: string) {
return this.makeRequest('sendOTP', 'POST', { email, inviteCode }); return this.makeRequest('sendOTP', 'POST', { email, inviteCode }, false);
} }
async verifyOTP(email: string, otp: string) { async verifyOTP(email: string, otp: string) {
return this.makeRequest('verifyOTP', 'POST', { email, otp }); return this.makeRequest('verifyOTP', 'POST', { email, otp }, false);
} }
async createInvitation(orgId: string, name: string, email: string, role?: string, department?: string): Promise<{ async createInvitation(name: string, email: string, role?: string, department?: string): Promise<{
success: boolean; success: boolean;
code: string; code: string;
employee: Employee; employee: Employee;
@@ -174,7 +225,7 @@ class SecureApiService {
emailLink: string; emailLink: string;
message: string; message: string;
}> { }> {
return this.makeRequest('createInvitation', 'POST', { orgId, name, email, role, department }); return this.makeRequest('createInvitation', 'POST', { name, email, role, department });
} }
async getInvitationStatus(code: string): Promise<{ used: boolean; employee: any; invite?: any } | null> { async getInvitationStatus(code: string): Promise<{ used: boolean; employee: any; invite?: any } | null> {
@@ -189,8 +240,8 @@ class SecureApiService {
return this.makeRequest('consumeInvitation', 'POST', { code, userId }); return this.makeRequest('consumeInvitation', 'POST', { code, userId });
} }
async submitEmployeeAnswers(orgId: string, employeeId: string, answers: any, inviteCode?: string) { async submitEmployeeAnswers(employeeId: string, answers: any, inviteCode?: string) {
return this.makeRequest('submitEmployeeAnswers', 'POST', { orgId, employeeId, answers, inviteCode }); return this.makeRequest('submitEmployeeAnswers', 'POST', { employeeId, answers, inviteCode });
} }
async generateEmployeeReport(employee: any, submission: any, companyWiki?: any) { async generateEmployeeReport(employee: any, submission: any, companyWiki?: any) {
@@ -205,30 +256,28 @@ class SecureApiService {
return this.makeRequest('chat', 'POST', { message, employeeId, context, mentions, attachments }); return this.makeRequest('chat', 'POST', { message, employeeId, context, mentions, attachments });
} }
async createOrganization(name: string, userId: string) { async createOrganization(name: string): Promise<CreateOrganization> {
return this.makeRequest('createOrganization', 'POST', { name, userId }); return this.makeRequest('createOrganization', 'POST', { name });
} }
async getUserOrganizations(userId: string) { async getUserOrganizations(): Promise<GetUserOrganizations> {
return this.makeRequest(`getUserOrganizations?userId=${userId}`); return this.makeRequest('getUserOrganizations', 'GET', null, true);
} }
async joinOrganization(userId: string, inviteCode: string) { async joinOrganization(inviteCode: string): Promise<JoinOrganization> {
return this.makeRequest('joinOrganization', 'POST', { userId, inviteCode }); return this.makeRequest('joinOrganization', 'POST', { inviteCode });
} }
async createCheckoutSession(orgId: string, userId: string, userEmail: string, priceId?: string) { async createCheckoutSession(userEmail: string, priceId?: string): Promise<CreateCheckoutSession> {
return this.makeRequest('createCheckoutSession', 'POST', { orgId, userId, userEmail, priceId }); return this.makeRequest('createCheckoutSession', 'POST', { userEmail, priceId });
} }
async getSubscriptionStatus(orgId: string) { async getSubscriptionStatus() {
return this.makeRequest(`getSubscriptionStatus?orgId=${orgId}`); return this.makeRequest('getSubscriptionStatus');
} }
// Image Storage Methods // Image Storage Methods
async uploadImage( async uploadImage(
orgId: string,
userId: string,
imageData: { imageData: {
collectionName: string; collectionName: string;
documentId: string; documentId: string;
@@ -240,12 +289,10 @@ class SecureApiService {
height: number; height: number;
} }
): Promise<{ success: boolean; imageId: string }> { ): Promise<{ success: boolean; imageId: string }> {
return this.makeRequest('uploadImage', 'POST', { orgId, userId, imageData }); return this.makeRequest('uploadImage', 'POST', { imageData });
} }
async getImage( async getImage(
orgId: string,
userId: string,
collectionName: string, collectionName: string,
documentId: string documentId: string
): Promise<{ ): Promise<{
@@ -260,7 +307,7 @@ class SecureApiService {
} | null> { } | null> {
try { try {
const response = await this.makeRequest<{ image: any }>( const response = await this.makeRequest<{ image: any }>(
`getImage?orgId=${orgId}&userId=${userId}&collectionName=${collectionName}&documentId=${documentId}` `getImage?collectionName=${collectionName}&documentId=${documentId}`
); );
return response.image || null; return response.image || null;
} catch (error) { } catch (error) {
@@ -270,13 +317,11 @@ class SecureApiService {
} }
async deleteImage( async deleteImage(
orgId: string,
userId: string,
collectionName: string, collectionName: string,
documentId: string documentId: string
): Promise<boolean> { ): Promise<boolean> {
try { try {
await this.makeRequest('deleteImage', 'DELETE', { orgId, userId, collectionName, documentId }); await this.makeRequest('deleteImage', 'DELETE', { collectionName, documentId });
return true; return true;
} catch (error) { } catch (error) {
console.error('Failed to delete image:', error); console.error('Failed to delete image:', error);

View File

@@ -17,7 +17,109 @@ export interface Employee {
isOwner?: boolean; // Company owner/HR access isOwner?: boolean; // Company owner/HR access
} }
export interface Report { export interface UserOrganization {
orgId: string;
name: string;
role: 'owner' | 'admin' | 'employee';
onboardingCompleted: boolean;
joinedAt: number;
}
export interface OnboardingData {
yourName: string;
companyLogo: string;
companyName: string;
companySize: string;
mission: string;
missionEvolution: string;
vision: string;
successDefinition: string;
coreValues: string;
industryReputation: string;
growthLimitations: string;
leadershipStructure: string;
criticalPeople: string;
leadershipGaps: string;
delegationOpportunities: string;
leadershipDevelopment: string;
heldOntoTooLong: string;
successionConfidence: string;
businessSystems: string;
personalDependencies: string;
operationalFriction: string;
workflowEfficiency: string;
recurringProblems: string;
wouldFixFirst: string;
cultureDescription: string;
livedValues: string;
internalFriction: string;
speakUpCulture: string;
clearExpectations: string;
staffFeedback: string;
highPerformerLove: string;
customerAcquisition: string;
competitiveAdvantage: string;
customerLoss: string;
customerLoyalty: string;
marketingROI: string;
overspending: string;
growthAccelerator: string;
untappedOpportunities: string;
newProducts: string;
innovationProcess: string;
uniquePosition: string;
industryDirection: string;
disruptionThreats: string;
ideaValidation: string;
idealPortfolio: string;
innovationBalance: string;
innovationPartnerships: string;
innovationMetrics: string;
businessWorries: string;
leadershipBlindSpots: string;
stressManagement: string;
regretfulDecisions: string;
leadershipGrowth: string;
leadershipStretched: string;
sabbaticalRisks: string;
companyLegacy: string;
yearSuccess: string;
}
export interface Organization {
name: string;
createdAt: number;
updatedAt: number;
onboardingCompleted: boolean;
ownerId: string;
// Subscription fields (will be populated after Stripe setup)
subscription: {
status: 'trial' | 'active' | 'past_due' | 'canceled';
stripeCustomerId: string | null;
stripeSubscriptionId: string | null;
currentPeriodStart: number | null;
currentPeriodEnd: number | null;
trialEnd: number | null;
};
// Usage tracking
usage: {
employeeCount: number;
reportsGenerated: number;
lastReportGeneration: number | null;
};
// Organization settings
settings: {
allowedEmployeeCount: number;
featuresEnabled: {
aiReports: boolean;
chat: boolean;
analytics: boolean;
}
};
onboardingData?: OnboardingData;
}
export interface EmployeeReport {
employeeId: string; employeeId: string;
department: string; department: string;
role: string; role: string;
@@ -109,16 +211,18 @@ export interface CompanyReport {
averagePerformanceScore?: number; averagePerformanceScore?: number;
riskLevel?: 'Low' | 'Medium' | 'High'; riskLevel?: 'Low' | 'Medium' | 'High';
}; };
// Personnel lifecycle weaknesses: {
title: string;
description: string;
}[];
// Personnel lifecycle - Categories should be Promoted, Reassigned, Fired
personnelChanges: { personnelChanges: {
newHires: { name: string; department: string; role: string; impact?: string }[]; newHires: { name: string; department: string; role: string; impact?: string }[]; // Is the reassignments?
promotions: { name: string; fromRole: string; toRole: string; impact?: string }[]; promotions: { name: string; fromRole: string; toRole: string; impact?: string }[];
departures: { name: string; department: string; reason: string; impact?: string }[]; departures: { name: string; department: string; reason: string; impact?: string }[];
}; };
// Backward-compatible flattened list (optional)
keyPersonnelChanges?: Array<{ employeeName: string; role: string; department: string; changeType: 'newHire' | 'promotion' | 'departure'; impact?: string }>;
// Hiring / talent gaps // Hiring / talent gaps
immediateHiringNeeds: { immediateHiringNeeds: { // Needs to be used!!
department: string; department: string;
role: string; role: string;
priority: 'High' | 'Medium' | 'Low'; priority: 'High' | 'Medium' | 'Low';
@@ -126,21 +230,11 @@ export interface CompanyReport {
urgency?: 'high' | 'medium' | 'low'; // UI alias urgency?: 'high' | 'medium' | 'low'; // UI alias
}[]; }[];
recommendations: string[]; recommendations: string[];
// Operating plan (dual naming for UI compatibility) forwardOperatingPlan?: {
operatingPlan: { title: string;
nextQuarterGoals: string[]; details: string[];
keyInitiatives: string[]; }[];
resourceNeeds: string[]; strengths: string[];
riskMitigation: string[];
};
forwardOperatingPlan?: { // deprecated; kept for existing calls until phased out
quarterlyGoals: string[];
resourceNeeds: string[];
riskMitigation: string[];
};
// Strengths / risks
organizationalStrengths: Array<{ icon?: string; area: string; description: string }>;
organizationalRisks: string[];
organizationalImpactSummary?: { organizationalImpactSummary?: {
missionCritical: { missionCritical: {
employeeName: string; employeeName: string;
@@ -165,14 +259,51 @@ export interface CompanyReport {
}; };
// Grading: array + map for UI // Grading: array + map for UI
gradingBreakdown: { gradingBreakdown: {
departmentNameShort: string; // For the tab label
departmentName: string; departmentName: string;
lead: string; lead: string; // Team lead
support: string; support: string; // Team leads' support person
summary: string; departmentGrade: string;
category: string; executiveSummary: string;
value: number; // 0-100 teamScores: {
rationale?: string; employeeName: string;
}[]; grade: string;
// Each of the following is out of 10, total being 50 points
// These gets displayed as radar charts
reliability: number;
roleFit: number;
scalability: number;
output: number;
initiative: number;
}[];
}
// gradingBreakdown: {
// departmentName: string;
// lead: string;
// support: string;
// summary: string;
// category: string;
// value: number; // 0-100
// rationale?: string;
// }[];
// Do not use any of the properties below!!! they need to be safely removed
organizationalRisks: string[];
operatingPlan: { // Do not use!!!
nextQuarterGoals: string[];
keyInitiatives: string[];
resourceNeeds: string[];
riskMitigation: string[];
};
organizationalStrengths: Array<{ icon?: string; area: string; description: string }>; // Do not use!!! needs to be removed
keyPersonnelChanges?: Array<{ // Do not use!!
employeeName: string;
role: string;
newRole?: string;
department: string;
changeType: 'reassignment' | 'promotion' | 'departure';
impact?: string;
}>;
gradingOverview?: Record<string, number>; // legacy (0-5 scale expected by old UI) gradingOverview?: Record<string, number>; // legacy (0-5 scale expected by old UI)
executiveSummary: string; executiveSummary: string;
} }