block io move to request evaluation step custom shown as non-templatized for the user
This commit is contained in:
parent
d725e523b3
commit
4c3d7fd28b
@ -137,7 +137,7 @@ export function StandardClosedRequestsFilters({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Templates</SelectItem>
|
<SelectItem value="all">All Templates</SelectItem>
|
||||||
<SelectItem value="CUSTOM">Custom</SelectItem>
|
<SelectItem value="CUSTOM">Non-Templatized</SelectItem>
|
||||||
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -126,7 +126,7 @@ export function StandardRequestsFilters({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Templates</SelectItem>
|
<SelectItem value="all">All Templates</SelectItem>
|
||||||
<SelectItem value="CUSTOM">Custom</SelectItem>
|
<SelectItem value="CUSTOM">Non-Templatized</SelectItem>
|
||||||
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { DollarSign, Download, CircleCheckBig, Target } from 'lucide-react';
|
import { DollarSign, Download, CircleCheckBig, Target } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@ -31,7 +30,6 @@ interface IOBlockedDetails {
|
|||||||
blockedDate: string;
|
blockedDate: string;
|
||||||
blockedBy: string; // User who blocked
|
blockedBy: string; // User who blocked
|
||||||
sapDocumentNumber: string;
|
sapDocumentNumber: string;
|
||||||
ioRemark?: string; // IO remark
|
|
||||||
status: 'blocked' | 'released' | 'failed';
|
status: 'blocked' | 'released' | 'failed';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +40,6 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
// Load existing IO data from apiRequest or request
|
// Load existing IO data from apiRequest or request
|
||||||
const internalOrder = apiRequest?.internalOrder || apiRequest?.internal_order || null;
|
const internalOrder = apiRequest?.internalOrder || apiRequest?.internal_order || null;
|
||||||
const existingIONumber = internalOrder?.ioNumber || internalOrder?.io_number || request?.ioNumber || '';
|
const existingIONumber = internalOrder?.ioNumber || internalOrder?.io_number || request?.ioNumber || '';
|
||||||
const existingIORemark = internalOrder?.ioRemark || internalOrder?.io_remark || '';
|
|
||||||
const existingBlockedAmount = internalOrder?.ioBlockedAmount || internalOrder?.io_blocked_amount || 0;
|
const existingBlockedAmount = internalOrder?.ioBlockedAmount || internalOrder?.io_blocked_amount || 0;
|
||||||
const existingAvailableBalance = internalOrder?.ioAvailableBalance || internalOrder?.io_available_balance || 0;
|
const existingAvailableBalance = internalOrder?.ioAvailableBalance || internalOrder?.io_available_balance || 0;
|
||||||
const existingRemainingBalance = internalOrder?.ioRemainingBalance || internalOrder?.io_remaining_balance || 0;
|
const existingRemainingBalance = internalOrder?.ioRemainingBalance || internalOrder?.io_remaining_balance || 0;
|
||||||
@ -51,15 +48,11 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
const organizer = internalOrder?.organizer || null;
|
const organizer = internalOrder?.organizer || null;
|
||||||
|
|
||||||
const [ioNumber, setIoNumber] = useState(existingIONumber);
|
const [ioNumber, setIoNumber] = useState(existingIONumber);
|
||||||
const [ioRemark, setIoRemark] = useState(existingIORemark);
|
|
||||||
const [fetchingAmount, setFetchingAmount] = useState(false);
|
const [fetchingAmount, setFetchingAmount] = useState(false);
|
||||||
const [fetchedAmount, setFetchedAmount] = useState<number | null>(null);
|
const [fetchedAmount, setFetchedAmount] = useState<number | null>(null);
|
||||||
const [amountToBlock, setAmountToBlock] = useState<string>('');
|
const [amountToBlock, setAmountToBlock] = useState<string>('');
|
||||||
const [blockedDetails, setBlockedDetails] = useState<IOBlockedDetails | null>(null);
|
const [blockedDetails, setBlockedDetails] = useState<IOBlockedDetails | null>(null);
|
||||||
const [blockingBudget, setBlockingBudget] = useState(false);
|
const [blockingBudget, setBlockingBudget] = useState(false);
|
||||||
|
|
||||||
const maxIoRemarkChars = 300;
|
|
||||||
const ioRemarkChars = ioRemark.length;
|
|
||||||
|
|
||||||
// Load existing IO block details from apiRequest
|
// Load existing IO block details from apiRequest
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -78,9 +71,8 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
organizer?.email ||
|
organizer?.email ||
|
||||||
'Unknown User';
|
'Unknown User';
|
||||||
|
|
||||||
// Set IO number and remark from existing data
|
// Set IO number from existing data
|
||||||
setIoNumber(existingIONumber);
|
setIoNumber(existingIONumber);
|
||||||
setIoRemark(existingIORemark);
|
|
||||||
|
|
||||||
// Only set blocked details if amount is blocked
|
// Only set blocked details if amount is blocked
|
||||||
if (existingBlockedAmount > 0) {
|
if (existingBlockedAmount > 0) {
|
||||||
@ -112,7 +104,6 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
blockedDate: internalOrder.organizedAt || internalOrder.organized_at || new Date().toISOString(),
|
blockedDate: internalOrder.organizedAt || internalOrder.organized_at || new Date().toISOString(),
|
||||||
blockedBy: blockedByName,
|
blockedBy: blockedByName,
|
||||||
sapDocumentNumber: sapDocNumber,
|
sapDocumentNumber: sapDocNumber,
|
||||||
ioRemark: existingIORemark,
|
|
||||||
status: (internalOrder.status === 'BLOCKED' ? 'blocked' :
|
status: (internalOrder.status === 'BLOCKED' ? 'blocked' :
|
||||||
internalOrder.status === 'RELEASED' ? 'released' : 'blocked') as 'blocked' | 'released' | 'failed',
|
internalOrder.status === 'RELEASED' ? 'released' : 'blocked') as 'blocked' | 'released' | 'failed',
|
||||||
});
|
});
|
||||||
@ -123,7 +114,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [internalOrder, existingIONumber, existingIORemark, existingBlockedAmount, existingAvailableBalance, existingRemainingBalance, sapDocNumber, organizer]);
|
}, [internalOrder, existingIONumber, existingBlockedAmount, existingAvailableBalance, existingRemainingBalance, sapDocNumber, organizer]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch available budget from SAP
|
* Fetch available budget from SAP
|
||||||
@ -166,50 +157,12 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Save IO details (IO number and remark) without blocking budget
|
|
||||||
*/
|
|
||||||
const handleSaveIODetails = async () => {
|
|
||||||
if (!ioNumber.trim()) {
|
|
||||||
toast.error('Please enter an IO number');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ioRemark.trim()) {
|
|
||||||
toast.error('Please enter an IO remark');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!requestId) {
|
|
||||||
toast.error('Request ID not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setBlockingBudget(true);
|
|
||||||
try {
|
|
||||||
// Save only IO number and remark (no balance fields)
|
|
||||||
const payload = {
|
|
||||||
ioNumber: ioNumber.trim(),
|
|
||||||
ioRemark: ioRemark.trim(),
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateIODetails(requestId, payload);
|
|
||||||
|
|
||||||
toast.success('IO details saved successfully');
|
|
||||||
|
|
||||||
// Refresh request details
|
|
||||||
onRefresh?.();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Failed to save IO details:', error);
|
|
||||||
const errorMessage = error?.response?.data?.message || error?.message || 'Failed to save IO details';
|
|
||||||
toast.error(errorMessage);
|
|
||||||
} finally {
|
|
||||||
setBlockingBudget(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block budget in SAP system
|
* Block budget in SAP system
|
||||||
|
* This function:
|
||||||
|
* 1. Validates the IO number and amount
|
||||||
|
* 2. Calls SAP to block the budget
|
||||||
|
* 3. Saves IO number, blocked amount, and balance details to database
|
||||||
*/
|
*/
|
||||||
const handleBlockBudget = async () => {
|
const handleBlockBudget = async () => {
|
||||||
if (!ioNumber.trim() || fetchedAmount === null) {
|
if (!ioNumber.trim() || fetchedAmount === null) {
|
||||||
@ -217,11 +170,6 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ioRemark.trim()) {
|
|
||||||
toast.error('Please enter IO remark before blocking the amount');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!requestId) {
|
if (!requestId) {
|
||||||
toast.error('Request ID not found');
|
toast.error('Request ID not found');
|
||||||
return;
|
return;
|
||||||
@ -234,9 +182,9 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round to 2 decimal places to avoid floating point precision issues
|
// Round to exactly 2 decimal places to avoid floating point precision issues
|
||||||
// This ensures we send clean values like 240.00 instead of 239.9999999
|
// Use parseFloat with toFixed to ensure exact 2 decimal precision
|
||||||
const blockAmount = Math.round(blockAmountRaw * 100) / 100;
|
const blockAmount = parseFloat(blockAmountRaw.toFixed(2));
|
||||||
|
|
||||||
if (blockAmount > fetchedAmount) {
|
if (blockAmount > fetchedAmount) {
|
||||||
toast.error('Amount to block exceeds available IO budget');
|
toast.error('Amount to block exceeds available IO budget');
|
||||||
@ -250,12 +198,14 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
// Call updateIODetails with blockedAmount to block budget in SAP and store in database
|
// Call updateIODetails with blockedAmount to block budget in SAP and store in database
|
||||||
// This will store in internal_orders and claim_budget_tracking tables
|
// This will store in internal_orders and claim_budget_tracking tables
|
||||||
// Note: Backend will recalculate remainingBalance from SAP's response, so we pass it for reference only
|
// Note: Backend will recalculate remainingBalance from SAP's response, so we pass it for reference only
|
||||||
|
// Ensure all amounts are rounded to 2 decimal places for consistency
|
||||||
|
const roundedFetchedAmount = parseFloat(fetchedAmount.toFixed(2));
|
||||||
|
const calculatedRemaining = parseFloat((roundedFetchedAmount - blockAmount).toFixed(2));
|
||||||
const payload = {
|
const payload = {
|
||||||
ioNumber: ioNumber.trim(),
|
ioNumber: ioNumber.trim(),
|
||||||
ioRemark: ioRemark.trim(),
|
ioAvailableBalance: roundedFetchedAmount,
|
||||||
ioAvailableBalance: fetchedAmount,
|
|
||||||
ioBlockedAmount: blockAmount,
|
ioBlockedAmount: blockAmount,
|
||||||
ioRemainingBalance: fetchedAmount - blockAmount, // Calculated value (backend will use SAP's actual value)
|
ioRemainingBalance: calculatedRemaining, // Calculated value (backend will use SAP's actual value)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sending to backend
|
// Sending to backend
|
||||||
@ -301,8 +251,6 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
currentUser?.email ||
|
currentUser?.email ||
|
||||||
'Current User';
|
'Current User';
|
||||||
|
|
||||||
const savedIoRemark = updatedInternalOrder.ioRemark || updatedInternalOrder.io_remark || ioRemark.trim();
|
|
||||||
|
|
||||||
const blocked: IOBlockedDetails = {
|
const blocked: IOBlockedDetails = {
|
||||||
ioNumber: updatedInternalOrder.ioNumber || updatedInternalOrder.io_number || ioNumber,
|
ioNumber: updatedInternalOrder.ioNumber || updatedInternalOrder.io_number || ioNumber,
|
||||||
blockedAmount: savedBlockedAmount,
|
blockedAmount: savedBlockedAmount,
|
||||||
@ -311,7 +259,6 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
blockedDate: updatedInternalOrder.organizedAt || updatedInternalOrder.organized_at || new Date().toISOString(),
|
blockedDate: updatedInternalOrder.organizedAt || updatedInternalOrder.organized_at || new Date().toISOString(),
|
||||||
blockedBy: blockedByName,
|
blockedBy: blockedByName,
|
||||||
sapDocumentNumber: updatedInternalOrder.sapDocumentNumber || updatedInternalOrder.sap_document_number || '',
|
sapDocumentNumber: updatedInternalOrder.sapDocumentNumber || updatedInternalOrder.sap_document_number || '',
|
||||||
ioRemark: savedIoRemark,
|
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -371,52 +318,13 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* IO Remark Input */}
|
{/* Instructions when IO number is entered but not fetched */}
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="ioRemark" className="text-sm font-medium text-gray-900">
|
|
||||||
IO Remark <span className="text-red-500">*</span>
|
|
||||||
</Label>
|
|
||||||
<Textarea
|
|
||||||
id="ioRemark"
|
|
||||||
placeholder="Enter remarks about IO organization (required before blocking amount)"
|
|
||||||
value={ioRemark}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
if (value.length <= maxIoRemarkChars) {
|
|
||||||
setIoRemark(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
rows={3}
|
|
||||||
disabled={!!blockedDetails}
|
|
||||||
className={`bg-white text-sm min-h-[80px] resize-none ${
|
|
||||||
fetchedAmount !== null && !ioRemark.trim() && !blockedDetails
|
|
||||||
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div>
|
|
||||||
{fetchedAmount !== null && !ioRemark.trim() && !blockedDetails && (
|
|
||||||
<p className="text-xs text-red-600">IO remark is mandatory before blocking the amount</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-600">
|
|
||||||
<span>{ioRemarkChars}/{maxIoRemarkChars}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Save IO Details Button (shown when IO number is entered but amount not fetched) */}
|
|
||||||
{!fetchedAmount && !blockedDetails && ioNumber.trim() && (
|
{!fetchedAmount && !blockedDetails && ioNumber.trim() && (
|
||||||
<Button
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
onClick={handleSaveIODetails}
|
<p className="text-sm text-blue-800">
|
||||||
disabled={blockingBudget || !ioNumber.trim() || !ioRemark.trim()}
|
<strong>Next Step:</strong> Click "Fetch Amount" to validate the IO number and get available balance from SAP.
|
||||||
variant="outline"
|
</p>
|
||||||
className="w-full border-[#2d4a3e] text-[#2d4a3e] hover:bg-[#2d4a3e] hover:text-white"
|
</div>
|
||||||
>
|
|
||||||
{blockingBudget ? 'Saving...' : 'Save IO Details'}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Fetched Amount Display */}
|
{/* Fetched Amount Display */}
|
||||||
@ -459,15 +367,12 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
{/* Block Button */}
|
{/* Block Button */}
|
||||||
<Button
|
<Button
|
||||||
onClick={handleBlockBudget}
|
onClick={handleBlockBudget}
|
||||||
disabled={blockingBudget || !amountToBlock || parseFloat(amountToBlock) <= 0 || parseFloat(amountToBlock) > fetchedAmount || !ioRemark.trim()}
|
disabled={blockingBudget || !amountToBlock || parseFloat(amountToBlock) <= 0 || parseFloat(amountToBlock) > fetchedAmount}
|
||||||
className="w-full bg-[#2d4a3e] hover:bg-[#1f3329]"
|
className="w-full bg-[#2d4a3e] hover:bg-[#1f3329]"
|
||||||
>
|
>
|
||||||
<Target className="w-4 h-4 mr-2" />
|
<Target className="w-4 h-4 mr-2" />
|
||||||
{blockingBudget ? 'Blocking in SAP...' : 'Block IO in SAP'}
|
{blockingBudget ? 'Blocking in SAP...' : 'Block IO in SAP'}
|
||||||
</Button>
|
</Button>
|
||||||
{!ioRemark.trim() && (
|
|
||||||
<p className="text-xs text-red-600 mt-1">Please enter IO remark before blocking the amount</p>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -508,12 +413,6 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">SAP Document Number</p>
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">SAP Document Number</p>
|
||||||
<p className="text-sm font-semibold text-gray-900">{blockedDetails.sapDocumentNumber || 'N/A'}</p>
|
<p className="text-sm font-semibold text-gray-900">{blockedDetails.sapDocumentNumber || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
{blockedDetails.ioRemark && (
|
|
||||||
<div className="p-4">
|
|
||||||
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">IO Remark</p>
|
|
||||||
<p className="text-sm font-medium text-gray-900 whitespace-pre-wrap">{blockedDetails.ioRemark}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="p-4 bg-green-50">
|
<div className="p-4 bg-green-50">
|
||||||
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Blocked Amount</p>
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Blocked Amount</p>
|
||||||
<p className="text-xl font-bold text-green-700">
|
<p className="text-xl font-bold text-green-700">
|
||||||
|
|||||||
@ -47,7 +47,6 @@ interface WorkflowStep {
|
|||||||
// Special fields for dealer claims
|
// Special fields for dealer claims
|
||||||
ioDetails?: {
|
ioDetails?: {
|
||||||
ioNumber: string;
|
ioNumber: string;
|
||||||
ioRemark: string;
|
|
||||||
organizedBy: string;
|
organizedBy: string;
|
||||||
organizedAt: string;
|
organizedAt: string;
|
||||||
blockedAmount?: number;
|
blockedAmount?: number;
|
||||||
@ -422,19 +421,8 @@ export function DealerClaimWorkflowTab({
|
|||||||
const internalOrder = request?.internalOrder || request?.internal_order;
|
const internalOrder = request?.internalOrder || request?.internal_order;
|
||||||
|
|
||||||
if (internalOrder?.ioNumber || internalOrder?.io_number) {
|
if (internalOrder?.ioNumber || internalOrder?.io_number) {
|
||||||
// Try multiple field name variations for ioRemark
|
|
||||||
const ioRemarkValue =
|
|
||||||
internalOrder.ioRemark ||
|
|
||||||
internalOrder.io_remark ||
|
|
||||||
internalOrder.IORemark ||
|
|
||||||
internalOrder.IO_Remark ||
|
|
||||||
(internalOrder as any)?.ioRemark ||
|
|
||||||
(internalOrder as any)?.io_remark ||
|
|
||||||
'';
|
|
||||||
|
|
||||||
ioDetails = {
|
ioDetails = {
|
||||||
ioNumber: internalOrder.ioNumber || internalOrder.io_number || '',
|
ioNumber: internalOrder.ioNumber || internalOrder.io_number || '',
|
||||||
ioRemark: (ioRemarkValue && typeof ioRemarkValue === 'string' && ioRemarkValue.trim()) ? ioRemarkValue.trim() : 'N/A',
|
|
||||||
blockedAmount: internalOrder.ioBlockedAmount || internalOrder.io_blocked_amount || 0,
|
blockedAmount: internalOrder.ioBlockedAmount || internalOrder.io_blocked_amount || 0,
|
||||||
availableBalance: internalOrder.ioAvailableBalance || internalOrder.io_available_balance || 0,
|
availableBalance: internalOrder.ioAvailableBalance || internalOrder.io_available_balance || 0,
|
||||||
remainingBalance: internalOrder.ioRemainingBalance || internalOrder.io_remaining_balance || 0,
|
remainingBalance: internalOrder.ioRemainingBalance || internalOrder.io_remaining_balance || 0,
|
||||||
@ -818,7 +806,6 @@ export function DealerClaimWorkflowTab({
|
|||||||
// Handle IO approval (Department Lead step - found dynamically)
|
// Handle IO approval (Department Lead step - found dynamically)
|
||||||
const handleIOApproval = async (data: {
|
const handleIOApproval = async (data: {
|
||||||
ioNumber: string;
|
ioNumber: string;
|
||||||
ioRemark: string;
|
|
||||||
comments: string;
|
comments: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
@ -846,17 +833,15 @@ export function DealerClaimWorkflowTab({
|
|||||||
|
|
||||||
const levelId = step3Level.levelId || step3Level.level_id;
|
const levelId = step3Level.levelId || step3Level.level_id;
|
||||||
|
|
||||||
// First, update IO details using dealer claim API
|
// First, update IO details using dealer claim API (if needed)
|
||||||
// Only pass ioNumber and ioRemark - don't override existing balance values
|
// Only pass ioNumber - don't override existing balance values
|
||||||
// Balance values should already be stored when amount was blocked earlier
|
// Balance values should already be stored when amount was blocked earlier
|
||||||
await updateIODetails(requestId, {
|
await updateIODetails(requestId, {
|
||||||
ioNumber: data.ioNumber,
|
ioNumber: data.ioNumber,
|
||||||
ioRemark: data.ioRemark,
|
|
||||||
// Don't pass balance fields - let backend preserve existing values
|
// Don't pass balance fields - let backend preserve existing values
|
||||||
});
|
});
|
||||||
|
|
||||||
// Approve Step 3 using real API
|
// Approve Step 3 using real API
|
||||||
// IO remark is stored in claimDetails, so we just pass the comments
|
|
||||||
await approveLevel(requestId, levelId, data.comments);
|
await approveLevel(requestId, levelId, data.comments);
|
||||||
|
|
||||||
// Activity is logged by backend approval service - no need to create work note
|
// Activity is logged by backend approval service - no need to create work note
|
||||||
@ -1342,12 +1327,6 @@ export function DealerClaimWorkflowTab({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="pt-1.5 border-t border-blue-100">
|
|
||||||
<p className="text-xs text-gray-600 mb-1">IO Remark:</p>
|
|
||||||
<p className="text-sm text-gray-900">
|
|
||||||
{step.ioDetails.ioRemark || 'N/A'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="pt-1.5 border-t border-blue-100 text-xs text-gray-500">
|
<div className="pt-1.5 border-t border-blue-100 text-xs text-gray-500">
|
||||||
Organised by {step.ioDetails.organizedBy || step.approver || 'N/A'} on{' '}
|
Organised by {step.ioDetails.organizedBy || step.approver || 'N/A'} on{' '}
|
||||||
{step.ioDetails.organizedAt
|
{step.ioDetails.organizedAt
|
||||||
@ -1424,6 +1403,8 @@ export function DealerClaimWorkflowTab({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */}
|
||||||
|
{/* Use initiatorStepNumber to handle cases where approvers are added between steps */}
|
||||||
{/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */}
|
{/* Step 2 (or shifted step): Review Request - Only for initiator or step approver */}
|
||||||
{/* Use initiatorStepNumber to handle cases where approvers are added between steps */}
|
{/* Use initiatorStepNumber to handle cases where approvers are added between steps */}
|
||||||
{step.step === initiatorStepNumber && (isInitiator || isStep2Approver) && (
|
{step.step === initiatorStepNumber && (isInitiator || isStep2Approver) && (
|
||||||
@ -1724,6 +1705,7 @@ export function DealerClaimWorkflowTab({
|
|||||||
dealerName={dealerName}
|
dealerName={dealerName}
|
||||||
activityName={activityName}
|
activityName={activityName}
|
||||||
requestId={request?.id || request?.requestId}
|
requestId={request?.id || request?.requestId}
|
||||||
|
request={request}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dept Lead IO Approval Modal */}
|
{/* Dept Lead IO Approval Modal */}
|
||||||
@ -1735,7 +1717,6 @@ export function DealerClaimWorkflowTab({
|
|||||||
requestTitle={request?.title}
|
requestTitle={request?.title}
|
||||||
requestId={request?.id || request?.requestId}
|
requestId={request?.id || request?.requestId}
|
||||||
preFilledIONumber={request?.internalOrder?.ioNumber || request?.internalOrder?.io_number || request?.internal_order?.ioNumber || request?.internal_order?.io_number || undefined}
|
preFilledIONumber={request?.internalOrder?.ioNumber || request?.internalOrder?.io_number || request?.internal_order?.ioNumber || request?.internal_order?.io_number || undefined}
|
||||||
preFilledIORemark={request?.internalOrder?.ioRemark || request?.internalOrder?.io_remark || request?.internal_order?.ioRemark || request?.internal_order?.io_remark || undefined}
|
|
||||||
preFilledBlockedAmount={request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount || undefined}
|
preFilledBlockedAmount={request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount || undefined}
|
||||||
preFilledRemainingBalance={request?.internalOrder?.ioRemainingBalance || request?.internalOrder?.io_remaining_balance || request?.internal_order?.ioRemainingBalance || request?.internal_order?.io_remaining_balance || undefined}
|
preFilledRemainingBalance={request?.internalOrder?.ioRemainingBalance || request?.internalOrder?.io_remaining_balance || request?.internal_order?.ioRemainingBalance || request?.internal_order?.io_remaining_balance || undefined}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -28,7 +28,6 @@ interface DeptLeadIOApprovalModalProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onApprove: (data: {
|
onApprove: (data: {
|
||||||
ioNumber: string;
|
ioNumber: string;
|
||||||
ioRemark: string;
|
|
||||||
comments: string;
|
comments: string;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
onReject: (comments: string) => Promise<void>;
|
onReject: (comments: string) => Promise<void>;
|
||||||
@ -36,7 +35,6 @@ interface DeptLeadIOApprovalModalProps {
|
|||||||
requestId?: string;
|
requestId?: string;
|
||||||
// Pre-filled IO data from IO table
|
// Pre-filled IO data from IO table
|
||||||
preFilledIONumber?: string;
|
preFilledIONumber?: string;
|
||||||
preFilledIORemark?: string;
|
|
||||||
preFilledBlockedAmount?: number;
|
preFilledBlockedAmount?: number;
|
||||||
preFilledRemainingBalance?: number;
|
preFilledRemainingBalance?: number;
|
||||||
}
|
}
|
||||||
@ -49,12 +47,10 @@ export function DeptLeadIOApprovalModal({
|
|||||||
requestTitle,
|
requestTitle,
|
||||||
requestId: _requestId,
|
requestId: _requestId,
|
||||||
preFilledIONumber,
|
preFilledIONumber,
|
||||||
preFilledIORemark,
|
|
||||||
preFilledBlockedAmount,
|
preFilledBlockedAmount,
|
||||||
preFilledRemainingBalance,
|
preFilledRemainingBalance,
|
||||||
}: DeptLeadIOApprovalModalProps) {
|
}: DeptLeadIOApprovalModalProps) {
|
||||||
const [actionType, setActionType] = useState<'approve' | 'reject'>('approve');
|
const [actionType, setActionType] = useState<'approve' | 'reject'>('approve');
|
||||||
const [ioRemark, setIoRemark] = useState('');
|
|
||||||
const [comments, setComments] = useState('');
|
const [comments, setComments] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
@ -64,16 +60,12 @@ export function DeptLeadIOApprovalModal({
|
|||||||
// Reset form when modal opens/closes
|
// Reset form when modal opens/closes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
// Prefill IO remark from props if available
|
|
||||||
setIoRemark(preFilledIORemark || '');
|
|
||||||
setComments('');
|
setComments('');
|
||||||
setActionType('approve');
|
setActionType('approve');
|
||||||
}
|
}
|
||||||
}, [isOpen, preFilledIORemark]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const ioRemarkChars = ioRemark.length;
|
|
||||||
const commentsChars = comments.length;
|
const commentsChars = comments.length;
|
||||||
const maxIoRemarkChars = 300;
|
|
||||||
const maxCommentsChars = 500;
|
const maxCommentsChars = 500;
|
||||||
|
|
||||||
// Validate form
|
// Validate form
|
||||||
@ -81,13 +73,12 @@ export function DeptLeadIOApprovalModal({
|
|||||||
if (actionType === 'reject') {
|
if (actionType === 'reject') {
|
||||||
return comments.trim().length > 0;
|
return comments.trim().length > 0;
|
||||||
}
|
}
|
||||||
// For approve, need IO number (from table), IO remark, and comments
|
// For approve, need IO number (from table) and comments
|
||||||
return (
|
return (
|
||||||
ioNumber.trim().length > 0 && // IO number must exist from IO table
|
ioNumber.trim().length > 0 && // IO number must exist from IO table
|
||||||
ioRemark.trim().length > 0 &&
|
|
||||||
comments.trim().length > 0
|
comments.trim().length > 0
|
||||||
);
|
);
|
||||||
}, [actionType, ioNumber, ioRemark, comments]);
|
}, [actionType, ioNumber, comments]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!isFormValid) {
|
if (!isFormValid) {
|
||||||
@ -96,10 +87,6 @@ export function DeptLeadIOApprovalModal({
|
|||||||
toast.error('IO number is required. Please block amount from IO tab first.');
|
toast.error('IO number is required. Please block amount from IO tab first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!ioRemark.trim()) {
|
|
||||||
toast.error('Please enter IO remark');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!comments.trim()) {
|
if (!comments.trim()) {
|
||||||
toast.error('Please provide comments');
|
toast.error('Please provide comments');
|
||||||
@ -114,7 +101,6 @@ export function DeptLeadIOApprovalModal({
|
|||||||
if (actionType === 'approve') {
|
if (actionType === 'approve') {
|
||||||
await onApprove({
|
await onApprove({
|
||||||
ioNumber: ioNumber.trim(),
|
ioNumber: ioNumber.trim(),
|
||||||
ioRemark: ioRemark.trim(),
|
|
||||||
comments: comments.trim(),
|
comments: comments.trim(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -133,7 +119,6 @@ export function DeptLeadIOApprovalModal({
|
|||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setActionType('approve');
|
setActionType('approve');
|
||||||
setIoRemark('');
|
|
||||||
setComments('');
|
setComments('');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -275,33 +260,6 @@ export function DeptLeadIOApprovalModal({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* IO Remark - Read-only field (prefilled from IO tab, cannot be modified) */}
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="ioRemark" className="text-xs lg:text-sm font-semibold text-gray-900 flex items-center gap-2">
|
|
||||||
IO Remark <span className="text-red-500">*</span>
|
|
||||||
</Label>
|
|
||||||
<Textarea
|
|
||||||
id="ioRemark"
|
|
||||||
placeholder="Enter remarks about IO organization"
|
|
||||||
value={ioRemark || '—'}
|
|
||||||
disabled
|
|
||||||
readOnly
|
|
||||||
rows={3}
|
|
||||||
className="bg-gray-100 text-xs lg:text-sm min-h-[80px] lg:min-h-[100px] resize-none cursor-not-allowed"
|
|
||||||
/>
|
|
||||||
<div className="flex items-center justify-between text-xs">
|
|
||||||
{preFilledIORemark ? (
|
|
||||||
<span className="text-blue-600">
|
|
||||||
✓ Loaded from IO tab
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-red-600">
|
|
||||||
⚠️ IO remark not found. Please add remark in IO tab first.
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="text-gray-600">{ioRemarkChars}/{maxIoRemarkChars}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -64,6 +64,7 @@ interface InitiatorProposalApprovalModalProps {
|
|||||||
dealerName?: string;
|
dealerName?: string;
|
||||||
activityName?: string;
|
activityName?: string;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
|
request?: any; // Request object to check IO blocking status
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InitiatorProposalApprovalModal({
|
export function InitiatorProposalApprovalModal({
|
||||||
@ -75,10 +76,16 @@ export function InitiatorProposalApprovalModal({
|
|||||||
dealerName = 'Dealer',
|
dealerName = 'Dealer',
|
||||||
activityName = 'Activity',
|
activityName = 'Activity',
|
||||||
requestId: _requestId, // Prefix with _ to indicate intentionally unused
|
requestId: _requestId, // Prefix with _ to indicate intentionally unused
|
||||||
|
request,
|
||||||
}: InitiatorProposalApprovalModalProps) {
|
}: InitiatorProposalApprovalModalProps) {
|
||||||
const [comments, setComments] = useState('');
|
const [comments, setComments] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [actionType, setActionType] = useState<'approve' | 'reject' | null>(null);
|
const [actionType, setActionType] = useState<'approve' | 'reject' | null>(null);
|
||||||
|
|
||||||
|
// Check if IO is blocked (IO blocking moved to Requestor Evaluation level)
|
||||||
|
const internalOrder = request?.internalOrder || request?.internal_order;
|
||||||
|
const ioBlockedAmount = internalOrder?.ioBlockedAmount || internalOrder?.io_blocked_amount || 0;
|
||||||
|
const isIOBlocked = ioBlockedAmount > 0;
|
||||||
const [previewDocument, setPreviewDocument] = useState<{
|
const [previewDocument, setPreviewDocument] = useState<{
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
@ -545,45 +552,54 @@ export function InitiatorProposalApprovalModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end px-6 pb-6 pt-3 lg:pt-4 flex-shrink-0 border-t bg-gray-50">
|
<DialogFooter className="flex flex-col gap-2 sm:flex-row sm:justify-end px-6 pb-6 pt-3 lg:pt-4 flex-shrink-0 border-t bg-gray-50">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
className="border-2"
|
className="border-2 w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-col gap-2 w-full sm:w-auto">
|
||||||
<Button
|
<div className="flex flex-col sm:flex-row gap-2">
|
||||||
onClick={handleReject}
|
<Button
|
||||||
disabled={!comments.trim() || submitting}
|
onClick={handleReject}
|
||||||
variant="destructive"
|
disabled={!comments.trim() || submitting}
|
||||||
className="bg-red-600 hover:bg-red-700"
|
variant="destructive"
|
||||||
>
|
className="bg-red-600 hover:bg-red-700 w-full sm:w-auto"
|
||||||
{submitting && actionType === 'reject' ? (
|
>
|
||||||
'Rejecting...'
|
{submitting && actionType === 'reject' ? (
|
||||||
) : (
|
'Rejecting...'
|
||||||
<>
|
) : (
|
||||||
<XCircle className="w-4 h-4 mr-2" />
|
<>
|
||||||
Reject (Cancel Request)
|
<XCircle className="w-4 h-4 mr-2" />
|
||||||
</>
|
Reject (Cancel Request)
|
||||||
)}
|
</>
|
||||||
</Button>
|
)}
|
||||||
<Button
|
</Button>
|
||||||
onClick={handleApprove}
|
<Button
|
||||||
disabled={!comments.trim() || submitting}
|
onClick={handleApprove}
|
||||||
className="bg-green-600 hover:bg-green-700 text-white"
|
disabled={!comments.trim() || !isIOBlocked || submitting}
|
||||||
>
|
className="bg-green-600 hover:bg-green-700 text-white disabled:opacity-50 disabled:cursor-not-allowed w-full sm:w-auto"
|
||||||
{submitting && actionType === 'approve' ? (
|
title={!isIOBlocked ? 'Please block IO budget before approving' : ''}
|
||||||
'Approving...'
|
>
|
||||||
) : (
|
{submitting && actionType === 'approve' ? (
|
||||||
<>
|
'Approving...'
|
||||||
<CheckCircle className="w-4 h-4 mr-2" />
|
) : (
|
||||||
Approve (Continue to Dept Lead)
|
<>
|
||||||
</>
|
<CheckCircle className="w-4 h-4 mr-2" />
|
||||||
)}
|
Approve (Continue to Dept Lead)
|
||||||
</Button>
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/* Warning for IO not blocked - shown below Approve button */}
|
||||||
|
{!isIOBlocked && (
|
||||||
|
<p className="text-xs text-red-600 text-center sm:text-left">
|
||||||
|
Please block IO budget in the IO Tab before approving
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -153,34 +153,10 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
|
|
||||||
// Determine if user is department lead (find dynamically by levelName, not hardcoded step number)
|
// Determine if user is department lead (find dynamically by levelName, not hardcoded step number)
|
||||||
const currentUserId = (user as any)?.userId || '';
|
const currentUserId = (user as any)?.userId || '';
|
||||||
const currentUserEmail = (user as any)?.email?.toLowerCase() || '';
|
|
||||||
// Use approvalFlow (transformed) or approvals (raw) - both have step/levelNumber
|
|
||||||
const approvalFlow = apiRequest?.approvalFlow || [];
|
|
||||||
const approvals = apiRequest?.approvals || [];
|
|
||||||
|
|
||||||
// Find Department Lead step dynamically by levelName (handles step shifts when approvers are added)
|
|
||||||
const deptLeadLevel = approvalFlow.find((level: any) => {
|
|
||||||
const levelName = (level.levelName || level.level_name || '').toLowerCase();
|
|
||||||
return levelName.includes('department lead');
|
|
||||||
}) || approvals.find((level: any) => {
|
|
||||||
const levelName = (level.levelName || level.level_name || '').toLowerCase();
|
|
||||||
return levelName.includes('department lead');
|
|
||||||
}) || approvalFlow.find((level: any) =>
|
|
||||||
(level.step || level.levelNumber || level.level_number) === 3
|
|
||||||
) || approvals.find((level: any) =>
|
|
||||||
(level.levelNumber || level.level_number) === 3
|
|
||||||
); // Fallback to step 3 for backwards compatibility
|
|
||||||
|
|
||||||
const deptLeadUserId = deptLeadLevel?.approverId || deptLeadLevel?.approver_id || deptLeadLevel?.approver?.userId;
|
|
||||||
const deptLeadEmail = (deptLeadLevel?.approverEmail || deptLeadLevel?.approver_email || deptLeadLevel?.approver?.email || '').toLowerCase().trim();
|
|
||||||
|
|
||||||
// User is department lead if they match the Department Lead approver (regardless of status or step number)
|
|
||||||
const isDeptLead = (deptLeadUserId && deptLeadUserId === currentUserId) ||
|
|
||||||
(deptLeadEmail && currentUserEmail && deptLeadEmail === currentUserEmail);
|
|
||||||
|
|
||||||
// IO tab visibility for dealer claims
|
// IO tab visibility for dealer claims
|
||||||
// Show IO tab only for department lead (found dynamically, not hardcoded to step 3)
|
// Show IO tab for initiator (Requestor Evaluation level) - initiator can now fetch and block IO
|
||||||
const showIOTab = isDeptLead;
|
const showIOTab = isInitiator;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mergedMessages,
|
mergedMessages,
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export function ClosedRequestCard({ request, onViewRequest }: ClosedRequestCardP
|
|||||||
const templateTypeUpper = templateType?.toUpperCase() || '';
|
const templateTypeUpper = templateType?.toUpperCase() || '';
|
||||||
|
|
||||||
// Direct mapping from templateType
|
// Direct mapping from templateType
|
||||||
let templateLabel = 'Custom';
|
let templateLabel = 'Non-Templatized';
|
||||||
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
||||||
|
|
||||||
if (templateTypeUpper === 'DEALER CLAIM') {
|
if (templateTypeUpper === 'DEALER CLAIM') {
|
||||||
|
|||||||
@ -135,7 +135,7 @@ export function ClosedRequestsFilters({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Templates</SelectItem>
|
<SelectItem value="all">All Templates</SelectItem>
|
||||||
<SelectItem value="CUSTOM">Custom</SelectItem>
|
<SelectItem value="CUSTOM">Non-Templatized</SelectItem>
|
||||||
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Lightbulb, FileText } from 'lucide-react';
|
|||||||
export const REQUEST_TEMPLATES: RequestTemplate[] = [
|
export const REQUEST_TEMPLATES: RequestTemplate[] = [
|
||||||
{
|
{
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
name: 'Custom Request',
|
name: 'Non-Templatized',
|
||||||
description:
|
description:
|
||||||
'Create a custom request for unique business needs with full flexibility to define your own workflow and requirements',
|
'Create a custom request for unique business needs with full flexibility to define your own workflow and requirements',
|
||||||
category: 'General',
|
category: 'General',
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export function MyRequestsFilters({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Templates</SelectItem>
|
<SelectItem value="all">All Templates</SelectItem>
|
||||||
<SelectItem value="CUSTOM">Custom</SelectItem>
|
<SelectItem value="CUSTOM">Non-Templatized</SelectItem>
|
||||||
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -103,7 +103,7 @@ export function RequestCard({ request, index, onViewRequest }: RequestCardProps)
|
|||||||
const templateTypeUpper = templateType?.toUpperCase() || '';
|
const templateTypeUpper = templateType?.toUpperCase() || '';
|
||||||
|
|
||||||
// Direct mapping from templateType
|
// Direct mapping from templateType
|
||||||
let templateLabel = 'Custom';
|
let templateLabel = 'Non-Templatized';
|
||||||
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
||||||
|
|
||||||
if (templateTypeUpper === 'DEALER CLAIM') {
|
if (templateTypeUpper === 'DEALER CLAIM') {
|
||||||
|
|||||||
@ -393,7 +393,7 @@ export function OpenRequests({ onViewRequest }: OpenRequestsProps) {
|
|||||||
const templateTypeUpper = templateType?.toUpperCase() || '';
|
const templateTypeUpper = templateType?.toUpperCase() || '';
|
||||||
|
|
||||||
// Direct mapping from templateType
|
// Direct mapping from templateType
|
||||||
let templateLabel = 'Custom';
|
let templateLabel = 'Non-Templatized';
|
||||||
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
||||||
|
|
||||||
if (templateTypeUpper === 'DEALER CLAIM') {
|
if (templateTypeUpper === 'DEALER CLAIM') {
|
||||||
|
|||||||
@ -80,12 +80,27 @@ export function RequestDetailHeader({
|
|||||||
{/* Template Type Badge */}
|
{/* Template Type Badge */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const workflowType = request?.workflowType || request?.workflow_type;
|
const workflowType = request?.workflowType || request?.workflow_type;
|
||||||
const templateType = request?.templateType || request?.template_type;
|
const templateType = request?.templateType || request?.template_type || '';
|
||||||
const isClaimManagement = workflowType === 'CLAIM_MANAGEMENT' || templateType === 'claim-management';
|
const templateTypeUpper = templateType?.toUpperCase() || '';
|
||||||
const templateLabel = isClaimManagement ? 'Claim Management' : 'Custom';
|
|
||||||
const templateColor = isClaimManagement
|
// Check for dealer claim - support multiple formats
|
||||||
? 'bg-blue-100 !text-blue-700 border-blue-200'
|
const isDealerClaim =
|
||||||
: 'bg-purple-100 !text-purple-600 border-purple-200';
|
workflowType === 'CLAIM_MANAGEMENT' ||
|
||||||
|
workflowType === 'DEALER_CLAIM' ||
|
||||||
|
templateType === 'claim-management' ||
|
||||||
|
templateTypeUpper === 'DEALER CLAIM' ||
|
||||||
|
templateTypeUpper === 'DEALER_CLAIM';
|
||||||
|
|
||||||
|
// Direct mapping from templateType
|
||||||
|
let templateLabel = 'Non-Templatized';
|
||||||
|
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
||||||
|
|
||||||
|
if (isDealerClaim) {
|
||||||
|
templateLabel = 'Dealer Claim';
|
||||||
|
templateColor = 'bg-blue-100 !text-blue-700 border-blue-200';
|
||||||
|
} else if (templateTypeUpper === 'TEMPLATE') {
|
||||||
|
templateLabel = 'Template';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
@ -563,7 +563,7 @@ export function Requests({ onViewRequest }: RequestsProps) {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">All Templates</SelectItem>
|
<SelectItem value="all">All Templates</SelectItem>
|
||||||
<SelectItem value="CUSTOM">Custom</SelectItem>
|
<SelectItem value="CUSTOM">Non-Templatized</SelectItem>
|
||||||
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
<SelectItem value="DEALER CLAIM">Dealer Claim</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@ -102,7 +102,7 @@ export function RequestCard({ request, index, onViewRequest }: RequestCardProps)
|
|||||||
const templateTypeUpper = templateType?.toUpperCase() || '';
|
const templateTypeUpper = templateType?.toUpperCase() || '';
|
||||||
|
|
||||||
// Direct mapping from templateType
|
// Direct mapping from templateType
|
||||||
let templateLabel = 'Custom';
|
let templateLabel = 'Non-Templatized';
|
||||||
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
let templateColor = 'bg-purple-100 !text-purple-600 border-purple-200';
|
||||||
|
|
||||||
if (templateTypeUpper === 'DEALER CLAIM') {
|
if (templateTypeUpper === 'DEALER CLAIM') {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user