Added status to the requirements
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAuth, useProject } from '@/hooks'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { requirementService, validationService, relationshipService, commentService, tagService, priorityService, groupService } from '@/services'
|
||||
import { requirementService, validationService, relationshipService, commentService, tagService, priorityService, groupService, requirementStatusService } from '@/services'
|
||||
import type { Requirement } from '@/services/requirementService'
|
||||
import type { RelationshipType, RequirementLink, RequirementSearchResult } from '@/services/relationshipService'
|
||||
import type { Tag } from '@/services/tagService'
|
||||
import type { Priority } from '@/services/priorityService'
|
||||
import type { Group } from '@/services/groupService'
|
||||
import type { ValidationStatus, ValidationHistory, Comment, RequirementHistory } from '@/types'
|
||||
import type { ValidationStatus, ValidationHistory, Comment, RequirementHistory, RequirementStatus } from '@/types'
|
||||
|
||||
// Tab types
|
||||
type TabType = 'description' | 'relationships' | 'acceptance-criteria' | 'shared-comments' | 'validate' | 'history'
|
||||
@@ -64,9 +64,11 @@ export default function RequirementDetailPage() {
|
||||
const [editTagId, setEditTagId] = useState<number | ''>('')
|
||||
const [editPriorityId, setEditPriorityId] = useState<number | ''>('')
|
||||
const [editGroupIds, setEditGroupIds] = useState<number[]>([])
|
||||
const [editStatusId, setEditStatusId] = useState<number | ''>(1)
|
||||
const [availableTags, setAvailableTags] = useState<Tag[]>([])
|
||||
const [availablePriorities, setAvailablePriorities] = useState<Priority[]>([])
|
||||
const [availableGroups, setAvailableGroups] = useState<Group[]>([])
|
||||
const [availableStatuses, setAvailableStatuses] = useState<RequirementStatus[]>([])
|
||||
const [editOptionsLoading, setEditOptionsLoading] = useState(false)
|
||||
|
||||
// Requirement history state
|
||||
@@ -429,21 +431,24 @@ export default function RequirementDetailPage() {
|
||||
setEditTagId(requirement.tag.id)
|
||||
setEditPriorityId(requirement.priority?.id || '')
|
||||
setEditGroupIds(requirement.groups.map(g => g.id))
|
||||
setEditStatusId(requirement.status?.id || 1)
|
||||
setEditError(null)
|
||||
setShowEditModal(true)
|
||||
|
||||
// Fetch options if not already loaded
|
||||
if (availableTags.length === 0 || availablePriorities.length === 0 || availableGroups.length === 0) {
|
||||
if (availableTags.length === 0 || availablePriorities.length === 0 || availableGroups.length === 0 || availableStatuses.length === 0) {
|
||||
try {
|
||||
setEditOptionsLoading(true)
|
||||
const [tags, priorities, groups] = await Promise.all([
|
||||
const [tags, priorities, groups, statuses] = await Promise.all([
|
||||
tagService.getTags(),
|
||||
priorityService.getPriorities(),
|
||||
groupService.getGroups()
|
||||
groupService.getGroups(),
|
||||
requirementStatusService.getStatuses()
|
||||
])
|
||||
setAvailableTags(tags)
|
||||
setAvailablePriorities(priorities)
|
||||
setAvailableGroups(groups)
|
||||
setAvailableStatuses(statuses)
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch edit options:', err)
|
||||
setEditError('Failed to load options. Please try again.')
|
||||
@@ -475,7 +480,8 @@ export default function RequirementDetailPage() {
|
||||
req_desc: editReqDesc.trim() || undefined,
|
||||
tag_id: editTagId as number,
|
||||
priority_id: editPriorityId ? editPriorityId as number : undefined,
|
||||
group_ids: editGroupIds
|
||||
group_ids: editGroupIds,
|
||||
status_id: editStatusId ? editStatusId as number : undefined,
|
||||
})
|
||||
|
||||
// Refetch to ensure consistency (trigger updates version)
|
||||
@@ -515,6 +521,9 @@ export default function RequirementDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if requirement is in draft status
|
||||
const isDraftStatus = requirement?.status?.status_code === 'DRAFT'
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-white flex items-center justify-center">
|
||||
@@ -561,9 +570,33 @@ export default function RequirementDetailPage() {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-gray-800 mb-2">Description</h3>
|
||||
|
||||
{/* Draft indicator banner */}
|
||||
{isDraftStatus && (
|
||||
<div className="mb-4 p-3 bg-amber-50 border border-amber-300 border-dashed rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-amber-600 text-lg">📝</span>
|
||||
<div>
|
||||
<p className="font-semibold text-amber-800">Draft Requirement</p>
|
||||
<p className="text-sm text-amber-700">This requirement is still in draft status and is not finalized. It may be subject to changes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-sm text-gray-700 mb-2">
|
||||
<span className="font-semibold">Version:</span> {requirement.version}
|
||||
</p>
|
||||
<p className="text-sm text-gray-700 mb-2">
|
||||
<span className="font-semibold">Status:</span>{' '}
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
isDraftStatus
|
||||
? 'bg-amber-100 text-amber-800 border border-amber-300'
|
||||
: 'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{requirement.status?.status_name || 'Unknown'}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-sm text-gray-700 mb-2">
|
||||
<span className="font-semibold">Validation Status:</span>{' '}
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getValidationStatusStyle(validationStatus)}`}>
|
||||
@@ -1220,6 +1253,14 @@ export default function RequirementDetailPage() {
|
||||
<div className="max-w-5xl mx-auto px-8 py-8">
|
||||
{/* Requirement Header */}
|
||||
<div className="text-center mb-8">
|
||||
{/* Draft Status Indicator */}
|
||||
{isDraftStatus && (
|
||||
<div className="mb-4 inline-flex items-center gap-2 px-4 py-2 bg-amber-100 border-2 border-dashed border-amber-400 rounded-lg">
|
||||
<span className="text-amber-600 text-lg">📝</span>
|
||||
<span className="text-amber-800 font-medium">Draft - Not Finalized</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tag */}
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-2">{tagCode}</h2>
|
||||
|
||||
@@ -1491,6 +1532,28 @@ export default function RequirementDetailPage() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Status
|
||||
</label>
|
||||
<select
|
||||
value={editStatusId}
|
||||
onChange={(e) => setEditStatusId(e.target.value ? Number(e.target.value) : '')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent"
|
||||
disabled={editLoading}
|
||||
>
|
||||
{availableStatuses.map((status) => (
|
||||
<option key={status.id} value={status.id}>
|
||||
{status.status_name} {status.description ? `- ${status.description}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Draft requirements are not finalized and marked with a visual indicator.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
|
||||
@@ -22,6 +22,11 @@ const getValidationStatusStyle = (status: string): { bgColor: string; textColor:
|
||||
}
|
||||
}
|
||||
|
||||
// Check if requirement is in draft status
|
||||
const isDraftStatus = (statusCode: string | undefined): boolean => {
|
||||
return statusCode === 'DRAFT'
|
||||
}
|
||||
|
||||
export default function RequirementsPage() {
|
||||
const { user, logout, isAuditor } = useAuth()
|
||||
const { currentProject, isLoading: projectLoading } = useProject()
|
||||
@@ -426,17 +431,32 @@ export default function RequirementsPage() {
|
||||
const validationStatus = req.validation_status || 'Not Validated'
|
||||
const validationStyle = getValidationStatusStyle(validationStatus)
|
||||
const isStale = req.validation_version !== null && req.validation_version !== req.version
|
||||
const isDraft = isDraftStatus(req.status?.status_code)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={req.id}
|
||||
className="flex items-center rounded overflow-hidden border border-gray-300 bg-white"
|
||||
className={`flex items-center rounded overflow-hidden border bg-white ${
|
||||
isDraft
|
||||
? 'border-amber-400 border-dashed border-2'
|
||||
: 'border-gray-300'
|
||||
}`}
|
||||
>
|
||||
{/* Tag and name section */}
|
||||
<div className="px-4 py-4 min-w-[280px]">
|
||||
<span className="font-bold text-gray-800">
|
||||
{tagLabel} - {req.req_name}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{isDraft && (
|
||||
<span
|
||||
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800 border border-amber-300"
|
||||
title="This requirement is still in draft and not finalized"
|
||||
>
|
||||
📝 Draft
|
||||
</span>
|
||||
)}
|
||||
<span className="font-bold text-gray-800">
|
||||
{tagLabel} - {req.req_name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Group chips */}
|
||||
|
||||
@@ -7,6 +7,7 @@ export { priorityService } from './priorityService'
|
||||
export type { Priority } from './priorityService'
|
||||
export { requirementService } from './requirementService'
|
||||
export type { Requirement, RequirementCreateRequest, RequirementUpdateRequest } from './requirementService'
|
||||
export { requirementStatusService } from './requirementStatusService'
|
||||
export { projectService } from './projectService'
|
||||
export type { Project, ProjectCreateRequest, ProjectUpdateRequest } from './projectService'
|
||||
export { validationService } from './validationService'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Group } from './groupService'
|
||||
import { Tag } from './tagService'
|
||||
import { Priority } from './priorityService'
|
||||
import type { RequirementHistory } from '@/types'
|
||||
import type { RequirementHistory, RequirementStatus } from '@/types'
|
||||
|
||||
const API_BASE_URL = '/api'
|
||||
|
||||
@@ -16,6 +16,9 @@ export interface Requirement {
|
||||
tag: Tag
|
||||
priority: Priority | null
|
||||
groups: Group[]
|
||||
// Requirement lifecycle status (Draft, Regular)
|
||||
status: RequirementStatus | null
|
||||
// Validation status (Approved, Denied, etc.)
|
||||
validation_status: string | null
|
||||
validated_by: string | null
|
||||
validated_at: string | null
|
||||
@@ -31,6 +34,7 @@ export interface RequirementCreateRequest {
|
||||
req_desc?: string
|
||||
priority_id?: number
|
||||
group_ids?: number[]
|
||||
status_id?: number
|
||||
}
|
||||
|
||||
export interface RequirementUpdateRequest {
|
||||
@@ -39,6 +43,7 @@ export interface RequirementUpdateRequest {
|
||||
tag_id?: number
|
||||
priority_id?: number
|
||||
group_ids?: number[]
|
||||
status_id?: number
|
||||
}
|
||||
|
||||
class RequirementService {
|
||||
|
||||
39
frontend/src/services/requirementStatusService.ts
Normal file
39
frontend/src/services/requirementStatusService.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { RequirementStatus } from '@/types'
|
||||
|
||||
const API_BASE_URL = '/api'
|
||||
|
||||
class RequirementStatusService {
|
||||
/**
|
||||
* Get all requirement lifecycle statuses.
|
||||
*/
|
||||
async getStatuses(): Promise<RequirementStatus[]> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/requirement-statuses`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const statuses: RequirementStatus[] = await response.json()
|
||||
return statuses
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch requirement statuses:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a requirement is in draft status.
|
||||
*/
|
||||
isDraft(status: RequirementStatus | null): boolean {
|
||||
return status?.status_code === 'DRAFT'
|
||||
}
|
||||
}
|
||||
|
||||
export const requirementStatusService = new RequirementStatusService()
|
||||
@@ -1,5 +1,13 @@
|
||||
export * from './auth'
|
||||
|
||||
// Requirement Status types (lifecycle status)
|
||||
export interface RequirementStatus {
|
||||
id: number
|
||||
status_code: string
|
||||
status_name: string
|
||||
description: string | null
|
||||
}
|
||||
|
||||
// Validation types
|
||||
export interface ValidationStatus {
|
||||
id: number
|
||||
|
||||
Reference in New Issue
Block a user