ui made stable for the non templatized and changed to support postman request submit
This commit is contained in:
parent
6b4b80c0d4
commit
3bab9c0481
@ -12,12 +12,12 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
|||||||
import { Switch } from '../ui/switch';
|
import { Switch } from '../ui/switch';
|
||||||
import { Calendar } from '../ui/calendar';
|
import { Calendar } from '../ui/calendar';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Calendar as CalendarIcon,
|
Calendar as CalendarIcon,
|
||||||
Upload,
|
Upload,
|
||||||
X,
|
X,
|
||||||
FileText,
|
FileText,
|
||||||
Check,
|
Check,
|
||||||
Users
|
Users
|
||||||
@ -42,6 +42,7 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
|||||||
spectators: [] as any[],
|
spectators: [] as any[],
|
||||||
documents: [] as File[]
|
documents: [] as File[]
|
||||||
});
|
});
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
const totalSteps = 5;
|
const totalSteps = 5;
|
||||||
|
|
||||||
@ -78,9 +79,36 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
|||||||
updateFormData('spectators', formData.spectators.filter(s => s.id !== userId));
|
updateFormData('spectators', formData.spectators.filter(s => s.id !== userId));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement> | React.DragEvent) => {
|
||||||
const files = Array.from(event.target.files || []);
|
let files: File[] = [];
|
||||||
updateFormData('documents', [...formData.documents, ...files]);
|
if ('target' in event && event.target instanceof HTMLInputElement && event.target.files) {
|
||||||
|
files = Array.from(event.target.files);
|
||||||
|
} else if ('dataTransfer' in event && event.dataTransfer.files) {
|
||||||
|
files = Array.from(event.dataTransfer.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
updateFormData('documents', [...formData.documents, ...files]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(false);
|
||||||
|
handleFileUpload(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeDocument = (index: number) => {
|
const removeDocument = (index: number) => {
|
||||||
@ -150,7 +178,7 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
|||||||
onChange={(e) => updateFormData('title', e.target.value)}
|
onChange={(e) => updateFormData('title', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="description">Description *</Label>
|
<Label htmlFor="description">Description *</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -215,9 +243,9 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
|||||||
<Label htmlFor="workflow-sequential" className="text-sm">Parallel</Label>
|
<Label htmlFor="workflow-sequential" className="text-sm">Parallel</Label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3 bg-muted/50 rounded-lg text-sm text-muted-foreground">
|
<div className="p-3 bg-muted/50 rounded-lg text-sm text-muted-foreground">
|
||||||
{formData.workflowType === 'sequential'
|
{formData.workflowType === 'sequential'
|
||||||
? 'Approvers will review the request one after another in the order you specify.'
|
? 'Approvers will review the request one after another in the order you specify.'
|
||||||
: 'All approvers will review the request simultaneously.'
|
: 'All approvers will review the request simultaneously.'
|
||||||
}
|
}
|
||||||
@ -311,7 +339,7 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{availableUsers
|
{availableUsers
|
||||||
.filter(user =>
|
.filter(user =>
|
||||||
!formData.spectators.find(s => s.id === user.id) &&
|
!formData.spectators.find(s => s.id === user.id) &&
|
||||||
!formData.approvers.find(a => a.id === user.id)
|
!formData.approvers.find(a => a.id === user.id)
|
||||||
)
|
)
|
||||||
@ -375,8 +403,14 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
|||||||
<p className="text-sm text-muted-foreground mb-2">
|
<p className="text-sm text-muted-foreground mb-2">
|
||||||
Attach supporting documents for your request. Maximum 10MB per file.
|
Attach supporting documents for your request. Maximum 10MB per file.
|
||||||
</p>
|
</p>
|
||||||
<div className="border-2 border-dashed border-border rounded-lg p-6 text-center">
|
<div
|
||||||
<Upload className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
|
className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${isDragging ? 'border-re-green bg-re-green/5' : 'border-border'
|
||||||
|
}`}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
<Upload className={`h-8 w-8 mx-auto mb-2 ${isDragging ? 'text-re-green' : 'text-muted-foreground'}`} />
|
||||||
<p className="text-sm text-muted-foreground mb-2">
|
<p className="text-sm text-muted-foreground mb-2">
|
||||||
Drag and drop files here, or click to browse
|
Drag and drop files here, or click to browse
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useState, ChangeEvent, DragEvent, RefObject } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -20,7 +21,7 @@ interface DocumentsStepProps {
|
|||||||
onDocumentsToDeleteChange: (ids: string[]) => void;
|
onDocumentsToDeleteChange: (ids: string[]) => void;
|
||||||
onPreviewDocument: (doc: any, isExisting: boolean) => void;
|
onPreviewDocument: (doc: any, isExisting: boolean) => void;
|
||||||
onDocumentErrors?: (errors: Array<{ fileName: string; reason: string }>) => void;
|
onDocumentErrors?: (errors: Array<{ fileName: string; reason: string }>) => void;
|
||||||
fileInputRef: React.RefObject<HTMLInputElement>;
|
fileInputRef: RefObject<HTMLInputElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,8 +48,9 @@ export function DocumentsStep({
|
|||||||
onDocumentErrors,
|
onDocumentErrors,
|
||||||
fileInputRef
|
fileInputRef
|
||||||
}: DocumentsStepProps) {
|
}: DocumentsStepProps) {
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const files = Array.from(event.target.files || []);
|
|
||||||
|
const processFiles = (files: File[]) => {
|
||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
|
||||||
// Validate files
|
// Validate files
|
||||||
@ -69,7 +71,7 @@ export function DocumentsStep({
|
|||||||
// Check file extension
|
// Check file extension
|
||||||
const fileName = file.name.toLowerCase();
|
const fileName = file.name.toLowerCase();
|
||||||
const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
|
const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
|
||||||
|
|
||||||
if (!documentPolicy.allowedFileTypes.includes(fileExtension)) {
|
if (!documentPolicy.allowedFileTypes.includes(fileExtension)) {
|
||||||
validationErrors.push({
|
validationErrors.push({
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
@ -90,6 +92,11 @@ export function DocumentsStep({
|
|||||||
if (validationErrors.length > 0 && onDocumentErrors) {
|
if (validationErrors.length > 0 && onDocumentErrors) {
|
||||||
onDocumentErrors(validationErrors);
|
onDocumentErrors(validationErrors);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const files = Array.from(event.target.files || []);
|
||||||
|
processFiles(files);
|
||||||
|
|
||||||
// Reset file input
|
// Reset file input
|
||||||
if (event.target) {
|
if (event.target) {
|
||||||
@ -97,6 +104,27 @@ export function DocumentsStep({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(false);
|
||||||
|
|
||||||
|
const files = Array.from(e.dataTransfer.files);
|
||||||
|
processFiles(files);
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemove = (index: number) => {
|
const handleRemove = (index: number) => {
|
||||||
const newDocs = documents.filter((_, i) => i !== index);
|
const newDocs = documents.filter((_, i) => i !== index);
|
||||||
onDocumentsChange(newDocs);
|
onDocumentsChange(newDocs);
|
||||||
@ -111,16 +139,16 @@ export function DocumentsStep({
|
|||||||
const type = (doc.fileType || doc.file_type || '').toLowerCase();
|
const type = (doc.fileType || doc.file_type || '').toLowerCase();
|
||||||
const name = (doc.originalFileName || doc.fileName || '').toLowerCase();
|
const name = (doc.originalFileName || doc.fileName || '').toLowerCase();
|
||||||
return type.includes('image') || type.includes('pdf') ||
|
return type.includes('image') || type.includes('pdf') ||
|
||||||
name.endsWith('.jpg') || name.endsWith('.jpeg') ||
|
name.endsWith('.jpg') || name.endsWith('.jpeg') ||
|
||||||
name.endsWith('.png') || name.endsWith('.gif') ||
|
name.endsWith('.png') || name.endsWith('.gif') ||
|
||||||
name.endsWith('.pdf');
|
name.endsWith('.pdf');
|
||||||
} else {
|
} else {
|
||||||
const type = (doc.type || '').toLowerCase();
|
const type = (doc.type || '').toLowerCase();
|
||||||
const name = (doc.name || '').toLowerCase();
|
const name = (doc.name || '').toLowerCase();
|
||||||
return type.includes('image') || type.includes('pdf') ||
|
return type.includes('image') || type.includes('pdf') ||
|
||||||
name.endsWith('.jpg') || name.endsWith('.jpeg') ||
|
name.endsWith('.jpg') || name.endsWith('.jpeg') ||
|
||||||
name.endsWith('.png') || name.endsWith('.gif') ||
|
name.endsWith('.png') || name.endsWith('.gif') ||
|
||||||
name.endsWith('.pdf');
|
name.endsWith('.pdf');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -156,8 +184,15 @@ export function DocumentsStep({
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors" data-testid="documents-upload-area">
|
<div
|
||||||
<Upload className="h-12 w-12 mx-auto mb-4 text-gray-400" />
|
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${isDragging ? 'border-re-green bg-re-green/5' : 'border-gray-300 hover:border-gray-400'
|
||||||
|
}`}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
data-testid="documents-upload-area"
|
||||||
|
>
|
||||||
|
<Upload className={`h-12 w-12 mx-auto mb-4 ${isDragging ? 'text-re-green' : 'text-gray-400'}`} />
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">Upload Files</h3>
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">Upload Files</h3>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 mb-4">
|
||||||
Drag and drop files here, or click to browse
|
Drag and drop files here, or click to browse
|
||||||
@ -172,10 +207,10 @@ export function DocumentsStep({
|
|||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
data-testid="documents-file-input"
|
data-testid="documents-file-input"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="lg"
|
size="lg"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
data-testid="documents-browse-button"
|
data-testid="documents-browse-button"
|
||||||
>
|
>
|
||||||
@ -206,7 +241,7 @@ export function DocumentsStep({
|
|||||||
const docId = doc.documentId || doc.document_id || '';
|
const docId = doc.documentId || doc.document_id || '';
|
||||||
const isDeleted = documentsToDelete.includes(docId);
|
const isDeleted = documentsToDelete.includes(docId);
|
||||||
if (isDeleted) return null;
|
if (isDeleted) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={docId} className="flex items-center justify-between p-4 rounded-lg border bg-gray-50" data-testid={`documents-existing-${docId}`}>
|
<div key={docId} className="flex items-center justify-between p-4 rounded-lg border bg-gray-50" data-testid={`documents-existing-${docId}`}>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -222,9 +257,9 @@ export function DocumentsStep({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{canPreview(doc, true) && (
|
{canPreview(doc, true) && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onPreviewDocument(doc, true)}
|
onClick={() => onPreviewDocument(doc, true)}
|
||||||
data-testid={`documents-existing-${docId}-preview`}
|
data-testid={`documents-existing-${docId}-preview`}
|
||||||
>
|
>
|
||||||
@ -276,9 +311,9 @@ export function DocumentsStep({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{canPreview(file, false) && (
|
{canPreview(file, false) && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onPreviewDocument(file, false)}
|
onClick={() => onPreviewDocument(file, false)}
|
||||||
data-testid={`documents-new-${index}-preview`}
|
data-testid={`documents-new-${index}-preview`}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -255,7 +255,7 @@ export function StandardUserAllRequestsFilters({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search initiator..."
|
placeholder="Use @ to search initiator..."
|
||||||
value={initiatorSearch.searchQuery}
|
value={initiatorSearch.searchQuery}
|
||||||
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
@ -325,7 +325,7 @@ export function StandardUserAllRequestsFilters({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search approver..."
|
placeholder="Use @ to search approver..."
|
||||||
value={approverSearch.searchQuery}
|
value={approverSearch.searchQuery}
|
||||||
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ interface DealerUserAllRequestsFiltersProps {
|
|||||||
customStartDate?: Date;
|
customStartDate?: Date;
|
||||||
customEndDate?: Date;
|
customEndDate?: Date;
|
||||||
showCustomDatePicker: boolean;
|
showCustomDatePicker: boolean;
|
||||||
|
|
||||||
// State for user search
|
// State for user search
|
||||||
initiatorSearch: {
|
initiatorSearch: {
|
||||||
selectedUser: { userId: string; email: string; displayName?: string } | null;
|
selectedUser: { userId: string; email: string; displayName?: string } | null;
|
||||||
@ -46,7 +46,7 @@ interface DealerUserAllRequestsFiltersProps {
|
|||||||
handleClear: () => void;
|
handleClear: () => void;
|
||||||
setShowResults: (show: boolean) => void;
|
setShowResults: (show: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
approverSearch: {
|
approverSearch: {
|
||||||
selectedUser: { userId: string; email: string; displayName?: string } | null;
|
selectedUser: { userId: string; email: string; displayName?: string } | null;
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
@ -57,7 +57,7 @@ interface DealerUserAllRequestsFiltersProps {
|
|||||||
handleClear: () => void;
|
handleClear: () => void;
|
||||||
setShowResults: (show: boolean) => void;
|
setShowResults: (show: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
onSearchChange: (value: string) => void;
|
onSearchChange: (value: string) => void;
|
||||||
onStatusChange: (value: string) => void;
|
onStatusChange: (value: string) => void;
|
||||||
@ -70,7 +70,7 @@ interface DealerUserAllRequestsFiltersProps {
|
|||||||
onShowCustomDatePickerChange?: (show: boolean) => void;
|
onShowCustomDatePickerChange?: (show: boolean) => void;
|
||||||
onApplyCustomDate?: () => void;
|
onApplyCustomDate?: () => void;
|
||||||
onClearFilters: () => void;
|
onClearFilters: () => void;
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
hasActiveFilters: boolean;
|
hasActiveFilters: boolean;
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ export function DealerUserAllRequestsFilters({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search initiator..."
|
placeholder="Use @ to search initiator..."
|
||||||
value={initiatorSearch.searchQuery}
|
value={initiatorSearch.searchQuery}
|
||||||
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
@ -242,7 +242,7 @@ export function DealerUserAllRequestsFilters({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search approver..."
|
placeholder="Use @ to search approver..."
|
||||||
value={approverSearch.searchQuery}
|
value={approverSearch.searchQuery}
|
||||||
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -139,6 +139,7 @@ export function useCreateRequestSubmission({
|
|||||||
user,
|
user,
|
||||||
documentsToDelete
|
documentsToDelete
|
||||||
);
|
);
|
||||||
|
(updatePayload as any).isDraft = true;
|
||||||
|
|
||||||
await updateWorkflowRequest(
|
await updateWorkflowRequest(
|
||||||
editRequestId,
|
editRequestId,
|
||||||
@ -164,6 +165,7 @@ export function useCreateRequestSubmission({
|
|||||||
selectedTemplate,
|
selectedTemplate,
|
||||||
user
|
user
|
||||||
);
|
);
|
||||||
|
(createPayload as any).isDraft = true;
|
||||||
|
|
||||||
const result = await createWorkflow(createPayload, documents);
|
const result = await createWorkflow(createPayload, documents);
|
||||||
|
|
||||||
|
|||||||
@ -59,28 +59,22 @@ export async function submitWorkflowRequest(requestId: string): Promise<void> {
|
|||||||
await submitWorkflow(requestId);
|
await submitWorkflow(requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and submit a workflow in one operation
|
|
||||||
*/
|
|
||||||
export async function createAndSubmitWorkflow(
|
export async function createAndSubmitWorkflow(
|
||||||
payload: CreateWorkflowPayload,
|
payload: CreateWorkflowPayload,
|
||||||
documents: File[]
|
documents: File[]
|
||||||
): Promise<{ id: string }> {
|
): Promise<{ id: string }> {
|
||||||
const result = await createWorkflow(payload, documents);
|
// Pass isDraft: false (or omit) to trigger backend auto-submit
|
||||||
await submitWorkflowRequest(result.id);
|
const res = await createWorkflow({ ...payload, isDraft: false }, documents);
|
||||||
return result;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update and submit a workflow in one operation
|
|
||||||
*/
|
|
||||||
export async function updateAndSubmitWorkflow(
|
export async function updateAndSubmitWorkflow(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
payload: UpdateWorkflowPayload,
|
payload: UpdateWorkflowPayload,
|
||||||
documents: File[],
|
documents: File[],
|
||||||
documentsToDelete: string[]
|
documentsToDelete: string[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await updateWorkflowRequest(requestId, payload, documents, documentsToDelete);
|
// Pass isDraft: false (or omit) to trigger backend auto-submit
|
||||||
await submitWorkflowRequest(requestId);
|
await updateWorkflowRequest(requestId, { ...payload, isDraft: false }, documents, documentsToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,6 +67,7 @@ export interface CreateWorkflowPayload {
|
|||||||
email: string;
|
email: string;
|
||||||
}>;
|
}>;
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
|
isDraft?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWorkflowPayload {
|
export interface UpdateWorkflowPayload {
|
||||||
@ -76,6 +77,7 @@ export interface UpdateWorkflowPayload {
|
|||||||
approvalLevels: ApprovalLevel[];
|
approvalLevels: ApprovalLevel[];
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
deleteDocumentIds?: string[];
|
deleteDocumentIds?: string[];
|
||||||
|
isDraft?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValidationModalState {
|
export interface ValidationModalState {
|
||||||
|
|||||||
@ -665,7 +665,7 @@ export function Requests({ onViewRequest }: RequestsProps) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search initiator..."
|
placeholder="Use @ to search initiator..."
|
||||||
value={initiatorSearch.searchQuery}
|
value={initiatorSearch.searchQuery}
|
||||||
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
@ -735,7 +735,7 @@ export function Requests({ onViewRequest }: RequestsProps) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search approver..."
|
placeholder="Use @ to search approver..."
|
||||||
value={approverSearch.searchQuery}
|
value={approverSearch.searchQuery}
|
||||||
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
|
|||||||
@ -45,14 +45,14 @@ export function useUserSearch({ allUsers, filterValue, onFilterChange }: UseUser
|
|||||||
clearTimeout(searchTimer.current);
|
clearTimeout(searchTimer.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!query || query.trim().length < 2) {
|
if (!query || !query.startsWith('@') || query.trim().length < 2) {
|
||||||
setSearchResults([]);
|
setSearchResults([]);
|
||||||
setShowResults(false);
|
setShowResults(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTimer.current = setTimeout(() => {
|
searchTimer.current = setTimeout(() => {
|
||||||
const searchLower = query.toLowerCase().trim();
|
const searchLower = query.slice(1).toLowerCase().trim();
|
||||||
const filtered = allUsers.filter((user) => {
|
const filtered = allUsers.filter((user) => {
|
||||||
const email = (user.email || '').toLowerCase();
|
const email = (user.email || '').toLowerCase();
|
||||||
const displayName = (user.displayName || '').toLowerCase();
|
const displayName = (user.displayName || '').toLowerCase();
|
||||||
|
|||||||
@ -102,6 +102,7 @@ export async function createWorkflowFromForm(form: CreateWorkflowFromFormPayload
|
|||||||
priority, // STANDARD | EXPRESS
|
priority, // STANDARD | EXPRESS
|
||||||
approvalLevels,
|
approvalLevels,
|
||||||
participants: participants.length ? participants : undefined,
|
participants: participants.length ? participants : undefined,
|
||||||
|
isDraft: (form as any).isDraft,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await apiClient.post('/workflows', payload);
|
const res = await apiClient.post('/workflows', payload);
|
||||||
@ -131,6 +132,7 @@ export async function createWorkflowMultipart(form: CreateWorkflowFromFormPayloa
|
|||||||
tatType: a.tatType || 'hours',
|
tatType: a.tatType || 'hours',
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
isDraft: (form as any).isDraft,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add spectators if any (simplified - only email required)
|
// Add spectators if any (simplified - only email required)
|
||||||
|
|||||||
@ -66,7 +66,9 @@ export const getPriorityConfig = (priority: string) => {
|
|||||||
* @returns Configuration object with Tailwind CSS classes
|
* @returns Configuration object with Tailwind CSS classes
|
||||||
*/
|
*/
|
||||||
export const getStatusConfig = (status: string) => {
|
export const getStatusConfig = (status: string) => {
|
||||||
switch (status) {
|
switch (status?.toLowerCase()) {
|
||||||
|
case 'in-review':
|
||||||
|
case 'in_progress':
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return {
|
return {
|
||||||
color: 'bg-yellow-100 text-yellow-800 border-yellow-200',
|
color: 'bg-yellow-100 text-yellow-800 border-yellow-200',
|
||||||
@ -77,11 +79,6 @@ export const getStatusConfig = (status: string) => {
|
|||||||
color: 'bg-gray-400 text-gray-100 border-gray-500',
|
color: 'bg-gray-400 text-gray-100 border-gray-500',
|
||||||
label: 'paused'
|
label: 'paused'
|
||||||
};
|
};
|
||||||
case 'in-review':
|
|
||||||
return {
|
|
||||||
color: 'bg-blue-100 text-blue-800 border-blue-200',
|
|
||||||
label: 'in-review'
|
|
||||||
};
|
|
||||||
case 'approved':
|
case 'approved':
|
||||||
return {
|
return {
|
||||||
color: 'bg-green-100 text-green-800 border-green-200',
|
color: 'bg-green-100 text-green-800 border-green-200',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user