mobile responsive changes

This commit is contained in:
laxmanhalaki 2025-11-06 10:13:57 +05:30
parent 75cfd57514
commit 6b7837540b
7 changed files with 373 additions and 359 deletions

View File

@ -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>

View File

@ -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)

View File

@ -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';

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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) => (