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:
parent
1d205a4038
commit
c97053e0e3
@ -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 },
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@ -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'
|
||||
}`}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user