QoL features to the dashboard
This commit is contained in:
@@ -111,6 +111,31 @@ export default function DashboardPage() {
|
|||||||
return { total, validated, notValidated: total - validated }
|
return { total, validated, notValidated: total - validated }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get requirements needing attention (Denied or Partial/Partially Approved)
|
||||||
|
const getRequirementsNeedingAttention = () => {
|
||||||
|
return requirements.filter(req => {
|
||||||
|
const status = req.validation_status?.toLowerCase() || ''
|
||||||
|
return status === 'denied' || status.includes('partial')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get requirements validated in a previous version (for auditors)
|
||||||
|
// These are requirements where the current version is greater than the version that was validated
|
||||||
|
const getRequirementsNeedingRevalidation = () => {
|
||||||
|
return requirements.filter(req => {
|
||||||
|
// Must have been validated at least once
|
||||||
|
if (!req.validation_version) return false
|
||||||
|
// Current version must be greater than the version that was validated
|
||||||
|
return req.version > req.validation_version
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const requirementsNeedingAttention = getRequirementsNeedingAttention()
|
||||||
|
const requirementsNeedingRevalidation = getRequirementsNeedingRevalidation()
|
||||||
|
|
||||||
|
// Determine if user is an auditor (role_id=2)
|
||||||
|
const isAuditor = user?.role_id === 2
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -150,6 +175,20 @@ export default function DashboardPage() {
|
|||||||
navigate('/requirements', { state: { openCreateModal: true } })
|
navigate('/requirements', { state: { openCreateModal: true } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleNeedsAttentionClick = () => {
|
||||||
|
// Filter will be handled case-insensitively on RequirementsPage
|
||||||
|
navigate('/requirements', { state: { validationFilter: ['Denied', 'Partial'], needsAttention: true } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNeedsRevalidationClick = () => {
|
||||||
|
// Navigate to requirements page with a special filter for outdated validations
|
||||||
|
navigate('/requirements', { state: { needsRevalidation: true } })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRequirementClick = (reqId: number) => {
|
||||||
|
navigate(`/requirements/${reqId}`)
|
||||||
|
}
|
||||||
|
|
||||||
const handleProjectSelect = (project: typeof currentProject) => {
|
const handleProjectSelect = (project: typeof currentProject) => {
|
||||||
if (project) {
|
if (project) {
|
||||||
setCurrentProject(project)
|
setCurrentProject(project)
|
||||||
@@ -534,6 +573,192 @@ export default function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Needs Attention Section */}
|
||||||
|
{!loading && !error && currentProject && requirementsNeedingAttention.length > 0 && (
|
||||||
|
<div className="mt-10">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-amber-100 rounded-lg">
|
||||||
|
<svg className="w-5 h-5 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800">
|
||||||
|
Needs Attention
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Requirements with denied or partial validation
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleNeedsAttentionClick}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-amber-700 bg-amber-50 hover:bg-amber-100 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
View All ({requirementsNeedingAttention.length})
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Requirements Cards */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{requirementsNeedingAttention.slice(0, 6).map((req) => {
|
||||||
|
const status = req.validation_status?.toLowerCase() || ''
|
||||||
|
const isDenied = status === 'denied'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={req.id}
|
||||||
|
onClick={() => handleRequirementClick(req.id)}
|
||||||
|
className={`p-4 bg-white border-l-4 rounded-lg shadow-sm hover:shadow-md cursor-pointer transition-all ${
|
||||||
|
isDenied ? 'border-l-red-500' : 'border-l-yellow-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<span className="text-xs font-medium text-gray-500 bg-gray-100 px-2 py-0.5 rounded">
|
||||||
|
{req.tag.tag_code}
|
||||||
|
</span>
|
||||||
|
<span className={`text-xs font-medium px-2 py-0.5 rounded ${
|
||||||
|
isDenied
|
||||||
|
? 'bg-red-100 text-red-700'
|
||||||
|
: 'bg-yellow-100 text-yellow-700'
|
||||||
|
}`}>
|
||||||
|
{req.validation_status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h4 className="font-medium text-gray-800 text-sm line-clamp-2 mb-2">
|
||||||
|
{req.req_name}
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||||
|
<span>v{req.version}</span>
|
||||||
|
{req.validated_by && (
|
||||||
|
<span className="truncate max-w-[120px]" title={`Validated by ${req.validated_by}`}>
|
||||||
|
by {req.validated_by}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Show more indicator */}
|
||||||
|
{requirementsNeedingAttention.length > 6 && (
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<button
|
||||||
|
onClick={handleNeedsAttentionClick}
|
||||||
|
className="text-sm text-amber-600 hover:text-amber-700 font-medium"
|
||||||
|
>
|
||||||
|
+ {requirementsNeedingAttention.length - 6} more requirements need attention
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Needs Revalidation Section - Only for Auditors */}
|
||||||
|
{!loading && !error && currentProject && isAuditor && requirementsNeedingRevalidation.length > 0 && (
|
||||||
|
<div className="mt-10">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-blue-100 rounded-lg">
|
||||||
|
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800">
|
||||||
|
Needs Revalidation
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Requirements updated since last validation
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleNeedsRevalidationClick}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 hover:bg-blue-100 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
View All ({requirementsNeedingRevalidation.length})
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Requirements Cards */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{requirementsNeedingRevalidation.slice(0, 6).map((req) => {
|
||||||
|
const versionDiff = req.version - (req.validation_version || 0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={req.id}
|
||||||
|
onClick={() => handleRequirementClick(req.id)}
|
||||||
|
className="p-4 bg-white border-l-4 border-l-blue-500 rounded-lg shadow-sm hover:shadow-md cursor-pointer transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<span className="text-xs font-medium text-gray-500 bg-gray-100 px-2 py-0.5 rounded">
|
||||||
|
{req.tag.tag_code}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs font-medium px-2 py-0.5 rounded bg-blue-100 text-blue-700">
|
||||||
|
{versionDiff} version{versionDiff > 1 ? 's' : ''} behind
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h4 className="font-medium text-gray-800 text-sm line-clamp-2 mb-2">
|
||||||
|
{req.req_name}
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||||
|
<span>
|
||||||
|
v{req.validation_version} → v{req.version}
|
||||||
|
</span>
|
||||||
|
{req.validated_by && (
|
||||||
|
<span className="truncate max-w-[120px]" title={`Last validated by ${req.validated_by}`}>
|
||||||
|
by {req.validated_by}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Show more indicator */}
|
||||||
|
{requirementsNeedingRevalidation.length > 6 && (
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<button
|
||||||
|
onClick={handleNeedsRevalidationClick}
|
||||||
|
className="text-sm text-blue-600 hover:text-blue-700 font-medium"
|
||||||
|
>
|
||||||
|
+ {requirementsNeedingRevalidation.length - 6} more requirements need revalidation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Empty Attention State - Show positive message when no issues */}
|
||||||
|
{!loading && !error && currentProject && requirements.length > 0 && requirementsNeedingAttention.length === 0 && (
|
||||||
|
<div className="mt-10 p-6 bg-green-50 border border-green-200 rounded-xl">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="p-2 bg-green-100 rounded-lg">
|
||||||
|
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-green-800">All Clear!</h3>
|
||||||
|
<p className="text-sm text-green-600">
|
||||||
|
No requirements need attention. All validations are either approved or pending review.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,18 @@ import type { DeletedRequirement } from '@/types'
|
|||||||
|
|
||||||
// Get validation status color
|
// Get validation status color
|
||||||
const getValidationStatusStyle = (status: string): { bgColor: string; textColor: string } => {
|
const getValidationStatusStyle = (status: string): { bgColor: string; textColor: string } => {
|
||||||
switch (status) {
|
const statusLower = status.toLowerCase()
|
||||||
case 'Approved':
|
if (statusLower === 'approved') {
|
||||||
return { bgColor: 'bg-green-100', textColor: 'text-green-800' }
|
return { bgColor: 'bg-green-100', textColor: 'text-green-800' }
|
||||||
case 'Denied':
|
|
||||||
return { bgColor: 'bg-red-100', textColor: 'text-red-800' }
|
|
||||||
case 'Partial':
|
|
||||||
return { bgColor: 'bg-yellow-100', textColor: 'text-yellow-800' }
|
|
||||||
case 'Not Validated':
|
|
||||||
default:
|
|
||||||
return { bgColor: 'bg-gray-100', textColor: 'text-gray-600' }
|
|
||||||
}
|
}
|
||||||
|
if (statusLower === 'denied') {
|
||||||
|
return { bgColor: 'bg-red-100', textColor: 'text-red-800' }
|
||||||
|
}
|
||||||
|
if (statusLower.includes('partial')) {
|
||||||
|
return { bgColor: 'bg-yellow-100', textColor: 'text-yellow-800' }
|
||||||
|
}
|
||||||
|
// 'Not Validated' or default
|
||||||
|
return { bgColor: 'bg-gray-100', textColor: 'text-gray-600' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if requirement is in draft status
|
// Check if requirement is in draft status
|
||||||
@@ -46,6 +47,8 @@ export default function RequirementsPage() {
|
|||||||
// Filter state
|
// Filter state
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [selectedGroups, setSelectedGroups] = useState<number[]>([])
|
const [selectedGroups, setSelectedGroups] = useState<number[]>([])
|
||||||
|
const [selectedValidationStatuses, setSelectedValidationStatuses] = useState<string[]>([])
|
||||||
|
const [needsRevalidationFilter, setNeedsRevalidationFilter] = useState(false)
|
||||||
const [orderBy, setOrderBy] = useState<'Date' | 'Priority' | 'Name'>('Date')
|
const [orderBy, setOrderBy] = useState<'Date' | 'Priority' | 'Name'>('Date')
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
@@ -134,6 +137,21 @@ export default function RequirementsPage() {
|
|||||||
// Clear the state so it doesn't reopen on refresh
|
// Clear the state so it doesn't reopen on refresh
|
||||||
navigate(location.pathname, { replace: true, state: {} })
|
navigate(location.pathname, { replace: true, state: {} })
|
||||||
}
|
}
|
||||||
|
// Handle needs revalidation filter from navigation state
|
||||||
|
if (location.state?.needsRevalidation) {
|
||||||
|
setNeedsRevalidationFilter(true)
|
||||||
|
navigate(location.pathname, { replace: true, state: {} })
|
||||||
|
}
|
||||||
|
// Handle validation status filter from navigation state (needsAttention mode)
|
||||||
|
else if (location.state?.needsAttention) {
|
||||||
|
// Special handling for "needs attention" - will filter Denied and Partial case-insensitively
|
||||||
|
setSelectedValidationStatuses(['__NEEDS_ATTENTION__'])
|
||||||
|
navigate(location.pathname, { replace: true, state: {} })
|
||||||
|
} else if (location.state?.validationFilter && Array.isArray(location.state.validationFilter)) {
|
||||||
|
setSelectedValidationStatuses(location.state.validationFilter)
|
||||||
|
// Clear the state so it doesn't persist on refresh
|
||||||
|
navigate(location.pathname, { replace: true, state: {} })
|
||||||
|
}
|
||||||
}, [location.state, isAuditor, projectLoading, currentProject, navigate, location.pathname])
|
}, [location.state, isAuditor, projectLoading, currentProject, navigate, location.pathname])
|
||||||
|
|
||||||
// Fetch deleted requirements when panel is opened
|
// Fetch deleted requirements when panel is opened
|
||||||
@@ -159,7 +177,7 @@ export default function RequirementsPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter requirements based on search and selected groups
|
// Filter requirements based on search, selected groups, validation status, and revalidation needs
|
||||||
const filteredRequirements = requirements.filter(req => {
|
const filteredRequirements = requirements.filter(req => {
|
||||||
const matchesSearch = searchQuery === '' ||
|
const matchesSearch = searchQuery === '' ||
|
||||||
req.tag.tag_code.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
req.tag.tag_code.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
@@ -168,7 +186,29 @@ export default function RequirementsPage() {
|
|||||||
const matchesGroup = selectedGroups.length === 0 ||
|
const matchesGroup = selectedGroups.length === 0 ||
|
||||||
req.groups.some(g => selectedGroups.includes(g.id))
|
req.groups.some(g => selectedGroups.includes(g.id))
|
||||||
|
|
||||||
return matchesSearch && matchesGroup
|
// Handle needs revalidation filter (requirements with outdated validation)
|
||||||
|
const matchesRevalidation = !needsRevalidationFilter ||
|
||||||
|
(req.validation_version !== null && req.version > req.validation_version)
|
||||||
|
|
||||||
|
// Handle validation status filtering
|
||||||
|
let matchesValidationStatus = true
|
||||||
|
if (selectedValidationStatuses.length > 0) {
|
||||||
|
const reqStatus = req.validation_status || 'Not Validated'
|
||||||
|
const reqStatusLower = reqStatus.toLowerCase()
|
||||||
|
|
||||||
|
// Special case: filter for "needs attention" (Denied or Partial variants)
|
||||||
|
if (selectedValidationStatuses.includes('__NEEDS_ATTENTION__')) {
|
||||||
|
matchesValidationStatus = reqStatusLower === 'denied' || reqStatusLower.includes('partial')
|
||||||
|
} else {
|
||||||
|
// Normal case-insensitive matching
|
||||||
|
matchesValidationStatus = selectedValidationStatuses.some(
|
||||||
|
status => status.toLowerCase() === reqStatusLower ||
|
||||||
|
(status.toLowerCase() === 'partial' && reqStatusLower.includes('partial'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchesSearch && matchesGroup && matchesValidationStatus && matchesRevalidation
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sort requirements
|
// Sort requirements
|
||||||
@@ -199,9 +239,19 @@ export default function RequirementsPage() {
|
|||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setSearchQuery('')
|
setSearchQuery('')
|
||||||
setSelectedGroups([])
|
setSelectedGroups([])
|
||||||
|
setSelectedValidationStatuses([])
|
||||||
|
setNeedsRevalidationFilter(false)
|
||||||
setSearchParams({})
|
setSearchParams({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleValidationStatusToggle = (status: string) => {
|
||||||
|
setSelectedValidationStatuses(prev =>
|
||||||
|
prev.includes(status)
|
||||||
|
? prev.filter(s => s !== status)
|
||||||
|
: [...prev, status]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
// Filtering is automatic, but this could trigger a fresh API call
|
// Filtering is automatic, but this could trigger a fresh API call
|
||||||
}
|
}
|
||||||
@@ -506,6 +556,72 @@ export default function RequirementsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filter Validation Status */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<p className="text-sm text-gray-600">Filter Validation Status</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{needsRevalidationFilter && (
|
||||||
|
<span className="text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded-full">
|
||||||
|
Showing: Needs Revalidation
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{selectedValidationStatuses.includes('__NEEDS_ATTENTION__') && (
|
||||||
|
<span className="text-xs bg-amber-100 text-amber-700 px-2 py-1 rounded-full">
|
||||||
|
Showing: Needs Attention
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{/* Needs Revalidation button - only for auditors */}
|
||||||
|
{isAuditor && (
|
||||||
|
<button
|
||||||
|
onClick={() => setNeedsRevalidationFilter(!needsRevalidationFilter)}
|
||||||
|
className={`px-3 py-1.5 rounded-full text-sm font-medium border-2 transition-colors flex items-center gap-1.5 ${
|
||||||
|
needsRevalidationFilter
|
||||||
|
? 'bg-blue-100 border-blue-500 text-blue-800'
|
||||||
|
: 'bg-white border-gray-300 text-gray-600 hover:border-blue-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
Needs Revalidation
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{['Approved', 'Denied', 'Partially Approved', 'Not Validated'].map((status) => {
|
||||||
|
// Check if this status is selected (direct match or via needs attention mode)
|
||||||
|
const isNeedsAttentionMode = selectedValidationStatuses.includes('__NEEDS_ATTENTION__')
|
||||||
|
const statusLower = status.toLowerCase()
|
||||||
|
const isSelected = selectedValidationStatuses.some(s => s.toLowerCase() === statusLower) ||
|
||||||
|
(isNeedsAttentionMode && (status === 'Denied' || statusLower.includes('partial')))
|
||||||
|
const statusStyles: Record<string, string> = {
|
||||||
|
'Approved': isSelected ? 'bg-green-100 border-green-500 text-green-800' : 'bg-white border-gray-300 text-gray-600 hover:border-green-400',
|
||||||
|
'Denied': isSelected ? 'bg-red-100 border-red-500 text-red-800' : 'bg-white border-gray-300 text-gray-600 hover:border-red-400',
|
||||||
|
'Partially Approved': isSelected ? 'bg-yellow-100 border-yellow-500 text-yellow-800' : 'bg-white border-gray-300 text-gray-600 hover:border-yellow-400',
|
||||||
|
'Not Validated': isSelected ? 'bg-gray-200 border-gray-500 text-gray-800' : 'bg-white border-gray-300 text-gray-600 hover:border-gray-400',
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={status}
|
||||||
|
onClick={() => {
|
||||||
|
// If in needs attention mode, switch to regular mode first
|
||||||
|
if (isNeedsAttentionMode) {
|
||||||
|
setSelectedValidationStatuses([status])
|
||||||
|
} else {
|
||||||
|
handleValidationStatusToggle(status)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`px-3 py-1.5 rounded-full text-sm font-medium border-2 transition-colors ${statusStyles[status]}`}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Order By */}
|
{/* Order By */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user