added shared summary notify and allowed current approver to add approver
This commit is contained in:
parent
ac10c461e4
commit
09ca04fee6
@ -108,6 +108,20 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
||||
|
||||
// Navigate to the request if URL provided
|
||||
if (notification.actionUrl && onNavigate) {
|
||||
// PRIORITY: For shared summaries or specific action URLs, use them directly
|
||||
if (notification.actionUrl && (
|
||||
notification.notificationType === 'summary_shared' ||
|
||||
notification.actionUrl.includes('shared-summaries')
|
||||
)) {
|
||||
console.log('[PageLayout] Navigating to shared summary:', notification.actionUrl);
|
||||
const targetUrl = notification.actionUrl.startsWith('/')
|
||||
? notification.actionUrl.substring(1)
|
||||
: notification.actionUrl;
|
||||
onNavigate(targetUrl);
|
||||
setNotificationsOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract request number from URL (e.g., /request/REQ-2025-12345)
|
||||
const requestNumber = notification.metadata?.requestNumber;
|
||||
if (requestNumber) {
|
||||
|
||||
@ -79,10 +79,10 @@ interface WorkNoteChatProps {
|
||||
skipSocketJoin?: boolean; // Set to true when embedded in RequestDetail (to avoid double join)
|
||||
requestTitle?: string; // Optional title for display
|
||||
onAttachmentsExtracted?: (attachments: any[]) => void; // Callback to pass attachments to parent
|
||||
isInitiator?: boolean; // Whether current user is the initiator
|
||||
isSpectator?: boolean; // Whether current user is a spectator (view-only)
|
||||
currentLevels?: any[]; // Current approval levels for add approver modal
|
||||
onAddApprover?: (email: string, tatHours: number, level: number) => Promise<void>; // Callback to add approver
|
||||
canAddApprover?: boolean; // Whether the current user can add approvers
|
||||
maxApprovalLevels?: number; // Maximum allowed approval levels from system policy
|
||||
onPolicyViolation?: (violations: Array<{ type: string; message: string; currentValue?: number; maxValue?: number }>) => void; // Callback for policy violations
|
||||
}
|
||||
@ -145,7 +145,7 @@ const FileIcon = ({ type }: { type: string }) => {
|
||||
return <Paperclip className={`${iconClass} text-gray-600`} />;
|
||||
};
|
||||
|
||||
export function WorkNoteChat({ requestId, messages: externalMessages, onSend, skipSocketJoin = false, requestTitle, onAttachmentsExtracted, isInitiator = false, isSpectator = false, currentLevels = [], onAddApprover, maxApprovalLevels, onPolicyViolation }: WorkNoteChatProps) {
|
||||
export function WorkNoteChat({ requestId, messages: externalMessages, onSend, skipSocketJoin = false, requestTitle, onAttachmentsExtracted, isSpectator = false, currentLevels = [], onAddApprover, canAddApprover, maxApprovalLevels, onPolicyViolation }: WorkNoteChatProps) {
|
||||
const routeParams = useParams<{ requestId: string }>();
|
||||
const effectiveRequestId = requestId || routeParams.requestId || '';
|
||||
const [message, setMessage] = useState('');
|
||||
@ -1743,8 +1743,8 @@ export function WorkNoteChat({ requestId, messages: externalMessages, onSend, sk
|
||||
<div className="p-4 sm:p-6 flex-shrink-0">
|
||||
<h4 className="font-semibold text-gray-900 mb-3 text-sm sm:text-base">Quick Actions</h4>
|
||||
<div className="space-y-2">
|
||||
{/* Only initiator can add approvers */}
|
||||
{isInitiator && (
|
||||
{/* Only initiator or current approver can add approvers in custom flows */}
|
||||
{canAddApprover && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@ -1805,7 +1805,7 @@ export function WorkNoteChat({ requestId, messages: externalMessages, onSend, sk
|
||||
)}
|
||||
|
||||
{/* Add Approver Modal - Hide for spectators */}
|
||||
{!effectiveIsSpectator && isInitiator && (
|
||||
{!effectiveIsSpectator && canAddApprover && (
|
||||
<AddApproverModal
|
||||
open={showAddApproverModal}
|
||||
onClose={() => setShowAddApproverModal(false)}
|
||||
|
||||
@ -56,7 +56,8 @@ export function ApprovalWorkflowStep({
|
||||
email: '',
|
||||
name: '',
|
||||
level: i + 1,
|
||||
tat: '' as any
|
||||
tat: 8 as any,
|
||||
tatType: 'hours'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -373,9 +374,9 @@ export function ApprovalWorkflowStep({
|
||||
<Input
|
||||
id={`tat-${level}`}
|
||||
type="number"
|
||||
placeholder={approver.tatType === 'days' ? '7' : '24'}
|
||||
placeholder={approver.tatType === 'days' ? '1' : '8'}
|
||||
min="1"
|
||||
max={approver.tatType === 'days' ? '30' : '720'}
|
||||
max={approver.tatType === 'days' ? '90' : '720'}
|
||||
value={approver.tat || ''}
|
||||
onChange={(e) => {
|
||||
const newApprovers = [...formData.approvers];
|
||||
@ -446,28 +447,7 @@ export function ApprovalWorkflowStep({
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-semibold text-emerald-900">TAT Summary</h4>
|
||||
<div className="text-right">
|
||||
{(() => {
|
||||
// Calculate total calendar days (for display)
|
||||
// Days: count as calendar days
|
||||
// Hours: convert to calendar days (hours / 24)
|
||||
const totalCalendarDays = formData.approvers?.reduce((sum: number, a: any) => {
|
||||
const tat = Number(a.tat || 0);
|
||||
const tatType = a.tatType || 'hours';
|
||||
if (tatType === 'days') {
|
||||
return sum + tat; // Calendar days
|
||||
} else {
|
||||
return sum + (tat / 24); // Convert hours to calendar days
|
||||
}
|
||||
}, 0) || 0;
|
||||
const displayDays = Math.ceil(totalCalendarDays);
|
||||
return (
|
||||
<>
|
||||
<div className="text-lg font-bold text-emerald-800">{displayDays} {displayDays === 1 ? 'Day' : 'Days'}</div>
|
||||
<div className="text-xs text-emerald-600">Total Duration</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
@ -475,8 +455,7 @@ export function ApprovalWorkflowStep({
|
||||
{formData.approvers?.map((approver: any, idx: number) => {
|
||||
const tat = Number(approver.tat || 0);
|
||||
const tatType = approver.tatType || 'hours';
|
||||
// Convert days to hours: 1 day = 24 hours
|
||||
const hours = tatType === 'days' ? tat * 24 : tat;
|
||||
const hours = tatType === 'days' ? tat * 8 : tat;
|
||||
if (!tat) return null;
|
||||
return (
|
||||
<div key={idx} className="bg-white/60 p-2 rounded border border-emerald-100">
|
||||
@ -489,15 +468,12 @@ export function ApprovalWorkflowStep({
|
||||
})}
|
||||
</div>
|
||||
{(() => {
|
||||
// Convert all TAT to hours first
|
||||
// Days: 1 day = 24 hours
|
||||
// Hours: already in hours
|
||||
const totalHours = formData.approvers?.reduce((sum: number, a: any) => {
|
||||
const tat = Number(a.tat || 0);
|
||||
const tatType = a.tatType || 'hours';
|
||||
if (tatType === 'days') {
|
||||
// 1 day = 24 hours
|
||||
return sum + (tat * 24);
|
||||
// 1 day = 8 working hours
|
||||
return sum + (tat * 8);
|
||||
} else {
|
||||
return sum + tat;
|
||||
}
|
||||
@ -509,12 +485,12 @@ export function ApprovalWorkflowStep({
|
||||
<div className="bg-white/80 p-3 rounded border border-emerald-200">
|
||||
<div className="grid grid-cols-2 gap-4 text-center">
|
||||
<div>
|
||||
<div className="text-lg font-bold text-emerald-800">{totalHours}{totalHours === 1 ? 'h' : 'h'}</div>
|
||||
<div className="text-lg font-bold text-emerald-800">{totalHours}h</div>
|
||||
<div className="text-xs text-emerald-600">Total Hours</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-lg font-bold text-emerald-800">{workingDays}</div>
|
||||
<div className="text-xs text-emerald-600">Working Days*</div>
|
||||
<div className="text-lg font-bold text-emerald-800">{workingDays} {workingDays === 1 ? 'Day' : 'Days'}</div>
|
||||
<div className="text-xs text-emerald-600">Total Days*</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-emerald-600 mt-2 text-center">*Based on 8-hour working days</p>
|
||||
|
||||
@ -47,6 +47,9 @@ import { CustomOverviewTab, CustomWorkflowTab } from '../index';
|
||||
import { SharedComponents } from '@/shared/components';
|
||||
const { DocumentsTab, ActivityTab, WorkNotesTab, SummaryTab, RequestDetailHeader, QuickActionsSidebar, RequestDetailModals } = SharedComponents;
|
||||
|
||||
// Utilities
|
||||
import { isCustomRequest } from '@/utils/requestTypeUtils';
|
||||
|
||||
// Other components
|
||||
import { ShareSummaryModal } from '@/components/modals/ShareSummaryModal';
|
||||
import { getSummaryDetails, getSummaryByRequestId, type SummaryDetails } from '@/services/summaryApi';
|
||||
@ -569,10 +572,10 @@ function CustomRequestDetailInner({ requestId: propRequestId, onBack, dynamicReq
|
||||
requestTitle={request.title}
|
||||
mergedMessages={mergedMessages}
|
||||
setWorkNoteAttachments={setWorkNoteAttachments}
|
||||
isInitiator={isInitiator}
|
||||
isSpectator={isSpectator}
|
||||
currentLevels={currentLevels}
|
||||
onAddApprover={handleAddApprover}
|
||||
canAddApprover={(isInitiator || !!currentApprovalLevel) && isCustomRequest(apiRequest)}
|
||||
maxApprovalLevels={systemPolicy.maxApprovalLevels}
|
||||
onPolicyViolation={(violations) => setPolicyViolationModal({ open: true, violations })}
|
||||
/>
|
||||
|
||||
@ -661,10 +661,10 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
||||
requestTitle={request.title}
|
||||
mergedMessages={mergedMessages}
|
||||
setWorkNoteAttachments={setWorkNoteAttachments}
|
||||
isInitiator={isInitiator}
|
||||
isSpectator={isSpectator}
|
||||
currentLevels={currentLevels}
|
||||
onAddApprover={handleAddApprover}
|
||||
canAddApprover={false} // Explicitly disabled for Dealer Claims
|
||||
maxApprovalLevels={systemPolicy.maxApprovalLevels}
|
||||
onPolicyViolation={(violations) => setPolicyViolationModal({ open: true, violations })}
|
||||
/>
|
||||
|
||||
@ -76,6 +76,19 @@ export function Notifications({ onNavigate }: NotificationsProps) {
|
||||
|
||||
// Navigate to the request if URL provided
|
||||
if (notification.actionUrl && onNavigate) {
|
||||
// PRIORITY: For shared summaries or specific action URLs, use them directly
|
||||
if (notification.actionUrl && (
|
||||
notification.notificationType === 'summary_shared' ||
|
||||
notification.actionUrl.includes('shared-summaries')
|
||||
)) {
|
||||
console.log('[Notifications] Navigating to shared summary:', notification.actionUrl);
|
||||
const targetUrl = notification.actionUrl.startsWith('/')
|
||||
? notification.actionUrl.substring(1)
|
||||
: notification.actionUrl;
|
||||
onNavigate(targetUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
const requestNumber = notification.metadata?.requestNumber;
|
||||
if (requestNumber) {
|
||||
let navigationUrl = `request/${requestNumber}`;
|
||||
|
||||
@ -13,6 +13,7 @@ import notificationApi, { type Notification } from '@/services/notificationApi';
|
||||
import { ProcessDetailsCard } from '@/dealer-claim/components/request-detail/claim-cards';
|
||||
import { isClaimManagementRequest } from '@/utils/claimRequestUtils';
|
||||
import { determineUserRole, getRoleBasedVisibility, mapToClaimManagementRequest } from '@/utils/claimDataMapper';
|
||||
import { isCustomRequest } from '@/utils/requestTypeUtils';
|
||||
|
||||
interface QuickActionsSidebarProps {
|
||||
request: any;
|
||||
@ -145,8 +146,8 @@ export function QuickActionsSidebar({
|
||||
<CardTitle className="text-sm sm:text-base">Quick Actions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{/* Add Approver */}
|
||||
{isInitiator && request.status !== 'closed' && (
|
||||
{/* Add Approver - Only for Initiator or Current Approver in Custom Workflows */}
|
||||
{(isInitiator || currentApprovalLevel) && isCustomRequest(request) && request.status !== 'closed' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full justify-start gap-2 bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:text-gray-900 h-9 sm:h-10 text-xs sm:text-sm"
|
||||
|
||||
@ -9,10 +9,10 @@ interface WorkNotesTabProps {
|
||||
requestTitle: string;
|
||||
mergedMessages: any[];
|
||||
setWorkNoteAttachments: (attachments: any[]) => void;
|
||||
isInitiator: boolean;
|
||||
isSpectator: boolean;
|
||||
currentLevels: any[];
|
||||
onAddApprover: (email: string, tatHours: number, level: number) => Promise<void>;
|
||||
canAddApprover?: boolean;
|
||||
maxApprovalLevels?: number;
|
||||
onPolicyViolation?: (violations: Array<{ type: string; message: string; currentValue?: number; maxValue?: number }>) => void;
|
||||
}
|
||||
@ -22,10 +22,10 @@ export function WorkNotesTab({
|
||||
requestTitle,
|
||||
mergedMessages,
|
||||
setWorkNoteAttachments,
|
||||
isInitiator,
|
||||
isSpectator,
|
||||
currentLevels,
|
||||
onAddApprover,
|
||||
canAddApprover,
|
||||
maxApprovalLevels,
|
||||
onPolicyViolation,
|
||||
}: WorkNotesTabProps) {
|
||||
@ -37,10 +37,10 @@ export function WorkNotesTab({
|
||||
skipSocketJoin={true}
|
||||
messages={mergedMessages}
|
||||
onAttachmentsExtracted={setWorkNoteAttachments}
|
||||
isInitiator={isInitiator}
|
||||
isSpectator={isSpectator}
|
||||
currentLevels={currentLevels}
|
||||
onAddApprover={onAddApprover}
|
||||
canAddApprover={canAddApprover}
|
||||
maxApprovalLevels={maxApprovalLevels}
|
||||
onPolicyViolation={onPolicyViolation}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user