ui conflicts resolved
This commit is contained in:
parent
6fe42e8e5b
commit
da1d0538e9
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal 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
12
.prettierrc
Normal 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
11
.vscode/extensions.json
vendored
Normal 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
306
MIGRATION_GUIDE.md
Normal 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
291
README.md
Normal 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
283
SETUP_INSTRUCTIONS.md
Normal 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
342
START_HERE.md
Normal 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! 🚀**
|
||||||
|
|
||||||
@ -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
35
eslint.config.js
Normal 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
19
fix-imports.ps1
Normal 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
59
index.html
Normal 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
53
migrate-files.ps1
Normal 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
7130
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
90
package.json
Normal file
90
package.json
Normal 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
7
postcss.config.cjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
2
public/vite.svg
Normal file
2
public/vite.svg
Normal 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 |
@ -1,18 +1,18 @@
|
|||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Layout } from './components/Layout';
|
import { PageLayout } from '@/components/layout/PageLayout';
|
||||||
import { Dashboard } from './components/Dashboard';
|
import { Dashboard } from '@/pages/Dashboard';
|
||||||
import { RequestsList } from './components/RequestsList';
|
import { OpenRequests } from '@/pages/OpenRequests';
|
||||||
import { RequestDetail } from './components/RequestDetail';
|
import { ClosedRequests } from '@/pages/ClosedRequests';
|
||||||
import { ClaimManagementDetail } from './components/ClaimManagementDetail';
|
import { RequestDetail } from '@/pages/RequestDetail';
|
||||||
import { WorkNoteView } from './components/WorkNoteView';
|
import { WorkNoteChat } from '@/components/workNote/WorkNoteChat';
|
||||||
import { NewRequestWizard } from './components/NewRequestWizard';
|
import { CreateRequest } from '@/pages/CreateRequest';
|
||||||
import { ClaimManagementWizard } from './components/ClaimManagementWizard';
|
import { ClaimManagementWizard } from '@/components/workflow/ClaimManagementWizard';
|
||||||
import { MyRequests } from './components/MyRequests';
|
import { MyRequests } from '@/pages/MyRequests';
|
||||||
import { ApprovalActionModal } from './components/modals/ApprovalActionModal';
|
import { ApprovalActionModal } from '@/components/modals/ApprovalActionModal';
|
||||||
import { Toaster } from './components/ui/sonner';
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { toast } from 'sonner';
|
||||||
import { CUSTOM_REQUEST_DATABASE } from './utils/customRequestDatabase';
|
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
|
||||||
import { CLAIM_MANAGEMENT_DATABASE } from './utils/claimManagementDatabase';
|
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
|
||||||
|
|
||||||
type Page = 'dashboard' | 'open-requests' | 'closed-requests' | 'my-requests' | 'request-detail' | 'work-notes' | 'new-request' | 'claim-management';
|
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)
|
// Legacy database - keeping for reference (will be removed in future)
|
||||||
|
/*
|
||||||
const LEGACY_REQUEST_DATABASE: any = {
|
const LEGACY_REQUEST_DATABASE: any = {
|
||||||
// ========== TEMPLATE-BASED REQUESTS - Claim Management ==========
|
// ========== TEMPLATE-BASED REQUESTS - Claim Management ==========
|
||||||
'RE-REQ-2024-CM-001': {
|
'RE-REQ-2024-CM-001': {
|
||||||
@ -1165,11 +1166,12 @@ const LEGACY_REQUEST_DATABASE: any = {
|
|||||||
tags: ['sustainability', 'green-energy', 'rejected', 'budget-constraints']
|
tags: ['sustainability', 'green-energy', 'rejected', 'budget-constraints']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [currentPage, setCurrentPage] = useState<Page>('dashboard');
|
const [currentPage, setCurrentPage] = useState<Page>('dashboard');
|
||||||
const [selectedRequestId, setSelectedRequestId] = useState<string>('');
|
const [selectedRequestId, setSelectedRequestId] = useState<string>('');
|
||||||
const [activeModal, setActiveModal] = useState<string>('');
|
const [, setActiveModal] = useState<string>('');
|
||||||
const [approvalAction, setApprovalAction] = useState<'approve' | 'reject' | null>(null);
|
const [approvalAction, setApprovalAction] = useState<'approve' | 'reject' | null>(null);
|
||||||
const [selectedRequestTitle, setSelectedRequestTitle] = useState<string>('');
|
const [selectedRequestTitle, setSelectedRequestTitle] = useState<string>('');
|
||||||
const [dynamicRequests, setDynamicRequests] = useState<any[]>([]);
|
const [dynamicRequests, setDynamicRequests] = useState<any[]>([]);
|
||||||
@ -1362,7 +1364,7 @@ export default function App() {
|
|||||||
// For other pages, check if user is initiator or approver
|
// For other pages, check if user is initiator or approver
|
||||||
const request = REQUEST_DATABASE[selectedRequestId as keyof typeof REQUEST_DATABASE];
|
const request = REQUEST_DATABASE[selectedRequestId as keyof typeof REQUEST_DATABASE];
|
||||||
const isInitiator = request && request.initiator.name === 'Current User';
|
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'
|
step.approver === 'Current User' || step.status === 'pending'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1423,63 +1425,31 @@ export default function App() {
|
|||||||
case 'dashboard':
|
case 'dashboard':
|
||||||
return <Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />;
|
return <Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />;
|
||||||
case 'open-requests':
|
case 'open-requests':
|
||||||
return <RequestsList type="open" onViewRequest={handleViewRequest} />;
|
return <OpenRequests onViewRequest={handleViewRequest} />;
|
||||||
case 'closed-requests':
|
case 'closed-requests':
|
||||||
return <RequestsList type="closed" onViewRequest={handleViewRequest} />;
|
return <ClosedRequests onViewRequest={handleViewRequest} />;
|
||||||
case 'my-requests':
|
case 'my-requests':
|
||||||
return <MyRequests onViewRequest={handleViewRequest} dynamicRequests={dynamicRequests} />;
|
return <MyRequests onViewRequest={handleViewRequest} dynamicRequests={dynamicRequests} />;
|
||||||
case 'request-detail':
|
case 'request-detail':
|
||||||
// Determine which component to render based on request type
|
// Always render RequestDetail for all request types
|
||||||
// Check static databases first
|
return (
|
||||||
const isClaimRequest = CLAIM_MANAGEMENT_DATABASE[selectedRequestId];
|
<RequestDetail
|
||||||
const isCustomRequest = CUSTOM_REQUEST_DATABASE[selectedRequestId];
|
requestId={selectedRequestId}
|
||||||
|
onBack={handleBack}
|
||||||
// Check dynamic requests
|
onOpenModal={handleOpenModal}
|
||||||
const dynamicRequest = dynamicRequests.find((req: any) => req.id === selectedRequestId);
|
dynamicRequests={dynamicRequests}
|
||||||
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
|
|
||||||
return (
|
|
||||||
<RequestDetail
|
|
||||||
requestId={selectedRequestId}
|
|
||||||
onBack={handleBack}
|
|
||||||
onOpenModal={handleOpenModal}
|
|
||||||
dynamicRequests={dynamicRequests}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Handle legacy requests from old database
|
|
||||||
return (
|
|
||||||
<RequestDetail
|
|
||||||
requestId={selectedRequestId}
|
|
||||||
onBack={handleBack}
|
|
||||||
onOpenModal={handleOpenModal}
|
|
||||||
dynamicRequests={dynamicRequests}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case 'work-notes':
|
case 'work-notes':
|
||||||
return (
|
return (
|
||||||
<WorkNoteView
|
<WorkNoteChat
|
||||||
requestId={selectedRequestId}
|
requestId={selectedRequestId}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'new-request':
|
case 'new-request':
|
||||||
return (
|
return (
|
||||||
<NewRequestWizard
|
<CreateRequest
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onSubmit={handleNewRequestSubmit}
|
onSubmit={handleNewRequestSubmit}
|
||||||
/>
|
/>
|
||||||
@ -1693,13 +1663,13 @@ export default function App() {
|
|||||||
{(currentPage === 'new-request' || currentPage === 'claim-management') ? (
|
{(currentPage === 'new-request' || currentPage === 'claim-management') ? (
|
||||||
renderCurrentPage()
|
renderCurrentPage()
|
||||||
) : (
|
) : (
|
||||||
<Layout
|
<PageLayout
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
onNewRequest={handleNewRequest}
|
onNewRequest={handleNewRequest}
|
||||||
>
|
>
|
||||||
{renderCurrentPage()}
|
{renderCurrentPage()}
|
||||||
</Layout>
|
</PageLayout>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Toaster
|
<Toaster
|
||||||
169
src/components/layout/PageLayout/PageLayout.tsx
Normal file
169
src/components/layout/PageLayout/PageLayout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/components/layout/PageLayout/index.ts
Normal file
1
src/components/layout/PageLayout/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { PageLayout } from './PageLayout';
|
||||||
@ -4,7 +4,7 @@ import { Button } from '../ui/button';
|
|||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
import { User, Eye, AtSign, Plus } from 'lucide-react';
|
import { User, Eye, AtSign, Plus } from 'lucide-react';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface AddUserModalProps {
|
interface AddUserModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -77,7 +77,7 @@ export function ApprovalActionModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||||
<DialogContent className="max-w-2xl">
|
<DialogContent className="max-w-2xl bg-white">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className={`p-2 rounded-lg ${action === 'approve' ? 'bg-green-100' : 'bg-red-100'}`}>
|
<div className={`p-2 rounded-lg ${action === 'approve' ? 'bg-green-100' : 'bg-red-100'}`}>
|
||||||
@ -5,7 +5,7 @@ import { Label } from '../ui/label';
|
|||||||
import { Textarea } from '../ui/textarea';
|
import { Textarea } from '../ui/textarea';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { Upload, FileText, X, CheckCircle, AlertCircle } from 'lucide-react';
|
import { Upload, FileText, X, CheckCircle, AlertCircle } from 'lucide-react';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface DealerDocumentModalProps {
|
interface DealerDocumentModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -6,7 +6,7 @@ import { Input } from '../ui/input';
|
|||||||
import { Textarea } from '../ui/textarea';
|
import { Textarea } from '../ui/textarea';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { CheckCircle, AlertCircle, DollarSign, FileText } from 'lucide-react';
|
import { CheckCircle, AlertCircle, DollarSign, FileText } from 'lucide-react';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface InitiatorVerificationModalProps {
|
interface InitiatorVerificationModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -18,7 +18,7 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
Check
|
Check
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
interface TemplateSelectionModalProps {
|
interface TemplateSelectionModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion@1.2.3";
|
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||||
import { ChevronDownIcon } from "lucide-react@0.487.0";
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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 { cn } from "./utils";
|
||||||
import { buttonVariants } from "./button";
|
import { buttonVariants } from "./button";
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"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({
|
function AspectRatio({
|
||||||
...props
|
...props
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot@1.1.2";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot@1.1.2";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react@0.487.0";
|
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot@1.1.2";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react@0.487.0";
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { DayPicker } from "react-day-picker@8.10.1";
|
import { DayPicker } from "react-day-picker";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
import { buttonVariants } from "./button";
|
import { buttonVariants } from "./button";
|
||||||
@ -3,8 +3,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import useEmblaCarousel, {
|
import useEmblaCarousel, {
|
||||||
type UseEmblaCarouselType,
|
type UseEmblaCarouselType,
|
||||||
} from "embla-carousel-react@8.6.0";
|
} from "embla-carousel-react";
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react@0.487.0";
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as RechartsPrimitive from "recharts@2.15.2";
|
import * as RechartsPrimitive from "recharts";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox@1.1.4";
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
import { CheckIcon } from "lucide-react@0.487.0";
|
import { CheckIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible@1.1.3";
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
||||||
|
|
||||||
function Collapsible({
|
function Collapsible({
|
||||||
...props
|
...props
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Command as CommandPrimitive } from "cmdk@1.1.1";
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
import { SearchIcon } from "lucide-react@0.487.0";
|
import { SearchIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
import {
|
import {
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu@2.2.6";
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog@1.1.6";
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
import { XIcon } from "lucide-react@0.487.0";
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ const DialogContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
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,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Drawer as DrawerPrimitive } from "vaul@1.1.2";
|
import { Drawer as DrawerPrimitive } from "vaul";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu@2.1.6";
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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 { Slot } from "@radix-ui/react-slot@1.1.2";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
@ -11,7 +11,7 @@ import {
|
|||||||
type ControllerProps,
|
type ControllerProps,
|
||||||
type FieldPath,
|
type FieldPath,
|
||||||
type FieldValues,
|
type FieldValues,
|
||||||
} from "react-hook-form@7.55.0";
|
} from "react-hook-form";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
import { Label } from "./label";
|
import { Label } from "./label";
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { OTPInput, OTPInputContext } from "input-otp@1.4.2";
|
import { OTPInput, OTPInputContext } from "input-otp";
|
||||||
import { MinusIcon } from "lucide-react@0.487.0";
|
import { MinusIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -8,8 +8,8 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
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",
|
"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:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"focus-visible:ring-[3px]",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as MenubarPrimitive from "@radix-ui/react-menubar@1.1.6";
|
import * as MenubarPrimitive from "@radix-ui/react-menubar";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu@1.2.5";
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
|
||||||
import { cva } from "class-variance-authority@0.7.1";
|
import { cva } from "class-variance-authority";
|
||||||
import { ChevronDownIcon } from "lucide-react@0.487.0";
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -3,7 +3,7 @@ import {
|
|||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
} from "lucide-react@0.487.0";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
import { Button, buttonVariants } from "./button";
|
import { Button, buttonVariants } from "./button";
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group@1.2.3";
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
||||||
import { CircleIcon } from "lucide-react@0.487.0";
|
import { CircleIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { GripVerticalIcon } from "lucide-react@0.487.0";
|
import { GripVerticalIcon } from "lucide-react";
|
||||||
import * as ResizablePrimitive from "react-resizable-panels@2.1.7";
|
import * as ResizablePrimitive from "react-resizable-panels";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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 {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
} from "lucide-react@0.487.0";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog@1.1.6";
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
import { XIcon } from "lucide-react@0.487.0";
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot@1.1.2";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { VariantProps, cva } from "class-variance-authority@0.7.1";
|
import { VariantProps, cva } from "class-variance-authority";
|
||||||
import { PanelLeftIcon } from "lucide-react@0.487.0";
|
import { PanelLeftIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useIsMobile } from "./use-mobile";
|
import { useIsMobile } from "./use-mobile";
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "next-themes@0.4.6";
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner, ToasterProps } from "sonner@2.0.3";
|
import { Toaster as Sonner, ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme();
|
const { theme = "system" } = useTheme();
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group@1.1.2";
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||||
import { type VariantProps } from "class-variance-authority@0.7.1";
|
import { type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
import { toggleVariants } from "./toggle";
|
import { toggleVariants } from "./toggle";
|
||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as TogglePrimitive from "@radix-ui/react-toggle@1.1.2";
|
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||||
import { cva, type VariantProps } from "class-variance-authority@0.7.1";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "./utils";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
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";
|
import { cn } from "./utils";
|
||||||
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
import { useState, useRef, useEffect, useMemo } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from './ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Avatar, AvatarFallback } from './ui/avatar';
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { ScrollArea } from './ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Separator } from './ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Textarea } from './ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Send,
|
Send,
|
||||||
@ -75,7 +75,7 @@ interface Participant {
|
|||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkNoteViewProps {
|
interface WorkNoteChatProps {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
onBack?: () => void;
|
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 [message, setMessage] = useState('');
|
||||||
const [isTyping, setIsTyping] = useState(false);
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState('chat');
|
const [activeTab, setActiveTab] = useState('chat');
|
||||||
1
src/components/workNote/WorkNoteChat/index.ts
Normal file
1
src/components/workNote/WorkNoteChat/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { WorkNoteChat } from './WorkNoteChat';
|
||||||
@ -1,14 +1,14 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { toast } from 'sonner';
|
||||||
import { DealerDocumentModal } from './modals/DealerDocumentModal';
|
import { DealerDocumentModal } from '@/components/modals/DealerDocumentModal';
|
||||||
import { InitiatorVerificationModal } from './modals/InitiatorVerificationModal';
|
import { InitiatorVerificationModal } from '@/components/modals/InitiatorVerificationModal';
|
||||||
import { Progress } from './ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Avatar, AvatarFallback } from './ui/avatar';
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { CLAIM_MANAGEMENT_DATABASE } from '../utils/claimManagementDatabase';
|
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Clock,
|
Clock,
|
||||||
1
src/components/workflow/ClaimManagementDetail/index.ts
Normal file
1
src/components/workflow/ClaimManagementDetail/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { ClaimManagementDetail } from './ClaimManagementDetail';
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from './ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from './ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from './ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Progress } from './ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Calendar } from './ui/calendar';
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@ -25,8 +25,8 @@ import {
|
|||||||
DollarSign
|
DollarSign
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { toast } from 'sonner';
|
||||||
import { getAllDealers, getDealerInfo, formatDealerAddress, type DealerInfo } from '../utils/dealerDatabase';
|
import { getAllDealers, getDealerInfo, formatDealerAddress, type DealerInfo } from '@/utils/dealerDatabase';
|
||||||
|
|
||||||
interface ClaimManagementWizardProps {
|
interface ClaimManagementWizardProps {
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
1
src/components/workflow/ClaimManagementWizard/index.ts
Normal file
1
src/components/workflow/ClaimManagementWizard/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { ClaimManagementWizard } from './ClaimManagementWizard';
|
||||||
11
src/lib/utils.ts
Normal file
11
src/lib/utils.ts
Normal 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
11
src/main.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
|
||||||
483
src/pages/ClosedRequests/ClosedRequests.tsx
Normal file
483
src/pages/ClosedRequests/ClosedRequests.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
2
src/pages/ClosedRequests/index.ts
Normal file
2
src/pages/ClosedRequests/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { ClosedRequests } from './ClosedRequests';
|
||||||
|
|
||||||
@ -1,25 +1,19 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from './ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from './ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from './ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Avatar, AvatarFallback } from './ui/avatar';
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
import { Progress } from './ui/progress';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Switch } from './ui/switch';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { Calendar } from './ui/calendar';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
|
import { TemplateSelectionModal } from '@/components/modals/TemplateSelectionModal';
|
||||||
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 {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Calendar as CalendarIcon,
|
|
||||||
Upload,
|
Upload,
|
||||||
X,
|
X,
|
||||||
User,
|
User,
|
||||||
@ -27,37 +21,25 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
Check,
|
Check,
|
||||||
Users,
|
Users,
|
||||||
Star,
|
|
||||||
Zap,
|
Zap,
|
||||||
Shield,
|
Shield,
|
||||||
Target,
|
Target,
|
||||||
Flame,
|
Flame,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
Building,
|
|
||||||
DollarSign,
|
DollarSign,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
Info,
|
Info,
|
||||||
Sparkles,
|
|
||||||
Rocket,
|
Rocket,
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
|
||||||
Search,
|
|
||||||
Filter,
|
|
||||||
Download,
|
|
||||||
Globe,
|
|
||||||
Lock,
|
|
||||||
Lightbulb,
|
Lightbulb,
|
||||||
PieChart,
|
|
||||||
BarChart3,
|
|
||||||
Activity,
|
|
||||||
Settings
|
Settings
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
interface NewRequestWizardProps {
|
interface CreateRequestProps {
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
onSubmit?: (requestData: any) => 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 }
|
{ id: '10', name: 'Michael Brown', role: 'CFO', avatar: 'MB', department: 'Finance', email: 'michael.brown@royalenfield.com', level: 5, canClose: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
const USER_LEVELS = [
|
// User levels - keeping for future use
|
||||||
{ level: 1, name: 'Junior Level', description: 'Junior staff and coordinators', color: 'bg-gray-100 text-gray-800' },
|
// const USER_LEVELS = [
|
||||||
{ level: 2, name: 'Mid Level', description: 'Team leads and supervisors', color: 'bg-blue-100 text-blue-800' },
|
// { level: 1, name: 'Junior Level', description: 'Junior staff and coordinators', color: 'bg-gray-100 text-gray-800' },
|
||||||
{ level: 3, name: 'Senior Level', description: 'Managers and senior staff', color: 'bg-green-100 text-green-800' },
|
// { level: 2, name: 'Mid Level', description: 'Team leads and supervisors', color: 'bg-blue-100 text-blue-800' },
|
||||||
{ level: 4, name: 'Executive Level', description: 'Department heads and directors', color: 'bg-orange-100 text-orange-800' },
|
// { level: 3, name: 'Senior Level', description: 'Managers and senior staff', color: 'bg-green-100 text-green-800' },
|
||||||
{ level: 5, name: 'C-Suite Level', description: 'Executive leadership', color: 'bg-purple-100 text-purple-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 = [
|
// SLA Templates - keeping for future use
|
||||||
{ id: 'urgent', name: 'Urgent', hours: 4, description: 'Critical business impact', color: 'bg-red-100 text-red-800' },
|
// const SLA_TEMPLATES = [
|
||||||
{ id: 'high', name: 'High Priority', hours: 24, description: 'High business impact', color: 'bg-orange-100 text-orange-800' },
|
// { id: 'urgent', name: 'Urgent', hours: 4, description: 'Critical business impact', color: 'bg-red-100 text-red-800' },
|
||||||
{ id: 'medium', name: 'Medium Priority', hours: 72, description: 'Moderate business impact', color: 'bg-yellow-100 text-yellow-800' },
|
// { id: 'high', name: 'High Priority', hours: 24, description: 'High business impact', color: 'bg-orange-100 text-orange-800' },
|
||||||
{ id: 'low', name: 'Low Priority', hours: 120, description: 'Low business impact', color: 'bg-green-100 text-green-800' },
|
// { id: 'medium', name: 'Medium Priority', hours: 72, description: 'Moderate business impact', color: 'bg-yellow-100 text-yellow-800' },
|
||||||
{ id: 'custom', name: 'Custom SLA', hours: 0, description: 'Define your own timeline', color: 'bg-blue-100 text-blue-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 = [
|
const STEP_NAMES = [
|
||||||
'Template Selection',
|
'Template Selection',
|
||||||
@ -145,11 +129,9 @@ const STEP_NAMES = [
|
|||||||
'Review & Submit'
|
'Review & Submit'
|
||||||
];
|
];
|
||||||
|
|
||||||
export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
|
export function CreateRequest({ onBack, onSubmit }: CreateRequestProps) {
|
||||||
const [currentStep, setCurrentStep] = useState(1);
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState<RequestTemplate | null>(null);
|
const [selectedTemplate, setSelectedTemplate] = useState<RequestTemplate | null>(null);
|
||||||
const [userSearch, setUserSearch] = useState('');
|
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
||||||
const [emailInput, setEmailInput] = useState('');
|
const [emailInput, setEmailInput] = useState('');
|
||||||
const [showTemplateModal, setShowTemplateModal] = useState(false);
|
const [showTemplateModal, setShowTemplateModal] = useState(false);
|
||||||
const [newUserData, setNewUserData] = useState({
|
const [newUserData, setNewUserData] = useState({
|
||||||
@ -159,7 +141,6 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
|
|||||||
department: '',
|
department: '',
|
||||||
level: 1
|
level: 1
|
||||||
});
|
});
|
||||||
const [showUserInvite, setShowUserInvite] = useState(false);
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
// Template and basic info
|
// Template and basic info
|
||||||
@ -221,23 +202,12 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
|
|||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
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) => {
|
const validateEmail = (email: string) => {
|
||||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
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 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 {
|
return {
|
||||||
id: `temp-${Date.now()}-${Math.random()}`,
|
id: `temp-${Date.now()}-${Math.random()}`,
|
||||||
name: name || email.split('@')[0],
|
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) => {
|
const getPriorityIcon = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'high': return <Flame className="w-4 h-4 text-red-600" />;
|
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
|
// Update max level if adding approver
|
||||||
if (type === 'approvers') {
|
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 removeUser = (userId: string, type: 'approvers' | 'spectators' | 'ccList' | 'invitedUsers') => {
|
||||||
const currentList = formData[type];
|
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
|
// Update max level if removing approver
|
||||||
if (type === 'approvers') {
|
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');
|
addUser(newUser, 'invitedUsers');
|
||||||
setEmailInput('');
|
setEmailInput('');
|
||||||
setNewUserData({ name: '', email: '', role: '', department: '', level: 1 });
|
setNewUserData({ name: '', email: '', role: '', department: '', level: 1 });
|
||||||
setShowUserInvite(false);
|
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const inviteAndAddUser = (type: 'approvers' | 'spectators' | 'ccList') => {
|
const inviteAndAddUser = (type: 'approvers' | 'spectators' | 'ccList') => {
|
||||||
const user = addUserByEmail();
|
const user = addUserByEmail();
|
||||||
if (user && type !== 'invitedUsers') {
|
if (user) {
|
||||||
addUser(user, type);
|
addUser(user, type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -851,7 +812,7 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
|
|||||||
// Ensure approvers array has enough items
|
// Ensure approvers array has enough items
|
||||||
if (!formData.approvers[index]) {
|
if (!formData.approvers[index]) {
|
||||||
const newApprovers = [...formData.approvers];
|
const newApprovers = [...formData.approvers];
|
||||||
newApprovers[index] = { email: '', name: '', level: level, tat: '' };
|
newApprovers[index] = { email: '', name: '', level: level, tat: '' as any };
|
||||||
updateFormData('approvers', newApprovers);
|
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="bg-white border-b border-gray-200 px-6 py-3 flex-shrink-0">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<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 key={index} className="flex items-center">
|
||||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold ${
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-semibold ${
|
||||||
index + 1 < currentStep
|
index + 1 < currentStep
|
||||||
@ -1765,11 +1726,11 @@ export function NewRequestWizard({ onBack, onSubmit }: NewRequestWizardProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:flex justify-between text-xs text-gray-600 mt-2">
|
<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={`${
|
<span key={index} className={`${
|
||||||
index + 1 === currentStep ? 'font-semibold text-blue-600' : ''
|
index + 1 === currentStep ? 'font-semibold text-blue-600' : ''
|
||||||
}`}>
|
}`}>
|
||||||
{stepName}
|
{step}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
1
src/pages/CreateRequest/index.ts
Normal file
1
src/pages/CreateRequest/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { CreateRequest } from './CreateRequest';
|
||||||
@ -1,16 +1,15 @@
|
|||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Progress } from './ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
Clock,
|
Clock,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
Users,
|
|
||||||
Zap,
|
Zap,
|
||||||
Shield,
|
Shield,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
1
src/pages/Dashboard/index.ts
Normal file
1
src/pages/Dashboard/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { Dashboard } from './Dashboard';
|
||||||
@ -1,29 +1,24 @@
|
|||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Button } from './ui/button';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Input } from './ui/input';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Badge } from './ui/badge';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Avatar, AvatarFallback } from './ui/avatar';
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
Search,
|
Search,
|
||||||
Filter,
|
|
||||||
Clock,
|
Clock,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
XCircle,
|
XCircle,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
TrendingUp,
|
|
||||||
Calendar,
|
|
||||||
User,
|
User,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
MoreHorizontal,
|
TrendingUp,
|
||||||
Eye,
|
Eye,
|
||||||
Edit,
|
Edit,
|
||||||
Copy
|
Flame,
|
||||||
|
Target
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
interface MyRequestsProps {
|
interface MyRequestsProps {
|
||||||
onViewRequest: (requestId: string, requestTitle?: string) => void;
|
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 getPriorityConfig = (priority: 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) => {
|
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'express':
|
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':
|
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:
|
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) {
|
export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsProps) {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState('all');
|
const [statusFilter, setStatusFilter] = useState('all');
|
||||||
@ -277,7 +292,7 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters and Search */}
|
{/* Filters and Search */}
|
||||||
<Card>
|
<Card className="border-gray-200">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex flex-col md:flex-row gap-4 items-start md:items-center">
|
<div className="flex flex-col md:flex-row gap-4 items-start md:items-center">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
@ -286,13 +301,13 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
|||||||
placeholder="Search requests by title, description, or ID..."
|
placeholder="Search requests by title, description, or ID..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
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>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
<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" />
|
<SelectValue placeholder="Status" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -306,7 +321,7 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
|
<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" />
|
<SelectValue placeholder="Priority" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -342,70 +357,74 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: index * 0.1 }}
|
transition={{ delay: index * 0.1 }}
|
||||||
>
|
>
|
||||||
<Card className="hover:shadow-lg transition-all duration-200 cursor-pointer group"
|
<Card
|
||||||
onClick={() => onViewRequest(request.id, request.title)}>
|
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">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start gap-4 flex-1">
|
{/* Header with Title and Status Badges */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-start justify-between">
|
||||||
{getStatusIcon(request.status)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-3 mb-2 flex-wrap">
|
<h4 className="text-lg font-semibold text-gray-900 mb-2 group-hover:text-blue-600 transition-colors">
|
||||||
<h3 className="font-semibold text-gray-900 group-hover:text-[--re-green] transition-colors">
|
{request.title}
|
||||||
{request.title}
|
</h4>
|
||||||
</h3>
|
<div className="flex items-center gap-2 mb-2">
|
||||||
{getStatusBadge(request.status)}
|
<Badge
|
||||||
{getPriorityBadge(request.priority)}
|
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 && (
|
{(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" />
|
<FileText className="w-3 h-3 mr-1" />
|
||||||
Template: {(request as any).templateName}
|
Template: {(request as any).templateName}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-gray-600 mb-3">
|
||||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
|
|
||||||
{request.description}
|
{request.description}
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex items-center gap-4 text-sm text-gray-500">
|
||||||
<div className="flex items-center gap-6 text-xs text-gray-500">
|
<span><span className="font-medium">ID:</span> {request.id}</span>
|
||||||
<div className="flex items-center gap-1">
|
<span><span className="font-medium">Submitted:</span> {request.submittedDate ? new Date(request.submittedDate).toLocaleDateString() : 'N/A'}</span>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 ml-4">
|
{/* Current Approver and Level Info */}
|
||||||
<ArrowRight className="w-4 h-4 text-gray-400 group-hover:text-[--re-green] transition-colors" />
|
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
|
||||||
</div>
|
<div className="flex items-center gap-4">
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
|
<User className="w-4 h-4 text-gray-400" />
|
||||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
<span className="text-sm">
|
||||||
<div className="flex items-center gap-4 text-sm">
|
<span className="text-gray-500">Current:</span> <span className="text-gray-900 font-medium">{request.currentApprover}</span>
|
||||||
<div className="flex items-center gap-2">
|
</span>
|
||||||
<User className="w-4 h-4 text-gray-400" />
|
</div>
|
||||||
<span className="text-gray-600">
|
<div className="flex items-center gap-2">
|
||||||
Current: <span className="font-medium text-gray-900">{request.currentApprover}</span>
|
<TrendingUp className="w-4 h-4 text-gray-400" />
|
||||||
|
<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-right">
|
||||||
|
<span className="text-sm text-gray-500">
|
||||||
|
<span className="font-medium">Estimated completion:</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-sm text-gray-600">
|
|
||||||
<span className="font-medium">Estimated completion:</span> {request.estimatedCompletion}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
1
src/pages/MyRequests/index.ts
Normal file
1
src/pages/MyRequests/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { MyRequests } from './MyRequests';
|
||||||
@ -1,31 +1,25 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from './ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
import { Progress } from './ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Clock,
|
Clock,
|
||||||
Filter,
|
Filter,
|
||||||
Search,
|
Search,
|
||||||
User,
|
|
||||||
FileText,
|
FileText,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle,
|
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
SortAsc,
|
SortAsc,
|
||||||
SortDesc,
|
SortDesc,
|
||||||
MoreHorizontal,
|
|
||||||
Flame,
|
Flame,
|
||||||
Target,
|
Target,
|
||||||
Eye,
|
Eye,
|
||||||
Users,
|
|
||||||
TrendingUp,
|
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Download,
|
|
||||||
Settings2,
|
Settings2,
|
||||||
X
|
X
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -34,7 +28,7 @@ interface Request {
|
|||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
status: 'pending' | 'approved' | 'rejected' | 'in-review';
|
status: 'pending' | 'in-review';
|
||||||
priority: 'express' | 'standard';
|
priority: 'express' | 'standard';
|
||||||
initiator: {
|
initiator: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -49,17 +43,15 @@ interface Request {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
dueDate: string;
|
dueDate: string;
|
||||||
approvalStep: string;
|
approvalStep: string;
|
||||||
reason?: string;
|
|
||||||
department?: string;
|
department?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RequestsListProps {
|
interface OpenRequestsProps {
|
||||||
type: 'open' | 'closed';
|
|
||||||
onViewRequest?: (requestId: string, requestTitle?: string) => void;
|
onViewRequest?: (requestId: string, requestTitle?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static data to prevent re-creation on each render
|
// Static data for open requests
|
||||||
const MOCK_REQUESTS: Request[] = [
|
const OPEN_REQUESTS: Request[] = [
|
||||||
{
|
{
|
||||||
id: 'RE-REQ-CM-001',
|
id: 'RE-REQ-CM-001',
|
||||||
title: 'Dealer Marketing Activity Claim - Diwali Festival Campaign',
|
title: 'Dealer Marketing Activity Claim - Diwali Festival Campaign',
|
||||||
@ -74,7 +66,7 @@ const MOCK_REQUESTS: Request[] = [
|
|||||||
dueDate: '2024-10-16',
|
dueDate: '2024-10-16',
|
||||||
approvalStep: 'Initiator Review & Confirmation',
|
approvalStep: 'Initiator Review & Confirmation',
|
||||||
department: 'Marketing - West Zone'
|
department: 'Marketing - West Zone'
|
||||||
} as any,
|
},
|
||||||
{
|
{
|
||||||
id: 'RE-REQ-001',
|
id: 'RE-REQ-001',
|
||||||
title: 'Marketing Campaign Budget Approval',
|
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
|
// Utility functions
|
||||||
const getPriorityConfig = (priority: string) => {
|
const getPriorityConfig = (priority: string) => {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
@ -214,18 +178,6 @@ const getStatusConfig = (status: string) => {
|
|||||||
icon: Eye,
|
icon: Eye,
|
||||||
iconColor: 'text-blue-600'
|
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:
|
default:
|
||||||
return {
|
return {
|
||||||
color: 'bg-gray-100 text-gray-800 border-gray-200',
|
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' };
|
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 [searchTerm, setSearchTerm] = useState('');
|
||||||
const [priorityFilter, setPriorityFilter] = useState('all');
|
const [priorityFilter, setPriorityFilter] = useState('all');
|
||||||
const [statusFilter, setStatusFilter] = useState('all');
|
const [statusFilter, setStatusFilter] = useState('all');
|
||||||
|
|
||||||
const [sortBy, setSortBy] = useState<'created' | 'due' | 'priority' | 'sla'>('due');
|
const [sortBy, setSortBy] = useState<'created' | 'due' | 'priority' | 'sla'>('due');
|
||||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||||
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
|
const [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
|
||||||
|
|
||||||
const sourceRequests = type === 'open' ? MOCK_REQUESTS : CLOSED_REQUESTS;
|
|
||||||
|
|
||||||
const filteredAndSortedRequests = useMemo(() => {
|
const filteredAndSortedRequests = useMemo(() => {
|
||||||
let filtered = sourceRequests.filter(request => {
|
let filtered = OPEN_REQUESTS.filter(request => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
request.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
request.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
request.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
request.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
@ -299,7 +248,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
}, [sourceRequests, searchTerm, priorityFilter, statusFilter, sortBy, sortOrder]);
|
}, [searchTerm, priorityFilter, statusFilter, sortBy, sortOrder]);
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
@ -323,28 +272,20 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
<FileText className="w-6 h-6 text-white" />
|
<FileText className="w-6 h-6 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">
|
<h1 className="text-3xl font-bold text-gray-900">Open Requests</h1>
|
||||||
{type === 'open' ? 'Open Requests' : 'Closed Requests'}
|
<p className="text-gray-600">Manage and track active approval requests</p>
|
||||||
</h1>
|
|
||||||
<p className="text-gray-600">
|
|
||||||
{type === 'open'
|
|
||||||
? 'Manage and track active approval requests'
|
|
||||||
: 'Review completed and archived requests'
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<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">
|
<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>
|
</Badge>
|
||||||
<Button variant="outline" size="sm" className="gap-2">
|
<Button variant="outline" size="sm" className="gap-2">
|
||||||
<RefreshCw className="w-4 h-4" />
|
<RefreshCw className="w-4 h-4" />
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -421,17 +362,8 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Statuses</SelectItem>
|
<SelectItem value="all">All Statuses</SelectItem>
|
||||||
{type === 'open' ? (
|
<SelectItem value="pending">⏳ Pending</SelectItem>
|
||||||
<>
|
<SelectItem value="in-review">👁️ In Review</SelectItem>
|
||||||
<SelectItem value="pending">⏳ Pending</SelectItem>
|
|
||||||
<SelectItem value="in-review">👁️ In Review</SelectItem>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<SelectItem value="approved">✅ Approved</SelectItem>
|
|
||||||
<SelectItem value="rejected">❌ Rejected</SelectItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
@ -444,7 +376,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
<SelectItem value="due">Due Date</SelectItem>
|
<SelectItem value="due">Due Date</SelectItem>
|
||||||
<SelectItem value="created">Date Created</SelectItem>
|
<SelectItem value="created">Date Created</SelectItem>
|
||||||
<SelectItem value="priority">Priority</SelectItem>
|
<SelectItem value="priority">Priority</SelectItem>
|
||||||
{type === 'open' && <SelectItem value="sla">SLA Progress</SelectItem>}
|
<SelectItem value="sla">SLA Progress</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
@ -458,8 +390,6 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@ -526,42 +456,36 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SLA Progress for Open Requests */}
|
{/* SLA Progress */}
|
||||||
{type === 'open' && (
|
<div className="bg-gray-50 rounded-lg p-4">
|
||||||
<div className="bg-gray-50 rounded-lg p-4">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<Clock className="w-4 h-4 text-gray-500" />
|
||||||
<Clock className="w-4 h-4 text-gray-500" />
|
<span className="text-sm font-medium text-gray-700">SLA Progress</span>
|
||||||
<span className="text-sm font-medium text-gray-700">SLA Progress</span>
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<span className={`text-sm font-semibold ${slaConfig.textColor}`}>
|
||||||
<span className={`text-sm font-semibold ${slaConfig.textColor}`}>
|
{request.slaRemaining} remaining
|
||||||
{request.slaRemaining} remaining
|
</span>
|
||||||
</span>
|
{slaConfig.urgency === 'critical' && (
|
||||||
{slaConfig.urgency === 'critical' && (
|
<Badge variant="destructive" className="animate-pulse text-xs">
|
||||||
<Badge variant="destructive" className="animate-pulse text-xs">
|
URGENT
|
||||||
URGENT
|
</Badge>
|
||||||
</Badge>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
|
||||||
value={request.slaProgress}
|
|
||||||
className="h-3 bg-gray-200"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<Progress
|
||||||
|
value={request.slaProgress}
|
||||||
|
className="h-3 bg-gray-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Status Info */}
|
{/* Status Info */}
|
||||||
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
|
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{type === 'open' ? (
|
<AlertCircle className="w-4 h-4 text-blue-500" />
|
||||||
<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">
|
<span className="text-sm text-gray-700 font-medium">
|
||||||
{type === 'open' ? request.approvalStep : request.reason}
|
{request.approvalStep}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -581,7 +505,7 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{type === 'open' && request.currentApprover && (
|
{request.currentApprover && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Avatar className="h-8 w-8 ring-2 ring-yellow-200 shadow-sm">
|
<Avatar className="h-8 w-8 ring-2 ring-yellow-200 shadow-sm">
|
||||||
<AvatarFallback className="bg-yellow-500 text-white text-sm font-semibold">
|
<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">
|
<p className="text-gray-600 text-center max-w-md">
|
||||||
{searchTerm || activeFiltersCount > 0
|
{searchTerm || activeFiltersCount > 0
|
||||||
? 'Try adjusting your filters or search terms to see more results.'
|
? '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>
|
</p>
|
||||||
{activeFiltersCount > 0 && (
|
{activeFiltersCount > 0 && (
|
||||||
@ -642,4 +566,5 @@ export function RequestsList({ type, onViewRequest }: RequestsListProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
2
src/pages/OpenRequests/index.ts
Normal file
2
src/pages/OpenRequests/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { OpenRequests } from './OpenRequests';
|
||||||
|
|
||||||
@ -1,25 +1,22 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Button } from './ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { toast } from 'sonner@2.0.3';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Progress } from './ui/progress';
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
import { Avatar, AvatarFallback } from './ui/avatar';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
|
||||||
import { CUSTOM_REQUEST_DATABASE } from '../utils/customRequestDatabase';
|
import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Clock,
|
Clock,
|
||||||
User,
|
User,
|
||||||
FileText,
|
FileText,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Users,
|
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
XCircle,
|
XCircle,
|
||||||
Download,
|
Download,
|
||||||
Eye,
|
Eye,
|
||||||
Flame,
|
|
||||||
Target,
|
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Activity,
|
Activity,
|
||||||
@ -27,7 +24,6 @@ import {
|
|||||||
Phone,
|
Phone,
|
||||||
Upload,
|
Upload,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
Edit3,
|
|
||||||
ClipboardList
|
ClipboardList
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
@ -158,11 +154,15 @@ export function RequestDetail({
|
|||||||
}: RequestDetailProps) {
|
}: RequestDetailProps) {
|
||||||
const [activeTab, setActiveTab] = useState('overview');
|
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(() => {
|
const request = useMemo(() => {
|
||||||
// First check static database
|
// First check custom request database
|
||||||
const staticRequest = CUSTOM_REQUEST_DATABASE[requestId];
|
const customRequest = CUSTOM_REQUEST_DATABASE[requestId];
|
||||||
if (staticRequest) return staticRequest;
|
if (customRequest) return customRequest;
|
||||||
|
|
||||||
|
// Then check claim management database
|
||||||
|
const claimRequest = CLAIM_MANAGEMENT_DATABASE[requestId];
|
||||||
|
if (claimRequest) return claimRequest;
|
||||||
|
|
||||||
// Then check dynamic requests
|
// Then check dynamic requests
|
||||||
const dynamicRequest = dynamicRequests.find((req: any) => req.id === requestId);
|
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="flex items-center justify-center h-screen">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">Request Not Found</h2>
|
<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}>
|
<Button onClick={onBack}>
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
Go Back
|
Go Back
|
||||||
@ -194,9 +194,9 @@ export function RequestDetail({
|
|||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<div className="max-w-7xl mx-auto p-6">
|
<div className="max-w-7xl mx-auto p-6">
|
||||||
{/* Header Section */}
|
{/* 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 */}
|
{/* 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 justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button
|
<Button
|
||||||
@ -256,7 +256,7 @@ export function RequestDetail({
|
|||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
<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">
|
<TabsTrigger value="overview" className="gap-2">
|
||||||
<ClipboardList className="w-4 h-4" />
|
<ClipboardList className="w-4 h-4" />
|
||||||
Overview
|
Overview
|
||||||
@ -326,8 +326,8 @@ export function RequestDetail({
|
|||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-gray-700 block mb-2">Description</label>
|
<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">
|
<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">
|
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">
|
||||||
{request.description}
|
{request.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -335,7 +335,7 @@ export function RequestDetail({
|
|||||||
|
|
||||||
{/* Additional Details */}
|
{/* Additional Details */}
|
||||||
{(request.category || request.subcategory) && (
|
{(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 && (
|
{request.category && (
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Category</label>
|
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Category</label>
|
||||||
@ -352,13 +352,13 @@ export function RequestDetail({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{request.amount && (
|
{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>
|
<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>
|
<p className="text-lg font-bold text-gray-900 mt-1">{request.amount}</p>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<label className="text-xs font-medium text-gray-500 uppercase tracking-wide">Created</label>
|
<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>
|
<p className="text-sm text-gray-900 font-medium mt-1">{request.createdAt}</p>
|
||||||
@ -370,6 +370,55 @@ export function RequestDetail({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Right Column - Quick Actions Sidebar (1/3 width) */}
|
{/* Right Column - Quick Actions Sidebar (1/3 width) */}
|
||||||
@ -381,7 +430,6 @@ export function RequestDetail({
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2">
|
<CardContent className="space-y-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
|
||||||
className="w-full justify-start gap-2 bg-[#1a472a] text-white hover:bg-[#152e1f] hover:text-white border-0"
|
className="w-full justify-start gap-2 bg-[#1a472a] text-white hover:bg-[#152e1f] hover:text-white border-0"
|
||||||
onClick={() => onOpenModal?.('work-note')}
|
onClick={() => onOpenModal?.('work-note')}
|
||||||
>
|
>
|
||||||
@ -390,7 +438,7 @@ export function RequestDetail({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
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')}
|
onClick={() => onOpenModal?.('add-approver')}
|
||||||
>
|
>
|
||||||
<UserPlus className="w-4 h-4" />
|
<UserPlus className="w-4 h-4" />
|
||||||
@ -398,7 +446,7 @@ export function RequestDetail({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
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')}
|
onClick={() => onOpenModal?.('add-spectator')}
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
@ -537,8 +585,8 @@ export function RequestDetail({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{step.comment && (
|
{step.comment && (
|
||||||
<div className="mt-3 p-3 bg-white rounded-lg border border-gray-200">
|
<div className="mt-3 p-3 bg-white rounded-lg border border-gray-300">
|
||||||
<p className="text-sm text-gray-700">{step.comment}</p>
|
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">{step.comment}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -581,7 +629,7 @@ export function RequestDetail({
|
|||||||
{request.documents.map((doc: any, index: number) => (
|
{request.documents.map((doc: any, index: number) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
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="flex items-center gap-3">
|
||||||
<div className="p-2 bg-blue-100 rounded-lg">
|
<div className="p-2 bg-blue-100 rounded-lg">
|
||||||
@ -625,20 +673,39 @@ export function RequestDetail({
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
{request.auditTrail && request.auditTrail.length > 0 ? request.auditTrail.map((entry: any, index: number) => (
|
{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 key={index} className="flex items-start gap-4">
|
||||||
<div className="mt-1">
|
{/* Avatar */}
|
||||||
{getActionTypeIcon(entry.type)}
|
<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>
|
</div>
|
||||||
|
|
||||||
|
{/* Message Content */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="bg-white rounded-lg border border-gray-300 p-4 shadow-sm">
|
||||||
<div>
|
{/* Header with user info and timestamp */}
|
||||||
<p className="font-medium text-gray-900">{entry.action}</p>
|
<div className="flex items-center justify-between mb-3">
|
||||||
<p className="text-sm text-gray-600 mt-1">{entry.details}</p>
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-xs text-gray-500 mt-1">by {entry.user}</p>
|
<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>
|
||||||
<span className="text-xs text-gray-500 whitespace-nowrap">{entry.timestamp}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
1
src/pages/RequestDetail/index.ts
Normal file
1
src/pages/RequestDetail/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { RequestDetail } from './RequestDetail';
|
||||||
6
src/redux/hooks.ts
Normal file
6
src/redux/hooks.ts
Normal 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;
|
||||||
69
src/redux/slices/authSlice.ts
Normal file
69
src/redux/slices/authSlice.ts
Normal 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
32
src/redux/store.ts
Normal 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
611
src/styles/globals.css
Normal 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
173
src/types/index.ts
Normal 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
Loading…
Reference in New Issue
Block a user