save draft an submit rquest adddd isDraft flag to support postman submit and dealer related code commented and made it completely non-templatized for production

This commit is contained in:
laxmanhalaki 2026-02-06 20:12:28 +05:30
parent 1d205a4038
commit c97053e0e3
11 changed files with 156 additions and 251 deletions

View File

@ -73,13 +73,14 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
{ id: 'dashboard', label: 'Dashboard', icon: Home },
// Add "All Requests" for all users (admin sees org-level, regular users see their participant requests)
{ id: 'requests', label: 'All Requests', icon: List },
{ id: 'admin/templates', label: 'Admin Templates', icon: Plus, adminOnly: true },
{ id: 'my-requests', label: 'My Requests', icon: User, adminOnly: false },
// { id: 'admin/templates', label: 'Admin Templates', icon: Plus, adminOnly: true },
];
// Add remaining menu items (exclude "My Requests" for dealers)
if (!isDealer) {
items.push({ id: 'my-requests', label: 'My Requests', icon: User });
}
// if (!isDealer) {
// items.push({ id: 'my-requests', label: 'My Requests', icon: User });
// }
items.push(
{ id: 'open-requests', label: 'Open Requests', icon: FileText },

View File

@ -378,7 +378,7 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
<div className="border-2 border-dashed border-border rounded-lg p-6 text-center">
<Upload className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
<p className="text-sm text-muted-foreground mb-2">
Drag and drop files here, or click to browse
click to browse
</p>
<input
type="file"

View File

@ -160,7 +160,7 @@ export function DocumentsStep({
<Upload className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">Upload Files</h3>
<p className="text-gray-600 mb-4">
Drag and drop files here, or click to browse
click to browse
</p>
<input
type="file"

View File

@ -52,18 +52,18 @@ export function TemplateSelectionStep({
const displayTemplates = viewMode === 'main'
? [
...templates,
{
id: 'admin-templates-category',
name: 'Admin Templates',
description: 'Browse standardized request workflows created by your organization administrators',
category: 'Organization',
icon: FolderOpen,
estimatedTime: 'Variable',
commonApprovers: [],
suggestedSLA: 0,
priority: 'medium',
fields: {}
} as any
// {
// id: 'admin-templates-category',
// name: 'Admin Templates',
// description: 'Browse standardized request workflows created by your organization administrators',
// category: 'Organization',
// icon: FolderOpen,
// estimatedTime: 'Variable',
// commonApprovers: [],
// suggestedSLA: 0,
// priority: 'medium',
// fields: {}
// } as any
]
: adminTemplates;

View File

@ -1,22 +1,12 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, Pencil, Trash2, Search, FileText, AlertTriangle } from 'lucide-react';
import { Plus, Pencil, Search, FileText } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { getTemplates, deleteTemplate, WorkflowTemplate, getCachedTemplates } from '@/services/workflowTemplateApi';
import { getTemplates, WorkflowTemplate, getCachedTemplates } from '@/services/workflowTemplateApi';
import { toast } from 'sonner';
export function AdminTemplatesList() {
@ -25,8 +15,6 @@ export function AdminTemplatesList() {
// Only show full loading skeleton if we don't have any data yet
const [loading, setLoading] = useState(() => !getCachedTemplates());
const [searchQuery, setSearchQuery] = useState('');
const [deleteId, setDeleteId] = useState<string | null>(null);
const [deleting, setDeleting] = useState(false);
const fetchTemplates = async () => {
try {
@ -49,22 +37,6 @@ export function AdminTemplatesList() {
fetchTemplates();
}, []);
const handleDelete = async () => {
if (!deleteId) return;
try {
setDeleting(true);
await deleteTemplate(deleteId);
toast.success('Template deleted successfully');
setTemplates(prev => prev.filter(t => t.id !== deleteId));
} catch (error) {
console.error('Failed to delete template:', error);
toast.error('Failed to delete template');
} finally {
setDeleting(false);
setDeleteId(null);
}
};
const filteredTemplates = templates.filter(template =>
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
@ -152,7 +124,7 @@ export function AdminTemplatesList() {
</Badge>
</div>
<CardTitle className="line-clamp-1 text-lg">{template.name}</CardTitle>
<CardDescription className="line-clamp-2 h-10">
<CardDescription className="line-clamp-3 min-h-[4.5rem]">
{template.description}
</CardDescription>
</CardHeader>
@ -181,14 +153,6 @@ export function AdminTemplatesList() {
<Pencil className="w-4 h-4 mr-2" />
Edit
</Button>
<Button
variant="outline"
className="flex-1 text-red-600 hover:text-red-700 hover:bg-red-50 border-red-100"
onClick={() => setDeleteId(template.id)}
>
<Trash2 className="w-4 h-4 mr-2" />
Delete
</Button>
</div>
</CardContent>
</Card>
@ -196,33 +160,6 @@ export function AdminTemplatesList() {
</div>
)}
<AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<AlertTriangle className="w-5 h-5 text-red-600" />
Delete Template
</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this template? This action cannot be undone.
Active requests using this template will not be affected.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={(e) => {
e.preventDefault();
handleDelete();
}}
className="bg-red-600 hover:bg-red-700"
disabled={deleting}
>
{deleting ? 'Deleting...' : 'Delete'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}

View File

@ -11,8 +11,6 @@ import {
validateApproversForSubmission,
} from '../utils/payloadBuilders';
import {
createAndSubmitWorkflow,
updateAndSubmitWorkflow,
createWorkflow,
updateWorkflowRequest,
} from '../services/createRequestService';
@ -59,14 +57,15 @@ export function useCreateRequestSubmission({
try {
if (isEditing && editRequestId) {
// Update existing workflow
// Update existing workflow with isDraft: false (Submit)
const updatePayload = buildUpdatePayload(
formData,
user,
documentsToDelete
documentsToDelete,
false
);
await updateAndSubmitWorkflow(
await updateWorkflowRequest(
editRequestId,
updatePayload,
documents,
@ -85,14 +84,15 @@ export function useCreateRequestSubmission({
template: selectedTemplate,
});
} else {
// Create new workflow
// Create new workflow with isDraft: false (Submit)
const createPayload = buildCreatePayload(
formData,
selectedTemplate,
user
user,
false
);
const result = await createAndSubmitWorkflow(createPayload, documents);
const result = await createWorkflow(createPayload, documents);
// Show toast after backend confirmation
toast.success('Request Submitted Successfully!', {
@ -133,11 +133,12 @@ export function useCreateRequestSubmission({
try {
if (isEditing && editRequestId) {
// Update existing draft
// Update existing draft with isDraft: true
const updatePayload = buildUpdatePayload(
formData,
user,
documentsToDelete
documentsToDelete,
true
);
await updateWorkflowRequest(
@ -158,11 +159,12 @@ export function useCreateRequestSubmission({
template: selectedTemplate,
});
} else {
// Create new draft
// Create new draft with isDraft: true
const createPayload = buildCreatePayload(
formData,
selectedTemplate,
user
user,
true
);
const result = await createWorkflow(createPayload, documents);

View File

@ -4,7 +4,6 @@
import {
createWorkflowMultipart,
submitWorkflow,
updateWorkflow,
updateWorkflowMultipart,
} from '@/services/workflowApi';
@ -14,7 +13,7 @@ import {
} from '../types/createRequest.types';
/**
* Create a new workflow
* Create a new workflow (supports both draft and direct submission via isDraft flag)
*/
export async function createWorkflow(
payload: CreateWorkflowPayload,
@ -29,7 +28,7 @@ export async function createWorkflow(
}
/**
* Update an existing workflow
* Update an existing workflow (supports both draft and direct submission via isDraft flag)
*/
export async function updateWorkflowRequest(
requestId: string,
@ -51,36 +50,3 @@ export async function updateWorkflowRequest(
await updateWorkflow(requestId, payload);
}
}
/**
* Submit a workflow
*/
export async function submitWorkflowRequest(requestId: string): Promise<void> {
await submitWorkflow(requestId);
}
/**
* Create and submit a workflow in one operation
*/
export async function createAndSubmitWorkflow(
payload: CreateWorkflowPayload,
documents: File[]
): Promise<{ id: string }> {
const result = await createWorkflow(payload, documents);
await submitWorkflowRequest(result.id);
return result;
}
/**
* Update and submit a workflow in one operation
*/
export async function updateAndSubmitWorkflow(
requestId: string,
payload: UpdateWorkflowPayload,
documents: File[],
documentsToDelete: string[]
): Promise<void> {
await updateWorkflowRequest(requestId, payload, documents, documentsToDelete);
await submitWorkflowRequest(requestId);
}

View File

@ -67,6 +67,7 @@ export interface CreateWorkflowPayload {
email: string;
}>;
participants: Participant[];
isDraft?: boolean;
}
export interface UpdateWorkflowPayload {
@ -76,6 +77,7 @@ export interface UpdateWorkflowPayload {
approvalLevels: ApprovalLevel[];
participants: Participant[];
deleteDocumentIds?: string[];
isDraft?: boolean;
}
export interface ValidationModalState {

View File

@ -17,16 +17,9 @@ import { buildApprovalLevels } from './approvalLevelBuilders';
export function buildCreatePayload(
formData: FormData,
selectedTemplate: RequestTemplate | null,
_user: any
_user: any,
isDraft: boolean = false
): CreateWorkflowPayload {
// Filter out spectators who are also approvers (backend will handle validation)
const approverEmails = new Set(
(formData.approvers || []).map((a: any) => a?.email?.toLowerCase()).filter(Boolean)
);
const filteredSpectators = (formData.spectators || []).filter(
(s: any) => s?.email && !approverEmails.has(s.email.toLowerCase())
);
return {
templateId: selectedTemplate?.id || null,
templateType: selectedTemplate?.id === 'custom' ? 'CUSTOM' : 'TEMPLATE',
@ -38,16 +31,17 @@ export function buildCreatePayload(
userId: a?.userId || '',
email: a?.email || '',
name: a?.name,
tat: a?.tat || '',
tat: a?.tat || 24,
tatType: a?.tatType || 'hours',
})),
spectators: filteredSpectators.map((s: any) => ({
spectators: (formData.spectators || []).map((s: any) => ({
userId: s?.userId || '',
name: s?.name || '',
email: s?.email || '',
})),
ccList: [], // Auto-generated by backend
participants: [], // Auto-generated by backend from approvers and spectators
isDraft,
};
}
@ -58,7 +52,8 @@ export function buildCreatePayload(
export function buildUpdatePayload(
formData: FormData,
_user: any,
documentsToDelete: string[]
documentsToDelete: string[],
isDraft: boolean = false
): UpdateWorkflowPayload {
const approvalLevels = buildApprovalLevels(
formData.approvers || [],
@ -72,6 +67,7 @@ export function buildUpdatePayload(
approvalLevels,
participants: [], // Auto-generated by backend from approval levels
deleteDocumentIds: documentsToDelete.length > 0 ? documentsToDelete : undefined,
isDraft,
};
}
@ -112,4 +108,3 @@ export function validateApproversForSubmission(
return { valid: true };
}

View File

@ -62,7 +62,7 @@ export function TATBreachReport({
</div>
</div>
<Badge variant="destructive" className="text-sm font-medium self-start sm:self-auto">
{breachedRequests.length} {breachedRequests.length === 1 ? 'Breach' : 'Breaches'}
{pagination.totalRecords} {pagination.totalRecords === 1 ? 'Breach' : 'Breaches'}
</Badge>
</div>
</CardHeader>
@ -164,8 +164,7 @@ export function TATBreachReport({
<td className="py-3 px-4">
<Badge
variant="outline"
className={`text-xs font-medium ${
req.priority === 'express'
className={`text-xs font-medium ${req.priority === 'express'
? 'bg-orange-100 text-orange-800 border-orange-200'
: 'bg-blue-100 text-blue-800 border-blue-200'
}`}

View File

@ -25,6 +25,7 @@ export interface CreateWorkflowFromFormPayload {
approvers: ApproverFormItem[];
spectators?: ParticipantItem[];
ccList?: ParticipantItem[];
isDraft?: boolean; // Added isDraft to the payload interface
}
// Utility to generate a RFC4122 v4 UUID (fallback if crypto.randomUUID not available)
@ -102,6 +103,7 @@ export async function createWorkflowFromForm(form: CreateWorkflowFromFormPayload
priority, // STANDARD | EXPRESS
approvalLevels,
participants: participants.length ? participants : undefined,
isDraft: form.isDraft, // Added isDraft to the payload
};
const res = await apiClient.post('/workflows', payload);
@ -116,6 +118,7 @@ export async function createWorkflowMultipart(form: CreateWorkflowFromFormPayloa
title: form.title,
description: form.description,
priority: form.priorityUi.toUpperCase() === 'EXPRESS' ? 'EXPRESS' : 'STANDARD',
isDraft: form.isDraft, // Added isDraft to the payload
// Simplified approvers format - only email and tatHours required
approvers: Array.from({ length: form.approverCount || 1 }, (_, i) => {
const a = form.approvers[i] || ({} as any);