Compare commits

..

2 Commits

Author SHA1 Message Date
1391e2d2f5 draft toast modification done for production 2026-01-21 18:44:38 +05:30
c7ffc475f9 non templatized card disabled 2025-12-30 09:46:17 +05:30
3 changed files with 194 additions and 149 deletions

View File

@ -43,7 +43,7 @@ interface AppProps {
function RequestsRoute({ onViewRequest }: { onViewRequest: (requestId: string) => void }) {
const { user } = useAuth();
const isAdmin = hasManagementAccess(user);
// Render separate screens based on user role
// Admin/Management users see all organization requests
// Regular users see only their participant requests (approver/spectator, NOT initiator)
@ -104,7 +104,7 @@ function AppRoutes({ onLogout }: AppProps) {
const handleViewRequest = async (requestId: string, requestTitle?: string, status?: string) => {
setSelectedRequestId(requestId);
setSelectedRequestTitle(requestTitle || 'Unknown Request');
// Check if request is a draft - if so, route to edit form instead of detail view
const isDraft = status?.toLowerCase() === 'draft' || status === 'DRAFT';
if (isDraft) {
@ -131,11 +131,11 @@ function AppRoutes({ onLogout }: AppProps) {
}
return;
}
// Regular custom request submission
// Generate unique ID for the new custom request
const requestId = `RE-REQ-2024-${String(Object.keys(CUSTOM_REQUEST_DATABASE).length + dynamicRequests.length + 1).padStart(3, '0')}`;
// Create full custom request object
const newCustomRequest = {
id: requestId,
@ -163,21 +163,21 @@ function AppRoutes({ onLogout }: AppProps) {
avatar: 'CU'
},
department: requestData.department || 'General',
createdAt: new Date().toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
createdAt: new Date().toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
hour12: true
}),
updatedAt: new Date().toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
updatedAt: new Date().toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
hour12: true
}),
dueDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toISOString(),
submittedDate: new Date().toISOString(),
@ -187,7 +187,7 @@ function AppRoutes({ onLogout }: AppProps) {
// Extract name from email if name is not available
const approverName = approver?.name || approver?.email?.split('@')[0] || `Approver ${index + 1}`;
const approverEmail = approver?.email || '';
return {
step: index + 1,
approver: `${approverName}${approverEmail ? ` (${approverEmail})` : ''}`,
@ -212,32 +212,28 @@ function AppRoutes({ onLogout }: AppProps) {
};
}),
auditTrail: [
{
type: 'created',
action: 'Request Created',
details: `Custom request "${requestData.title}" created`,
user: 'Current User',
{
type: 'created',
action: 'Request Created',
details: `Custom request "${requestData.title}" created`,
user: 'Current User',
timestamp: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true })
},
{
type: 'assignment',
action: 'Assigned to Approver',
details: `Request assigned to ${requestData.approvers?.[0]?.name || requestData.approvers?.[0]?.email || 'first approver'}`,
user: 'System',
{
type: 'assignment',
action: 'Assigned to Approver',
details: `Request assigned to ${requestData.approvers?.[0]?.name || requestData.approvers?.[0]?.email || 'first approver'}`,
user: 'System',
timestamp: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true })
}
],
tags: requestData.tags || ['custom-request']
};
// Add to dynamic requests
setDynamicRequests([...dynamicRequests, newCustomRequest]);
navigate('/my-requests');
toast.success('Request Submitted Successfully!', {
description: `Your request "${requestData.title}" (${requestId}) has been created and sent for approval.`,
duration: 5000,
});
};
const handleApprovalSubmit = (action: 'approve' | 'reject', _comment: string) => {
@ -254,7 +250,7 @@ function AppRoutes({ onLogout }: AppProps) {
duration: 5000,
});
}
setApprovalAction(null);
resolve(true);
}, 1000);
@ -268,7 +264,7 @@ function AppRoutes({ onLogout }: AppProps) {
const handleClaimManagementSubmit = (claimData: any) => {
// Generate unique ID for the new claim request
const requestId = `RE-REQ-2024-CM-${String(dynamicRequests.length + 2).padStart(3, '0')}`;
// Create full request object
const newRequest = {
id: requestId,
@ -295,21 +291,21 @@ function AppRoutes({ onLogout }: AppProps) {
avatar: 'CU'
},
department: 'Marketing',
createdAt: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
createdAt: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
hour12: true
}),
updatedAt: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
updatedAt: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
hour12: true
}),
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
conclusionRemark: '',
@ -429,30 +425,30 @@ function AppRoutes({ onLogout }: AppProps) {
documents: [],
spectators: [],
auditTrail: [
{
type: 'created',
action: 'Request Created',
details: `Claim request for ${claimData.activityName} created`,
user: 'Current User',
timestamp: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
{
type: 'created',
action: 'Request Created',
details: `Claim request for ${claimData.activityName} created`,
user: 'Current User',
timestamp: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
hour12: true
})
}
],
tags: ['claim-management', 'new-request', claimData.activityType?.toLowerCase().replace(/\s+/g, '-')]
};
// Add to dynamic requests
setDynamicRequests(prev => [...prev, newRequest]);
// Also add to REQUEST_DATABASE for immediate viewing
(REQUEST_DATABASE as any)[requestId] = newRequest;
toast.success('Claim Request Submitted', {
description: 'Your claim management request has been created successfully.',
});
@ -463,134 +459,134 @@ function AppRoutes({ onLogout }: AppProps) {
<div className="min-h-screen h-screen flex flex-col overflow-hidden bg-background">
<Routes>
{/* Auth Callback - Must be before other routes */}
<Route
path="/login/callback"
element={<AuthCallback />}
<Route
path="/login/callback"
element={<AuthCallback />}
/>
{/* Dashboard */}
<Route
path="/"
<Route
path="/"
element={
<PageLayout currentPage="dashboard" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />
</PageLayout>
}
}
/>
<Route
path="/dashboard"
<Route
path="/dashboard"
element={
<PageLayout currentPage="dashboard" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />
</PageLayout>
}
}
/>
{/* Open Requests */}
<Route
path="/open-requests"
<Route
path="/open-requests"
element={
<PageLayout currentPage="open-requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<OpenRequests onViewRequest={handleViewRequest} />
</PageLayout>
}
}
/>
{/* Closed Requests */}
<Route
path="/closed-requests"
<Route
path="/closed-requests"
element={
<PageLayout currentPage="closed-requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<ClosedRequests onViewRequest={handleViewRequest} />
</PageLayout>
}
}
/>
{/* Shared Summaries */}
<Route
path="/shared-summaries"
<Route
path="/shared-summaries"
element={
<PageLayout currentPage="shared-summaries" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<SharedSummaries />
</PageLayout>
}
}
/>
{/* Shared Summary Detail */}
<Route
path="/shared-summaries/:sharedSummaryId"
<Route
path="/shared-summaries/:sharedSummaryId"
element={
<PageLayout currentPage="shared-summaries" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<SharedSummaryDetail />
</PageLayout>
}
}
/>
{/* My Requests */}
<Route
path="/my-requests"
<Route
path="/my-requests"
element={
<PageLayout currentPage="my-requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<MyRequests onViewRequest={handleViewRequest} dynamicRequests={dynamicRequests} />
</PageLayout>
}
}
/>
{/* Requests - Separate screens for Admin and Regular Users */}
<Route
path="/requests"
<Route
path="/requests"
element={
<PageLayout currentPage="requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<RequestsRoute onViewRequest={handleViewRequest} />
</PageLayout>
}
}
/>
{/* Approver Performance - Detailed Performance Analysis */}
<Route
path="/approver-performance"
<Route
path="/approver-performance"
element={
<PageLayout currentPage="approver-performance" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<ApproverPerformance />
</PageLayout>
}
}
/>
{/* Request Detail - requestId will be read from URL params */}
<Route
path="/request/:requestId"
<Route
path="/request/:requestId"
element={
<PageLayout currentPage="request-detail" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<RequestDetail
requestId=""
<RequestDetail
requestId=""
onBack={handleBack}
dynamicRequests={dynamicRequests}
/>
</PageLayout>
}
}
/>
{/* Work Notes - Dedicated Full-Screen Page */}
<Route
path="/work-notes/:requestId"
element={<WorkNotes />}
<Route
path="/work-notes/:requestId"
element={<WorkNotes />}
/>
{/* New Request (Custom) */}
<Route
path="/new-request"
<Route
path="/new-request"
element={
<CreateRequest
onBack={handleBack}
onSubmit={handleNewRequestSubmit}
/>
}
}
/>
{/* Edit Draft Request */}
<Route
path="/edit-request/:requestId"
<Route
path="/edit-request/:requestId"
element={
<CreateRequest
onBack={handleBack}
@ -598,72 +594,72 @@ function AppRoutes({ onLogout }: AppProps) {
requestId={undefined} // Will be read from URL params
isEditMode={true}
/>
}
}
/>
{/* Claim Management Wizard */}
<Route
path="/claim-management"
<Route
path="/claim-management"
element={
<ClaimManagementWizard
onBack={handleBack}
onSubmit={handleClaimManagementSubmit}
/>
}
}
/>
{/* Profile */}
<Route
path="/profile"
<Route
path="/profile"
element={
<PageLayout currentPage="profile" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Profile />
</PageLayout>
}
}
/>
{/* Settings */}
<Route
path="/settings"
<Route
path="/settings"
element={
<PageLayout currentPage="settings" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Settings />
</PageLayout>
}
}
/>
{/* Notifications */}
<Route
path="/notifications"
<Route
path="/notifications"
element={
<PageLayout currentPage="notifications" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Notifications onNavigate={handleNavigate} />
</PageLayout>
}
}
/>
{/* Detailed Reports */}
<Route
path="/detailed-reports"
<Route
path="/detailed-reports"
element={
<PageLayout currentPage="detailed-reports" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<DetailedReports />
</PageLayout>
}
}
/>
{/* Admin Control Panel */}
<Route
path="/admin"
<Route
path="/admin"
element={
<PageLayout currentPage="admin" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Admin />
</PageLayout>
}
}
/>
</Routes>
<Toaster
<Toaster
position="top-right"
toastOptions={{
style: {
@ -696,7 +692,7 @@ interface MainAppProps {
export default function App(props?: MainAppProps) {
const { onLogout } = props || {};
return (
<BrowserRouter>
<AppRoutes onLogout={onLogout} />

View File

@ -59,23 +59,29 @@ export function TemplateSelectionStep({
className="w-full max-w-6xl grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8"
data-testid="template-selection-grid"
>
{templates.map((template) => (
<motion.div
key={template.id}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
data-testid={`template-card-${template.id}`}
>
<Card
className={`cursor-pointer h-full transition-all duration-300 border-2 ${
selectedTemplate?.id === template.id
? 'border-blue-500 shadow-xl bg-blue-50/50 ring-2 ring-blue-200'
: 'border-gray-200 hover:border-blue-300 hover:shadow-lg'
}`}
onClick={() => onSelectTemplate(template)}
data-testid={`template-card-${template.id}-clickable`}
{templates.map((template) => {
const isComingSoon = template.id === 'existing-template';
const isDisabled = isComingSoon;
return (
<motion.div
key={template.id}
whileHover={!isDisabled ? { scale: 1.03 } : {}}
whileTap={!isDisabled ? { scale: 0.98 } : {}}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
data-testid={`template-card-${template.id}`}
>
<Card
className={`h-full transition-all duration-300 border-2 ${
isDisabled
? 'border-gray-200 bg-gray-50/50 opacity-85 cursor-not-allowed'
: selectedTemplate?.id === template.id
? 'border-blue-500 shadow-xl bg-blue-50/50 ring-2 ring-blue-200 cursor-pointer'
: 'border-gray-200 hover:border-blue-300 hover:shadow-lg cursor-pointer'
}`}
onClick={!isDisabled ? () => onSelectTemplate(template) : undefined}
data-testid={`template-card-${template.id}-clickable`}
>
<CardHeader className="space-y-4 pb-4">
<div className="flex items-start justify-between">
<div
@ -108,9 +114,20 @@ export function TemplateSelectionStep({
)}
</div>
<div className="text-left">
<CardTitle className="text-xl mb-2" data-testid={`template-card-${template.id}-name`}>
{template.name}
</CardTitle>
<div className="flex items-start justify-between gap-2 mb-2">
<CardTitle className="text-xl" data-testid={`template-card-${template.id}-name`}>
{template.name}
</CardTitle>
{isComingSoon && (
<Badge
variant="outline"
className="text-xs bg-yellow-100 text-yellow-700 border-yellow-300 font-semibold"
data-testid={`template-card-${template.id}-coming-soon-badge`}
>
Coming Soon
</Badge>
)}
</div>
<div className="flex items-center gap-2">
<Badge variant="secondary" className="text-xs" data-testid={`template-card-${template.id}-category`}>
{template.category}
@ -140,7 +157,8 @@ export function TemplateSelectionStep({
</CardContent>
</Card>
</motion.div>
))}
);
})}
</div>
{/* Template Details Card */}

View File

@ -3,6 +3,7 @@
*/
import { useState } from 'react';
import { toast } from 'sonner';
import { FormData, RequestTemplate } from '@/hooks/useCreateRequestForm';
import {
buildCreatePayload,
@ -72,6 +73,12 @@ export function useCreateRequestSubmission({
documentsToDelete
);
// Show toast after backend confirmation
toast.success('Request Submitted Successfully!', {
description: `Your request "${formData.title}" has been submitted and sent for approval.`,
duration: 5000,
});
onSubmit?.({
...formData,
backendId: editRequestId,
@ -87,14 +94,24 @@ export function useCreateRequestSubmission({
const result = await createAndSubmitWorkflow(createPayload, documents);
// Show toast after backend confirmation
toast.success('Request Submitted Successfully!', {
description: `Your request "${formData.title}" has been created and sent for approval.`,
duration: 5000,
});
onSubmit?.({
...formData,
backendId: result.id,
template: selectedTemplate,
});
}
} catch (error) {
} catch (error: any) {
console.error('Failed to submit workflow:', error);
toast.error('Failed to Submit Request', {
description: error?.response?.data?.message || error?.message || 'An error occurred while submitting the request.',
duration: 5000,
});
setSubmitting(false);
}
};
@ -130,6 +147,11 @@ export function useCreateRequestSubmission({
documentsToDelete
);
toast.success('Draft Saved Successfully!', {
description: `Your request "${formData.title}" has been saved as draft.`,
duration: 5000,
});
onSubmit?.({
...formData,
backendId: editRequestId,
@ -145,14 +167,23 @@ export function useCreateRequestSubmission({
const result = await createWorkflow(createPayload, documents);
toast.success('Draft Saved Successfully!', {
description: `Your request "${formData.title}" has been saved as draft.`,
duration: 5000,
});
onSubmit?.({
...formData,
backendId: result.id,
template: selectedTemplate,
});
}
} catch (error) {
} catch (error: any) {
console.error('Failed to save draft:', error);
toast.error('Failed to Save Draft', {
description: error?.response?.data?.message || error?.message || 'An error occurred while saving the draft.',
duration: 5000,
});
setSavingDraft(false);
}
};