mobile responsive changes
This commit is contained in:
parent
75cfd57514
commit
6b7837540b
@ -250,7 +250,7 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 p-6 overflow-auto min-w-0">
|
||||
<main className="flex-1 p-2 sm:p-4 lg:p-6 overflow-auto min-w-0">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -390,7 +390,11 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
const base = (import.meta as any).env.VITE_BASE_URL || window.location.origin;
|
||||
// Get backend URL from environment (same as API calls)
|
||||
// Strip /api/v1 suffix if present to get base WebSocket URL
|
||||
const apiBaseUrl = (import.meta as any).env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1';
|
||||
const base = apiBaseUrl.replace(/\/api\/v1$/, '');
|
||||
console.log('[WorkNoteChat] Connecting socket to:', base);
|
||||
const s = getSocket(base);
|
||||
|
||||
// Only join room if not skipped (standalone mode)
|
||||
|
||||
@ -337,8 +337,8 @@ function BackendAuthProvider({ children }: { children: ReactNode }) {
|
||||
try {
|
||||
setError(null);
|
||||
// Redirect to Okta login
|
||||
const oktaDomain = 'https://dev-830839.oktapreview.com';
|
||||
const clientId = '0oa2j8slwj5S4bG5k0h8';
|
||||
const oktaDomain = import.meta.env.VITE_OKTA_DOMAIN || 'https://dev-830839.oktapreview.com';
|
||||
const clientId = import.meta.env.VITE_OKTA_CLIENT_ID || '0oa2jgzvrpdwx2iqd0h8';
|
||||
const redirectUri = `${window.location.origin}/login/callback`;
|
||||
const responseType = 'code';
|
||||
const scope = 'openid profile email';
|
||||
|
||||
@ -180,43 +180,44 @@ export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
|
||||
].filter(Boolean).length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6 max-w-7xl mx-auto">
|
||||
<div className="space-y-4 sm:space-y-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 className="flex flex-col lg:flex-row lg:items-center justify-between gap-3 sm:gap-4 md:gap-6">
|
||||
<div className="space-y-1 sm:space-y-2">
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-slate-800 to-slate-900 rounded-xl flex items-center justify-center shadow-lg">
|
||||
<FileText className="w-5 h-5 sm:w-6 sm: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>
|
||||
<h1 className="text-xl sm:text-2xl md:text-3xl font-bold text-gray-900">Closed Requests</h1>
|
||||
<p className="text-sm sm:text-base 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">
|
||||
{loading ? 'Loading…' : `${filteredAndSortedRequests.length} closed requests`}
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<Badge variant="secondary" className="text-xs sm:text-sm md:text-base px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 bg-slate-100 text-slate-800 font-semibold">
|
||||
{loading ? 'Loading…' : `${filteredAndSortedRequests.length} closed`}
|
||||
<span className="hidden sm:inline ml-1">requests</span>
|
||||
</Badge>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
Refresh
|
||||
<Button variant="outline" size="sm" className="gap-1 sm:gap-2 h-8 sm:h-9">
|
||||
<RefreshCw className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<span className="hidden sm:inline">Refresh</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Filters Section */}
|
||||
<Card className="shadow-lg border-0">
|
||||
<CardHeader className="pb-4">
|
||||
<CardHeader className="pb-3 sm:pb-4 px-3 sm:px-6">
|
||||
<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 className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="p-1.5 sm:p-2 bg-blue-100 rounded-lg">
|
||||
<Filter className="h-4 w-4 sm:h-5 sm:w-5 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg">Filters & Search</CardTitle>
|
||||
<CardDescription>
|
||||
<CardTitle className="text-base sm:text-lg">Filters & Search</CardTitle>
|
||||
<CardDescription className="text-xs sm:text-sm">
|
||||
{activeFiltersCount > 0 && (
|
||||
<span className="text-blue-600 font-medium">
|
||||
{activeFiltersCount} filter{activeFiltersCount > 1 ? 's' : ''} active
|
||||
@ -225,45 +226,45 @@ export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 sm:gap-2">
|
||||
{activeFiltersCount > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={clearFilters}
|
||||
className="text-red-600 hover:bg-red-50 gap-1"
|
||||
className="text-red-600 hover:bg-red-50 gap-1 h-8 sm:h-9 px-2 sm:px-3"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
Clear
|
||||
<X className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
|
||||
<span className="text-xs sm:text-sm">Clear</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
|
||||
className="gap-2"
|
||||
className="gap-1 sm:gap-2 h-8 sm:h-9 px-2 sm:px-3"
|
||||
>
|
||||
<Settings2 className="w-4 h-4" />
|
||||
{showAdvancedFilters ? 'Basic' : 'Advanced'}
|
||||
<Settings2 className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<span className="text-xs sm:text-sm hidden md:inline">{showAdvancedFilters ? 'Basic' : 'Advanced'}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<CardContent className="space-y-3 sm:space-y-4 px-3 sm:px-6">
|
||||
{/* Primary filters */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 sm: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" />
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<Input
|
||||
placeholder="Search requests, IDs, or initiators..."
|
||||
placeholder="Search requests, IDs..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 h-11 bg-gray-50 border-gray-200 focus:bg-white transition-colors"
|
||||
className="pl-9 sm:pl-10 h-9 sm:h-10 md:h-11 text-sm sm:text-base 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">
|
||||
<SelectTrigger className="h-9 sm:h-10 md:h-11 text-sm sm:text-base bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectValue placeholder="All Priorities" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -284,7 +285,7 @@ export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
|
||||
</Select>
|
||||
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="h-11 bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectTrigger className="h-9 sm:h-10 md:h-11 text-sm sm:text-base bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectValue placeholder="All Statuses" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -306,7 +307,7 @@ export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
|
||||
|
||||
<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">
|
||||
<SelectTrigger className="h-9 sm:h-10 md:h-11 text-sm sm:text-base bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectValue placeholder="Sort by" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -320,9 +321,9 @@ export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
|
||||
className="px-3 h-11"
|
||||
className="px-2 sm:px-3 h-9 sm:h-10 md:h-11"
|
||||
>
|
||||
{sortOrder === 'asc' ? <SortAsc className="w-4 h-4" /> : <SortDesc className="w-4 h-4" />}
|
||||
{sortOrder === 'asc' ? <SortAsc className="w-3.5 h-3.5 sm:w-4 sm:h-4" /> : <SortDesc className="w-3.5 h-3.5 sm:w-4 sm:h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -341,89 +342,87 @@ export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
|
||||
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">
|
||||
<CardContent className="p-3 sm:p-6">
|
||||
<div className="flex flex-col sm:flex-row items-start gap-3 sm: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 className="flex sm:flex-col items-center gap-2 pt-1 w-full sm:w-auto">
|
||||
<div className={`p-2 sm:p-3 rounded-xl ${priorityConfig.color} border flex-shrink-0`}>
|
||||
<priorityConfig.icon className={`w-4 h-4 sm:w-5 sm:h-5 ${priorityConfig.iconColor}`} />
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs font-medium ${priorityConfig.color} capitalize`}
|
||||
className={`text-xs font-medium ${priorityConfig.color} capitalize flex-shrink-0`}
|
||||
>
|
||||
{request.priority}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 min-w-0 space-y-4">
|
||||
<div className="flex-1 min-w-0 space-y-3 sm:space-y-4 w-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex items-start justify-between gap-2 sm: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">
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-3 mb-2">
|
||||
<h3 className="text-sm sm:text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors">
|
||||
{(request as any).displayId || request.id}
|
||||
</h3>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${statusConfig.color} border font-medium`}
|
||||
className={`${statusConfig.color} border font-medium text-xs shrink-0`}
|
||||
>
|
||||
<statusConfig.icon className="w-3 h-3 mr-1" />
|
||||
{request.status}
|
||||
<span className="capitalize">{request.status}</span>
|
||||
</Badge>
|
||||
{request.department && (
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-700">
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-700 text-xs hidden sm:inline-flex shrink-0">
|
||||
{request.department}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-gray-900 mb-2 line-clamp-1">
|
||||
<h4 className="text-base sm:text-xl font-bold text-gray-900 mb-2 line-clamp-2">
|
||||
{request.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 line-clamp-2 leading-relaxed">
|
||||
<p className="text-xs sm:text-sm 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 className="flex flex-col items-end gap-2 flex-shrink-0">
|
||||
<ArrowRight className="w-4 h-4 sm:w-5 sm: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">
|
||||
<div className="flex items-center gap-2 sm:gap-4 p-3 sm:p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<CheckCircle className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-green-500 flex-shrink-0" />
|
||||
<span className="text-xs sm:text-sm text-gray-700 font-medium truncate">
|
||||
{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 className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-6">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Avatar className="h-7 w-7 sm:h-8 sm:w-8 ring-2 ring-white shadow-sm flex-shrink-0">
|
||||
<AvatarFallback className="bg-slate-700 text-white text-xs sm:text-sm font-semibold">
|
||||
{request.initiator.avatar}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-900 truncate">{request.initiator.name}</p>
|
||||
<p className="text-xs text-gray-500">Initiator</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<div className="text-left sm:text-right">
|
||||
<div className="flex flex-col gap-1 text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="w-3 h-3" />
|
||||
Created {request.createdAt}
|
||||
<Calendar className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="truncate">Created {request.createdAt}</span>
|
||||
</span>
|
||||
<span>Closed {request.dueDate}</span>
|
||||
<span className="truncate">Closed {request.dueDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -210,78 +210,78 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-900 flex items-center gap-2">
|
||||
<User className="w-7 h-7 text-[--re-green]" />
|
||||
<h1 className="text-xl sm:text-2xl md:text-3xl font-semibold text-gray-900 flex items-center gap-2">
|
||||
<User className="w-5 h-5 sm:w-6 sm:h-6 md:w-7 md:h-7 text-[--re-green]" />
|
||||
My Requests
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-1">
|
||||
<p className="text-sm sm:text-base text-gray-600 mt-1">
|
||||
Track and manage all your submitted requests
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Overview */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 sm:gap-4">
|
||||
<Card className="bg-gradient-to-br from-blue-50 to-blue-100 border-blue-200">
|
||||
<CardContent className="p-4">
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-blue-700 font-medium">Total</p>
|
||||
<p className="text-2xl font-bold text-blue-900">{stats.total}</p>
|
||||
<p className="text-xs sm:text-sm text-blue-700 font-medium">Total</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-blue-900">{stats.total}</p>
|
||||
</div>
|
||||
<FileText className="w-8 h-8 text-blue-600" />
|
||||
<FileText className="w-6 h-6 sm:w-8 sm:h-8 text-blue-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-br from-orange-50 to-orange-100 border-orange-200">
|
||||
<CardContent className="p-4">
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-orange-700 font-medium">In Progress</p>
|
||||
<p className="text-2xl font-bold text-orange-900">{stats.pending + stats.inReview}</p>
|
||||
<p className="text-xs sm:text-sm text-orange-700 font-medium">In Progress</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-orange-900">{stats.pending + stats.inReview}</p>
|
||||
</div>
|
||||
<Clock className="w-8 h-8 text-orange-600" />
|
||||
<Clock className="w-6 h-6 sm:w-8 sm:h-8 text-orange-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-br from-green-50 to-green-100 border-green-200">
|
||||
<CardContent className="p-4">
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-green-700 font-medium">Approved</p>
|
||||
<p className="text-2xl font-bold text-green-900">{stats.approved}</p>
|
||||
<p className="text-xs sm:text-sm text-green-700 font-medium">Approved</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-green-900">{stats.approved}</p>
|
||||
</div>
|
||||
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||
<CheckCircle className="w-6 h-6 sm:w-8 sm:h-8 text-green-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-br from-red-50 to-red-100 border-red-200">
|
||||
<CardContent className="p-4">
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-red-700 font-medium">Rejected</p>
|
||||
<p className="text-2xl font-bold text-red-900">{stats.rejected}</p>
|
||||
<p className="text-xs sm:text-sm text-red-700 font-medium">Rejected</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-red-900">{stats.rejected}</p>
|
||||
</div>
|
||||
<XCircle className="w-8 h-8 text-red-600" />
|
||||
<XCircle className="w-6 h-6 sm:w-8 sm:h-8 text-red-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gradient-to-br from-gray-50 to-gray-100 border-gray-200">
|
||||
<CardContent className="p-4">
|
||||
<CardContent className="p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-700 font-medium">Draft</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{stats.draft}</p>
|
||||
<p className="text-xs sm:text-sm text-gray-700 font-medium">Draft</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">{stats.draft}</p>
|
||||
</div>
|
||||
<Edit className="w-8 h-8 text-gray-600" />
|
||||
<Edit className="w-6 h-6 sm:w-8 sm:h-8 text-gray-600" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -289,21 +289,21 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
||||
|
||||
{/* Filters and Search */}
|
||||
<Card className="border-gray-200">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex flex-col md:flex-row gap-4 items-start md:items-center">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<CardContent className="p-3 sm:p-4 md:p-6">
|
||||
<div className="flex flex-col md:flex-row gap-3 sm:gap-4 items-start md:items-center">
|
||||
<div className="flex-1 relative w-full">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<Input
|
||||
placeholder="Search requests by title, description, or ID..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-9 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green"
|
||||
className="pl-9 text-sm sm:text-base bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green h-9 sm:h-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex gap-2 sm:gap-3 w-full md:w-auto">
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-32 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green">
|
||||
<SelectTrigger className="flex-1 md:w-28 lg:w-32 text-xs sm:text-sm bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green h-9 sm:h-10">
|
||||
<SelectValue placeholder="Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -317,7 +317,7 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
||||
</Select>
|
||||
|
||||
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
|
||||
<SelectTrigger className="w-32 bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green">
|
||||
<SelectTrigger className="flex-1 md:w-28 lg:w-32 text-xs sm:text-sm bg-white border-gray-300 hover:border-gray-400 focus:border-re-green focus:ring-1 focus:ring-re-green h-9 sm:h-10">
|
||||
<SelectValue placeholder="Priority" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -361,61 +361,65 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
|
||||
className="group hover:shadow-lg transition-all duration-300 cursor-pointer border border-gray-200 shadow-sm hover:shadow-md"
|
||||
onClick={() => onViewRequest(request.id, request.title)}
|
||||
>
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
<CardContent className="p-3 sm:p-6">
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{/* Header with Title and Status Badges */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-lg font-semibold text-gray-900 mb-2 group-hover:text-blue-600 transition-colors">
|
||||
<h4 className="text-base sm:text-lg font-semibold text-gray-900 mb-2 group-hover:text-blue-600 transition-colors line-clamp-2">
|
||||
{request.title}
|
||||
</h4>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2 mb-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${getStatusConfig(request.status).color} border font-medium text-xs`}
|
||||
className={`${getStatusConfig(request.status).color} border font-medium text-xs shrink-0`}
|
||||
>
|
||||
{(() => {
|
||||
const IconComponent = getStatusConfig(request.status).icon;
|
||||
return <IconComponent className="w-3 h-3 mr-1" />;
|
||||
})()}
|
||||
{request.status}
|
||||
<span className="capitalize">{request.status}</span>
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${getPriorityConfig(request.priority).color} border font-medium text-xs capitalize`}
|
||||
className={`${getPriorityConfig(request.priority).color} border font-medium text-xs capitalize shrink-0`}
|
||||
>
|
||||
{(() => {
|
||||
const IconComponent = getPriorityConfig(request.priority).icon;
|
||||
return <IconComponent className="w-3 h-3 mr-1" />;
|
||||
})()}
|
||||
{request.priority}
|
||||
</Badge>
|
||||
{(request as any).templateType && (
|
||||
<Badge variant="secondary" className="bg-purple-100 text-purple-700 text-xs">
|
||||
<Badge variant="secondary" className="bg-purple-100 text-purple-700 text-xs shrink-0 hidden sm:inline-flex">
|
||||
<FileText className="w-3 h-3 mr-1" />
|
||||
Template: {(request as any).templateName}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-3">
|
||||
<p className="text-xs sm:text-sm text-gray-600 mb-2 sm:mb-3 line-clamp-2">
|
||||
{request.description}
|
||||
</p>
|
||||
<div className="flex items-center gap-4 text-sm text-gray-500">
|
||||
<span><span className="font-medium">ID:</span> {(request as any).displayId || request.id}</span>
|
||||
<span><span className="font-medium">Submitted:</span> {request.submittedDate ? new Date(request.submittedDate).toLocaleDateString() : 'N/A'}</span>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-4 text-xs sm:text-sm text-gray-500">
|
||||
<span className="truncate"><span className="font-medium">ID:</span> {(request as any).displayId || request.id}</span>
|
||||
<span className="truncate"><span className="font-medium">Submitted:</span> {request.submittedDate ? new Date(request.submittedDate).toLocaleDateString() : 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0" />
|
||||
<ArrowRight className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400 group-hover:text-blue-600 transition-colors flex-shrink-0 mt-1" />
|
||||
</div>
|
||||
|
||||
{/* Current Approver and Level Info */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-sm">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4 pt-3 border-t border-gray-100">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<User className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-gray-400 flex-shrink-0" />
|
||||
<span className="text-xs sm:text-sm truncate">
|
||||
<span className="text-gray-500">Current:</span> <span className="text-gray-900 font-medium">{request.currentApprover}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-sm">
|
||||
<TrendingUp className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-gray-400 flex-shrink-0" />
|
||||
<span className="text-xs sm:text-sm">
|
||||
<span className="text-gray-500">Level:</span> <span className="text-gray-900 font-medium">{request.approverLevel}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -243,43 +243,44 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
].filter(Boolean).length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6 max-w-7xl mx-auto">
|
||||
<div className="space-y-4 sm:space-y-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 className="flex flex-col lg:flex-row lg:items-center justify-between gap-3 sm:gap-4 md:gap-6">
|
||||
<div className="space-y-1 sm:space-y-2">
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-slate-800 to-slate-900 rounded-xl flex items-center justify-center shadow-lg">
|
||||
<FileText className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Open Requests</h1>
|
||||
<p className="text-gray-600">Manage and track active approval requests</p>
|
||||
<h1 className="text-xl sm:text-2xl md:text-3xl font-bold text-gray-900">Open Requests</h1>
|
||||
<p className="text-sm sm:text-base text-gray-600">Manage and track active approval requests</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="secondary" className="text-lg px-4 py-2 bg-slate-100 text-slate-800 font-semibold">
|
||||
{loading ? 'Loading…' : `${filteredAndSortedRequests.length} open requests`}
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<Badge variant="secondary" className="text-xs sm:text-sm md:text-base px-2 sm:px-3 md:px-4 py-1 sm:py-1.5 md:py-2 bg-slate-100 text-slate-800 font-semibold">
|
||||
{loading ? 'Loading…' : `${filteredAndSortedRequests.length} open`}
|
||||
<span className="hidden sm:inline ml-1">requests</span>
|
||||
</Badge>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
Refresh
|
||||
<Button variant="outline" size="sm" className="gap-1 sm:gap-2 h-8 sm:h-9">
|
||||
<RefreshCw className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<span className="hidden sm:inline">Refresh</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Filters Section */}
|
||||
<Card className="shadow-lg border-0">
|
||||
<CardHeader className="pb-4">
|
||||
<CardHeader className="pb-3 sm:pb-4 px-3 sm:px-6">
|
||||
<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 className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="p-1.5 sm:p-2 bg-blue-100 rounded-lg">
|
||||
<Filter className="h-4 w-4 sm:h-5 sm:w-5 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg">Filters & Search</CardTitle>
|
||||
<CardDescription>
|
||||
<CardTitle className="text-base sm:text-lg">Filters & Search</CardTitle>
|
||||
<CardDescription className="text-xs sm:text-sm">
|
||||
{activeFiltersCount > 0 && (
|
||||
<span className="text-blue-600 font-medium">
|
||||
{activeFiltersCount} filter{activeFiltersCount > 1 ? 's' : ''} active
|
||||
@ -288,45 +289,45 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 sm:gap-2">
|
||||
{activeFiltersCount > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={clearFilters}
|
||||
className="text-red-600 hover:bg-red-50 gap-1"
|
||||
className="text-red-600 hover:bg-red-50 gap-1 h-8 sm:h-9 px-2 sm:px-3"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
Clear
|
||||
<X className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
|
||||
<span className="text-xs sm:text-sm">Clear</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
|
||||
className="gap-2"
|
||||
className="gap-1 sm:gap-2 h-8 sm:h-9 px-2 sm:px-3"
|
||||
>
|
||||
<Settings2 className="w-4 h-4" />
|
||||
{showAdvancedFilters ? 'Basic' : 'Advanced'}
|
||||
<Settings2 className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<span className="text-xs sm:text-sm hidden md:inline">{showAdvancedFilters ? 'Basic' : 'Advanced'}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<CardContent className="space-y-3 sm:space-y-4 px-3 sm:px-6">
|
||||
{/* Primary filters */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 sm: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" />
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<Input
|
||||
placeholder="Search requests, IDs, or initiators..."
|
||||
placeholder="Search requests, IDs..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 h-11 bg-gray-50 border-gray-200 focus:bg-white transition-colors"
|
||||
className="pl-9 sm:pl-10 h-9 sm:h-10 md:h-11 text-sm sm:text-base 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">
|
||||
<SelectTrigger className="h-9 sm:h-10 md:h-11 text-sm sm:text-base bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectValue placeholder="All Priorities" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -347,7 +348,7 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
</Select>
|
||||
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="h-11 bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectTrigger className="h-9 sm:h-10 md:h-11 text-sm sm:text-base bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectValue placeholder="All Statuses" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -359,7 +360,7 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
|
||||
<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">
|
||||
<SelectTrigger className="h-9 sm:h-10 md:h-11 text-sm sm:text-base bg-gray-50 border-gray-200 focus:bg-white">
|
||||
<SelectValue placeholder="Sort by" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@ -374,9 +375,9 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
|
||||
className="px-3 h-11"
|
||||
className="px-2 sm:px-3 h-9 sm:h-10 md:h-11"
|
||||
>
|
||||
{sortOrder === 'asc' ? <SortAsc className="w-4 h-4" /> : <SortDesc className="w-4 h-4" />}
|
||||
{sortOrder === 'asc' ? <SortAsc className="w-3.5 h-3.5 sm:w-4 sm:h-4" /> : <SortDesc className="w-3.5 h-3.5 sm:w-4 sm:h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -396,69 +397,69 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
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">
|
||||
<CardContent className="p-3 sm:p-6">
|
||||
<div className="flex flex-col sm:flex-row items-start gap-3 sm: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 className="flex sm:flex-col items-center gap-2 pt-1 w-full sm:w-auto">
|
||||
<div className={`p-2 sm:p-3 rounded-xl ${priorityConfig.color} border flex-shrink-0`}>
|
||||
<priorityConfig.icon className={`w-4 h-4 sm:w-5 sm:h-5 ${priorityConfig.iconColor}`} />
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs font-medium ${priorityConfig.color} capitalize`}
|
||||
className={`text-xs font-medium ${priorityConfig.color} capitalize flex-shrink-0`}
|
||||
>
|
||||
{request.priority}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 min-w-0 space-y-4">
|
||||
<div className="flex-1 min-w-0 space-y-3 sm:space-y-4 w-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex items-start justify-between gap-2 sm: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">
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-3 mb-2">
|
||||
<h3 className="text-sm sm:text-lg font-semibold text-gray-900 group-hover:text-blue-600 transition-colors">
|
||||
{(request as any).displayId || request.id}
|
||||
</h3>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${statusConfig.color} border font-medium`}
|
||||
className={`${statusConfig.color} border font-medium text-xs shrink-0`}
|
||||
>
|
||||
<statusConfig.icon className="w-3 h-3 mr-1" />
|
||||
{request.status}
|
||||
<span className="capitalize">{request.status}</span>
|
||||
</Badge>
|
||||
{request.department && (
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-700">
|
||||
<Badge variant="secondary" className="bg-gray-100 text-gray-700 text-xs hidden sm:inline-flex shrink-0">
|
||||
{request.department}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-gray-900 mb-2 line-clamp-1">
|
||||
<h4 className="text-base sm:text-xl font-bold text-gray-900 mb-2 line-clamp-2">
|
||||
{request.title}
|
||||
</h4>
|
||||
<p className="text-gray-600 line-clamp-2 leading-relaxed">
|
||||
<p className="text-xs sm:text-sm 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 className="flex flex-col items-end gap-2 flex-shrink-0">
|
||||
<ArrowRight className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400 group-hover:text-blue-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SLA Progress */}
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="bg-gray-50 rounded-lg p-3 sm:p-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-sm font-medium text-gray-700">SLA Progress</span>
|
||||
<Clock className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-gray-500 flex-shrink-0" />
|
||||
<span className="text-xs sm:text-sm font-medium text-gray-700">SLA Progress</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-sm font-semibold ${slaConfig.textColor}`}>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className={`text-xs sm:text-sm font-semibold ${slaConfig.textColor}`}>
|
||||
{request.slaRemaining} remaining
|
||||
</span>
|
||||
{slaConfig.urgency === 'critical' && (
|
||||
<Badge variant="destructive" className="animate-pulse text-xs">
|
||||
<Badge variant="destructive" className="animate-pulse text-xs shrink-0">
|
||||
URGENT
|
||||
</Badge>
|
||||
)}
|
||||
@ -466,59 +467,59 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
||||
</div>
|
||||
<Progress
|
||||
value={request.slaProgress}
|
||||
className="h-3 bg-gray-200"
|
||||
className="h-2 sm:h-3 bg-gray-200"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Status Info */}
|
||||
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertCircle className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-sm text-gray-700 font-medium">
|
||||
<div className="flex items-center gap-2 sm:gap-4 p-3 sm:p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<AlertCircle className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-blue-500 flex-shrink-0" />
|
||||
<span className="text-xs sm:text-sm text-gray-700 font-medium truncate">
|
||||
{request.approvalStep}
|
||||
</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">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-6 min-w-0">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Avatar className="h-7 w-7 sm:h-8 sm:w-8 ring-2 ring-white shadow-sm flex-shrink-0">
|
||||
<AvatarFallback className="bg-slate-700 text-white text-xs sm:text-sm font-semibold">
|
||||
{request.initiator.avatar}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">{request.initiator.name}</p>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-900 truncate">{request.initiator.name}</p>
|
||||
<p className="text-xs text-gray-500">Initiator</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{request.currentApprover && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-8 w-8 ring-2 ring-yellow-200 shadow-sm">
|
||||
<AvatarFallback className="bg-yellow-500 text-white text-sm font-semibold">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Avatar className="h-7 w-7 sm:h-8 sm:w-8 ring-2 ring-yellow-200 shadow-sm flex-shrink-0">
|
||||
<AvatarFallback className="bg-yellow-500 text-white text-xs sm:text-sm font-semibold">
|
||||
{request.currentApprover.avatar}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">{request.currentApprover.name}</p>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-900 truncate">{request.currentApprover.name}</p>
|
||||
<p className="text-xs text-gray-500">Current Approver</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-4 text-xs text-gray-500">
|
||||
<div className="text-left sm:text-right">
|
||||
<div className="flex flex-col gap-1 text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="w-3 h-3" />
|
||||
Created: {request.createdAt !== '—' ? formatDateShort(request.createdAt) : '—'}
|
||||
<Calendar className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="truncate">Created: {request.createdAt !== '—' ? formatDateShort(request.createdAt) : '—'}</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
Due: {request.dueDate ? formatDateShort(request.dueDate) : 'Not set'}
|
||||
<Clock className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="truncate">Due: {request.dueDate ? formatDateShort(request.dueDate) : 'Not set'}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -169,16 +169,16 @@ const getSLAConfig = (progress: number) => {
|
||||
const getStepIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return <CheckCircle className="w-5 h-5 text-green-600" />;
|
||||
return <CheckCircle className="w-4 h-4 sm:w-5 sm:h-5 text-green-600" />;
|
||||
case 'rejected':
|
||||
return <XCircle className="w-5 h-5 text-red-600" />;
|
||||
return <XCircle className="w-4 h-4 sm:w-5 sm:h-5 text-red-600" />;
|
||||
case 'pending':
|
||||
case 'in-review':
|
||||
return <Clock className="w-5 h-5 text-blue-600" />;
|
||||
return <Clock className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" />;
|
||||
case 'waiting':
|
||||
return <Clock className="w-5 h-5 text-gray-400" />;
|
||||
return <Clock className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400" />;
|
||||
default:
|
||||
return <Clock className="w-5 h-5 text-gray-400" />;
|
||||
return <Clock className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400" />;
|
||||
}
|
||||
};
|
||||
|
||||
@ -996,63 +996,65 @@ function RequestDetailInner({
|
||||
return (
|
||||
<>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="max-w-7xl mx-auto p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header Section */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-300 mb-6">
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-300 mb-4 sm:mb-6">
|
||||
{/* Top Header */}
|
||||
<div className="p-6 border-b border-gray-300">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 sm:p-4 md:p-6 border-b border-gray-300">
|
||||
<div className="flex items-start sm:items-center justify-between gap-2 sm:gap-4">
|
||||
<div className="flex items-start sm:items-center gap-2 sm:gap-4 min-w-0 flex-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onBack}
|
||||
className="rounded-lg"
|
||||
className="rounded-lg flex-shrink-0 h-8 w-8 sm:h-10 sm:w-10"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
<ArrowLeft className="h-4 w-4 sm:h-5 sm:w-5" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
|
||||
<FileText className="w-5 h-5 text-blue-600" />
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
|
||||
<FileText className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-lg font-bold text-gray-900">{request.id || 'N/A'}</h1>
|
||||
<Badge className={`${priorityConfig.color} rounded-full px-3`} variant="outline">
|
||||
{priorityConfig.label}
|
||||
</Badge>
|
||||
<Badge className={`${statusConfig.color} rounded-full px-3`} variant="outline">
|
||||
{statusConfig.label}
|
||||
</Badge>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 min-w-0 flex-1">
|
||||
<h1 className="text-sm sm:text-base md:text-lg font-bold text-gray-900 truncate">{request.id || 'N/A'}</h1>
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2">
|
||||
<Badge className={`${priorityConfig.color} rounded-full px-2 sm:px-3 text-xs capitalize shrink-0`} variant="outline">
|
||||
{priorityConfig.label}
|
||||
</Badge>
|
||||
<Badge className={`${statusConfig.color} rounded-full px-2 sm:px-3 text-xs capitalize shrink-0`} variant="outline">
|
||||
{statusConfig.label}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
Refresh
|
||||
<Button variant="outline" size="sm" className="gap-1 sm:gap-2 flex-shrink-0 h-8 sm:h-9">
|
||||
<RefreshCw className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
<span className="hidden sm:inline">Refresh</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 ml-14">
|
||||
<h2 className="text-xl font-semibold text-gray-900">{request.title}</h2>
|
||||
<div className="mt-3 ml-0 sm:ml-14">
|
||||
<h2 className="text-base sm:text-lg md:text-xl font-semibold text-gray-900 line-clamp-2">{request.title}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SLA Progress */}
|
||||
<div className={`${slaConfig.bg} px-6 py-4`}>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className={`${slaConfig.bg} px-3 sm:px-4 md:px-6 py-3 sm:py-4`}>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className={`h-4 w-4 ${slaConfig.textColor}`} />
|
||||
<span className="text-sm font-medium text-gray-900">SLA Progress</span>
|
||||
<Clock className={`h-3.5 w-3.5 sm:h-4 sm:w-4 ${slaConfig.textColor} flex-shrink-0`} />
|
||||
<span className="text-xs sm:text-sm font-medium text-gray-900">SLA Progress</span>
|
||||
</div>
|
||||
<span className={`text-sm font-semibold ${slaConfig.textColor}`}>
|
||||
<span className={`text-xs sm:text-sm font-semibold ${slaConfig.textColor}`}>
|
||||
{request.slaRemaining}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={request.slaProgress} className="h-2 mb-2" />
|
||||
<p className="text-xs text-gray-600">
|
||||
<p className="text-[10px] sm:text-xs text-gray-600">
|
||||
Due: {request.slaEndDate ? formatDateTime(request.slaEndDate) : 'Not set'} • {request.slaProgress}% elapsed
|
||||
</p>
|
||||
</div>
|
||||
@ -1060,28 +1062,28 @@ function RequestDetailInner({
|
||||
|
||||
{/* Tabs */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-5 bg-gray-100 h-10 mb-6">
|
||||
<TabsTrigger value="overview" className="flex items-center gap-2 text-xs sm:text-sm px-2">
|
||||
<ClipboardList className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
Overview
|
||||
<TabsList className="grid w-full grid-cols-5 bg-gray-100 h-auto sm:h-10 mb-4 sm:mb-6 p-1">
|
||||
<TabsTrigger value="overview" className="flex flex-col sm:flex-row items-center justify-center gap-1 sm:gap-2 text-[10px] sm:text-xs md:text-sm px-1 sm:px-2 py-2 sm:py-0 min-h-[44px] sm:min-h-0">
|
||||
<ClipboardList className="w-4 h-4 sm:w-3.5 sm:h-3.5 md:w-4 md:h-4 flex-shrink-0" />
|
||||
<span className="leading-tight">Overview</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="workflow" className="flex items-center gap-2 text-xs sm:text-sm px-2">
|
||||
<TrendingUp className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
Workflow
|
||||
<TabsTrigger value="workflow" className="flex flex-col sm:flex-row items-center justify-center gap-1 sm:gap-2 text-[10px] sm:text-xs md:text-sm px-1 sm:px-2 py-2 sm:py-0 min-h-[44px] sm:min-h-0">
|
||||
<TrendingUp className="w-4 h-4 sm:w-3.5 sm:h-3.5 md:w-4 md:h-4 flex-shrink-0" />
|
||||
<span className="leading-tight">Workflow</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="documents" className="flex items-center gap-2 text-xs sm:text-sm px-2">
|
||||
<FileText className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
Documents
|
||||
<TabsTrigger value="documents" className="flex flex-col sm:flex-row items-center justify-center gap-1 sm:gap-2 text-[10px] sm:text-xs md:text-sm px-1 sm:px-2 py-2 sm:py-0 min-h-[44px] sm:min-h-0">
|
||||
<FileText className="w-4 h-4 sm:w-3.5 sm:h-3.5 md:w-4 md:h-4 flex-shrink-0" />
|
||||
<span className="leading-tight">Docs</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="activity" className="flex items-center gap-2 text-xs sm:text-sm px-2">
|
||||
<Activity className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
Activity
|
||||
<TabsTrigger value="activity" className="flex flex-col sm:flex-row items-center justify-center gap-1 sm:gap-2 text-[10px] sm:text-xs md:text-sm px-1 sm:px-2 py-2 sm:py-0 min-h-[44px] sm:min-h-0">
|
||||
<Activity className="w-4 h-4 sm:w-3.5 sm:h-3.5 md:w-4 md:h-4 flex-shrink-0" />
|
||||
<span className="leading-tight">Activity</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="worknotes" className="flex items-center gap-2 text-xs sm:text-sm px-2 relative">
|
||||
<MessageSquare className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||
Work Notes
|
||||
<TabsTrigger value="worknotes" className="flex flex-col sm:flex-row items-center justify-center gap-1 sm:gap-2 text-[10px] sm:text-xs md:text-sm px-1 sm:px-2 py-2 sm:py-0 relative min-h-[44px] sm:min-h-0">
|
||||
<MessageSquare className="w-4 h-4 sm:w-3.5 sm:h-3.5 md:w-4 md:h-4 flex-shrink-0" />
|
||||
<span className="leading-tight">Notes</span>
|
||||
{unreadWorkNotes > 0 && (
|
||||
<Badge className="absolute -top-1 -right-1 h-5 w-5 rounded-full bg-red-500 text-white text-[10px] flex items-center justify-center p-0">
|
||||
<Badge className="absolute top-1 right-1 sm:-top-1 sm:-right-1 h-4 w-4 sm:h-5 sm:w-5 rounded-full bg-red-500 text-white text-[8px] sm:text-[10px] flex items-center justify-center p-0">
|
||||
{unreadWorkNotes > 9 ? '9+' : unreadWorkNotes}
|
||||
</Badge>
|
||||
)}
|
||||
@ -1095,34 +1097,34 @@ function RequestDetailInner({
|
||||
|
||||
{/* Overview Tab */}
|
||||
<TabsContent value="overview" className="mt-0">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{/* Request Initiator */}
|
||||
<Card>
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<User className="w-5 h-5 text-blue-600" />
|
||||
<CardHeader className="pb-3 sm:pb-4">
|
||||
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
|
||||
<User className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" />
|
||||
Request Initiator
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-start gap-4">
|
||||
<Avatar className="h-12 w-12 ring-2 ring-white shadow-sm">
|
||||
<AvatarFallback className="bg-gray-700 text-white font-semibold">
|
||||
<div className="flex items-start gap-3 sm:gap-4">
|
||||
<Avatar className="h-10 w-10 sm:h-12 sm:w-12 ring-2 ring-white shadow-sm flex-shrink-0">
|
||||
<AvatarFallback className="bg-gray-700 text-white font-semibold text-sm">
|
||||
{request.initiator?.avatar || 'U'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-gray-900">{request.initiator?.name || 'N/A'}</h3>
|
||||
<p className="text-sm text-gray-600">{request.initiator?.role || 'N/A'}</p>
|
||||
<p className="text-sm text-gray-500">{request.initiator?.department || 'N/A'}</p>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 text-sm sm:text-base truncate">{request.initiator?.name || 'N/A'}</h3>
|
||||
<p className="text-xs sm:text-sm text-gray-600 truncate">{request.initiator?.role || 'N/A'}</p>
|
||||
<p className="text-xs sm:text-sm text-gray-500 truncate">{request.initiator?.department || 'N/A'}</p>
|
||||
|
||||
<div className="mt-3 space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Mail className="w-4 h-4" />
|
||||
<span>{request.initiator?.email || 'N/A'}</span>
|
||||
<div className="mt-2 sm:mt-3 space-y-1.5 sm:space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs sm:text-sm text-gray-600 min-w-0">
|
||||
<Mail className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
|
||||
<span className="truncate">{request.initiator?.email || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Phone className="w-4 h-4" />
|
||||
<div className="flex items-center gap-2 text-xs sm:text-sm text-gray-600">
|
||||
<Phone className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
|
||||
<span>{request.initiator?.phone || 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -1133,17 +1135,17 @@ function RequestDetailInner({
|
||||
|
||||
{/* Request Details */}
|
||||
<Card>
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<FileText className="w-5 h-5 text-blue-600" />
|
||||
<CardHeader className="pb-3 sm:pb-4">
|
||||
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
|
||||
<FileText className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" />
|
||||
Request Details
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<CardContent className="space-y-3 sm:space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2">Description</label>
|
||||
<div className="bg-gray-50 rounded-lg p-4 border border-gray-300">
|
||||
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">
|
||||
<label className="text-xs sm:text-sm font-medium text-gray-700 block mb-2">Description</label>
|
||||
<div className="bg-gray-50 rounded-lg p-3 sm:p-4 border border-gray-300">
|
||||
<p className="text-xs sm:text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">
|
||||
{request.description}
|
||||
</p>
|
||||
</div>
|
||||
@ -1242,18 +1244,18 @@ function RequestDetailInner({
|
||||
<TabsContent value="workflow" className="mt-0">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5 text-blue-600" />
|
||||
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
|
||||
<TrendingUp className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" />
|
||||
Approval Workflow
|
||||
</CardTitle>
|
||||
<CardDescription className="mt-2">
|
||||
<CardDescription className="mt-1 sm:mt-2 text-xs sm:text-sm">
|
||||
Track the approval progress through each step
|
||||
</CardDescription>
|
||||
</div>
|
||||
{request.totalSteps && (
|
||||
<Badge variant="outline" className="font-medium">
|
||||
<Badge variant="outline" className="font-medium text-xs sm:text-sm shrink-0">
|
||||
Step {request.currentStep} of {request.totalSteps}
|
||||
</Badge>
|
||||
)}
|
||||
@ -1261,7 +1263,7 @@ function RequestDetailInner({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{request.approvalFlow && request.approvalFlow.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{request.approvalFlow.map((step: any, index: number) => {
|
||||
const isActive = step.status === 'pending' || step.status === 'in-review';
|
||||
const isCompleted = step.status === 'approved';
|
||||
@ -1271,7 +1273,7 @@ function RequestDetailInner({
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`relative p-5 rounded-lg border-2 transition-all ${
|
||||
className={`relative p-3 sm:p-4 md:p-5 rounded-lg border-2 transition-all ${
|
||||
isActive
|
||||
? 'border-blue-500 bg-blue-50 shadow-md'
|
||||
: isCompleted
|
||||
@ -1283,8 +1285,8 @@ function RequestDetailInner({
|
||||
: 'border-gray-200 bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`p-3 rounded-xl ${
|
||||
<div className="flex items-start gap-2 sm:gap-3 md:gap-4">
|
||||
<div className={`p-2 sm:p-2.5 md:p-3 rounded-xl flex-shrink-0 ${
|
||||
isActive ? 'bg-blue-100' :
|
||||
isCompleted ? 'bg-green-100' :
|
||||
isRejected ? 'bg-red-100' :
|
||||
@ -1295,25 +1297,25 @@ function RequestDetailInner({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-4 mb-2">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h4 className="font-semibold text-gray-900">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2 sm:gap-4 mb-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-1.5 sm:gap-2 mb-1">
|
||||
<h4 className="font-semibold text-gray-900 text-sm sm:text-base">
|
||||
{step.step ? `Step ${step.step}: ` : ''}{step.role}
|
||||
</h4>
|
||||
<Badge variant="outline" className={
|
||||
<Badge variant="outline" className={`text-xs shrink-0 ${
|
||||
isActive ? 'bg-blue-100 text-blue-800 border-blue-200' :
|
||||
isCompleted ? 'bg-green-100 text-green-800 border-green-200' :
|
||||
isRejected ? 'bg-red-100 text-red-800 border-red-200' :
|
||||
isWaiting ? 'bg-gray-200 text-gray-600 border-gray-300' :
|
||||
'bg-gray-100 text-gray-800 border-gray-200'
|
||||
}>
|
||||
}`}>
|
||||
{step.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">{step.approver}</p>
|
||||
<p className="text-xs sm:text-sm text-gray-600 truncate">{step.approver}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-left sm:text-right flex-shrink-0">
|
||||
{step.tatHours && (
|
||||
<p className="text-xs text-gray-500">TAT: {step.tatHours}h</p>
|
||||
)}
|
||||
@ -1321,24 +1323,24 @@ function RequestDetailInner({
|
||||
<p className="text-xs text-gray-600 font-medium">Elapsed: {step.elapsedHours}h</p>
|
||||
)}
|
||||
{step.actualHours !== undefined && (
|
||||
<p className="text-xs text-gray-600 font-medium">Completed in: {step.actualHours.toFixed(2)}h</p>
|
||||
<p className="text-xs text-gray-600 font-medium">Done: {step.actualHours.toFixed(2)}h</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{step.comment && (
|
||||
<div className="mt-3 p-3 bg-white rounded-lg border border-gray-300">
|
||||
<p className="text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">{step.comment}</p>
|
||||
<div className="mt-2 sm:mt-3 p-2 sm:p-3 bg-white rounded-lg border border-gray-300">
|
||||
<p className="text-xs sm:text-sm text-gray-700 whitespace-pre-line leading-relaxed break-words">{step.comment}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAT Alerts/Reminders */}
|
||||
{step.tatAlerts && step.tatAlerts.length > 0 && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<div className="mt-2 sm:mt-3 space-y-2">
|
||||
{step.tatAlerts.map((alert: any, alertIndex: number) => (
|
||||
<div
|
||||
key={alertIndex}
|
||||
className={`p-3 rounded-lg border ${
|
||||
className={`p-2 sm:p-3 rounded-lg border ${
|
||||
alert.isBreached
|
||||
? 'bg-red-50 border-red-200'
|
||||
: (alert.thresholdPercentage || 0) === 75
|
||||
@ -1347,34 +1349,34 @@ function RequestDetailInner({
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="text-lg">
|
||||
<div className="text-base sm:text-lg flex-shrink-0">
|
||||
{(alert.thresholdPercentage || 0) === 50 && '⏳'}
|
||||
{(alert.thresholdPercentage || 0) === 75 && '⚠️'}
|
||||
{(alert.thresholdPercentage || 0) === 100 && '⏰'}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<p className="text-sm font-semibold text-gray-900">
|
||||
Reminder {alertIndex + 1} - {alert.thresholdPercentage || 0}% TAT Threshold
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2 mb-1">
|
||||
<p className="text-xs sm:text-sm font-semibold text-gray-900">
|
||||
Reminder {alertIndex + 1} - {alert.thresholdPercentage || 0}% TAT
|
||||
</p>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={
|
||||
className={`text-[10px] sm:text-xs shrink-0 ${
|
||||
alert.isBreached
|
||||
? 'bg-red-100 text-red-800 border-red-300'
|
||||
: 'bg-amber-100 text-amber-800 border-amber-300'
|
||||
}
|
||||
}`}
|
||||
>
|
||||
{alert.isBreached ? 'BREACHED' : 'WARNING'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-700 mt-1">
|
||||
<p className="text-[10px] sm:text-xs md:text-sm text-gray-700 mt-1">
|
||||
{alert.thresholdPercentage || 0}% of SLA breach reminder have been sent
|
||||
</p>
|
||||
|
||||
{/* Time Tracking Details */}
|
||||
<div className="mt-2 grid grid-cols-2 gap-2 text-xs">
|
||||
<div className="mt-2 grid grid-cols-2 gap-1.5 sm:gap-2 text-[10px] sm:text-xs">
|
||||
<div className="bg-white/50 rounded px-2 py-1">
|
||||
<span className="text-gray-500">Allocated:</span>
|
||||
<span className="ml-1 font-medium text-gray-900">
|
||||
@ -1414,17 +1416,17 @@ function RequestDetailInner({
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-gray-200">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs text-gray-500">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1">
|
||||
<p className="text-[10px] sm:text-xs text-gray-500">
|
||||
Reminder sent by system automatically
|
||||
</p>
|
||||
{(alert.metadata?.testMode || alert.metadata?.tatTestMode) && (
|
||||
<Badge variant="outline" className="bg-purple-50 text-purple-700 border-purple-300 text-[10px] px-1.5 py-0">
|
||||
<Badge variant="outline" className="bg-purple-50 text-purple-700 border-purple-300 text-[10px] px-1.5 py-0 shrink-0">
|
||||
TEST MODE
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 font-medium mt-0.5">
|
||||
<p className="text-[10px] sm:text-xs text-gray-600 font-medium mt-0.5">
|
||||
Sent at: {alert.alertSentAt ? formatDateTime(alert.alertSentAt) : 'N/A'}
|
||||
</p>
|
||||
{(alert.metadata?.testMode || alert.metadata?.tatTestMode) && (
|
||||
@ -1448,11 +1450,11 @@ function RequestDetailInner({
|
||||
|
||||
{/* Skip Approver Button - Only show for pending/in-review levels */}
|
||||
{(isActive || step.status === 'pending') && !isCompleted && !isRejected && step.levelId && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-200">
|
||||
<div className="mt-2 sm:mt-3 pt-2 sm:pt-3 border-t border-gray-200">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full border-orange-300 text-orange-700 hover:bg-orange-50"
|
||||
className="w-full border-orange-300 text-orange-700 hover:bg-orange-50 h-9 sm:h-10 text-xs sm:text-sm"
|
||||
onClick={() => {
|
||||
if (!step.levelId) {
|
||||
alert('Level ID not available');
|
||||
@ -1466,10 +1468,10 @@ function RequestDetailInner({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AlertCircle className="w-4 h-4 mr-2" />
|
||||
<AlertCircle className="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-2" />
|
||||
Skip This Approver
|
||||
</Button>
|
||||
<p className="text-xs text-gray-500 mt-1 text-center">
|
||||
<p className="text-[10px] sm:text-xs text-gray-500 mt-1 text-center">
|
||||
Skip if approver is unavailable and move to next level
|
||||
</p>
|
||||
</div>
|
||||
@ -1489,25 +1491,29 @@ function RequestDetailInner({
|
||||
|
||||
{/* Documents Tab */}
|
||||
<TabsContent value="documents" className="mt-0">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{/* Section 1: Request Documents */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-blue-600" />
|
||||
Request Documents
|
||||
</CardTitle>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
|
||||
<FileText className="w-4 h-4 sm:w-5 sm:h-5 text-blue-600" />
|
||||
Request Documents
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs sm:text-sm mt-1">Documents attached while creating the request</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={triggerFileInput}
|
||||
disabled={uploadingDocument}
|
||||
className="gap-1 sm:gap-2 h-8 sm:h-9 text-xs sm:text-sm shrink-0"
|
||||
>
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
{uploadingDocument ? 'Uploading...' : 'Upload Document'}
|
||||
<Upload className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
{uploadingDocument ? 'Uploading...' : 'Upload'}
|
||||
<span className="hidden sm:inline">Document</span>
|
||||
</Button>
|
||||
</div>
|
||||
<CardDescription>Documents attached while creating the request</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{request.documents && request.documents.length > 0 ? (
|
||||
@ -1585,11 +1591,11 @@ function RequestDetailInner({
|
||||
{/* Section 2: Work Note Attachments */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<MessageSquare className="w-5 h-5 text-purple-600" />
|
||||
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
|
||||
<MessageSquare className="w-4 h-4 sm:w-5 sm:h-5 text-purple-600" />
|
||||
Work Note Attachments
|
||||
</CardTitle>
|
||||
<CardDescription>Files shared in work notes discussions</CardDescription>
|
||||
<CardDescription className="text-xs sm:text-sm">Files shared in work notes discussions</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{workNoteAttachments && workNoteAttachments.length > 0 ? (
|
||||
@ -1682,16 +1688,16 @@ function RequestDetailInner({
|
||||
<TabsContent value="activity" className="mt-0">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Activity className="w-5 h-5 text-orange-600" />
|
||||
<CardTitle className="flex items-center gap-2 text-sm sm:text-base">
|
||||
<Activity className="w-4 h-4 sm:w-5 sm:h-5 text-orange-600" />
|
||||
Activity Timeline
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<CardDescription className="text-xs sm:text-sm">
|
||||
Complete audit trail of all request activities
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{request.auditTrail && request.auditTrail.length > 0 ? request.auditTrail.map((entry: any, index: number) => (
|
||||
<div key={index} className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
@ -1746,50 +1752,50 @@ function RequestDetailInner({
|
||||
|
||||
{/* Right Column - Quick Actions Sidebar (1/3 width) - Hidden for Work Notes Tab */}
|
||||
{activeTab !== 'worknotes' && (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{/* Quick Actions */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">Quick Actions</CardTitle>
|
||||
<CardTitle className="text-sm sm:text-base">Quick Actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{!isSpectator && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900"
|
||||
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900 h-9 sm:h-10 text-xs sm:text-sm"
|
||||
onClick={() => setShowAddApproverModal(true)}
|
||||
>
|
||||
<UserPlus className="w-4 h-4" />
|
||||
<UserPlus className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
Add Approver
|
||||
</Button>
|
||||
)}
|
||||
{!isSpectator && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900"
|
||||
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900 h-9 sm:h-10 text-xs sm:text-sm"
|
||||
onClick={() => setShowAddSpectatorModal(true)}
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
<Eye className="w-3.5 h-3.5 sm:w-4 sm:h-4" />
|
||||
Add Spectator
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="pt-4 space-y-2">
|
||||
<div className="pt-3 sm:pt-4 space-y-2">
|
||||
{!isSpectator && currentApprovalLevel && (
|
||||
<>
|
||||
<Button
|
||||
className="w-full bg-green-600 hover:bg-green-700 text-white"
|
||||
className="w-full bg-green-600 hover:bg-green-700 text-white h-9 sm:h-10 text-xs sm:text-sm"
|
||||
onClick={() => setShowApproveModal(true)}
|
||||
>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
<CheckCircle className="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-2" />
|
||||
Approve Request
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
className="w-full h-9 sm:h-10 text-xs sm:text-sm"
|
||||
onClick={() => setShowRejectModal(true)}
|
||||
>
|
||||
<XCircle className="w-4 h-4 mr-2" />
|
||||
<XCircle className="w-3.5 h-3.5 sm:w-4 sm:h-4 mr-2" />
|
||||
Reject Request
|
||||
</Button>
|
||||
</>
|
||||
@ -1802,7 +1808,7 @@ function RequestDetailInner({
|
||||
{request.spectators && request.spectators.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">Spectators</CardTitle>
|
||||
<CardTitle className="text-sm sm:text-base">Spectators</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{request.spectators.map((spectator: any, index: number) => (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user