ui conflicts resolved

This commit is contained in:
laxmanhalaki 2025-10-22 19:23:55 +05:30
parent 6fe42e8e5b
commit da1d0538e9
111 changed files with 11115 additions and 923 deletions

47
.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build outputs
build
out
.next
.cache
# Testing
coverage
.nyc_output
# Misc
.turbo
*.tsbuildinfo

12
.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}

11
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"christian-kohler.path-intellisense",
"visualstudioexptteam.vscodeintellicode"
]
}

306
MIGRATION_GUIDE.md Normal file
View File

@ -0,0 +1,306 @@
# 🔄 Migration Guide - Project Setup Complete
## ✅ What Has Been Created
### Configuration Files ✓
- ✅ `package.json` - Dependencies and scripts
- ✅ `tsconfig.json` - TypeScript configuration
- ✅ `tsconfig.node.json` - Node TypeScript config
- ✅ `vite.config.ts` - Vite build configuration
- ✅ `tailwind.config.ts` - Tailwind CSS configuration
- ✅ `postcss.config.js` - PostCSS configuration
- ✅ `eslint.config.js` - ESLint configuration
- ✅ `.prettierrc` - Prettier configuration
- ✅ `.gitignore` - Git ignore rules
- ✅ `.env.example` - Environment variables template
- ✅ `index.html` - HTML entry point
### VS Code Configuration ✓
- ✅ `.vscode/settings.json` - Editor settings
- ✅ `.vscode/extensions.json` - Recommended extensions
### Documentation ✓
- ✅ `README.md` - Comprehensive project documentation
- ✅ `MIGRATION_GUIDE.md` - This file
### Source Files Created ✓
- ✅ `src/main.tsx` - Application entry point
- ✅ `src/vite-env.d.ts` - Vite environment types
- ✅ `src/types/index.ts` - TypeScript type definitions
---
## 🚀 Next Steps - File Migration
### Step 1: Install Dependencies
\`\`\`bash
npm install
\`\`\`
This will install all required packages (~5 minutes).
### Step 2: Migrate Files to src Directory
You need to manually move the existing files to the `src` directory:
#### A. Move App.tsx
\`\`\`bash
# Windows Command Prompt
move App.tsx src\\App.tsx
# Or manually drag and drop in VS Code
\`\`\`
#### B. Move Components Directory
\`\`\`bash
# Windows Command Prompt
move components src\\components
# Or manually drag and drop in VS Code
\`\`\`
#### C. Move Utils Directory
\`\`\`bash
# Windows Command Prompt
move utils src\\utils
# Or manually drag and drop in VS Code
\`\`\`
#### D. Move Styles Directory
\`\`\`bash
# Windows Command Prompt
move styles src\\styles
# Or manually drag and drop in VS Code
\`\`\`
### Step 3: Update Import Paths
After moving files, you'll need to update import statements to use path aliases.
#### Example Changes:
**Before:**
\`\`\`typescript
import { Layout } from './components/Layout';
import { Dashboard } from './components/Dashboard';
\`\`\`
**After:**
\`\`\`typescript
import { Layout } from '@/components/Layout';
import { Dashboard } from '@/components/Dashboard';
\`\`\`
**Files that need updating:**
1. `src/App.tsx` - Update all component imports
2. All files in `src/components/` - Update relative imports
3. All modal files in `src/components/modals/`
### Step 4: Fix Sonner Import
In `src/App.tsx`, update the sonner import:
**Before:**
\`\`\`typescript
import { toast } from 'sonner@2.0.3';
\`\`\`
**After:**
\`\`\`typescript
import { toast } from 'sonner';
\`\`\`
### Step 5: Start Development Server
\`\`\`bash
npm run dev
\`\`\`
The app should open at `http://localhost:3000`
---
## 🔍 Common Issues & Solutions
### Issue 1: Module not found errors
**Problem:** TypeScript can't find modules after migration.
**Solution:**
1. Restart VS Code TypeScript server: `Ctrl+Shift+P` → "TypeScript: Restart TS Server"
2. Clear node_modules and reinstall:
\`\`\`bash
rm -rf node_modules package-lock.json
npm install
\`\`\`
### Issue 2: Path alias not working
**Problem:** `@/` imports show errors.
**Solution:**
1. Check `tsconfig.json` paths configuration
2. Check `vite.config.ts` resolve.alias configuration
3. Restart VS Code
### Issue 3: Tailwind classes not applying
**Problem:** Styles not working after migration.
**Solution:**
1. Ensure `globals.css` is imported in `src/main.tsx`
2. Check `tailwind.config.ts` content paths
3. Restart dev server: `Ctrl+C` then `npm run dev`
### Issue 4: Build errors
**Problem:** TypeScript compilation errors.
**Solution:**
1. Run type check: `npm run type-check`
2. Fix any TypeScript errors shown
3. Run build again: `npm run build`
---
## 📋 Migration Checklist
Use this checklist to track your migration progress:
### Files Migration
- [ ] Installed dependencies (`npm install`)
- [ ] Moved `App.tsx` to `src/`
- [ ] Moved `components/` to `src/components/`
- [ ] Moved `utils/` to `src/utils/`
- [ ] Moved `styles/` to `src/styles/`
- [ ] Created `src/main.tsx` (already done)
### Import Updates
- [ ] Updated imports in `src/App.tsx`
- [ ] Updated imports in `src/components/Layout.tsx`
- [ ] Updated imports in `src/components/Dashboard.tsx`
- [ ] Updated imports in all other component files
- [ ] Fixed `sonner` import in `App.tsx`
### Testing
- [ ] Dev server starts successfully (`npm run dev`)
- [ ] Application loads at `http://localhost:3000`
- [ ] No console errors
- [ ] Dashboard displays correctly
- [ ] Navigation works
- [ ] New request wizard works
- [ ] Claim management wizard works
### Code Quality
- [ ] ESLint passes (`npm run lint`)
- [ ] TypeScript compiles (`npm run type-check`)
- [ ] Code formatted (`npm run format`)
- [ ] Build succeeds (`npm run build`)
### Environment
- [ ] Created `.env` from `.env.example`
- [ ] Updated environment variables if needed
- [ ] VS Code extensions installed
---
## 🎯 After Migration
### 1. Clean Up Old Files
After confirming everything works in `src/`:
\`\`\`bash
# Delete old documentation files from root (optional)
# Keep only if you want them at root level
\`\`\`
### 2. Commit Changes
\`\`\`bash
git add .
git commit -m "feat: migrate to standard React project structure with Vite"
\`\`\`
### 3. Update Team
Inform team members about:
- New project structure
- Updated npm scripts
- Path alias usage (`@/`)
- Required VS Code extensions
---
## 📚 Additional Resources
### Documentation
- [Vite Documentation](https://vitejs.dev/)
- [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/)
- [Tailwind CSS Docs](https://tailwindcss.com/docs)
- [shadcn/ui Components](https://ui.shadcn.com/)
### Scripts Reference
\`\`\`bash
npm run dev # Start development server
npm run build # Build for production
npm run preview # Preview production build
npm run lint # Check for linting errors
npm run lint:fix # Auto-fix linting errors
npm run format # Format code with Prettier
npm run type-check # Check TypeScript types
\`\`\`
---
## 💡 Tips for Development
### 1. Use Path Aliases
\`\`\`typescript
// Good ✓
import { Button } from '@/components/ui/button';
import { getDealerInfo } from '@/utils/dealerDatabase';
// Avoid ✗
import { Button } from '../../../components/ui/button';
\`\`\`
### 2. Type Safety
\`\`\`typescript
// Import types
import type { Request, DealerInfo } from '@/types';
// Use them in your components
const request: Request = { ... };
\`\`\`
### 3. Code Formatting
Set up auto-format on save in VS Code (already configured in `.vscode/settings.json`)
### 4. Commit Conventions
Use conventional commits:
- `feat:` for new features
- `fix:` for bug fixes
- `docs:` for documentation
- `style:` for formatting changes
- `refactor:` for code refactoring
---
## ❓ Need Help?
If you encounter issues:
1. Check this migration guide
2. Check the main README.md
3. Review error messages carefully
4. Check VS Code Problems panel
5. Restart VS Code TypeScript server
6. Clear node_modules and reinstall
---
**Migration prepared by the Development Team**
**Date: 2024**

291
README.md Normal file
View File

@ -0,0 +1,291 @@
# 🏍️ Royal Enfield Approval Portal
A modern, enterprise-grade approval and request management system built with React, TypeScript, and Tailwind CSS.
## 📋 Table of Contents
- [Features](#features)
- [Tech Stack](#tech-stack)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Development](#development)
- [Project Structure](#project-structure)
- [Available Scripts](#available-scripts)
- [Configuration](#configuration)
- [Contributing](#contributing)
## ✨ Features
- **🔄 Dual Workflow System**
- Custom Request Workflow with user-defined approvers
- Claim Management Workflow (8-step predefined process)
- **📊 Comprehensive Dashboard**
- Real-time statistics and metrics
- High-priority alerts
- Recent activity tracking
- **🎯 Request Management**
- Create, track, and manage approval requests
- Document upload and management
- Work notes and audit trails
- Spectator and stakeholder management
- **🎨 Modern UI/UX**
- Responsive design (mobile, tablet, desktop)
- Dark mode support
- Accessible components (WCAG compliant)
- Royal Enfield brand theming
- **🔔 Notifications**
- Real-time toast notifications
- SLA tracking and reminders
- Approval status updates
## 🛠️ Tech Stack
- **Framework:** React 18.3+
- **Language:** TypeScript 5.6+
- **Build Tool:** Vite 5.4+
- **Styling:** Tailwind CSS 3.4+
- **UI Components:** shadcn/ui + Radix UI
- **Icons:** Lucide React
- **Notifications:** Sonner
- **State Management:** React Hooks (useState, useMemo)
## 📦 Prerequisites
- **Node.js:** >= 18.0.0
- **npm:** >= 9.0.0 (or yarn/pnpm)
- **Git:** Latest version
## 🚀 Installation
### 1. Clone the repository
\`\`\`bash
git clone <repository-url>
cd Re_Figma_Code
\`\`\`
### 2. Install dependencies
\`\`\`bash
npm install
\`\`\`
### 3. Set up environment variables
\`\`\`bash
cp .env.example .env
\`\`\`
Edit `.env` with your configuration:
\`\`\`env
VITE_API_BASE_URL=http://localhost:5000/api
VITE_APP_NAME=Royal Enfield Approval Portal
\`\`\`
### 4. Move files to src directory
\`\`\`bash
# Create src directory structure
mkdir -p src/components src/utils src/styles src/types
# Move existing files (you'll need to do this manually or run the migration script)
# The structure should match the project structure below
\`\`\`
## 💻 Development
### Start development server
\`\`\`bash
npm run dev
\`\`\`
The application will open at `http://localhost:3000`
### Build for production
\`\`\`bash
npm run build
\`\`\`
### Preview production build
\`\`\`bash
npm run preview
\`\`\`
## 📁 Project Structure
\`\`\`
Re_Figma_Code/
├── src/
│ ├── components/
│ │ ├── ui/ # Reusable UI components (40+)
│ │ ├── modals/ # Modal components
│ │ ├── figma/ # Figma-specific components
│ │ ├── Dashboard.tsx
│ │ ├── Layout.tsx
│ │ ├── ClaimManagementWizard.tsx
│ │ ├── NewRequestWizard.tsx
│ │ ├── RequestDetail.tsx
│ │ ├── ClaimManagementDetail.tsx
│ │ ├── MyRequests.tsx
│ │ └── ...
│ ├── utils/
│ │ ├── customRequestDatabase.ts
│ │ ├── claimManagementDatabase.ts
│ │ └── dealerDatabase.ts
│ ├── styles/
│ │ └── globals.css
│ ├── types/
│ │ └── index.ts # TypeScript type definitions
│ ├── App.tsx
│ └── main.tsx
├── public/ # Static assets
├── .vscode/ # VS Code settings
├── index.html
├── vite.config.ts
├── tsconfig.json
├── tailwind.config.ts
├── postcss.config.js
├── eslint.config.js
├── .prettierrc
├── .gitignore
├── package.json
└── README.md
\`\`\`
## 📜 Available Scripts
| Script | Description |
|--------|-------------|
| `npm run dev` | Start development server |
| `npm run build` | Build for production |
| `npm run preview` | Preview production build |
| `npm run lint` | Run ESLint |
| `npm run lint:fix` | Fix ESLint errors |
| `npm run format` | Format code with Prettier |
| `npm run type-check` | Check TypeScript types |
## ⚙️ Configuration
### TypeScript Path Aliases
The project uses path aliases for cleaner imports:
\`\`\`typescript
import { Button } from '@/components/ui/button';
import { getDealerInfo } from '@/utils/dealerDatabase';
\`\`\`
### Tailwind CSS Customization
Custom Royal Enfield colors are defined in `tailwind.config.ts`:
\`\`\`typescript
colors: {
're-green': '#2d4a3e',
're-gold': '#c9b037',
're-dark': '#1a1a1a',
're-light-green': '#8a9b8e',
}
\`\`\`
### Environment Variables
All environment variables must be prefixed with `VITE_` to be accessible in the app:
\`\`\`typescript
const apiUrl = import.meta.env.VITE_API_BASE_URL;
\`\`\`
## 🔧 Next Steps
### 1. File Migration
Move existing files to the `src` directory:
\`\`\`bash
# Move App.tsx
mv App.tsx src/
# Move components
mv components src/
# Move utils
mv utils src/
# Move styles
mv styles src/
\`\`\`
### 2. Create main.tsx entry point
Create `src/main.tsx`:
\`\`\`typescript
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './styles/globals.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
\`\`\`
### 3. Update imports
Update all import paths to use the `@/` alias:
\`\`\`typescript
// Before
import { Button } from './components/ui/button';
// After
import { Button } from '@/components/ui/button';
\`\`\`
### 4. Backend Integration
When ready to connect to a real API:
1. Create `src/services/api.ts` for API calls
2. Replace mock databases with API calls
3. Add authentication layer
4. Implement error handling
## 🧪 Testing (Future Enhancement)
Add testing framework:
\`\`\`bash
npm install -D vitest @testing-library/react @testing-library/jest-dom
\`\`\`
## 🤝 Contributing
1. Follow the existing code style
2. Run `npm run lint:fix` before committing
3. Run `npm run type-check` to ensure type safety
4. Write meaningful commit messages
## 📝 License
Private - Royal Enfield Internal Use Only
## 👥 Team
Built by the Royal Enfield Development Team
---
**For support or questions, contact the development team.**

283
SETUP_INSTRUCTIONS.md Normal file
View File

@ -0,0 +1,283 @@
# 🎯 Quick Setup Instructions
## ✅ Project Setup Complete!
Your Royal Enfield Approval Portal has been configured with industry-standard React development tools and structure.
---
## 🚀 Quick Start (5 Minutes)
### Option 1: Automated Migration (Recommended)
**For Windows PowerShell:**
```powershell
# 1. Run the migration script
.\migrate-files.ps1
# 2. Install dependencies
npm install
# 3. Start development server
npm run dev
```
### Option 2: Manual Migration
**If you prefer manual control:**
```bash
# 1. Create src directories
mkdir src\components src\utils src\styles
# 2. Move files
move App.tsx src\
move components src\
move utils src\
move styles src\
# 3. Install dependencies
npm install
# 4. Start development server
npm run dev
```
---
## 📦 What Was Created
### Core Configuration ✅
- ✅ `package.json` - 50+ dependencies installed
- ✅ `vite.config.ts` - Build tool (fast, modern)
- ✅ `tsconfig.json` - TypeScript settings
- ✅ `tailwind.config.ts` - Styling configuration
- ✅ `eslint.config.js` - Code quality rules
- ✅ `.prettierrc` - Code formatting
### Project Structure ✅
```
Re_Figma_Code/
├── src/ ← NEW! All code goes here
│ ├── main.tsx ← Entry point (created)
│ ├── App.tsx ← Move here
│ ├── components/ ← Move here
│ ├── utils/ ← Move here
│ ├── styles/ ← Move here
│ └── types/ ← Type definitions (created)
├── public/ ← Static assets
├── index.html ← HTML entry
└── [config files] ← All created
```
### VS Code Setup ✅
- ✅ `.vscode/settings.json` - Auto-format, Tailwind IntelliSense
- ✅ `.vscode/extensions.json` - Recommended extensions
---
## 🔧 After Running Setup
### 1. Fix Imports in App.tsx
**Update these imports:**
```typescript
// OLD (relative paths)
import { Layout } from './components/Layout';
import { Dashboard } from './components/Dashboard';
import { toast } from 'sonner@2.0.3';
// NEW (path aliases)
import { Layout } from '@/components/Layout';
import { Dashboard } from '@/components/Dashboard';
import { toast } from 'sonner';
```
### 2. Update Component Imports
**In all component files, change:**
```typescript
// OLD
import { Button } from './ui/button';
import { Card } from '../ui/card';
// NEW
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
```
### 3. Verify Everything Works
```bash
# Check for TypeScript errors
npm run type-check
# Check for linting issues
npm run lint
# Format code
npm run format
# Build for production (test)
npm run build
```
---
## 🎨 Development Workflow
### Daily Development
```bash
npm run dev # Start dev server (http://localhost:3000)
```
### Code Quality
```bash
npm run lint # Check for issues
npm run lint:fix # Auto-fix issues
npm run format # Format code with Prettier
```
### Building
```bash
npm run build # Production build
npm run preview # Preview production build
```
---
## 🆘 Troubleshooting
### Issue: "Module not found"
**Solution:**
```bash
# Clear cache and reinstall
rm -rf node_modules package-lock.json
npm install
```
### Issue: "@/ path alias not working"
**Solution:**
1. Restart VS Code
2. Press `Ctrl+Shift+P` → "TypeScript: Restart TS Server"
### Issue: "Tailwind classes not applying"
**Solution:**
1. Check `src/main.tsx` imports `'./styles/globals.css'`
2. Restart dev server: `Ctrl+C` then `npm run dev`
### Issue: Build errors
**Solution:**
```bash
npm run type-check # See TypeScript errors
npm run lint # See ESLint errors
```
---
## 📚 Important Files
### Environment Variables
Copy and edit `.env.example`:
```bash
cp .env.example .env
```
Edit values:
```env
VITE_API_BASE_URL=http://localhost:5000/api
VITE_APP_NAME=Royal Enfield Approval Portal
```
### Type Definitions
Use types from `src/types/index.ts`:
```typescript
import type { Request, DealerInfo, Priority } from '@/types';
const request: Request = { /* ... */ };
```
---
## ✨ New Features Available
### 1. Path Aliases
```typescript
import { Button } from '@/components/ui/button';
import { getDealerInfo } from '@/utils/dealerDatabase';
import type { Request } from '@/types';
```
### 2. Code Quality Tools
- **ESLint** - Catches bugs and enforces best practices
- **Prettier** - Consistent code formatting
- **TypeScript** - Type safety and IntelliSense
### 3. Optimized Build
- **Code splitting** - Faster load times
- **Tree shaking** - Smaller bundle size
- **Source maps** - Easy debugging
### 4. Development Experience
- **Hot Module Replacement** - Instant updates
- **Fast Refresh** - Preserve component state
- **Better Error Messages** - Easier debugging
---
## 🎯 Next Steps
1. ✅ Run migration script or move files manually
2. ✅ Install dependencies: `npm install`
3. ✅ Update imports to use `@/` aliases
4. ✅ Fix sonner import
5. ✅ Start dev server: `npm run dev`
6. ✅ Test all features work
7. ✅ Commit changes to git
---
## 📖 Documentation
- **README.md** - Comprehensive project documentation
- **MIGRATION_GUIDE.md** - Detailed migration steps
- **package.json** - All available scripts
---
## 🤝 Team Guidelines
### Code Style
- Use path aliases (`@/`) for all imports
- Format code before committing (`npm run format`)
- Fix linting issues (`npm run lint:fix`)
- Write TypeScript types (avoid `any`)
### Git Commits
```bash
git add .
git commit -m "feat: add new feature"
git commit -m "fix: resolve bug"
git commit -m "docs: update documentation"
```
---
## ✅ Success Checklist
- [ ] Dependencies installed (`npm install`)
- [ ] Files migrated to `src/`
- [ ] Imports updated to use `@/`
- [ ] Sonner import fixed
- [ ] Dev server runs (`npm run dev`)
- [ ] No console errors
- [ ] Application loads correctly
- [ ] All features work
- [ ] Build succeeds (`npm run build`)
---
**🎉 You're all set! Happy coding!**
For detailed help, see `MIGRATION_GUIDE.md` or `README.md`

342
START_HERE.md Normal file
View File

@ -0,0 +1,342 @@
# 🚀 START HERE - Royal Enfield Approval Portal
## ✅ Your Project is Now Configured!
All standard React configuration files have been created. Your project now follows industry best practices.
---
## 🎯 Quick Start (Choose One Method)
### Method 1: PowerShell Script (Easiest - Windows)
```powershell
# Run in PowerShell
.\migrate-files.ps1
npm install
npm run dev
```
### Method 2: Manual (All Platforms)
```bash
# 1. Create src structure
mkdir -p src/components src/utils src/styles
# 2. Move files
mv App.tsx src/
mv components src/
mv utils src/
mv styles src/
# 3. Install & run
npm install
npm run dev
```
---
## 📁 What Changed
### ✅ Created Configuration Files
```
✅ package.json - Dependencies & scripts
✅ vite.config.ts - Build configuration
✅ tsconfig.json - TypeScript settings
✅ tailwind.config.ts - Tailwind CSS config
✅ eslint.config.js - Code quality rules
✅ .prettierrc - Code formatting
✅ postcss.config.js - CSS processing
✅ .gitignore - Git ignore rules
✅ index.html - Entry HTML file
```
### ✅ Created Project Structure
```
src/
├── main.tsx ✅ Created - App entry point
├── vite-env.d.ts ✅ Created - Vite types
├── types/
│ └── index.ts ✅ Created - TypeScript types
└── lib/
└── utils.ts ✅ Created - Utility functions
Need to move:
├── App.tsx → src/App.tsx
├── components/ → src/components/
├── utils/ → src/utils/
└── styles/ → src/styles/
```
### ✅ Created Documentation
```
✅ README.md - Full project documentation
✅ MIGRATION_GUIDE.md - Detailed migration steps
✅ SETUP_INSTRUCTIONS.md - Quick setup guide
✅ START_HERE.md - This file
```
### ✅ VS Code Configuration
```
.vscode/
├── settings.json ✅ Auto-format, Tailwind IntelliSense
└── extensions.json ✅ Recommended extensions
```
---
## 🔄 Migration Steps
### Step 1: Move Files (Pick One)
**Option A - PowerShell Script:**
```powershell
.\migrate-files.ps1
```
**Option B - Manual:**
```bash
move App.tsx src\
move components src\
move utils src\
move styles src\
```
### Step 2: Install Dependencies
```bash
npm install
```
This installs ~50 packages (takes 2-3 minutes).
### Step 3: Update Imports in src/App.tsx
**Find and Replace in App.tsx:**
1. Change all component imports:
```typescript
// OLD
import { Layout } from './components/Layout';
import { Dashboard } from './components/Dashboard';
// ... all other component imports
// NEW
import { Layout } from '@/components/Layout';
import { Dashboard } from '@/components/Dashboard';
// ... use @/ for all imports
```
2. Fix sonner import:
```typescript
// OLD
import { toast } from 'sonner@2.0.3';
// NEW
import { toast } from 'sonner';
```
### Step 4: Update Component Files
In all files under `src/components/`, change relative imports:
```typescript
// OLD in any component file
import { Button } from './ui/button';
import { Card } from '../ui/card';
// NEW
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
```
### Step 5: Start Development Server
```bash
npm run dev
```
Visit: http://localhost:3000
---
## 🛠️ Available Commands
```bash
# Development
npm run dev # Start dev server (port 3000)
# Build
npm run build # Build for production
npm run preview # Preview production build
# Code Quality
npm run lint # Check for errors
npm run lint:fix # Auto-fix errors
npm run format # Format with Prettier
npm run type-check # Check TypeScript types
```
---
## ✨ New Features You Get
### 1. **Path Aliases** - Cleaner Imports
```typescript
// Before
import { Button } from '../../../components/ui/button';
// After
import { Button } from '@/components/ui/button';
```
### 2. **TypeScript Types** - Better IntelliSense
```typescript
import type { Request, DealerInfo } from '@/types';
const request: Request = { /* auto-complete works! */ };
```
### 3. **Code Quality** - Auto-fix on Save
- ESLint catches bugs
- Prettier formats code
- TypeScript ensures type safety
### 4. **Fast Development**
- Hot Module Replacement (HMR)
- Instant updates on file save
- Optimized build with code splitting
---
## 🆘 Common Issues & Fixes
### Issue 1: "Cannot find module '@/...'"
**Fix:**
```bash
# Restart TypeScript server
# In VS Code: Ctrl+Shift+P → "TypeScript: Restart TS Server"
```
### Issue 2: "Module not found: sonner@2.0.3"
**Fix in src/App.tsx:**
```typescript
// Change this:
import { toast } from 'sonner@2.0.3';
// To this:
import { toast } from 'sonner';
```
### Issue 3: Tailwind classes not working
**Fix:**
1. Ensure `src/main.tsx` has: `import './styles/globals.css';`
2. Restart dev server: `Ctrl+C` then `npm run dev`
### Issue 4: Build fails
**Fix:**
```bash
npm run type-check # See what TypeScript errors exist
npm run lint # See what ESLint errors exist
```
---
## 📚 Documentation
| File | Purpose |
|------|---------|
| `README.md` | Full project documentation |
| `MIGRATION_GUIDE.md` | Step-by-step migration |
| `SETUP_INSTRUCTIONS.md` | Quick setup guide |
| `START_HERE.md` | This file - quick overview |
---
## ✅ Success Checklist
Track your progress:
- [ ] Run migration script OR move files manually
- [ ] Run `npm install` (2-3 minutes)
- [ ] Update imports in `src/App.tsx` to use `@/`
- [ ] Fix sonner import in `src/App.tsx`
- [ ] Update imports in all component files
- [ ] Run `npm run dev`
- [ ] Open http://localhost:3000
- [ ] Verify app loads without errors
- [ ] Test dashboard navigation
- [ ] Test creating new request
- [ ] Run `npm run build` to verify production build
- [ ] Commit changes to git
---
## 🎓 Tech Stack Overview
| Technology | Purpose | Version |
|------------|---------|---------|
| **React** | UI Framework | 18.3+ |
| **TypeScript** | Type Safety | 5.6+ |
| **Vite** | Build Tool | 5.4+ |
| **Tailwind CSS** | Styling | 3.4+ |
| **shadcn/ui** | UI Components | Latest |
| **ESLint** | Code Quality | 9.15+ |
| **Prettier** | Formatting | 3.3+ |
---
## 🎯 Next Actions
1. **Immediate** - Run the migration (5 minutes)
2. **Today** - Update imports and test (15 minutes)
3. **This Week** - Review new features and documentation
4. **Future** - Add backend API, authentication, tests
---
## 💡 Pro Tips
### Tip 1: Use VS Code Extensions
Install the recommended extensions when VS Code prompts you.
### Tip 2: Format on Save
Already configured! Your code auto-formats when you save.
### Tip 3: Type Everything
Replace `any` types with proper TypeScript types from `@/types`.
### Tip 4: Use Path Aliases
Always use `@/` imports for cleaner code.
---
## 🎉 You're Ready!
Your project is now set up with industry-standard React development tools.
**Next Step:** Run the migration script and start coding!
```powershell
.\migrate-files.ps1
npm install
npm run dev
```
**Questions?** Check the detailed guides:
- `MIGRATION_GUIDE.md` - Detailed steps
- `README.md` - Full documentation
- `SETUP_INSTRUCTIONS.md` - Setup help
---
**Happy Coding! 🚀**

View File

@ -1,155 +0,0 @@
import React from 'react';
import { Bell, Settings, User, Plus, Search, Home, FileText, CheckCircle, LogOut } from 'lucide-react';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Badge } from './ui/badge';
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
import { Sidebar, SidebarContent, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarProvider, SidebarTrigger } from './ui/sidebar';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './ui/dropdown-menu';
interface LayoutProps {
children: React.ReactNode;
currentPage?: string;
onNavigate?: (page: string) => void;
onNewRequest?: () => void;
}
export function Layout({ children, currentPage = 'dashboard', onNavigate, onNewRequest }: LayoutProps) {
const menuItems = [
{ id: 'dashboard', label: 'Dashboard', icon: Home },
{ id: 'my-requests', label: 'My Requests', icon: User },
{ id: 'open-requests', label: 'Open Requests', icon: FileText },
{ id: 'closed-requests', label: 'Closed Requests', icon: CheckCircle },
];
return (
<SidebarProvider>
<div className="min-h-screen flex w-full bg-background">
<Sidebar className="border-r border-sidebar-border">
<SidebarHeader className="p-3 lg:p-4 border-b border-sidebar-border">
<div className="flex items-center gap-2 lg:gap-3">
<div className="w-8 h-8 lg:w-10 lg:h-10 bg-re-green rounded-lg flex items-center justify-center shrink-0">
<div className="w-5 h-5 lg:w-6 lg:h-6 bg-re-gold rounded-full"></div>
</div>
<div className="min-w-0 flex-1">
<h2 className="text-sm lg:text-base font-semibold text-sidebar-foreground truncate">Royal Enfield</h2>
<p className="text-xs lg:text-sm text-sidebar-foreground/70 truncate">Approval Portal</p>
</div>
</div>
</SidebarHeader>
<SidebarContent className="p-2 lg:p-3">
<SidebarMenu className="space-y-1 lg:space-y-2">
{menuItems.map((item) => (
<SidebarMenuItem key={item.id}>
<SidebarMenuButton
onClick={() => onNavigate?.(item.id)}
isActive={currentPage === item.id}
className="w-full justify-start text-sidebar-foreground hover:bg-sidebar-accent transition-colors text-sm lg:text-base"
>
<item.icon className="w-4 h-4 shrink-0" />
<span className="truncate">{item.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
{/* Quick Action in Sidebar */}
<div className="mt-4 lg:mt-6 p-2 lg:p-3 bg-re-green/10 rounded-lg border border-re-green/20">
<Button
onClick={onNewRequest}
className="w-full bg-re-green hover:bg-re-green/90 text-white text-xs lg:text-sm"
size="sm"
>
<Plus className="w-3 h-3 lg:w-4 lg:h-4 mr-1 lg:mr-2" />
<span className="hidden sm:inline">Raise New Request</span>
<span className="sm:hidden">New</span>
</Button>
</div>
</SidebarContent>
</Sidebar>
<div className="flex-1 flex flex-col min-w-0">
{/* Header */}
<header className="h-14 lg:h-16 border-b border-border bg-card flex items-center justify-between px-3 lg:px-6 shrink-0">
<div className="flex items-center gap-2 lg:gap-4 min-w-0 flex-1">
<SidebarTrigger />
<div className="relative max-w-xs lg:max-w-md flex-1 lg:flex-none">
<Search className="absolute left-2 lg:left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-3 h-3 lg:w-4 lg:h-4" />
<Input
placeholder="Search..."
className="pl-8 lg:pl-10 bg-input-background border-border w-full text-sm lg:text-base h-8 lg:h-10"
/>
</div>
</div>
<div className="flex items-center gap-1 lg:gap-4 shrink-0">
<Button
onClick={onNewRequest}
className="bg-re-green hover:bg-re-green/90 text-white gap-1 lg:gap-2 hidden md:flex text-sm lg:text-base"
size="sm"
>
<Plus className="w-3 h-3 lg:w-4 lg:h-4" />
<span className="hidden lg:inline">New Request</span>
<span className="lg:hidden">New</span>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative shrink-0 h-8 w-8 lg:h-10 lg:w-10">
<Bell className="w-4 h-4 lg:w-5 lg:h-5" />
<Badge className="absolute -top-1 -right-1 w-4 h-4 lg:w-5 lg:h-5 rounded-full bg-destructive text-destructive-foreground text-xs flex items-center justify-center p-0">
3
</Badge>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-72 lg:w-80">
<div className="p-3 border-b">
<h4 className="font-semibold text-sm lg:text-base">Notifications</h4>
</div>
<div className="p-3 space-y-2">
<div className="text-sm">
<p className="font-medium">RE-REQ-001 needs approval</p>
<p className="text-muted-foreground text-xs">SLA expires in 2 hours</p>
</div>
<div className="text-sm">
<p className="font-medium">New comment on RE-REQ-003</p>
<p className="text-muted-foreground text-xs">From John Doe - 5 min ago</p>
</div>
</div>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="cursor-pointer shrink-0 h-8 w-8 lg:h-10 lg:w-10">
<AvatarImage src="" />
<AvatarFallback className="bg-re-green text-white text-xs lg:text-sm">JD</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<User className="w-4 h-4 mr-2" />
Profile
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="w-4 h-4 mr-2" />
Settings
</DropdownMenuItem>
<DropdownMenuItem>
<LogOut className="w-4 h-4 mr-2" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
{/* Main Content */}
<main className="flex-1 p-3 lg:p-6 overflow-auto min-w-0">
{children}
</main>
</div>
</div>
</SidebarProvider>
);
}

35
eslint.config.js Normal file
View File

@ -0,0 +1,35 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist', 'node_modules', '*.config.js', '*.config.ts'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
}
);

19
fix-imports.ps1 Normal file
View File

@ -0,0 +1,19 @@
# Fix all imports with version numbers
$files = Get-ChildItem -Path "src" -Filter "*.tsx" -Recurse
foreach ($file in $files) {
$content = Get-Content $file.FullName -Raw
# Remove version numbers from ALL package imports (universal pattern)
# Matches: package-name@version, @scope/package-name@version
$content = $content -replace '(from\s+[''"])([^''"]+)@[\d.]+([''"])', '$1$2$3'
$content = $content -replace '(import\s+[''"])([^''"]+)@[\d.]+([''"])', '$1$2$3'
# Also fix motion/react to framer-motion
$content = $content -replace 'motion/react', 'framer-motion'
Set-Content -Path $file.FullName -Value $content -NoNewline
}
Write-Host "Fixed all imports!" -ForegroundColor Green

59
index.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Royal Enfield Approval & Request Management Portal - Streamlined approval workflows for enterprise operations" />
<meta name="theme-color" content="#2d4a3e" />
<title>Royal Enfield | Approval Portal</title>
<!-- Preload critical fonts and icons -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Ensure proper icon rendering and layout -->
<style>
/* Ensure Lucide icons render properly */
svg {
display: inline-block;
vertical-align: middle;
}
/* Fix for icon alignment in buttons */
button svg {
flex-shrink: 0;
}
/* Ensure proper text rendering */
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* Fix for mobile viewport and sidebar */
@media (max-width: 768px) {
html {
overflow-x: hidden;
}
}
/* Ensure proper sidebar toggle behavior */
.sidebar-toggle {
transition: all 0.3s ease-in-out;
}
/* Fix for icon button hover states */
button:hover svg {
transform: scale(1.05);
transition: transform 0.2s ease;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

53
migrate-files.ps1 Normal file
View File

@ -0,0 +1,53 @@
# PowerShell script to migrate files to src directory
# Run this script: .\migrate-files.ps1
Write-Host "Starting file migration to src directory..." -ForegroundColor Green
# Check if src directory exists
if (-not (Test-Path "src")) {
Write-Host "Creating src directory..." -ForegroundColor Yellow
New-Item -ItemType Directory -Path "src" -Force | Out-Null
}
# Function to move files with checks
function Move-WithCheck {
param($Source, $Destination)
if (Test-Path $Source) {
Write-Host "Moving $Source to $Destination..." -ForegroundColor Cyan
Move-Item -Path $Source -Destination $Destination -Force
Write-Host "Moved $Source" -ForegroundColor Green
} else {
Write-Host "$Source not found, skipping..." -ForegroundColor Yellow
}
}
# Move App.tsx
if (Test-Path "App.tsx") {
Move-WithCheck "App.tsx" "src\App.tsx"
}
# Move components directory
if (Test-Path "components") {
Move-WithCheck "components" "src\components"
}
# Move utils directory
if (Test-Path "utils") {
Move-WithCheck "utils" "src\utils"
}
# Move styles directory
if (Test-Path "styles") {
Move-WithCheck "styles" "src\styles"
}
Write-Host ""
Write-Host "Migration complete!" -ForegroundColor Green
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Yellow
Write-Host "1. Update imports in src/App.tsx to use '@/' aliases" -ForegroundColor White
Write-Host "2. Fix the sonner import: import { toast } from 'sonner';" -ForegroundColor White
Write-Host "3. Run: npm run dev" -ForegroundColor White
Write-Host ""
Write-Host "See MIGRATION_GUIDE.md for detailed instructions" -ForegroundColor Cyan

7130
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

90
package.json Normal file
View File

@ -0,0 +1,90 @@
{
"name": "royal-enfield-approval-portal",
"version": "1.0.0",
"description": "Royal Enfield Approval & Request Management Portal",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css,md}\"",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.2",
"@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3",
"@reduxjs/toolkit": "^2.9.1",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.12.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^12.23.24",
"input-otp": "^1.2.4",
"lucide-react": "^0.454.0",
"next-themes": "^0.4.6",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.4",
"recharts": "^2.13.3",
"sonner": "^1.5.0",
"tailwind-merge": "^2.5.4",
"vaul": "^1.0.0"
},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.15",
"@types/node": "^24.9.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"eslint": "^9.15.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.9",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.3",
"vite": "^5.4.11"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}

7
postcss.config.cjs Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

2
public/vite.svg Normal file
View File

@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,18 +1,18 @@
import React, { useState } from 'react';
import { Layout } from './components/Layout';
import { Dashboard } from './components/Dashboard';
import { RequestsList } from './components/RequestsList';
import { RequestDetail } from './components/RequestDetail';
import { ClaimManagementDetail } from './components/ClaimManagementDetail';
import { WorkNoteView } from './components/WorkNoteView';
import { NewRequestWizard } from './components/NewRequestWizard';
import { ClaimManagementWizard } from './components/ClaimManagementWizard';
import { MyRequests } from './components/MyRequests';
import { ApprovalActionModal } from './components/modals/ApprovalActionModal';
import { Toaster } from './components/ui/sonner';
import { toast } from 'sonner@2.0.3';
import { CUSTOM_REQUEST_DATABASE } from './utils/customRequestDatabase';
import { CLAIM_MANAGEMENT_DATABASE } from './utils/claimManagementDatabase';
import { useState } from 'react';
import { PageLayout } from '@/components/layout/PageLayout';
import { Dashboard } from '@/pages/Dashboard';
import { OpenRequests } from '@/pages/OpenRequests';
import { ClosedRequests } from '@/pages/ClosedRequests';
import { RequestDetail } from '@/pages/RequestDetail';
import { WorkNoteChat } from '@/components/workNote/WorkNoteChat';
import { CreateRequest } from '@/pages/CreateRequest';
import { ClaimManagementWizard } from '@/components/workflow/ClaimManagementWizard';
import { MyRequests } from '@/pages/MyRequests';
import { ApprovalActionModal } from '@/components/modals/ApprovalActionModal';
import { Toaster } from '@/components/ui/sonner';
import { toast } from 'sonner';
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
type Page = 'dashboard' | 'open-requests' | 'closed-requests' | 'my-requests' | 'request-detail' | 'work-notes' | 'new-request' | 'claim-management';
@ -24,6 +24,7 @@ export const REQUEST_DATABASE: any = {
};
// Legacy database - keeping for reference (will be removed in future)
/*
const LEGACY_REQUEST_DATABASE: any = {
// ========== TEMPLATE-BASED REQUESTS - Claim Management ==========
'RE-REQ-2024-CM-001': {
@ -1165,11 +1166,12 @@ const LEGACY_REQUEST_DATABASE: any = {
tags: ['sustainability', 'green-energy', 'rejected', 'budget-constraints']
}
};
*/
export default function App() {
const [currentPage, setCurrentPage] = useState<Page>('dashboard');
const [selectedRequestId, setSelectedRequestId] = useState<string>('');
const [activeModal, setActiveModal] = useState<string>('');
const [, setActiveModal] = useState<string>('');
const [approvalAction, setApprovalAction] = useState<'approve' | 'reject' | null>(null);
const [selectedRequestTitle, setSelectedRequestTitle] = useState<string>('');
const [dynamicRequests, setDynamicRequests] = useState<any[]>([]);
@ -1362,7 +1364,7 @@ export default function App() {
// For other pages, check if user is initiator or approver
const request = REQUEST_DATABASE[selectedRequestId as keyof typeof REQUEST_DATABASE];
const isInitiator = request && request.initiator.name === 'Current User';
const isApprover = request && request.approvalFlow.some(step =>
const isApprover = request && request.approvalFlow.some((step: any) =>
step.approver === 'Current User' || step.status === 'pending'
);
@ -1423,34 +1425,13 @@ export default function App() {
case 'dashboard':
return <Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />;
case 'open-requests':
return <RequestsList type="open" onViewRequest={handleViewRequest} />;
return <OpenRequests onViewRequest={handleViewRequest} />;
case 'closed-requests':
return <RequestsList type="closed" onViewRequest={handleViewRequest} />;
return <ClosedRequests onViewRequest={handleViewRequest} />;
case 'my-requests':
return <MyRequests onViewRequest={handleViewRequest} dynamicRequests={dynamicRequests} />;
case 'request-detail':
// Determine which component to render based on request type
// Check static databases first
const isClaimRequest = CLAIM_MANAGEMENT_DATABASE[selectedRequestId];
const isCustomRequest = CUSTOM_REQUEST_DATABASE[selectedRequestId];
// Check dynamic requests
const dynamicRequest = dynamicRequests.find((req: any) => req.id === selectedRequestId);
const isDynamicClaim = dynamicRequest?.templateType === 'claim-management' || dynamicRequest?.template === 'claim-management';
const isDynamicCustom = dynamicRequest && !isDynamicClaim;
if (isClaimRequest || isDynamicClaim) {
// Render Claim Management Detail for claim management requests
return (
<ClaimManagementDetail
requestId={selectedRequestId}
onBack={handleBack}
onOpenModal={handleOpenModal}
dynamicRequests={dynamicRequests}
/>
);
} else if (isCustomRequest || isDynamicCustom) {
// Render Request Detail for custom requests
// Always render RequestDetail for all request types
return (
<RequestDetail
requestId={selectedRequestId}
@ -1459,27 +1440,16 @@ export default function App() {
dynamicRequests={dynamicRequests}
/>
);
} else {
// Handle legacy requests from old database
return (
<RequestDetail
requestId={selectedRequestId}
onBack={handleBack}
onOpenModal={handleOpenModal}
dynamicRequests={dynamicRequests}
/>
);
}
case 'work-notes':
return (
<WorkNoteView
<WorkNoteChat
requestId={selectedRequestId}
onBack={handleBack}
/>
);
case 'new-request':
return (
<NewRequestWizard
<CreateRequest
onBack={handleBack}
onSubmit={handleNewRequestSubmit}
/>
@ -1693,13 +1663,13 @@ export default function App() {
{(currentPage === 'new-request' || currentPage === 'claim-management') ? (
renderCurrentPage()
) : (
<Layout
<PageLayout
currentPage={currentPage}
onNavigate={handleNavigate}
onNewRequest={handleNewRequest}
>
{renderCurrentPage()}
</Layout>
</PageLayout>
)}
<Toaster

View File

@ -0,0 +1,169 @@
import { useState } from 'react';
import { Bell, Settings, User, Plus, Search, Home, FileText, CheckCircle, LogOut, PanelLeft, PanelLeftClose } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
interface PageLayoutProps {
children: React.ReactNode;
currentPage?: string;
onNavigate?: (page: string) => void;
onNewRequest?: () => void;
}
export function PageLayout({ children, currentPage = 'dashboard', onNavigate, onNewRequest }: PageLayoutProps) {
const [sidebarOpen, setSidebarOpen] = useState(true);
const menuItems = [
{ id: 'dashboard', label: 'Dashboard', icon: Home },
{ id: 'my-requests', label: 'My Requests', icon: User },
{ id: 'open-requests', label: 'Open Requests', icon: FileText },
{ id: 'closed-requests', label: 'Closed Requests', icon: CheckCircle },
];
const toggleSidebar = () => {
setSidebarOpen(!sidebarOpen);
};
return (
<div className="min-h-screen flex w-full bg-background">
{/* Sidebar */}
<div className={`${sidebarOpen ? 'w-64' : 'w-0'} transition-all duration-300 ease-in-out overflow-hidden flex-shrink-0`}>
<div className="w-64 h-full border-r border-gray-800 bg-black">
<div className="p-4 border-b border-gray-800">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-re-green rounded-lg flex items-center justify-center shrink-0">
<div className="w-6 h-6 bg-re-gold rounded-full"></div>
</div>
<div className="min-w-0 flex-1">
<h2 className="text-base font-semibold text-white truncate">Royal Enfield</h2>
<p className="text-sm text-gray-400 truncate">Approval Portal</p>
</div>
</div>
</div>
<div className="p-3">
<div className="space-y-2">
{menuItems.map((item) => (
<button
key={item.id}
onClick={() => onNavigate?.(item.id)}
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors ${
currentPage === item.id
? 'bg-re-green text-white font-medium'
: 'text-gray-300 hover:bg-gray-900 hover:text-white'
}`}
>
<item.icon className="w-4 h-4 shrink-0" />
<span className="truncate">{item.label}</span>
</button>
))}
</div>
{/* Quick Action in Sidebar */}
<div className="mt-6 p-3 bg-gray-900 rounded-lg border border-gray-800">
<Button
onClick={onNewRequest}
className="w-full bg-re-green hover:bg-re-green/90 text-white text-sm"
size="sm"
>
<Plus className="w-4 h-4 mr-2" />
Raise New Request
</Button>
</div>
</div>
</div>
</div>
{/* Main Content Area */}
<div className="flex-1 flex flex-col min-w-0">
{/* Header */}
<header className="h-16 border-b border-gray-200 bg-white flex items-center justify-between px-6 shrink-0">
<div className="flex items-center gap-4 min-w-0 flex-1">
<Button
variant="ghost"
size="icon"
onClick={toggleSidebar}
className="shrink-0 h-10 w-10 sidebar-toggle"
>
{sidebarOpen ? <PanelLeftClose className="w-5 h-5 text-gray-600" /> : <PanelLeft className="w-5 h-5 text-gray-600" />}
</Button>
<div className="relative max-w-md flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="Search..."
className="pl-10 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green w-full text-sm h-10"
/>
</div>
</div>
<div className="flex items-center gap-4 shrink-0">
<Button
onClick={onNewRequest}
className="bg-re-green hover:bg-re-green/90 text-white gap-2 hidden md:flex text-sm"
size="sm"
>
<Plus className="w-4 h-4" />
New Request
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative shrink-0 h-10 w-10">
<Bell className="w-5 h-5" />
<Badge className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-destructive text-destructive-foreground text-xs flex items-center justify-center p-0">
3
</Badge>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-80">
<div className="p-3 border-b">
<h4 className="font-semibold text-base">Notifications</h4>
</div>
<div className="p-3 space-y-2">
<div className="text-sm">
<p className="font-medium">RE-REQ-001 needs approval</p>
<p className="text-muted-foreground text-xs">SLA expires in 2 hours</p>
</div>
<div className="text-sm">
<p className="font-medium">New comment on RE-REQ-003</p>
<p className="text-muted-foreground text-xs">From John Doe - 5 min ago</p>
</div>
</div>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="cursor-pointer shrink-0 h-10 w-10">
<AvatarImage src="" />
<AvatarFallback className="bg-re-green text-white text-sm">JD</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<User className="w-4 h-4 mr-2" />
Profile
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="w-4 h-4 mr-2" />
Settings
</DropdownMenuItem>
<DropdownMenuItem>
<LogOut className="w-4 h-4 mr-2" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
{/* Main Content */}
<main className="flex-1 p-6 overflow-auto min-w-0">
{children}
</main>
</div>
</div>
);
}

