spectator access removed for the IO tab and document upload
This commit is contained in:
parent
248b163239
commit
5162f6553c
@ -1587,12 +1587,13 @@ export function WorkNoteChat({ requestId, messages: externalMessages, onSend, sk
|
|||||||
})()}
|
})()}
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Type your message... Use @username to mention someone"
|
placeholder={effectiveIsSpectator ? "Spectators cannot send messages" : "Type your message... Use @username to mention someone"}
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
onKeyPress={handleKeyPress}
|
onKeyPress={handleKeyPress}
|
||||||
className="min-h-[50px] sm:min-h-[60px] resize-none border-gray-200 focus:ring-blue-500 focus:border-blue-500 w-full text-sm"
|
className="min-h-[50px] sm:min-h-[60px] resize-none border-gray-200 focus:ring-blue-500 focus:border-blue-500 w-full text-sm"
|
||||||
rows={2}
|
rows={2}
|
||||||
|
disabled={effectiveIsSpectator}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Emoji Picker Popup */}
|
{/* Emoji Picker Popup */}
|
||||||
@ -1632,27 +1633,27 @@ export function WorkNoteChat({ requestId, messages: externalMessages, onSend, sk
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-gray-500 h-8 w-8 p-0 hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
|
||||||
onClick={handleAttachmentClick}
|
onClick={handleAttachmentClick}
|
||||||
title="Attach file"
|
disabled={effectiveIsSpectator}
|
||||||
|
title={effectiveIsSpectator ? "Spectators cannot attach files" : "Attach file"}
|
||||||
>
|
>
|
||||||
<Paperclip className="h-4 w-4" />
|
<Paperclip className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-gray-500 h-8 w-8 p-0 hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
|
||||||
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
||||||
title="Add emoji"
|
disabled={effectiveIsSpectator}
|
||||||
|
title={effectiveIsSpectator ? "Spectators cannot add emojis" : "Add emoji"}
|
||||||
>
|
>
|
||||||
<Smile className="h-4 w-4" />
|
<Smile className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-gray-500 h-8 w-8 p-0 hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
|
||||||
onClick={() => setMessage(prev => prev + '@')}
|
onClick={() => setMessage(prev => prev + '@')}
|
||||||
title="Mention someone"
|
disabled={effectiveIsSpectator}
|
||||||
|
title={effectiveIsSpectator ? "Spectators cannot mention users" : "Mention someone"}
|
||||||
>
|
>
|
||||||
<AtSign className="h-4 w-4" />
|
<AtSign className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -1665,9 +1666,10 @@ export function WorkNoteChat({ requestId, messages: externalMessages, onSend, sk
|
|||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
disabled={!message.trim() && selectedFiles.length === 0}
|
disabled={(!message.trim() && selectedFiles.length === 0) || effectiveIsSpectator}
|
||||||
className="bg-blue-600 hover:bg-blue-700 h-8 sm:h-9 px-3 sm:px-4 disabled:opacity-50 disabled:cursor-not-allowed flex-shrink-0"
|
className="bg-blue-600 hover:bg-blue-700 h-8 sm:h-9 px-3 sm:px-4 disabled:opacity-50 disabled:cursor-not-allowed flex-shrink-0"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
title={effectiveIsSpectator ? "Spectators cannot send messages" : "Send"}
|
||||||
>
|
>
|
||||||
<Send className="h-4 w-4 sm:mr-2" />
|
<Send className="h-4 w-4 sm:mr-2" />
|
||||||
<span className="hidden sm:inline">Send</span>
|
<span className="hidden sm:inline">Send</span>
|
||||||
|
|||||||
@ -588,6 +588,7 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
|
|||||||
triggerFileInput={triggerFileInput}
|
triggerFileInput={triggerFileInput}
|
||||||
setPreviewDocument={setPreviewDocument}
|
setPreviewDocument={setPreviewDocument}
|
||||||
downloadDocument={downloadDocument}
|
downloadDocument={downloadDocument}
|
||||||
|
isSpectator={isSpectator}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ interface IOTabProps {
|
|||||||
request: any;
|
request: any;
|
||||||
apiRequest?: any;
|
apiRequest?: any;
|
||||||
onRefresh?: () => void;
|
onRefresh?: () => void;
|
||||||
|
isSpectator?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IOBlockedDetails {
|
interface IOBlockedDetails {
|
||||||
@ -33,7 +34,7 @@ interface IOBlockedDetails {
|
|||||||
status: 'blocked' | 'released' | 'failed' | 'pending';
|
status: 'blocked' | 'released' | 'failed' | 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
export function IOTab({ request, apiRequest, onRefresh, isSpectator = false }: IOTabProps) {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const requestId = apiRequest?.requestId || request?.requestId;
|
const requestId = apiRequest?.requestId || request?.requestId;
|
||||||
|
|
||||||
@ -335,12 +336,12 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
placeholder="Enter IO number (e.g., IO-2024-12345)"
|
placeholder="Enter IO number (e.g., IO-2024-12345)"
|
||||||
value={ioNumber}
|
value={ioNumber}
|
||||||
onChange={(e) => setIoNumber(e.target.value)}
|
onChange={(e) => setIoNumber(e.target.value)}
|
||||||
disabled={fetchingAmount || (blockedIOs.length > 0 && !isAdditionalBlockingNeeded)}
|
disabled={fetchingAmount || (blockedIOs.length > 0 && !isAdditionalBlockingNeeded) || isSpectator}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleFetchAmount}
|
onClick={handleFetchAmount}
|
||||||
disabled={!ioNumber.trim() || fetchingAmount || (blockedIOs.length > 0 && !isAdditionalBlockingNeeded)}
|
disabled={!ioNumber.trim() || fetchingAmount || (blockedIOs.length > 0 && !isAdditionalBlockingNeeded) || isSpectator}
|
||||||
className="bg-[#2d4a3e] hover:bg-[#1f3329]"
|
className="bg-[#2d4a3e] hover:bg-[#1f3329]"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
@ -398,6 +399,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
setAmountToBlock(val);
|
setAmountToBlock(val);
|
||||||
}}
|
}}
|
||||||
className="pl-8"
|
className="pl-8"
|
||||||
|
disabled={isSpectator}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{estimatedBudget > 0 && (
|
{estimatedBudget > 0 && (
|
||||||
@ -417,7 +419,8 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
!amountToBlock ||
|
!amountToBlock ||
|
||||||
parseFloat(amountToBlock) <= 0 ||
|
parseFloat(amountToBlock) <= 0 ||
|
||||||
parseFloat(amountToBlock) > fetchedAmount ||
|
parseFloat(amountToBlock) > fetchedAmount ||
|
||||||
(estimatedBudget > 0 && Math.abs((blockedIOs.reduce((s, i) => s + i.blockedAmount, 0) + parseFloat(amountToBlock)) - estimatedBudget) > 0.01)
|
(estimatedBudget > 0 && Math.abs((blockedIOs.reduce((s, i) => s + i.blockedAmount, 0) + parseFloat(amountToBlock)) - estimatedBudget) > 0.01) ||
|
||||||
|
isSpectator
|
||||||
}
|
}
|
||||||
className="w-full bg-[#2d4a3e] hover:bg-[#1f3329]"
|
className="w-full bg-[#2d4a3e] hover:bg-[#1f3329]"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -41,6 +41,7 @@ interface DealerClaimWorkflowTabProps {
|
|||||||
maxFileSizeMB: number;
|
maxFileSizeMB: number;
|
||||||
allowedFileTypes: string[];
|
allowedFileTypes: string[];
|
||||||
};
|
};
|
||||||
|
isSpectator?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkflowStep {
|
interface WorkflowStep {
|
||||||
@ -178,7 +179,8 @@ export function DealerClaimWorkflowTab({
|
|||||||
isInitiator,
|
isInitiator,
|
||||||
onSkipApprover: _onSkipApprover,
|
onSkipApprover: _onSkipApprover,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
documentPolicy
|
documentPolicy,
|
||||||
|
isSpectator = false
|
||||||
}: DealerClaimWorkflowTabProps) {
|
}: DealerClaimWorkflowTabProps) {
|
||||||
const [showProposalModal, setShowProposalModal] = useState(false);
|
const [showProposalModal, setShowProposalModal] = useState(false);
|
||||||
const [showApprovalModal, setShowApprovalModal] = useState(false);
|
const [showApprovalModal, setShowApprovalModal] = useState(false);
|
||||||
@ -2226,8 +2228,8 @@ export function DealerClaimWorkflowTab({
|
|||||||
(stepLevel?.levelName && stepLevel.levelName.toLowerCase().includes('dealer'));
|
(stepLevel?.levelName && stepLevel.levelName.toLowerCase().includes('dealer'));
|
||||||
const isUserAuthorized = isUserApproverForThisStep || (isDealerStep && isDealer);
|
const isUserAuthorized = isUserApproverForThisStep || (isDealerStep && isDealer);
|
||||||
|
|
||||||
// Step must be active AND user must be authorized
|
// Step must be active AND user must be authorized AND NOT a spectator
|
||||||
return isActive && isUserAuthorized;
|
return isActive && isUserAuthorized && !isSpectator;
|
||||||
})() && (
|
})() && (
|
||||||
<div className="mt-4 flex gap-2">
|
<div className="mt-4 flex gap-2">
|
||||||
{/* Step 1: Submit Proposal Button - Only for dealer or step 1 approver */}
|
{/* Step 1: Submit Proposal Button - Only for dealer or step 1 approver */}
|
||||||
|
|||||||
@ -153,14 +153,22 @@ 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();
|
||||||
|
|
||||||
|
const isDepartmentLead = (request?.approvalFlow || []).some((level: any) =>
|
||||||
|
level.role === 'Department Lead Approval' &&
|
||||||
|
level.approverEmail?.toLowerCase() === currentUserEmail
|
||||||
|
);
|
||||||
|
|
||||||
// IO tab visibility for dealer claims
|
// IO tab visibility for dealer claims
|
||||||
// Restricted: Hide from Dealers, show for internal roles (Initiator, Dept Lead, Finance, Admin)
|
// Restricted: Hide from Dealers, show only for Initiator and Department Lead
|
||||||
const isDealer = (user as any)?.jobTitle === 'Dealer' || (user as any)?.designation === 'Dealer';
|
const isDealer = (user as any)?.jobTitle === 'Dealer' || (user as any)?.designation === 'Dealer';
|
||||||
const isClaimManagement = request?.workflowType === 'CLAIM_MANAGEMENT' ||
|
const isClaimManagement = request?.workflowType === 'CLAIM_MANAGEMENT' ||
|
||||||
apiRequest?.workflowType === 'CLAIM_MANAGEMENT' ||
|
apiRequest?.workflowType === 'CLAIM_MANAGEMENT' ||
|
||||||
request?.templateType === 'claim-management';
|
request?.templateType === 'claim-management';
|
||||||
const showIOTab = isClaimManagement && !isDealer;
|
|
||||||
|
// Requirement: IO tab visible only for Initiator and Department Lead Approver
|
||||||
|
const showIOTab = isClaimManagement && !isDealer && (isInitiator || isDepartmentLead);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mergedMessages,
|
mergedMessages,
|
||||||
@ -616,6 +624,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
request={request}
|
request={request}
|
||||||
user={user}
|
user={user}
|
||||||
isInitiator={isInitiator}
|
isInitiator={isInitiator}
|
||||||
|
isSpectator={isSpectator}
|
||||||
onSkipApprover={(data) => {
|
onSkipApprover={(data) => {
|
||||||
if (!data.levelId) {
|
if (!data.levelId) {
|
||||||
alert('Level ID not available');
|
alert('Level ID not available');
|
||||||
@ -635,6 +644,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
request={request}
|
request={request}
|
||||||
apiRequest={apiRequest}
|
apiRequest={apiRequest}
|
||||||
onRefresh={refreshDetails}
|
onRefresh={refreshDetails}
|
||||||
|
isSpectator={isSpectator}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
)}
|
)}
|
||||||
@ -648,6 +658,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
triggerFileInput={triggerFileInput}
|
triggerFileInput={triggerFileInput}
|
||||||
setPreviewDocument={setPreviewDocument}
|
setPreviewDocument={setPreviewDocument}
|
||||||
downloadDocument={downloadDocument}
|
downloadDocument={downloadDocument}
|
||||||
|
isSpectator={isSpectator}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ interface DocumentsTabProps {
|
|||||||
triggerFileInput: () => void;
|
triggerFileInput: () => void;
|
||||||
setPreviewDocument: (doc: any) => void;
|
setPreviewDocument: (doc: any) => void;
|
||||||
downloadDocument: (documentId: string) => Promise<void>;
|
downloadDocument: (documentId: string) => Promise<void>;
|
||||||
|
isSpectator?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DocumentsTab({
|
export function DocumentsTab({
|
||||||
@ -25,6 +26,7 @@ export function DocumentsTab({
|
|||||||
triggerFileInput,
|
triggerFileInput,
|
||||||
setPreviewDocument,
|
setPreviewDocument,
|
||||||
downloadDocument,
|
downloadDocument,
|
||||||
|
isSpectator = false,
|
||||||
}: DocumentsTabProps) {
|
}: DocumentsTabProps) {
|
||||||
const isForm16 = (request?.templateType || request?.template_type || '').toString().toUpperCase() === 'FORM_16';
|
const isForm16 = (request?.templateType || request?.template_type || '').toString().toUpperCase() === 'FORM_16';
|
||||||
|
|
||||||
@ -105,7 +107,7 @@ export function DocumentsTab({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={triggerFileInput}
|
onClick={triggerFileInput}
|
||||||
disabled={uploadingDocument || request.status === 'closed'}
|
disabled={uploadingDocument || request.status === 'closed' || isSpectator}
|
||||||
className="gap-1 sm:gap-2 h-8 sm:h-9 text-xs sm:text-sm shrink-0"
|
className="gap-1 sm:gap-2 h-8 sm:h-9 text-xs sm:text-sm shrink-0"
|
||||||
data-testid="upload-document-btn"
|
data-testid="upload-document-btn"
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user