dealer access to the create request disabled
This commit is contained in:
parent
7d3b6a9da2
commit
7893b52183
@ -19,6 +19,7 @@ import { ReLogo } from '@/assets';
|
|||||||
import notificationApi, { Notification } from '@/services/notificationApi';
|
import notificationApi, { Notification } from '@/services/notificationApi';
|
||||||
import { getSocket, joinUserRoom } from '@/utils/socket';
|
import { getSocket, joinUserRoom } from '@/utils/socket';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
|
import { TokenManager } from '@/utils/tokenManager';
|
||||||
|
|
||||||
interface PageLayoutProps {
|
interface PageLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -36,6 +37,17 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
const [notificationsOpen, setNotificationsOpen] = useState(false);
|
const [notificationsOpen, setNotificationsOpen] = useState(false);
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
// Check if user is a Dealer
|
||||||
|
const isDealer = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const userData = TokenManager.getUserData();
|
||||||
|
return userData?.jobTitle === 'Dealer';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[PageLayout] Error checking dealer status:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Get user initials for avatar
|
// Get user initials for avatar
|
||||||
const getUserInitials = () => {
|
const getUserInitials = () => {
|
||||||
try {
|
try {
|
||||||
@ -63,16 +75,19 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
{ id: 'requests', label: 'All Requests', icon: List },
|
{ id: 'requests', label: 'All Requests', icon: List },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add remaining menu items
|
// Add remaining menu items (exclude "My Requests" for dealers)
|
||||||
|
if (!isDealer) {
|
||||||
|
items.push({ id: 'my-requests', label: 'My Requests', icon: User });
|
||||||
|
}
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
{ id: 'my-requests', label: 'My Requests', icon: User },
|
|
||||||
{ id: 'open-requests', label: 'Open Requests', icon: FileText },
|
{ id: 'open-requests', label: 'Open Requests', icon: FileText },
|
||||||
{ id: 'closed-requests', label: 'Closed Requests', icon: CheckCircle },
|
{ id: 'closed-requests', label: 'Closed Requests', icon: CheckCircle },
|
||||||
{ id: 'shared-summaries', label: 'Shared Summary', icon: Share2 }
|
{ id: 'shared-summaries', label: 'Shared Summary', icon: Share2 }
|
||||||
);
|
);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}, []);
|
}, [isDealer]);
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
setSidebarOpen(!sidebarOpen);
|
setSidebarOpen(!sidebarOpen);
|
||||||
@ -256,6 +271,7 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Action in Sidebar - Right below menu items */}
|
{/* Quick Action in Sidebar - Right below menu items */}
|
||||||
|
{!isDealer && (
|
||||||
<div className="mt-6 pt-6 border-t border-gray-800 px-3">
|
<div className="mt-6 pt-6 border-t border-gray-800 px-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={onNewRequest}
|
onClick={onNewRequest}
|
||||||
@ -266,6 +282,7 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
Raise New Request
|
Raise New Request
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@ -294,6 +311,7 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4 shrink-0">
|
<div className="flex items-center gap-4 shrink-0">
|
||||||
|
{!isDealer && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onNewRequest}
|
onClick={onNewRequest}
|
||||||
className="bg-re-green hover:bg-re-green/90 text-white gap-2 hidden md:flex text-sm"
|
className="bg-re-green hover:bg-re-green/90 text-white gap-2 hidden md:flex text-sm"
|
||||||
@ -302,6 +320,7 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
New Request
|
New Request
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<DropdownMenu open={notificationsOpen} onOpenChange={setNotificationsOpen}>
|
<DropdownMenu open={notificationsOpen} onOpenChange={setNotificationsOpen}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Check, Clock, Users, Info, Flame, Target, TrendingUp } from 'lucide-react';
|
import { Check, Clock, Users, Flame, Target, TrendingUp } from 'lucide-react';
|
||||||
import { RequestTemplate } from '@/hooks/useCreateRequestForm';
|
import { RequestTemplate } from '@/hooks/useCreateRequestForm';
|
||||||
|
|
||||||
interface TemplateSelectionStepProps {
|
interface TemplateSelectionStepProps {
|
||||||
@ -142,63 +141,6 @@ export function TemplateSelectionStep({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Template Details Card */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{selectedTemplate && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20, height: 0 }}
|
|
||||||
animate={{ opacity: 1, y: 0, height: 'auto' }}
|
|
||||||
exit={{ opacity: 0, y: -20, height: 0 }}
|
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
className="w-full max-w-6xl"
|
|
||||||
data-testid="template-details-card"
|
|
||||||
>
|
|
||||||
<Card className="bg-gradient-to-br from-blue-50 to-indigo-50 border-2 border-blue-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2 text-blue-900" data-testid="template-details-title">
|
|
||||||
<Info className="w-5 h-5" />
|
|
||||||
{selectedTemplate.name} - Template Details
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
||||||
<div className="bg-white/60 p-3 rounded-lg" data-testid="template-details-sla">
|
|
||||||
<Label className="text-blue-900 font-semibold">Suggested SLA</Label>
|
|
||||||
<p className="text-blue-700 mt-1">{selectedTemplate.suggestedSLA} days</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/60 p-3 rounded-lg" data-testid="template-details-priority">
|
|
||||||
<Label className="text-blue-900 font-semibold">Priority Level</Label>
|
|
||||||
<div className="flex items-center gap-1 mt-1">
|
|
||||||
{getPriorityIcon(selectedTemplate.priority)}
|
|
||||||
<span className="text-blue-700 capitalize">{selectedTemplate.priority}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/60 p-3 rounded-lg" data-testid="template-details-duration">
|
|
||||||
<Label className="text-blue-900 font-semibold">Estimated Duration</Label>
|
|
||||||
<p className="text-blue-700 mt-1">{selectedTemplate.estimatedTime}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/60 p-3 rounded-lg" data-testid="template-details-approvers">
|
|
||||||
<Label className="text-blue-900 font-semibold">Common Approvers</Label>
|
|
||||||
<div className="flex flex-wrap gap-2 mt-2">
|
|
||||||
{selectedTemplate.commonApprovers.map((approver, index) => (
|
|
||||||
<Badge
|
|
||||||
key={`${selectedTemplate.id}-approver-${index}-${approver}`}
|
|
||||||
variant="outline"
|
|
||||||
className="border-blue-300 text-blue-700 bg-white"
|
|
||||||
data-testid={`template-details-approver-${index}`}
|
|
||||||
>
|
|
||||||
{approver}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -251,7 +251,7 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
|
|||||||
|
|
||||||
if (!verifiedDealer.isLoggedIn) {
|
if (!verifiedDealer.isLoggedIn) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`Dealer "${verifiedDealer.dealerName || verifiedDealer.displayName}" (${verifiedDealer.dealerCode}) has not logged in to the system. Please ask them to log in first.`,
|
`Dealer "${verifiedDealer.dealerName || verifiedDealer.displayName}" (${verifiedDealer.dealerCode}) is not mapped to the system.`,
|
||||||
{ duration: 5000 }
|
{ duration: 5000 }
|
||||||
);
|
);
|
||||||
// Clear the selection
|
// Clear the selection
|
||||||
@ -277,9 +277,9 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
|
|||||||
setDealerSearchInput(verifiedDealer.dealerName || verifiedDealer.displayName);
|
setDealerSearchInput(verifiedDealer.dealerName || verifiedDealer.displayName);
|
||||||
setDealerSearchResults([]);
|
setDealerSearchResults([]);
|
||||||
|
|
||||||
toast.success(`Dealer "${verifiedDealer.dealerName || verifiedDealer.displayName}" verified and logged in`);
|
toast.success(`Dealer "${verifiedDealer.dealerName || verifiedDealer.displayName}" verified and mapped to the System`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error.message || 'Failed to verify dealer login';
|
const errorMessage = 'Dealer is not mapped to the system'
|
||||||
toast.error(errorMessage, { duration: 5000 });
|
toast.error(errorMessage, { duration: 5000 });
|
||||||
// Clear the selection
|
// Clear the selection
|
||||||
setDealerSearchInput('');
|
setDealerSearchInput('');
|
||||||
@ -537,18 +537,6 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex items-center gap-2 text-xs text-gray-500">
|
|
||||||
<span>Status:</span>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<CheckCircle className="w-3 h-3 text-green-600" />
|
|
||||||
<span>Logged in</span>
|
|
||||||
</div>
|
|
||||||
<span className="mx-1">•</span>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<XCircle className="w-3 h-3 text-red-500" />
|
|
||||||
<span>Not logged in</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{formData.dealerCode && (
|
{formData.dealerCode && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
|
|||||||
@ -395,10 +395,18 @@ export function DealerClaimWorkflowTab({
|
|||||||
return `Step ${stepNumber} approval required.`;
|
return `Step ${stepNumber} approval required.`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get backend currentLevel to determine which steps are active vs waiting
|
||||||
|
// This needs to be calculated before mapping steps so we can use it in status normalization
|
||||||
|
// Convert to number to ensure proper comparison
|
||||||
|
const backendCurrentLevel = request?.currentLevel || request?.current_level || request?.currentStep;
|
||||||
|
const currentLevelNumber = backendCurrentLevel !== undefined && backendCurrentLevel !== null
|
||||||
|
? Number(backendCurrentLevel)
|
||||||
|
: null;
|
||||||
|
|
||||||
// Transform approval flow to dealer claim workflow steps
|
// Transform approval flow to dealer claim workflow steps
|
||||||
const workflowSteps: WorkflowStep[] = approvalFlow.map((step: any, index: number) => {
|
const workflowSteps: WorkflowStep[] = approvalFlow.map((step: any, index: number) => {
|
||||||
// Get actual step number from levelNumber or step field
|
// Get actual step number from levelNumber or step field - ensure it's a number
|
||||||
const actualStepNumber = step.levelNumber || step.level_number || step.step || index + 1;
|
const actualStepNumber = Number(step.levelNumber || step.level_number || step.step || index + 1);
|
||||||
|
|
||||||
// Get levelName from the approval level if available
|
// Get levelName from the approval level if available
|
||||||
const levelName = step.levelName || step.level_name;
|
const levelName = step.levelName || step.level_name;
|
||||||
@ -467,26 +475,75 @@ export function DealerClaimWorkflowTab({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize status - handle "in-review" and other variations
|
// Normalize status - ALWAYS check step position first, then use approval/step status
|
||||||
// Check both step.status and approval.status (approval status is more accurate after approval)
|
// This ensures future steps are always 'waiting' regardless of approval/step status
|
||||||
const stepStatus = step.status || approval?.status || 'waiting';
|
let normalizedStatus: string;
|
||||||
let normalizedStatus = stepStatus.toLowerCase();
|
|
||||||
|
|
||||||
// Handle status variations
|
// First, check step position relative to currentLevel (this is the source of truth)
|
||||||
if (normalizedStatus === 'in-review' || normalizedStatus === 'in_review' || normalizedStatus === 'in review') {
|
// Use currentLevelNumber which is guaranteed to be a number or null
|
||||||
normalizedStatus = 'in_progress';
|
if (currentLevelNumber !== null && currentLevelNumber > 0) {
|
||||||
}
|
if (actualStepNumber > currentLevelNumber) {
|
||||||
|
// Future step - MUST be waiting (ignore approval status and step.status)
|
||||||
// If approval exists and has a status, prefer that (it's more up-to-date)
|
normalizedStatus = 'waiting';
|
||||||
|
} else if (actualStepNumber < currentLevelNumber) {
|
||||||
|
// Past step - should be approved/rejected
|
||||||
|
// Use approval status if available, otherwise default to waiting
|
||||||
|
if (approval?.status) {
|
||||||
|
const approvalStatus = approval.status.toLowerCase();
|
||||||
|
if (approvalStatus === 'approved') {
|
||||||
|
normalizedStatus = 'approved';
|
||||||
|
} else if (approvalStatus === 'rejected') {
|
||||||
|
normalizedStatus = 'rejected';
|
||||||
|
} else {
|
||||||
|
normalizedStatus = 'waiting'; // Past step with unexpected status
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
normalizedStatus = 'waiting'; // Past step with no approval
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Current step - use approval status if available, otherwise use step.status
|
||||||
if (approval?.status) {
|
if (approval?.status) {
|
||||||
const approvalStatus = approval.status.toLowerCase();
|
const approvalStatus = approval.status.toLowerCase();
|
||||||
// Map backend status values to frontend status values
|
|
||||||
if (approvalStatus === 'approved') {
|
if (approvalStatus === 'approved') {
|
||||||
normalizedStatus = 'approved';
|
normalizedStatus = 'approved';
|
||||||
} else if (approvalStatus === 'rejected') {
|
} else if (approvalStatus === 'rejected') {
|
||||||
normalizedStatus = 'rejected';
|
normalizedStatus = 'rejected';
|
||||||
} else if (approvalStatus === 'pending' || approvalStatus === 'in_progress' || approvalStatus === 'in-progress') {
|
} else if (approvalStatus === 'pending' || approvalStatus === 'in_progress' || approvalStatus === 'in-progress') {
|
||||||
normalizedStatus = 'in_progress';
|
normalizedStatus = 'in_progress';
|
||||||
|
} else {
|
||||||
|
normalizedStatus = 'in_progress'; // Default for current step
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No approval object - normalize step.status
|
||||||
|
const stepStatus = (step.status || 'pending').toLowerCase();
|
||||||
|
if (stepStatus === 'in-review' || stepStatus === 'in_review' || stepStatus === 'in review' || stepStatus === 'pending') {
|
||||||
|
normalizedStatus = 'in_progress';
|
||||||
|
} else {
|
||||||
|
normalizedStatus = stepStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No backend currentLevel - use approval status if available, otherwise step.status
|
||||||
|
if (approval?.status) {
|
||||||
|
const approvalStatus = approval.status.toLowerCase();
|
||||||
|
if (approvalStatus === 'approved') {
|
||||||
|
normalizedStatus = 'approved';
|
||||||
|
} else if (approvalStatus === 'rejected') {
|
||||||
|
normalizedStatus = 'rejected';
|
||||||
|
} else if (approvalStatus === 'pending' || approvalStatus === 'in_progress' || approvalStatus === 'in-progress') {
|
||||||
|
normalizedStatus = 'in_progress';
|
||||||
|
} else {
|
||||||
|
normalizedStatus = (step.status || 'waiting').toLowerCase();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No approval and no currentLevel - normalize step.status
|
||||||
|
const stepStatus = (step.status || 'waiting').toLowerCase();
|
||||||
|
if (stepStatus === 'in-review' || stepStatus === 'in_review' || stepStatus === 'in review' || stepStatus === 'pending') {
|
||||||
|
normalizedStatus = 'in_progress';
|
||||||
|
} else {
|
||||||
|
normalizedStatus = stepStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,10 +579,12 @@ export function DealerClaimWorkflowTab({
|
|||||||
// IMPORTANT: Use the workflow's currentLevel from backend (most accurate)
|
// IMPORTANT: Use the workflow's currentLevel from backend (most accurate)
|
||||||
// Fallback to finding first pending step if currentLevel not available
|
// Fallback to finding first pending step if currentLevel not available
|
||||||
// Note: Status normalization already handled in workflowSteps mapping above
|
// Note: Status normalization already handled in workflowSteps mapping above
|
||||||
const backendCurrentLevel = request?.currentLevel || request?.current_level || request?.currentStep;
|
// backendCurrentLevel is already calculated above before the map function
|
||||||
|
|
||||||
// Find the step that matches backend's currentLevel
|
// Find the step that matches backend's currentLevel
|
||||||
const activeStepFromBackend = workflowSteps.find(s => s.step === backendCurrentLevel);
|
const activeStepFromBackend = currentLevelNumber !== null
|
||||||
|
? workflowSteps.find(s => s.step === currentLevelNumber)
|
||||||
|
: null;
|
||||||
|
|
||||||
// If backend currentLevel exists and step is pending/in_progress, use it
|
// If backend currentLevel exists and step is pending/in_progress, use it
|
||||||
// Otherwise, find first pending/in_progress step
|
// Otherwise, find first pending/in_progress step
|
||||||
@ -537,7 +596,7 @@ export function DealerClaimWorkflowTab({
|
|||||||
return status === 'pending' || status === 'in_progress' || status === 'in-review' || status === 'in_review';
|
return status === 'pending' || status === 'in_progress' || status === 'in-review' || status === 'in_review';
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentStep = activeStep ? activeStep.step : (backendCurrentLevel || request?.currentStep || 1);
|
const currentStep = activeStep ? activeStep.step : (currentLevelNumber || request?.currentStep || 1);
|
||||||
|
|
||||||
// Check if current user is the dealer (for steps 1 and 5)
|
// Check if current user is the dealer (for steps 1 and 5)
|
||||||
const userEmail = (user as any)?.email?.toLowerCase() || '';
|
const userEmail = (user as any)?.email?.toLowerCase() || '';
|
||||||
|
|||||||
@ -89,7 +89,7 @@ export function Auth() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<LogIn className="mr-2 h-5 w-5" />
|
<LogIn className="mr-2 h-5 w-5" />
|
||||||
Login with OKTA
|
RE Employee Login
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@ -119,7 +119,7 @@ export function Auth() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Shield className="mr-2 h-5 w-5" />
|
<Shield className="mr-2 h-5 w-5" />
|
||||||
Login with Tanflow
|
Dealer Login
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { type DateRange } from '@/services/dashboard.service';
|
|||||||
import { useAuth, hasManagementAccess } from '@/contexts/AuthContext';
|
import { useAuth, hasManagementAccess } from '@/contexts/AuthContext';
|
||||||
import { useAppSelector, useAppDispatch } from '@/redux/hooks';
|
import { useAppSelector, useAppDispatch } from '@/redux/hooks';
|
||||||
import { setViewAsUser } from './redux/dashboardSlice';
|
import { setViewAsUser } from './redux/dashboardSlice';
|
||||||
|
import { TokenManager } from '@/utils/tokenManager';
|
||||||
|
|
||||||
// Custom Hooks
|
// Custom Hooks
|
||||||
import { useDashboardFilters } from './hooks/useDashboardFilters';
|
import { useDashboardFilters } from './hooks/useDashboardFilters';
|
||||||
@ -161,8 +162,19 @@ export function Dashboard({ onNavigate, onNewRequest }: DashboardProps) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dateRange, customStartDate, customEndDate, viewAsUser]);
|
}, [dateRange, customStartDate, customEndDate, viewAsUser]);
|
||||||
|
|
||||||
|
// Check if user is a Dealer
|
||||||
|
const isDealer = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const userData = TokenManager.getUserData();
|
||||||
|
return userData?.jobTitle === 'Dealer';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Dashboard] Error checking dealer status:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Quick actions
|
// Quick actions
|
||||||
const quickActions = useMemo(() => getQuickActions(isAdmin, onNewRequest, onNavigate), [isAdmin, onNewRequest, onNavigate]);
|
const quickActions = useMemo(() => getQuickActions(isAdmin, onNewRequest, onNavigate, isDealer), [isAdmin, onNewRequest, onNavigate, isDealer]);
|
||||||
|
|
||||||
// KPI click handler
|
// KPI click handler
|
||||||
const handleKPIClick = useCallback((filters: {
|
const handleKPIClick = useCallback((filters: {
|
||||||
|
|||||||
@ -50,16 +50,23 @@ export interface QuickAction {
|
|||||||
export function getQuickActions(
|
export function getQuickActions(
|
||||||
isAdmin: boolean,
|
isAdmin: boolean,
|
||||||
onNewRequest?: () => void,
|
onNewRequest?: () => void,
|
||||||
onNavigate?: (page: string) => void
|
onNavigate?: (page: string) => void,
|
||||||
|
isDealer?: boolean
|
||||||
): QuickAction[] {
|
): QuickAction[] {
|
||||||
const actions: QuickAction[] = [
|
const actions: QuickAction[] = [];
|
||||||
{ label: 'New Request', icon: FileText, action: () => onNewRequest?.(), color: 'bg-emerald-600 hover:bg-emerald-700' },
|
|
||||||
|
// Only add "New Request" action if user is not a dealer
|
||||||
|
if (!isDealer) {
|
||||||
|
actions.push({ label: 'New Request', icon: FileText, action: () => onNewRequest?.(), color: 'bg-emerald-600 hover:bg-emerald-700' });
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.push(
|
||||||
{ label: 'View Pending', icon: Clock, action: () => onNavigate?.('open-requests'), color: 'bg-blue-600 hover:bg-blue-700' },
|
{ label: 'View Pending', icon: Clock, action: () => onNavigate?.('open-requests'), color: 'bg-blue-600 hover:bg-blue-700' },
|
||||||
{ label: 'Settings', icon: Settings, action: () => onNavigate?.('settings'), color: 'bg-slate-600 hover:bg-slate-700' }
|
{ label: 'Settings', icon: Settings, action: () => onNavigate?.('settings'), color: 'bg-slate-600 hover:bg-slate-700' }
|
||||||
];
|
);
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
actions.splice(2, 0, { label: 'Reports', icon: Activity, action: () => onNavigate?.('detailed-reports'), color: 'bg-purple-600 hover:bg-purple-700' });
|
actions.splice(actions.length - 1, 0, { label: 'Reports', icon: Activity, action: () => onNavigate?.('detailed-reports'), color: 'bg-purple-600 hover:bg-purple-700' });
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user