View File

@ -0,0 +1 @@
export { PageLayout } from './PageLayout';

View File

@ -4,7 +4,7 @@ import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { Label } from '../ui/label';
import { User, Eye, AtSign, Plus } from 'lucide-react';
import { toast } from 'sonner@2.0.3';
import { toast } from 'sonner';
interface AddUserModalProps {
isOpen: boolean;

View File

@ -77,7 +77,7 @@ export function ApprovalActionModal({
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-2xl">
<DialogContent className="max-w-2xl bg-white">
<DialogHeader>
<div className="flex items-center gap-3 mb-2">
<div className={`p-2 rounded-lg ${action === 'approve' ? 'bg-green-100' : 'bg-red-100'}`}>

View File

@ -5,7 +5,7 @@ import { Label } from '../ui/label';
import { Textarea } from '../ui/textarea';
import { Badge } from '../ui/badge';
import { Upload, FileText, X, CheckCircle, AlertCircle } from 'lucide-react';
import { toast } from 'sonner@2.0.3';
import { toast } from 'sonner';
interface DealerDocumentModalProps {
isOpen: boolean;

View File

@ -6,7 +6,7 @@ import { Input } from '../ui/input';
import { Textarea } from '../ui/textarea';
import { Badge } from '../ui/badge';
import { CheckCircle, AlertCircle, DollarSign, FileText } from 'lucide-react';
import { toast } from 'sonner@2.0.3';
import { toast } from 'sonner';
interface InitiatorVerificationModalProps {
isOpen: boolean;

View File

@ -18,7 +18,7 @@ import {
Sparkles,
Check
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { motion, AnimatePresence } from 'framer-motion';
interface TemplateSelectionModalProps {
open: boolean;

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion@1.2.3";
import { ChevronDownIcon } from "lucide-react@0.487.0";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog@1.1.6";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "./utils";
import { buttonVariants } from "./button";

View File

@ -1,5 +1,5 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "./utils";

View File

@ -1,6 +1,6 @@
"use client";
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio@1.1.2";
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
function AspectRatio({
...props

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar@1.1.3";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "./utils";

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot@1.1.2";
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "./utils";

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot@1.1.2";
import { ChevronRight, MoreHorizontal } from "lucide-react@0.487.0";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot@1.1.2";
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react@0.487.0";
import { DayPicker } from "react-day-picker@8.10.1";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker";
import { cn } from "./utils";
import { buttonVariants } from "./button";

View File

@ -3,8 +3,8 @@
import * as React from "react";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react@8.6.0";
import { ArrowLeft, ArrowRight } from "lucide-react@0.487.0";
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { cn } from "./utils";
import { Button } from "./button";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as RechartsPrimitive from "recharts@2.15.2";
import * as RechartsPrimitive from "recharts";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox@1.1.4";
import { CheckIcon } from "lucide-react@0.487.0";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,6 +1,6 @@
"use client";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible@1.1.3";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
function Collapsible({
...props

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import { Command as CommandPrimitive } from "cmdk@1.1.1";
import { SearchIcon } from "lucide-react@0.487.0";
import { Command as CommandPrimitive } from "cmdk";
import { SearchIcon } from "lucide-react";
import { cn } from "./utils";
import {

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu@2.2.6";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog@1.1.6";
import { XIcon } from "lucide-react@0.487.0";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import { cn } from "./utils";
@ -56,7 +56,7 @@ const DialogContent = React.forwardRef<
ref={ref}
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
"bg-white data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul@1.1.2";
import { Drawer as DrawerPrimitive } from "vaul";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu@2.1.6";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label@2.1.2";
import { Slot } from "@radix-ui/react-slot@1.1.2";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
FormProvider,
@ -11,7 +11,7 @@ import {
type ControllerProps,
type FieldPath,
type FieldValues,
} from "react-hook-form@7.55.0";
} from "react-hook-form";
import { cn } from "./utils";
import { Label } from "./label";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card@1.1.6";
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import { OTPInput, OTPInputContext } from "input-otp@1.4.2";
import { MinusIcon } from "lucide-react@0.487.0";
import { OTPInput, OTPInputContext } from "input-otp";
import { MinusIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -8,8 +8,8 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base bg-input-background transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
)}

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label@2.1.2";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as MenubarPrimitive from "@radix-ui/react-menubar@1.1.6";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
import * as MenubarPrimitive from "@radix-ui/react-menubar";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
import * as React from "react";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu@1.2.5";
import { cva } from "class-variance-authority@0.7.1";
import { ChevronDownIcon } from "lucide-react@0.487.0";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -3,7 +3,7 @@ import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react@0.487.0";
} from "lucide-react";
import { cn } from "./utils";
import { Button, buttonVariants } from "./button";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover@1.1.6";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as ProgressPrimitive from "@radix-ui/react-progress@1.1.2";
import * as ProgressPrimitive from "@radix-ui/react-progress";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group@1.2.3";
import { CircleIcon } from "lucide-react@0.487.0";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { CircleIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import { GripVerticalIcon } from "lucide-react@0.487.0";
import * as ResizablePrimitive from "react-resizable-panels@2.1.7";
import { GripVerticalIcon } from "lucide-react";
import * as ResizablePrimitive from "react-resizable-panels";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area@1.2.3";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "./utils";

View File

@ -1,12 +1,12 @@
"use client";
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select@2.1.6";
import * as SelectPrimitive from "@radix-ui/react-select";
import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "lucide-react@0.487.0";
} from "lucide-react";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator@1.1.2";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog@1.1.6";
import { XIcon } from "lucide-react@0.487.0";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import { cn } from "./utils";

View File

@ -1,9 +1,9 @@
"use client";
import * as React from "react";
import { Slot } from "@radix-ui/react-slot@1.1.2";
import { VariantProps, cva } from "class-variance-authority@0.7.1";
import { PanelLeftIcon } from "lucide-react@0.487.0";
import { Slot } from "@radix-ui/react-slot";
import { VariantProps, cva } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react";
import { useIsMobile } from "./use-mobile";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as SliderPrimitive from "@radix-ui/react-slider@1.2.3";
import * as SliderPrimitive from "@radix-ui/react-slider";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import { useTheme } from "next-themes@0.4.6";
import { Toaster as Sonner, ToasterProps } from "sonner@2.0.3";
import { useTheme } from "next-themes";
import { Toaster as Sonner, ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch@1.1.3";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs@1.1.3";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "./utils";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group@1.1.2";
import { type VariantProps } from "class-variance-authority@0.7.1";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import { type VariantProps } from "class-variance-authority";
import { cn } from "./utils";
import { toggleVariants } from "./toggle";

View File

@ -1,8 +1,8 @@
"use client";
import * as React from "react";
import * as TogglePrimitive from "@radix-ui/react-toggle@1.1.2";
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
import * as TogglePrimitive from "@radix-ui/react-toggle";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "./utils";

View File

@ -1,7 +1,7 @@
"use client";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip@1.1.8";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "./utils";

View File

@ -1,13 +1,13 @@
import React, { useState, useRef, useEffect, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Avatar, AvatarFallback } from './ui/avatar';
import { Badge } from './ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { ScrollArea } from './ui/scroll-area';
import { Separator } from './ui/separator';
import { Textarea } from './ui/textarea';
import { useState, useRef, useEffect, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import {
ArrowLeft,
Send,
@ -75,7 +75,7 @@ interface Participant {
permissions: string[];
}
interface WorkNoteViewProps {
interface WorkNoteChatProps {
requestId: string;
onBack?: () => void;
}
@ -297,7 +297,7 @@ const getFileIcon = (type: string) => {
}
};
export function WorkNoteView({ requestId, onBack }: WorkNoteViewProps) {
export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
const [message, setMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [activeTab, setActiveTab] = useState('chat');

View File

@ -0,0 +1 @@
export { WorkNoteChat } from './WorkNoteChat';

View File

@ -1,14 +1,14 @@
import React, { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { toast } from 'sonner@2.0.3';
import { DealerDocumentModal } from './modals/DealerDocumentModal';
import { InitiatorVerificationModal } from './modals/InitiatorVerificationModal';
import { Progress } from './ui/progress';
import { Avatar, AvatarFallback } from './ui/avatar';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { CLAIM_MANAGEMENT_DATABASE } from '../utils/claimManagementDatabase';
import { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
import { DealerDocumentModal } from '@/components/modals/DealerDocumentModal';
import { InitiatorVerificationModal } from '@/components/modals/InitiatorVerificationModal';
import { Progress } from '@/components/ui/progress';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
import {
ArrowLeft,
Clock,

View File

@ -0,0 +1 @@
export { ClaimManagementDetail } from './ClaimManagementDetail';

View File

@ -1,15 +1,15 @@
import React, { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Label } from './ui/label';
import { Textarea } from './ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Badge } from './ui/badge';
import { Progress } from './ui/progress';
import { Calendar } from './ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
import { motion, AnimatePresence } from 'motion/react';
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Calendar } from '@/components/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { motion, AnimatePresence } from 'framer-motion';
import {
ArrowLeft,
ArrowRight,
@ -25,8 +25,8 @@ import {
DollarSign
} from 'lucide-react';
import { format } from 'date-fns';
import { toast } from 'sonner@2.0.3';
import { getAllDealers, getDealerInfo, formatDealerAddress, type DealerInfo } from '../utils/dealerDatabase';
import { toast } from 'sonner';
import { getAllDealers, getDealerInfo, formatDealerAddress, type DealerInfo } from '@/utils/dealerDatabase';
interface ClaimManagementWizardProps {
onBack?: () => void;

View File

@ -0,0 +1 @@
export { ClaimManagementWizard } from './ClaimManagementWizard';

11
src/lib/utils.ts Normal file
View File

@ -0,0 +1,11 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
/**
* Utility function to merge Tailwind CSS classes
* Combines clsx and tailwind-merge for optimal class management
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

11
src/main.tsx Normal file
View File

@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './styles/globals.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,483 @@
import { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import {
Calendar,
Filter,
Search,
FileText,
AlertCircle,
CheckCircle,
ArrowRight,
SortAsc,
SortDesc,
Flame,
Target,
RefreshCw,
Settings2,
X
} from 'lucide-react';
interface Request {
id: string;
title: string;
description: string;
status: 'approved' | 'rejected';
priority: 'express' | 'standard';
initiator: {
name: string;
avatar: string;
};
createdAt: string;
dueDate: string;
reason?: string;
department?: string;
}
interface ClosedRequestsProps {
onViewRequest?: (requestId: string, requestTitle?: string) => void;
}
// Static data for closed requests
const CLOSED_REQUESTS: Request[] = [
{
id: 'RE-REQ-CM-001',
title: 'Dealer Marketing Activity Claim - Diwali Festival Campaign',
description: 'Claim request for dealer-led Diwali festival marketing campaign using Claim Management template workflow.',
status: 'approved',
priority: 'standard',
initiator: { name: 'Sneha Patil', avatar: 'SP' },
createdAt: '2024-10-07',
dueDate: '2024-10-16',
reason: 'Budget approved with quarterly review conditions',
department: 'Marketing - West Zone'
},
{
id: 'RE-REQ-001',
title: 'Marketing Campaign Budget Approval',
description: 'Request for Q4 marketing campaign budget allocation of $50,000 for digital advertising across social media platforms and content creation.',
status: 'approved',
priority: 'express',
initiator: { name: 'Sarah Chen', avatar: 'SC' },
createdAt: '2024-10-07',
dueDate: '2024-10-09',
reason: 'All equipment approved and ordered through preferred vendor',
department: 'Marketing'
},
{
id: 'RE-REQ-002',
title: 'IT Equipment Purchase',
description: 'Purchase of 10 new laptops for the development team including software licenses and accessories for enhanced productivity.',
status: 'rejected',
priority: 'standard',
initiator: { name: 'David Kumar', avatar: 'DK' },
createdAt: '2024-10-06',
dueDate: '2024-10-12',
reason: 'Pricing not competitive, seek alternative vendors'
},
{
id: 'RE-REQ-003',
title: 'Vendor Contract Renewal',
description: 'Annual renewal of cleaning services contract with updated terms and pricing structure for office maintenance.',
status: 'approved',
priority: 'standard',
initiator: { name: 'John Doe', avatar: 'JD' },
createdAt: '2024-10-05',
dueDate: '2024-10-08',
reason: 'Lease terms negotiated and approved by legal team'
},
{
id: 'RE-REQ-004',
title: 'Office Space Expansion',
description: 'Lease additional office space for growing team, 2000 sq ft in the same building with modern amenities.',
status: 'approved',
priority: 'express',
initiator: { name: 'Lisa Wong', avatar: 'LW' },
createdAt: '2024-10-04',
dueDate: '2024-10-15',
reason: 'Program approved with budget adjustments'
}
];
// Utility functions
const getPriorityConfig = (priority: string) => {
switch (priority) {
case 'express':
return {
color: 'bg-red-100 text-red-800 border-red-200',
icon: Flame,
iconColor: 'text-red-600'
};
case 'standard':
return {
color: 'bg-blue-100 text-blue-800 border-blue-200',
icon: Target,
iconColor: 'text-blue-600'
};
default:
return {
color: 'bg-gray-100 text-gray-800 border-gray-200',
icon: Target,
iconColor: 'text-gray-600'
};
}
};
const getStatusConfig = (status: string) => {
switch (status) {
case 'approved':
return {
color: 'bg-green-100 text-green-800 border-green-200',
icon: CheckCircle,
iconColor: 'text-green-600'
};
case 'rejected':
return {
color: 'bg-red-100 text-red-800 border-red-200',
icon: AlertCircle,
iconColor: 'text-red-600'
};
default:
return {
color: 'bg-gray-100 text-gray-800 border-gray-200',
icon: AlertCircle,
iconColor: 'text-gray-600'
};
}
};
export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
const [searchTerm, setSearchTerm] = useState('');
const [priorityFilter, setPriorityFilter] = useState('all');
const [statusFilter, setStatusFilter] = useState('all');
const [sortBy, setSortBy] = useState<'created' | 'due' | 'priority'>('due');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
const filteredAndSortedRequests = useMemo(() => {
let filtered = CLOSED_REQUESTS.filter(request => {
const matchesSearch =
request.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
request.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
request.initiator.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesPriority = priorityFilter === 'all' || request.priority === priorityFilter;
const matchesStatus = statusFilter === 'all' || request.status === statusFilter;
return matchesSearch && matchesPriority && matchesStatus;
});
// Sort requests
filtered.sort((a, b) => {
let aValue: any, bValue: any;
switch (sortBy) {
case 'created':
aValue = new Date(a.createdAt);
bValue = new Date(b.createdAt);
break;
case 'due':
aValue = new Date(a.dueDate);
bValue = new Date(b.dueDate);
break;
case 'priority':
const priorityOrder = { express: 2, standard: 1 };
aValue = priorityOrder[a.priority as keyof typeof priorityOrder];
bValue = priorityOrder[b.priority as keyof typeof priorityOrder];
break;
default:
return 0;
}
if (sortOrder === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
return filtered;
}, [searchTerm, priorityFilter, statusFilter, sortBy, sortOrder]);
const clearFilters = () => {
setSearchTerm('');
setPriorityFilter('all');
setStatusFilter('all');
};
const activeFiltersCount = [
searchTerm,
priorityFilter !== 'all' ? priorityFilter : null,
statusFilter !== 'all' ? statusFilter : null
].filter(Boolean).length;
return (
<div className="space-y-6 p-6 max-w-7xl mx-auto">
{/* Enhanced Header */}
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-6">
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-br from-slate-800 to-slate-900 rounded-xl flex items-center justify-center shadow-lg">
<FileText className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900">Closed Requests</h1>
<p className="text-gray-600">Review completed and archived requests</p>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<Badge variant="secondary" className="text-lg px-4 py-2 bg-slate-100 text-slate-800 font-semibold">
{filteredAndSortedRequests.length} closed requests
</Badge>
<Button variant="outline" size="sm" className="gap-2">
<RefreshCw className="w-4 h-4" />
Refresh
</Button>
</div>
</div>
{/* Enhanced Filters Section */}
<Card className="shadow-lg border-0">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
<Filter className="h-5 w-5 text-blue-600" />
</div>
<div>
<CardTitle className="text-lg">Filters & Search</CardTitle>
<CardDescription>
{activeFiltersCount > 0 && (
<span className="text-blue-600 font-medium">
{activeFiltersCount} filter{activeFiltersCount > 1 ? 's' : ''} active
</span>
)}
</CardDescription>
</div>
</div>
<div className="flex items-center gap-2">
{activeFiltersCount > 0 && (
<Button
variant="ghost"
size="sm"
onClick={clearFilters}
className="text-red-600 hover:bg-red-50 gap-1"
>
<X className="w-3 h-3" />
Clear
</Button>
)}
<Button
variant="ghost"
size="sm"
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
className="gap-2"
>
<Settings2 className="w-4 h-4" />
{showAdvancedFilters ? 'Basic' : 'Advanced'}
</Button>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Primary filters */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="Search requests, IDs, or initiators..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 h-11 bg-gray-50 border-gray-200 focus:bg-white transition-colors"
/>
</div>
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
<SelectTrigger className="h-11 bg-gray-50 border-gray-200 focus:bg-white">
<SelectValue placeholder="All Priorities" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Priorities</SelectItem>
<SelectItem value="express">🔥 Express</SelectItem>
<SelectItem value="standard">🎯 Standard</SelectItem>
</SelectContent>
</Select>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="h-11 bg-gray-50 border-gray-200 focus:bg-white">
<SelectValue placeholder="All Statuses" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="approved"> Approved</SelectItem>
<SelectItem value="rejected"> Rejected</SelectItem>
</SelectContent>
</Select>
<div className="flex gap-2">
<Select value={sortBy} onValueChange={(value: any) => setSortBy(value)}>
<SelectTrigger className="h-11 bg-gray-50 border-gray-200 focus:bg-white">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="due">Due Date</SelectItem>
<SelectItem value="created">Date Created</SelectItem>
<SelectItem value="priority">Priority</SelectItem>
</SelectContent>
</Select>
<Button
variant="outline"
size="sm"
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
className="px-3 h-11"
>
{sortOrder === 'asc' ? <SortAsc className="w-4 h-4" /> : <SortDesc className="w-4 h-4" />}
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Requests List */}
<div className="space-y-4">
{filteredAndSortedRequests.map((request) => {
const priorityConfig = getPriorityConfig(request.priority);
const statusConfig = getStatusConfig(request.status);
return (
<Card
key={request.id}
className="group hover:shadow-xl transition-all duration-300 cursor-pointer border-0 shadow-md hover:scale-[1.01]"
onClick={() => onViewRequest?.(request.id, request.title)}
>
<CardContent className="p-6">
<div className="flex items-start gap-6">
{/* Priority Indicator */}
<div className="flex flex-col items-center gap-2 pt-1">
<div className={`p-3 rounded-xl ${priorityConfig.color} border`}>
<priorityConfig.icon className={`w-5 h-5 ${priorityConfig.iconColor}`} />
</div>
<Badge
variant="outline"
className={`text-xs font-medium ${priorityConfig.color} capitalize`}
>
{request.priority}
</Badge>
</div>
{/* Main Content */}
<div className="flex-1 min-w-0 space-y-4">
{/* Header */}
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors">
{request.id}
</h3>
<Badge
variant="outline"
className={`${statusConfig.color} border font-medium`}
>
<statusConfig.icon className="w-3 h-3 mr-1" />
{request.status}
</Badge>
{request.department && (
<Badge variant="secondary" className="bg-gray-100 text-gray-700">
{request.department}
</Badge>
)}
</div>
<h4 className="text-xl font-bold text-gray-900 mb-2 line-clamp-1">
{request.title}
</h4>
<p className="text-gray-600 line-clamp-2 leading-relaxed">
{request.description}
</p>
</div>
<div className="flex flex-col items-end gap-2">
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors" />
</div>
</div>
{/* Status Info */}
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
<div className="flex items-center gap-2">
<CheckCircle className="w-4 h-4 text-green-500" />
<span className="text-sm text-gray-700 font-medium">
{request.reason}
</span>
</div>
</div>
{/* Participants & Metadata */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8 ring-2 ring-white shadow-sm">
<AvatarFallback className="bg-slate-700 text-white text-sm font-semibold">
{request.initiator.avatar}
</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium text-gray-900">{request.initiator.name}</p>
<p className="text-xs text-gray-500">Initiator</p>
</div>
</div>
</div>
<div className="text-right">
<div className="flex items-center gap-4 text-xs text-gray-500">
<span className="flex items-center gap-1">
<Calendar className="w-3 h-3" />
Created {request.createdAt}
</span>
<span>Closed {request.dueDate}</span>
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
{/* Empty State */}
{filteredAndSortedRequests.length === 0 && (
<Card className="shadow-lg border-0">
<CardContent className="flex flex-col items-center justify-center py-16">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<FileText className="h-8 w-8 text-gray-400" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">No requests found</h3>
<p className="text-gray-600 text-center max-w-md">
{searchTerm || activeFiltersCount > 0
? 'Try adjusting your filters or search terms to see more results.'
: 'No closed requests available at the moment.'
}
</p>
{activeFiltersCount > 0 && (
<Button
variant="outline"
className="mt-4"
onClick={clearFilters}
>
Clear all filters
</Button>
)}
</CardContent>
</Card>
)}
</div>
);
}

View File

@ -0,0 +1,2 @@
export { ClosedRequests } from './ClosedRequests';

View File

@ -1,25 +1,19 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Label } from './ui/label';
import { Textarea } from './ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Badge } from './ui/badge';
import { Avatar, AvatarFallback } from './ui/avatar';
import { Progress } from './ui/progress';
import { Switch } from './ui/switch';
import { Calendar } from './ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
import { Separator } from './ui/separator';
import { RadioGroup, RadioGroupItem } from './ui/radio-group';
import { Checkbox } from './ui/checkbox';
import { motion, AnimatePresence } from 'motion/react';
import { TemplateSelectionModal } from './modals/TemplateSelectionModal';
import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Separator } from '@/components/ui/separator';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { motion, AnimatePresence } from 'framer-motion';
import { TemplateSelectionModal } from '@/components/modals/TemplateSelectionModal';
import {
ArrowLeft,
ArrowRight,
Calendar as CalendarIcon,
Upload,
X,
User,
@ -27,37 +21,25 @@ import {
FileText,
Check,
Users,
Star,
Zap,
Shield,
Target,
Flame,
TrendingUp,
Building,
DollarSign,
AlertCircle,
CheckCircle,
Info,
Sparkles,
Rocket,
Plus,
Minus,
Eye,
EyeOff,
Search,
Filter,
Download,
Globe,
Lock,
Lightbulb,
PieChart,
BarChart3,
Activity,
Settings
} from 'lucide-react';
import { format } from 'date-fns';
interface NewRequestWizardProps {
interface CreateRequestProps {
onBack?: () => void;
onSubmit?: (requestData: any) => void;
}
@ -120,21 +102,23 @@ const MOCK_USERS = [
{ id: '10', name: 'Michael Brown', role: 'CFO', avatar: 'MB', department: 'Finance', email: 'michael.brown@royalenfield.com', level: 5, canClose: true }
];
const USER_LEVELS = [
{ level: 1, name: 'Junior Level', description: 'Junior staff and coordinators', color: 'bg-gray-100 text-gray-800' },
{ level: 2, name: 'Mid Level', description: 'Team leads and supervisors', color: 'bg-blue-100 text-blue-800' },
{ level: 3, name: 'Senior Level', description: 'Managers and senior staff', color: 'bg-green-100 text-green-800' },
{ level: 4, name: 'Executive Level', description: 'Department heads and directors', color: 'bg-orange-100 text-orange-800' },
{ level: 5, name: 'C-Suite Level', description: 'Executive leadership', color: 'bg-purple-100 text-purple-800' }
];
// User levels - keeping for future use
// const USER_LEVELS = [
// { level: 1, name: 'Junior Level', description: 'Junior staff and coordinators', color: 'bg-gray-100 text-gray-800' },
// { level: 2, name: 'Mid Level', description: 'Team leads and supervisors', color: 'bg-blue-100 text-blue-800' },
// { level: 3, name: 'Senior Level', description: 'Managers and senior staff', color: 'bg-green-100 text-green-800' },
// { level: 4, name: 'Executive Level', description: 'Department heads and directors', color: 'bg-orange-100 text-orange-800' },
// { level: 5, name: 'C-Suite Level', description: 'Executive leadership', color: 'bg-purple-100 text-purple-800' }
// ];
const SLA_TEMPLATES = [
{ id: 'urgent', name: 'Urgent', hours: 4, description: 'Critical business impact', color: 'bg-red-100 text-red-800' },
{ id: 'high', name: 'High Priority', hours: 24, description: 'High business impact', color: 'bg-orange-100 text-orange-800' },
{ id: 'medium', name: 'Medium Priority', hours: 72, description: 'Moderate business impact', color: 'bg-yellow-100 text-yellow-800' },
{ id: 'low', name: 'Low Priority', hours: 120, description: 'Low business impact', color: 'bg-green-100 text-green-800' },
{ id: 'custom', name: 'Custom SLA', hours: 0, description: 'Define your own timeline', color: 'bg-blue-100 text-blue-800' }
];
// SLA Templates - keeping for future use
// const SLA_TEMPLATES = [
// { id: 'urgent', name: 'Urgent', hours: 4, description: 'Critical business impact', color: 'bg-red-100 text-red-800' },
// { id: 'high', name: 'High Priority', hours: 24, description: 'High business impact', color: 'bg-orange-100 text-orange-800' },
// { id: 'medium', name: 'Medium Priority', hours: 72, description: 'Moderate business impact', color: 'bg-yellow-100 text-yellow-800' },
// { id: 'low', name: 'Low Priority', hours: 120, description: 'Low business impact', color: 'bg-green-100 text-green-800' },
// { id: 'custom', name: 'Custom SLA', hours: 0, description: 'Define your own timeline', color: 'bg-blue-100 text-blue-800' }
// ];
const STEP_NAMES = [
'Template Selection',
@ -145,11 +129,9 @@ const STEP_NAMES = [
'Review & Submit'
];
export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
export function CreateRequest({ onBack, onSubmit }: CreateRequestProps) {
const [currentStep, setCurrentStep] = useState(1);
const [selectedTemplate, setSelectedTemplate] = useState<RequestTemplate | null>(null);
const [userSearch, setUserSearch] = useState('');
const [showAdvanced, setShowAdvanced] = useState(false);
const [emailInput, setEmailInput] = useState('');
const [showTemplateModal, setShowTemplateModal] = useState(false);
const [newUserData, setNewUserData] = useState({
@ -159,7 +141,6 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
department: '',
level: 1
});
const [showUserInvite, setShowUserInvite] = useState(false);
const [formData, setFormData] = useState({
// Template and basic info
@ -221,23 +202,12 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
setFormData(prev => ({ ...prev, [field]: value }));
};
const filteredUsers = MOCK_USERS.filter(user =>
user.name.toLowerCase().includes(userSearch.toLowerCase()) ||
user.role.toLowerCase().includes(userSearch.toLowerCase()) ||
user.department.toLowerCase().includes(userSearch.toLowerCase()) ||
user.email.toLowerCase().includes(userSearch.toLowerCase())
);
const validateEmail = (email: string) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
const getLevelInfo = (level: number) => {
return USER_LEVELS.find(l => l.level === level) || USER_LEVELS[0];
};
const createUserFromEmail = (email: string, name: string = '', role: string = '', department: string = '', level: number = 1) => {
const avatar = name ? name.split(' ').map(n => n[0]).join('').toUpperCase() : email.split('@')[0].substring(0, 2).toUpperCase();
const avatar = name ? name.split(' ').map(n => (n?.[0] || '')).join('').toUpperCase() : email.split('@')[0]?.substring(0, 2).toUpperCase() || 'XX';
return {
id: `temp-${Date.now()}-${Math.random()}`,
name: name || email.split('@')[0],
@ -251,17 +221,6 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
};
};
const calculateSLAEndDate = (hours: number) => {
const now = new Date();
const endDate = new Date(now.getTime() + (hours * 60 * 60 * 1000));
return endDate;
};
const updateMaxLevel = () => {
const maxApproverLevel = Math.max(...formData.approvers.map(a => a.level), 0);
updateFormData('maxLevel', maxApproverLevel);
};
const getPriorityIcon = (priority: string) => {
switch (priority) {
case 'high': return <Flame className="w-4 h-4 text-red-600" />;
@ -340,18 +299,21 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
// Update max level if adding approver
if (type === 'approvers') {
setTimeout(updateMaxLevel, 0);
const maxApproverLevel = Math.max(...updatedList.map((a: any) => a.level), 0);
updateFormData('maxLevel', maxApproverLevel);
}
}
};
const removeUser = (userId: string, type: 'approvers' | 'spectators' | 'ccList' | 'invitedUsers') => {
const currentList = formData[type];
updateFormData(type, currentList.filter((u: any) => u.id !== userId));
const updatedList = currentList.filter((u: any) => u.id !== userId);
updateFormData(type, updatedList);
// Update max level if removing approver
if (type === 'approvers') {
setTimeout(updateMaxLevel, 0);
const maxApproverLevel = Math.max(...updatedList.map((a: any) => a.level), 0);
updateFormData('maxLevel', maxApproverLevel);
}
};
@ -377,14 +339,13 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
addUser(newUser, 'invitedUsers');
setEmailInput('');
setNewUserData({ name: '', email: '', role: '', department: '', level: 1 });
setShowUserInvite(false);
return newUser;
};
const inviteAndAddUser = (type: 'approvers' | 'spectators' | 'ccList') => {
const user = addUserByEmail();
if (user && type !== 'invitedUsers') {
if (user) {
addUser(user, type);
}
};
@ -851,7 +812,7 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
// Ensure approvers array has enough items
if (!formData.approvers[index]) {
const newApprovers = [...formData.approvers];
newApprovers[index] = { email: '', name: '', level: level, tat: '' };
newApprovers[index] = { email: '', name: '', level: level, tat: '' as any };
updateFormData('approvers', newApprovers);
}
@ -1741,7 +1702,7 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
<div className="bg-white border-b border-gray-200 px-6 py-3 flex-shrink-0">
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-between mb-2">
{STEP_NAMES.map((stepName, index) => (
{STEP_NAMES.map((_, index) => (
<div key={index} className="flex items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold ${
index + 1 < currentStep
@ -1765,11 +1726,11 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
))}
</div>
<div className="hidden lg:flex justify-between text-xs text-gray-600 mt-2">
{STEP_NAMES.map((stepName, index) => (
{STEP_NAMES.map((step, index) => (
<span key={index} className={`${
index + 1 === currentStep ? 'font-semibold text-blue-600' : ''
}`}>
{stepName}
{step}
</span>
))}
</div>

View File

@ -0,0 +1 @@
export { CreateRequest } from './CreateRequest';

View File

@ -1,16 +1,15 @@
import React, { useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { Progress } from './ui/progress';
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
import { useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import {
FileText,
Clock,
AlertTriangle,
TrendingUp,
CheckCircle,
Users,
Zap,
Shield,
ArrowRight,

View File

@ -0,0 +1 @@
export { Dashboard } from './Dashboard';

View File

@ -1,29 +1,24 @@
import React, { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Badge } from './ui/badge';
import { Avatar, AvatarFallback } from './ui/avatar';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import {
FileText,
Search,
Filter,
Clock,
CheckCircle,
XCircle,
AlertCircle,
TrendingUp,
Calendar,
User,
ArrowRight,
MoreHorizontal,
TrendingUp,
Eye,
Edit,
Copy
Flame,
Target
} from 'lucide-react';
import { motion } from 'motion/react';
import { motion } from 'framer-motion';
interface MyRequestsProps {
onViewRequest: (requestId: string, requestTitle?: string) => void;
@ -105,52 +100,72 @@ const MY_REQUESTS_DATA = [
}
];
const getStatusIcon = (status: string) => {
switch (status) {
case 'approved':
return <CheckCircle className="w-4 h-4 text-green-600" />;
case 'rejected':
return <XCircle className="w-4 h-4 text-red-600" />;
case 'pending':
return <Clock className="w-4 h-4 text-yellow-600" />;
case 'in-review':
return <AlertCircle className="w-4 h-4 text-blue-600" />;
case 'draft':
return <Edit className="w-4 h-4 text-gray-600" />;
default:
return <FileText className="w-4 h-4 text-gray-600" />;
}
};
const getStatusBadge = (status: string) => {
const baseClasses = "text-xs font-medium px-2 py-1 rounded-full";
switch (status) {
case 'approved':
return <Badge className={`${baseClasses} bg-green-100 text-green-800 border-green-200`}>Approved</Badge>;
case 'rejected':
return <Badge className={`${baseClasses} bg-red-100 text-red-800 border-red-200`}>Rejected</Badge>;
case 'pending':
return <Badge className={`${baseClasses} bg-yellow-100 text-yellow-800 border-yellow-200`}>Pending</Badge>;
case 'in-review':
return <Badge className={`${baseClasses} bg-blue-100 text-blue-800 border-blue-200`}>In Review</Badge>;
case 'draft':
return <Badge className={`${baseClasses} bg-gray-100 text-gray-800 border-gray-200`}>Draft</Badge>;
default:
return <Badge className={`${baseClasses} bg-gray-100 text-gray-800 border-gray-200`}>Unknown</Badge>;
}
};
const getPriorityBadge = (priority: string) => {
const getPriorityConfig = (priority: string) => {
switch (priority) {
case 'express':
return <Badge variant="destructive" className="text-xs">Express</Badge>;
return {
color: 'bg-red-100 text-red-800 border-red-200',
icon: Flame,
iconColor: 'text-red-600'
};
case 'standard':
return <Badge className="bg-blue-100 text-blue-800 border-blue-200 text-xs">Standard</Badge>;
return {
color: 'bg-blue-100 text-blue-800 border-blue-200',
icon: Target,
iconColor: 'text-blue-600'
};
default:
return <Badge variant="secondary" className="text-xs">Normal</Badge>;
return {
color: 'bg-gray-100 text-gray-800 border-gray-200',
icon: Target,
iconColor: 'text-gray-600'
};
}
};
const getStatusConfig = (status: string) => {
switch (status) {
case 'approved':
return {
color: 'bg-green-100 text-green-800 border-green-200',
icon: CheckCircle,
iconColor: 'text-green-600'
};
case 'rejected':
return {
color: 'bg-red-100 text-red-800 border-red-200',
icon: XCircle,
iconColor: 'text-red-600'
};
case 'pending':
return {
color: 'bg-yellow-100 text-yellow-800 border-yellow-200',
icon: Clock,
iconColor: 'text-yellow-600'
};
case 'in-review':
return {
color: 'bg-blue-100 text-blue-800 border-blue-200',
icon: Eye,
iconColor: 'text-blue-600'
};
case 'draft':
return {
color: 'bg-gray-100 text-gray-800 border-gray-200',
icon: Edit,
iconColor: 'text-gray-600'
};
default:
return {
color: 'bg-gray-100 text-gray-800 border-gray-200',
icon: AlertCircle,
iconColor: 'text-gray-600'
};
}
};
export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsProps) {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
@ -277,7 +292,7 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
</div>
{/* Filters and Search */}
<Card>
<Card className="border-gray-200">
<CardContent className="p-6">
<div className="flex flex-col md:flex-row gap-4 items-start md:items-center">
<div className="flex-1 relative">
@ -286,13 +301,13 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
placeholder="Search requests by title, description, or ID..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-9"
className="pl-9 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green"
/>
</div>
<div className="flex gap-3">
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-32">
<SelectTrigger className="w-32 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
@ -306,7 +321,7 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
</Select>
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
<SelectTrigger className="w-32">
<SelectTrigger className="w-32 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green">
<SelectValue placeholder="Priority" />
</SelectTrigger>
<SelectContent>
@ -342,70 +357,74 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
>
<Card className="hover:shadow-lg transition-all duration-200 cursor-pointer group"
onClick={() => onViewRequest(request.id, request.title)}>
<Card
className="group hover:shadow-lg transition-all duration-300 cursor-pointer border border-gray-200 shadow-sm hover:shadow-md"
onClick={() => onViewRequest(request.id, request.title)}
>
<CardContent className="p-6">
<div className="flex items-start justify-between mb-4">
<div className="flex items-start gap-4 flex-1">
<div className="flex items-center gap-2">
{getStatusIcon(request.status)}
</div>
<div className="space-y-4">
{/* Header with Title and Status Badges */}
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2 flex-wrap">
<h3 className="font-semibold text-gray-900 group-hover:text-[--re-green] transition-colors">
<h4 className="text-lg font-semibold text-gray-900 mb-2 group-hover:text-blue-600 transition-colors">
{request.title}
</h3>
{getStatusBadge(request.status)}
{getPriorityBadge(request.priority)}
</h4>
<div className="flex items-center gap-2 mb-2">
<Badge
variant="outline"
className={`${getStatusConfig(request.status).color} border font-medium text-xs`}
>
{(() => {
const IconComponent = getStatusConfig(request.status).icon;
return <IconComponent className="w-3 h-3 mr-1" />;
})()}
{request.status}
</Badge>
<Badge
variant="outline"
className={`${getPriorityConfig(request.priority).color} border font-medium text-xs capitalize`}
>
{request.priority}
</Badge>
{(request as any).templateType && (
<Badge className="text-xs bg-purple-100 text-purple-800 border-purple-200">
<Badge variant="secondary" className="bg-purple-100 text-purple-700 text-xs">
<FileText className="w-3 h-3 mr-1" />
Template: {(request as any).templateName}
</Badge>
)}
</div>
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
<p className="text-sm text-gray-600 mb-3">
{request.description}
</p>
<div className="flex items-center gap-6 text-xs text-gray-500">
<div className="flex items-center gap-1">
<span className="font-medium text-gray-700">ID:</span>
<span className="font-mono">{request.id}</span>
</div>
<div className="flex items-center gap-1">
<Calendar className="w-3 h-3" />
<span>Submitted: {new Date(request.submittedDate).toLocaleDateString()}</span>
</div>
<div className="flex items-center gap-4 text-sm text-gray-500">
<span><span className="font-medium">ID:</span> {request.id}</span>
<span><span className="font-medium">Submitted:</span> {request.submittedDate ? new Date(request.submittedDate).toLocaleDateString() : 'N/A'}</span>
</div>
</div>
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0" />
</div>
<div className="flex items-center gap-2 ml-4">
<ArrowRight className="w-4 h-4 text-gray-400 group-hover:text-[--re-green] transition-colors" />
</div>
</div>
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div className="flex items-center gap-4 text-sm">
{/* Current Approver and Level Info */}
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<User className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">
Current: <span className="font-medium text-gray-900">{request.currentApprover}</span>
<span className="text-sm">
<span className="text-gray-500">Current:</span> <span className="text-gray-900 font-medium">{request.currentApprover}</span>
</span>
</div>
<div className="flex items-center gap-2">
<TrendingUp className="w-4 h-4 text-gray-400" />
<span className="text-gray-600">
Level: <span className="font-medium text-gray-900">{request.approverLevel}</span>
<span className="text-sm">
<span className="text-gray-500">Level:</span> <span className="text-gray-900 font-medium">{request.approverLevel}</span>
</span>
</div>
</div>
<div className="text-sm text-gray-600">
<span className="font-medium">Estimated completion:</span> {request.estimatedCompletion}
<div className="text-right">
<span className="text-sm text-gray-500">
<span className="font-medium">Estimated completion:</span>
</span>
</div>
</div>
</div>
</CardContent>

View File

@ -0,0 +1 @@
export { MyRequests } from './MyRequests';

View File

@ -1,31 +1,25 @@
import React, { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
import { Progress } from './ui/progress';
import { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Progress } from '@/components/ui/progress';
import {
Calendar,
Clock,
Filter,
Search,
User,
FileText,
AlertCircle,
CheckCircle,
ArrowRight,
SortAsc,
SortDesc,
MoreHorizontal,
Flame,
Target,
Eye,
Users,
TrendingUp,
RefreshCw,
Download,
Settings2,
X
} from 'lucide-react';
@ -34,7 +28,7 @@ interface Request {
id: string;
title: string;
description: string;
status: 'pending' | 'approved' | 'rejected' | 'in-review';
status: 'pending' | 'in-review';
priority: 'express' | 'standard';
initiator: {
name: string;
@ -49,17 +43,15 @@ interface Request {
createdAt: string;
dueDate: string;
approvalStep: string;
reason?: string;
department?: string;
}
interface RequestsListProps {
type: 'open' | 'closed';
interface OpenRequestsProps {
onViewRequest?: (requestId: string, requestTitle?: string) => void;
}
// Static data to prevent re-creation on each render
const MOCK_REQUESTS: Request[] = [
// Static data for open requests
const OPEN_REQUESTS: Request[] = [
{
id: 'RE-REQ-CM-001',
title: 'Dealer Marketing Activity Claim - Diwali Festival Campaign',
@ -74,7 +66,7 @@ const MOCK_REQUESTS: Request[] = [
dueDate: '2024-10-16',
approvalStep: 'Initiator Review & Confirmation',
department: 'Marketing - West Zone'
} as any,
},
{
id: 'RE-REQ-001',
title: 'Marketing Campaign Budget Approval',
@ -148,34 +140,6 @@ const MOCK_REQUESTS: Request[] = [
}
];
const CLOSED_REQUESTS: Request[] = [
{
...MOCK_REQUESTS[0],
status: 'approved',
reason: 'Budget approved with quarterly review conditions'
},
{
...MOCK_REQUESTS[1],
status: 'approved',
reason: 'All equipment approved and ordered through preferred vendor'
},
{
...MOCK_REQUESTS[2],
status: 'rejected',
reason: 'Pricing not competitive, seek alternative vendors'
},
{
...MOCK_REQUESTS[3],
status: 'approved',
reason: 'Lease terms negotiated and approved by legal team'
},
{
...MOCK_REQUESTS[4],
status: 'approved',
reason: 'Program approved with budget adjustments'
}
];
// Utility functions
const getPriorityConfig = (priority: string) => {
switch (priority) {
@ -214,18 +178,6 @@ const getStatusConfig = (status: string) => {
icon: Eye,
iconColor: 'text-blue-600'
};
case 'approved':
return {
color: 'bg-green-100 text-green-800 border-green-200',
icon: CheckCircle,
iconColor: 'text-green-600'
};
case 'rejected':
return {
color: 'bg-red-100 text-red-800 border-red-200',
icon: AlertCircle,
iconColor: 'text-red-600'
};
default:
return {
color: 'bg-gray-100 text-gray-800 border-gray-200',
@ -241,19 +193,16 @@ const getSLAUrgency = (progress: number) => {
return { color: 'bg-green-500', textColor: 'text-green-600', urgency: 'normal' };
};
export function RequestsList({ type, onViewRequest }: RequestsListProps) {
export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
const [searchTerm, setSearchTerm] = useState('');
const [priorityFilter, setPriorityFilter] = useState('all');
const [statusFilter, setStatusFilter] = useState('all');
const [sortBy, setSortBy] = useState<'created' | 'due' | 'priority' | 'sla'>('due');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
const sourceRequests = type === 'open' ? MOCK_REQUESTS : CLOSED_REQUESTS;
const filteredAndSortedRequests = useMemo(() => {
let filtered = sourceRequests.filter(request => {
let filtered = OPEN_REQUESTS.filter(request => {
const matchesSearch =
request.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
request.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
@ -299,7 +248,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
});
return filtered;
}, [sourceRequests, searchTerm, priorityFilter, statusFilter, sortBy, sortOrder]);
}, [searchTerm, priorityFilter, statusFilter, sortBy, sortOrder]);
const clearFilters = () => {
setSearchTerm('');
@ -323,28 +272,20 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
<FileText className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900">
{type === 'open' ? 'Open Requests' : 'Closed Requests'}
</h1>
<p className="text-gray-600">
{type === 'open'
? 'Manage and track active approval requests'
: 'Review completed and archived requests'
}
</p>
<h1 className="text-3xl font-bold text-gray-900">Open Requests</h1>
<p className="text-gray-600">Manage and track active approval requests</p>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<Badge variant="secondary" className="text-lg px-4 py-2 bg-slate-100 text-slate-800 font-semibold">
{filteredAndSortedRequests.length} {type} requests
{filteredAndSortedRequests.length} open requests
</Badge>
<Button variant="outline" size="sm" className="gap-2">
<RefreshCw className="w-4 h-4" />
Refresh
</Button>
</div>
</div>
@ -421,17 +362,8 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
{type === 'open' ? (
<>
<SelectItem value="pending"> Pending</SelectItem>
<SelectItem value="in-review">👁 In Review</SelectItem>
</>
) : (
<>
<SelectItem value="approved"> Approved</SelectItem>
<SelectItem value="rejected"> Rejected</SelectItem>
</>
)}
</SelectContent>
</Select>
@ -444,7 +376,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
<SelectItem value="due">Due Date</SelectItem>
<SelectItem value="created">Date Created</SelectItem>
<SelectItem value="priority">Priority</SelectItem>
{type === 'open' && <SelectItem value="sla">SLA Progress</SelectItem>}
<SelectItem value="sla">SLA Progress</SelectItem>
</SelectContent>
</Select>
@ -458,8 +390,6 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
</Button>
</div>
</div>
</CardContent>
</Card>
@ -526,8 +456,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
</div>
</div>
{/* SLA Progress for Open Requests */}
{type === 'open' && (
{/* SLA Progress */}
<div className="bg-gray-50 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
@ -550,18 +479,13 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
className="h-3 bg-gray-200"
/>
</div>
)}
{/* Status Info */}
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
<div className="flex items-center gap-2">
{type === 'open' ? (
<AlertCircle className="w-4 h-4 text-blue-500" />
) : (
<CheckCircle className="w-4 h-4 text-green-500" />
)}
<span className="text-sm text-gray-700 font-medium">
{type === 'open' ? request.approvalStep : request.reason}
{request.approvalStep}
</span>
</div>
</div>
@ -581,7 +505,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
</div>
</div>
{type === 'open' && request.currentApprover && (
{request.currentApprover && (
<div className="flex items-center gap-2">
<Avatar className="h-8 w-8 ring-2 ring-yellow-200 shadow-sm">
<AvatarFallback className="bg-yellow-500 text-white text-sm font-semibold">
@ -625,7 +549,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
<p className="text-gray-600 text-center max-w-md">
{searchTerm || activeFiltersCount > 0
? 'Try adjusting your filters or search terms to see more results.'
: `No ${type} requests available at the moment.`
: 'No open requests available at the moment.'
}
</p>
{activeFiltersCount > 0 && (
@ -643,3 +567,4 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
</div>
);
}

View File

@ -0,0 +1,2 @@
export { OpenRequests } from './OpenRequests';

View File

@ -1,25 +1,22 @@
import React, { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { toast } from 'sonner@2.0.3';
import { Progress } from './ui/progress';
import { Avatar, AvatarFallback } from './ui/avatar';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { CUSTOM_REQUEST_DATABASE } from '../utils/customRequestDatabase';
import { useState, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
import {
ArrowLeft,
Clock,
User,
FileText,
MessageSquare,
Users,
CheckCircle,
XCircle,
Download,
Eye,
Flame,
Target,
TrendingUp,
RefreshCw,
Activity,
@ -27,7 +24,6 @@ import {
Phone,
Upload,
UserPlus,
Edit3,
ClipboardList
} from 'lucide-react';
@ -158,11 +154,15 @@ export function RequestDetail({
}: RequestDetailProps) {
const [activeTab, setActiveTab] = useState('overview');
// Get request from custom request database or dynamic requests
// Get request from any database or dynamic requests
const request = useMemo(() => {
// First check static database
const staticRequest = CUSTOM_REQUEST_DATABASE[requestId];
if (staticRequest) return staticRequest;
// First check custom request database
const customRequest = CUSTOM_REQUEST_DATABASE[requestId];
if (customRequest) return customRequest;
// Then check claim management database
const claimRequest = CLAIM_MANAGEMENT_DATABASE[requestId];
if (claimRequest) return claimRequest;
// Then check dynamic requests
const dynamicRequest = dynamicRequests.find((req: any) => req.id === requestId);
@ -176,7 +176,7 @@ export function RequestDetail({
<div className="flex items-center justify-center h-screen">
<div className="text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Request Not Found</h2>
<p className="text-gray-600 mb-4">The custom request you're looking for doesn't exist.</p>
<p className="text-gray-600 mb-4">The request you're looking for doesn't exist.</p>
<Button onClick={onBack}>
<ArrowLeft className="w-4 h-4 mr-2" />
Go Back
@ -194,9 +194,9 @@ export function RequestDetail({
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto p-6">
{/* Header Section */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200 mb-6">
<div className="bg-white rounded-lg shadow-sm border border-gray-300 mb-6">
{/* Top Header */}
<div className="p-6 border-b border-gray-200">
<div className="p-6 border-b border-gray-300">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button
@ -256,7 +256,7 @@ export function RequestDetail({
{/* Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="bg-white border border-gray-200 shadow-sm mb-6">
<TabsList className="bg-white border border-gray-300 shadow-sm mb-6">
<TabsTrigger value="overview" className="gap-2">
<ClipboardList className="w-4 h-4" />
Overview
@ -326,8 +326,8 @@ export function RequestDetail({
<CardContent className="space-y-4">
<div>
<label className="text-sm font-medium text-gray-700 block mb-2">Description</label>
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed">
<div className="bg-gray-50 rounded-lg p-4 border border-gray-300">
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">
{request.description}
</p>
</div>
@ -335,7 +335,7 @@ export function RequestDetail({
{/* Additional Details */}
{(request.category || request.subcategory) && (
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200">
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-300">
{request.category && (
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Category</label>
@ -352,13 +352,13 @@ export function RequestDetail({
)}
{request.amount && (
<div className="pt-4 border-t border-gray-200">
<div className="pt-4 border-t border-gray-300">
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Amount</label>
<p className="text-lg font-bold text-gray-900 mt-1">{request.amount}</p>
</div>
)}
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-200">
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-gray-300">
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Created</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.createdAt}</p>
@ -370,6 +370,55 @@ export function RequestDetail({
</div>
</CardContent>
</Card>
{/* Claim Management Details - Show only for claim management requests */}
{request.claimDetails && (
<Card>
<CardHeader className="pb-4">
<CardTitle className="flex items-center gap-2 text-base">
<FileText className="w-5 h-5 text-purple-600" />
Claim Management Details
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Activity Name</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.claimDetails.activityName || 'N/A'}</p>
</div>
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Activity Type</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.claimDetails.activityType || 'N/A'}</p>
</div>
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Location</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.claimDetails.location || 'N/A'}</p>
</div>
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Activity Date</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.claimDetails.activityDate || 'N/A'}</p>
</div>
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Dealer Code</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.claimDetails.dealerCode || 'N/A'}</p>
</div>
<div>
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Dealer Name</label>
<p className="text-sm text-gray-900 font-medium mt-1">{request.claimDetails.dealerName || 'N/A'}</p>
</div>
</div>
{request.claimDetails.requestDescription && (
<div className="pt-4 border-t border-gray-300">
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Request Description</label>
<p className="text-sm text-gray-700 mt-2 bg-gray-50 p-3 rounded-lg whitespace-pre-line">
{request.claimDetails.requestDescription}
</p>
</div>
)}
</CardContent>
</Card>
)}
</div>
{/* Right Column - Quick Actions Sidebar (1/3 width) */}
@ -381,7 +430,6 @@ export function RequestDetail({
</CardHeader>
<CardContent className="space-y-2">
<Button
variant="outline"
className="w-full justify-start gap-2 bg-[#1a472a] text-white hover:bg-[#152e1f] hover:text-white border-0"
onClick={() => onOpenModal?.('work-note')}
>
@ -390,7 +438,7 @@ export function RequestDetail({
</Button>
<Button
variant="outline"
className="w-full justify-start gap-2 border-gray-300 hover:bg-gray-50"
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900"
onClick={() => onOpenModal?.('add-approver')}
>
<UserPlus className="w-4 h-4" />
@ -398,7 +446,7 @@ export function RequestDetail({
</Button>
<Button
variant="outline"
className="w-full justify-start gap-2 border-gray-300 hover:bg-gray-50"
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900"
onClick={() => onOpenModal?.('add-spectator')}
>
<Eye className="w-4 h-4" />
@ -537,8 +585,8 @@ export function RequestDetail({
</div>
{step.comment && (
<div className="mt-3 p-3 bg-white rounded-lg border border-gray-200">
<p className="text-sm text-gray-700">{step.comment}</p>
<div className="mt-3 p-3 bg-white rounded-lg border border-gray-300">
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">{step.comment}</p>
</div>
)}
@ -581,7 +629,7 @@ export function RequestDetail({
{request.documents.map((doc: any, index: number) => (
<div
key={index}
className="flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:bg-gray-50 transition-colors"
className="flex items-center justify-between p-4 rounded-lg border border-gray-300 hover:bg-gray-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
@ -625,21 +673,40 @@ export function RequestDetail({
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-6">
{request.auditTrail && request.auditTrail.length > 0 ? request.auditTrail.map((entry: any, index: number) => (
<div key={index} className="flex items-start gap-4 p-4 rounded-lg hover:bg-gray-50 transition-colors border border-gray-100">
<div className="mt-1">
<div key={index} className="flex items-start gap-4">
{/* Avatar */}
<div className="flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
{getActionTypeIcon(entry.type)}
</div>
</div>
{/* Message Content */}
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div>
<p className="font-medium text-gray-900">{entry.action}</p>
<p className="text-sm text-gray-600 mt-1">{entry.details}</p>
<p className="text-xs text-gray-500 mt-1">by {entry.user}</p>
<div className="bg-white rounded-lg border border-gray-300 p-4 shadow-sm">
{/* Header with user info and timestamp */}
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<span className="font-semibold text-gray-900">{entry.user}</span>
<Badge variant="outline" className="text-xs">
{entry.type}
</Badge>
</div>
<span className="text-xs text-gray-500 whitespace-nowrap">{entry.timestamp}</span>
</div>
{/* Action title */}
<div className="mb-2">
<p className="font-medium text-gray-900">{entry.action}</p>
</div>
{/* Details with proper text alignment */}
<div className="text-sm text-gray-700 leading-relaxed">
<p className="whitespace-pre-line break-words">{entry.details}</p>
</div>
</div>
</div>
</div>
)) : (

View File

@ -0,0 +1 @@
export { RequestDetail } from './RequestDetail';

6
src/redux/hooks.ts Normal file
View File

@ -0,0 +1,6 @@
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@ -0,0 +1,69 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { User } from '@/types/user.types';
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
const initialState: AuthState = {
user: null,
token: localStorage.getItem('token'),
isAuthenticated: false,
isLoading: false,
error: null,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
loginStart: (state) => {
state.isLoading = true;
state.error = null;
},
loginSuccess: (state, action: PayloadAction<{ user: User; token: string }>) => {
state.isLoading = false;
state.isAuthenticated = true;
state.user = action.payload.user;
state.token = action.payload.token;
state.error = null;
localStorage.setItem('token', action.payload.token);
},
loginFailure: (state, action: PayloadAction<string>) => {
state.isLoading = false;
state.isAuthenticated = false;
state.user = null;
state.token = null;
state.error = action.payload;
localStorage.removeItem('token');
},
logout: (state) => {
state.isAuthenticated = false;
state.user = null;
state.token = null;
state.error = null;
localStorage.removeItem('token');
},
clearError: (state) => {
state.error = null;
},
updateUser: (state, action: PayloadAction<User>) => {
state.user = action.payload;
},
},
});
export const {
loginStart,
loginSuccess,
loginFailure,
logout,
clearError,
updateUser,
} = authSlice.actions;
export default authSlice;

32
src/redux/store.ts Normal file
View File

@ -0,0 +1,32 @@
import { configureStore } from '@reduxjs/toolkit';
import { authSlice } from './slices/authSlice';
import { workflowSlice } from './slices/workflowSlice';
import { approvalSlice } from './slices/approvalSlice';
import { notificationSlice } from './slices/notificationSlice';
import { documentSlice } from './slices/documentSlice';
import { workNoteSlice } from './slices/workNoteSlice';
import { participantSlice } from './slices/participantSlice';
import { uiSlice } from './slices/uiSlice';
export const store = configureStore({
reducer: {
auth: authSlice.reducer,
workflow: workflowSlice.reducer,
approval: approvalSlice.reducer,
notification: notificationSlice.reducer,
document: documentSlice.reducer,
workNote: workNoteSlice.reducer,
participant: participantSlice.reducer,
ui: uiSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST'],
},
}),
devTools: process.env.NODE_ENV !== 'production',
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

611
src/styles/globals.css Normal file
View File

@ -0,0 +1,611 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@custom-variant dark (&:is(.dark *));
:root {
--font-size: 16px;
--background: 35 8% 96%;
--foreground: 0 0% 10%;
--card: 0 0% 100%;
--card-foreground: 0 0% 10%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 10%;
--primary: 158 24% 24%;
--primary-foreground: 0 0% 100%;
--secondary: 150 7% 57%;
--secondary-foreground: 0 0% 100%;
--muted: 35 8% 90%;
--muted-foreground: 0 0% 42%;
--accent: 43 62% 49%;
--accent-foreground: 0 0% 10%;
--destructive: 0 84% 50%;
--destructive-foreground: 0 0% 100%;
--border: 35 8% 82%;
--input: transparent;
--input-background: 0 0% 100%;
--switch-background: 35 8% 82%;
--font-weight-medium: 500;
--font-weight-normal: 400;
--ring: 158 24% 24%;
--chart-1: 158 24% 24%;
--chart-2: 150 7% 57%;
--chart-3: 43 62% 49%;
--chart-4: 0 84% 50%;
--chart-5: 0 0% 42%;
--radius: 0.625rem;
--sidebar: 0 0% 10%;
--sidebar-foreground: 0 0% 100%;
--sidebar-primary: 158 24% 24%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 158 24% 24%;
--sidebar-accent-foreground: 0 0% 100%;
--sidebar-border: 0 0% 20%;
--sidebar-ring: 158 24% 24%;
--re-green: #2d4a3e;
--re-gold: #c9b037;
--re-dark: #1a1a1a;
--re-light-green: #8a9b8e;
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--font-weight-medium: 500;
--font-weight-normal: 400;
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-input-background: var(--input-background);
--color-switch-background: var(--switch-background);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-re-green: var(--re-green);
--color-re-gold: var(--re-gold);
--color-re-dark: var(--re-dark);
--color-re-light-green: var(--re-light-green);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
/* CSS Reset for better text alignment */
* {
box-sizing: border-box;
}
/* Ensure proper text alignment inheritance */
p, div, span, h1, h2, h3, h4, h5, h6 {
text-align: inherit;
}
/* Fix for text wrapping in all elements */
* {
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Better text rendering */
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
/**
* Base typography. This is not applied to elements which have an ancestor with a Tailwind text class.
*/
@layer base {
:where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) {
h1 {
font-size: 1.875rem;
font-weight: var(--font-weight-medium);
line-height: 1.4;
letter-spacing: -0.025em;
}
h2 {
font-size: 1.5rem;
font-weight: var(--font-weight-medium);
line-height: 1.4;
letter-spacing: -0.025em;
}
h3 {
font-size: 1.25rem;
font-weight: var(--font-weight-medium);
line-height: 1.4;
}
h4 {
font-size: 1rem;
font-weight: var(--font-weight-medium);
line-height: 1.5;
}
p {
font-size: 0.875rem;
font-weight: var(--font-weight-normal);
line-height: 1.6;
}
label {
font-size: 0.875rem;
font-weight: var(--font-weight-medium);
line-height: 1.5;
}
button {
font-size: 0.875rem;
font-weight: var(--font-weight-medium);
line-height: 1.5;
}
input {
font-size: 0.875rem;
font-weight: var(--font-weight-normal);
line-height: 1.5;
}
}
}
/* Utility classes for better spacing and layout */
@layer components {
/* Input focus styles for navbar */
input[type="text"]:focus,
input[type="search"]:focus,
input:focus {
outline: none;
}
/* Ensure gray borders render properly */
.border-gray-300 {
border-color: #d1d5db !important;
}
.border-gray-200 {
border-color: #e5e7eb !important;
}
.hover\:border-gray-400:hover {
border-color: #9ca3af !important;
}
/* Focus states for inputs */
.focus\:border-re-green:focus {
border-color: var(--re-green) !important;
}
.focus\:ring-re-green:focus {
--tw-ring-color: var(--re-green) !important;
}
.focus\:ring-1:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
/* Ensure gray text colors render properly */
.text-gray-400 {
color: #9ca3af !important;
}
.text-gray-600 {
color: #4b5563 !important;
}
/* Navbar specific styling */
header.bg-white {
background-color: #ffffff !important;
}
/* Card border colors */
.border-gray-200 {
border-color: #e5e7eb !important;
}
/* Ensure cards with gray borders render properly */
[class*="border-gray"] {
border-style: solid;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.min-w-0 {
min-width: 0;
}
/* Line clamp utilities for text truncation */
.line-clamp-1 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.line-clamp-2 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.line-clamp-3 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
/* Text alignment and wrapping utilities */
.text-wrap-balance {
text-wrap: balance;
}
.text-wrap-pretty {
text-wrap: pretty;
}
/* Better text alignment for conversation bubbles */
.conversation-text {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
text-align: left;
line-height: 1.6;
}
/* Fix for text alignment in flex containers */
.flex-text-align {
display: flex;
flex-direction: column;
align-items: flex-start;
}
/* Ensure proper text flow in message bubbles */
.message-content {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
text-align: left;
line-height: 1.5;
}
/* Fix for avatar alignment */
.avatar-container {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
/* Message bubble container */
.message-bubble {
position: relative;
display: block;
width: 100%;
text-align: left;
}
/* Fix for text indentation issues */
.text-indent-reset {
text-indent: 0;
padding-left: 0;
margin-left: 0;
}
/* Additional utilities for better text alignment */
.text-align-left {
text-align: left;
}
.text-align-justify {
text-align: justify;
}
/* Fix for text wrapping in containers */
.text-wrap-normal {
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Better line height for readability */
.leading-relaxed {
line-height: 1.625;
}
.leading-loose {
line-height: 2;
}
/* Fix for flex item alignment */
.flex-start {
align-items: flex-start;
}
.flex-center {
align-items: center;
}
/* Message bubble specific styles */
.message-bubble-content {
display: block;
width: 100%;
text-align: left;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: pre-wrap;
line-height: 1.5;
}
/* Fix for text alignment in nested elements */
.text-content {
text-align: left;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: pre-wrap;
line-height: 1.6;
}
/* Ensure CSS custom properties work with Tailwind */
.text-re-green {
color: var(--re-green);
}
.group:hover .group-hover\:text-re-green {
color: var(--re-green);
}
/* Fix for gradient backgrounds */
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
/* Ensure proper CSS loading */
.space-y-6 > * + * {
margin-top: 1.5rem;
}
.space-y-4 > * + * {
margin-top: 1rem;
}
/* Fix for flex utilities */
.flex-1 {
flex: 1 1 0%;
}
.min-w-0 {
min-width: 0px;
}
/* Ensure proper text wrapping */
.break-words {
overflow-wrap: break-word;
word-wrap: break-word;
}
/* Sidebar and layout fixes */
.sidebar-fixed {
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 50;
}
.main-content-with-sidebar {
margin-left: 16rem; /* 256px - matches w-64 */
}
/* Ensure proper flex behavior */
.flex-shrink-0 {
flex-shrink: 0;
}
.min-w-0 {
min-width: 0;
}
/* Fix for sidebar trigger positioning */
.sidebar-trigger {
z-index: 10;
}
/* Ensure search bar doesn't overlap */
.search-container {
position: relative;
z-index: 5;
}
/* Mobile sidebar fixes */
@media (max-width: 768px) {
.sidebar-mobile {
position: fixed;
top: 0;
left: -100%;
width: 16rem;
height: 100vh;
z-index: 50;
transition: left 0.3s ease;
}
.sidebar-mobile.open {
left: 0;
}
.main-content-mobile {
width: 100%;
margin-left: 0;
}
}
/* Ensure proper sidebar behavior */
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 40;
}
/* Sidebar toggle animations */
.sidebar-container {
transition: width 0.3s ease-in-out;
overflow: hidden;
}
.sidebar-content {
width: 16rem; /* 256px */
height: 100vh;
position: relative;
}
.main-content-full {
width: 100%;
transition: all 0.3s ease-in-out;
}
.main-content-with-sidebar {
width: calc(100% - 16rem);
transition: all 0.3s ease-in-out;
}
/* Ensure smooth transitions */
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
/* Fix for search bar positioning */
.search-container {
position: relative;
z-index: 1;
}
/* Ensure proper flex behavior */
.flex-1 {
flex: 1 1 0%;
}
.min-w-0 {
min-width: 0;
}
/* Fix for destructive button variant */
.bg-destructive {
background-color: #dc2626 !important; /* red-600 */
}
.hover\:bg-destructive\/90:hover {
background-color: rgb(220 38 38 / 0.9) !important;
}
/* Ensure border colors work properly */
.border-gray-200 {
border-color: rgb(229 231 235) !important;
}
.border-gray-300 {
border-color: rgb(209 213 219) !important;
}
.hover\:border-gray-400:hover {
border-color: rgb(156 163 175) !important;
}
/* Fix for card backgrounds */
.bg-white {
background-color: rgb(255 255 255) !important;
}
/* Ensure dialog backgrounds are white */
[data-slot="dialog-content"] {
background-color: rgb(255 255 255) !important;
}
}
html {
font-size: var(--font-size);
}

173
src/types/index.ts Normal file
View File

@ -0,0 +1,173 @@
// Type definitions for Royal Enfield Approval Portal
export type Priority = 'express' | 'urgent' | 'standard';
export type Status = 'pending' | 'in-review' | 'approved' | 'rejected' | 'cancelled';
export type ApprovalStatus = 'pending' | 'waiting' | 'approved' | 'rejected' | 'cancelled';
export interface Initiator {
name: string;
role: string;
department: string;
email: string;
phone: string;
avatar: string;
}
export interface ApprovalStep {
step: number;
approver: string;
role: string;
status: ApprovalStatus;
tatHours: number;
elapsedHours?: number;
actualHours?: number;
assignedAt: string | null;
comment: string | null;
timestamp: string | null;
description?: string;
reminderHistory?: ReminderHistoryItem[];
}
export interface ReminderHistoryItem {
type: 'auto' | 'manual';
sentAt: string;
sentBy: string;
}
export interface Document {
name: string;
size: string;
type: string;
uploadedBy: string;
uploadedAt: string;
}
export interface Spectator {
name: string;
role: string;
avatar: string;
}
export interface AuditTrailItem {
type: string;
action: string;
details: string;
user: string;
timestamp: string;
}
export interface BaseRequest {
id: string;
title: string;
description: string;
category: string;
subcategory: string;
status: Status;
priority: Priority;
amount: string;
slaProgress: number;
slaRemaining: string;
slaEndDate: string;
currentStep: number;
totalSteps: number;
initiator: Initiator;
department: string;
createdAt: string;
updatedAt: string;
dueDate: string;
conclusionRemark: string;
approvalFlow: ApprovalStep[];
documents: Document[];
spectators: Spectator[];
auditTrail: AuditTrailItem[];
tags: string[];
}
export interface CustomRequest extends BaseRequest {
template: 'custom';
currentApprover?: string;
approverLevel?: string;
submittedDate?: string;
estimatedCompletion?: string;
}
export interface ClaimDetails {
activityName: string;
activityType: string;
activityDate: string;
location: string;
dealerCode: string;
dealerName: string;
dealerEmail: string;
dealerPhone: string;
dealerAddress: string;
requestDescription: string;
estimatedBudget: string;
periodStart: string;
periodEnd: string;
}
export interface ClaimManagementRequest extends BaseRequest {
template: 'claim-management';
templateType: 'claim-management';
templateName: string;
claimDetails: ClaimDetails;
}
export type Request = CustomRequest | ClaimManagementRequest;
export interface DealerInfo {
code: string;
name: string;
email: string;
phone: string;
address: string;
city: string;
state: string;
region: string;
managerName: string;
}
export interface RequestFormData {
title: string;
description: string;
category: string;
subcategory: string;
priority: Priority;
budget?: string;
department: string;
initiatorRole?: string;
approvers?: Array<{
name?: string;
email: string;
role?: string;
level?: number;
tat?: number | string;
}>;
spectators?: Array<{
name?: string;
email: string;
role?: string;
department?: string;
}>;
tags?: string[];
templateType?: string;
}
export interface ClaimFormData {
activityName: string;
activityType: string;
activityDate: string;
location: string;
dealerCode: string;
dealerName: string;
dealerEmail?: string;
dealerPhone?: string;
dealerAddress?: string;
requestDescription: string;
estimatedBudget?: string;
periodStartDate: string;
periodEndDate: string;
workflowSteps?: ApprovalStep[];
}

Some files were not shown because too many files have changed in this diff Show More