ui made stable for the non templatized and changed to support postman request submit
This commit is contained in:
parent
6b4b80c0d4
commit
3bab9c0481
@ -42,6 +42,7 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
||||
spectators: [] as any[],
|
||||
documents: [] as File[]
|
||||
});
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const totalSteps = 5;
|
||||
|
||||
@ -78,9 +79,36 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
||||
updateFormData('spectators', formData.spectators.filter(s => s.id !== userId));
|
||||
};
|
||||
|
||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(event.target.files || []);
|
||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement> | React.DragEvent) => {
|
||||
let files: File[] = [];
|
||||
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) => {
|
||||
@ -375,8 +403,14 @@ export function NewRequestModal({ open, onClose, onSubmit }: NewRequestModalProp
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Attach supporting documents for your request. Maximum 10MB per file.
|
||||
</p>
|
||||
<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" />
|
||||
<div
|
||||
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">
|
||||
Drag and drop files here, or click to browse
|
||||
</p>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useState, ChangeEvent, DragEvent, RefObject } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@ -20,7 +21,7 @@ interface DocumentsStepProps {
|
||||
onDocumentsToDeleteChange: (ids: string[]) => void;
|
||||
onPreviewDocument: (doc: any, isExisting: boolean) => void;
|
||||
onDocumentErrors?: (errors: Array<{ fileName: string; reason: string }>) => void;
|
||||
fileInputRef: React.RefObject<HTMLInputElement>;
|
||||
fileInputRef: RefObject<HTMLInputElement>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,8 +48,9 @@ export function DocumentsStep({
|
||||
onDocumentErrors,
|
||||
fileInputRef
|
||||
}: DocumentsStepProps) {
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(event.target.files || []);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const processFiles = (files: File[]) => {
|
||||
if (files.length === 0) return;
|
||||
|
||||
// Validate files
|
||||
@ -90,6 +92,11 @@ export function DocumentsStep({
|
||||
if (validationErrors.length > 0 && onDocumentErrors) {
|
||||
onDocumentErrors(validationErrors);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(event.target.files || []);
|
||||
processFiles(files);
|
||||
|
||||
// Reset file input
|
||||
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 newDocs = documents.filter((_, i) => i !== index);
|
||||
onDocumentsChange(newDocs);
|
||||
@ -156,8 +184,15 @@ export function DocumentsStep({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<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">
|
||||
<Upload className="h-12 w-12 mx-auto mb-4 text-gray-400" />
|
||||
<div
|
||||
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>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Drag and drop files here, or click to browse
|
||||
|
||||
@ -255,7 +255,7 @@ export function StandardUserAllRequestsFilters({
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search initiator..."
|
||||
placeholder="Use @ to search initiator..."
|
||||
value={initiatorSearch.searchQuery}
|
||||
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
||||
onFocus={() => {
|
||||
@ -325,7 +325,7 @@ export function StandardUserAllRequestsFilters({
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search approver..."
|
||||
placeholder="Use @ to search approver..."
|
||||
value={approverSearch.searchQuery}
|
||||
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
||||
onFocus={() => {
|
||||
|
||||
@ -172,7 +172,7 @@ export function DealerUserAllRequestsFilters({
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search initiator..."
|
||||
placeholder="Use @ to search initiator..."
|
||||
value={initiatorSearch.searchQuery}
|
||||
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
||||
onFocus={() => {
|
||||
@ -242,7 +242,7 @@ export function DealerUserAllRequestsFilters({
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search approver..."
|
||||
placeholder="Use @ to search approver..."
|
||||
value={approverSearch.searchQuery}
|
||||
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
||||
onFocus={() => {
|
||||
|
||||
@ -107,6 +107,8 @@ const getStepIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return <CircleCheckBig className="w-5 h-5 text-green-600" />;
|
||||
case 'in_progress':
|
||||
return <RotateCw className="w-5 h-5 text-purple-600 animate-spin-slow" />;
|
||||
case 'pending':
|
||||
return <Clock className="w-5 h-5 text-blue-600" />;
|
||||
case 'rejected':
|
||||
@ -123,6 +125,8 @@ const getStepBadgeVariant = (status: string) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return 'bg-green-100 text-green-800 border-green-200';
|
||||
case 'in_progress':
|
||||
return 'bg-blue-100 text-blue-800 border-blue-200';
|
||||
case 'pending':
|
||||
return 'bg-purple-100 text-purple-800 border-purple-200';
|
||||
case 'rejected':
|
||||
@ -155,6 +159,8 @@ const getStepIconBg = (status: string) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return 'bg-green-100';
|
||||
case 'in_progress':
|
||||
return 'bg-blue-100';
|
||||
case 'pending':
|
||||
return 'bg-purple-100';
|
||||
case 'rejected':
|
||||
@ -1720,8 +1726,7 @@ export function DealerClaimWorkflowTab({
|
||||
</div>
|
||||
|
||||
{/* Current Approver - Time Tracking */}
|
||||
<div className={`border rounded-lg p-3 ${
|
||||
isPaused ? 'bg-gray-100 border-gray-300' :
|
||||
<div className={`border rounded-lg p-3 ${isPaused ? 'bg-gray-100 border-gray-300' :
|
||||
(approval.sla.percentageUsed || 0) >= 100 ? 'bg-red-50 border-red-200' :
|
||||
(approval.sla.percentageUsed || 0) >= 75 ? 'bg-orange-50 border-orange-200' :
|
||||
(approval.sla.percentageUsed || 0) >= 50 ? 'bg-amber-50 border-amber-200' :
|
||||
|
||||
@ -139,6 +139,7 @@ export function useCreateRequestSubmission({
|
||||
user,
|
||||
documentsToDelete
|
||||
);
|
||||
(updatePayload as any).isDraft = true;
|
||||
|
||||
await updateWorkflowRequest(
|
||||
editRequestId,
|
||||
@ -164,6 +165,7 @@ export function useCreateRequestSubmission({
|
||||
selectedTemplate,
|
||||
user
|
||||
);
|
||||
(createPayload as any).isDraft = true;
|
||||
|
||||
const result = await createWorkflow(createPayload, documents);
|
||||
|
||||
|
||||
@ -59,28 +59,22 @@ 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;
|
||||
// Pass isDraft: false (or omit) to trigger backend auto-submit
|
||||
const res = await createWorkflow({ ...payload, isDraft: false }, documents);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// Pass isDraft: false (or omit) to trigger backend auto-submit
|
||||
await updateWorkflowRequest(requestId, { ...payload, isDraft: false }, documents, documentsToDelete);
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -665,7 +665,7 @@ export function Requests({ onViewRequest }: RequestsProps) {
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search initiator..."
|
||||
placeholder="Use @ to search initiator..."
|
||||
value={initiatorSearch.searchQuery}
|
||||
onChange={(e) => initiatorSearch.handleSearch(e.target.value)}
|
||||
onFocus={() => {
|
||||
@ -735,7 +735,7 @@ export function Requests({ onViewRequest }: RequestsProps) {
|
||||
) : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search approver..."
|
||||
placeholder="Use @ to search approver..."
|
||||
value={approverSearch.searchQuery}
|
||||
onChange={(e) => approverSearch.handleSearch(e.target.value)}
|
||||
onFocus={() => {
|
||||
|
||||
@ -45,14 +45,14 @@ export function useUserSearch({ allUsers, filterValue, onFilterChange }: UseUser
|
||||
clearTimeout(searchTimer.current);
|
||||
}
|
||||
|
||||
if (!query || query.trim().length < 2) {
|
||||
if (!query || !query.startsWith('@') || query.trim().length < 2) {
|
||||
setSearchResults([]);
|
||||
setShowResults(false);
|
||||
return;
|
||||
}
|
||||
|
||||
searchTimer.current = setTimeout(() => {
|
||||
const searchLower = query.toLowerCase().trim();
|
||||
const searchLower = query.slice(1).toLowerCase().trim();
|
||||
const filtered = allUsers.filter((user) => {
|
||||
const email = (user.email || '').toLowerCase();
|
||||
const displayName = (user.displayName || '').toLowerCase();
|
||||
|
||||
@ -102,6 +102,7 @@ export async function createWorkflowFromForm(form: CreateWorkflowFromFormPayload
|
||||
priority, // STANDARD | EXPRESS
|
||||
approvalLevels,
|
||||
participants: participants.length ? participants : undefined,
|
||||
isDraft: (form as any).isDraft,
|
||||
};
|
||||
|
||||
const res = await apiClient.post('/workflows', payload);
|
||||
@ -131,6 +132,7 @@ export async function createWorkflowMultipart(form: CreateWorkflowFromFormPayloa
|
||||
tatType: a.tatType || 'hours',
|
||||
};
|
||||
}),
|
||||
isDraft: (form as any).isDraft,
|
||||
};
|
||||
|
||||
// Add spectators if any (simplified - only email required)
|
||||
|
||||
@ -66,7 +66,9 @@ export const getPriorityConfig = (priority: string) => {
|
||||
* @returns Configuration object with Tailwind CSS classes
|
||||
*/
|
||||
export const getStatusConfig = (status: string) => {
|
||||
switch (status) {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'in-review':
|
||||
case 'in_progress':
|
||||
case 'pending':
|
||||
return {
|
||||
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',
|
||||
label: 'paused'
|
||||
};
|
||||
case 'in-review':
|
||||
return {
|
||||
color: 'bg-blue-100 text-blue-800 border-blue-200',
|
||||
label: 'in-review'
|
||||
};
|
||||
case 'approved':
|
||||
return {
|
||||
color: 'bg-green-100 text-green-800 border-green-200',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user