diff --git a/frontend/src/pages/RequirementDetailPage.tsx b/frontend/src/pages/RequirementDetailPage.tsx index 09fd9f7..febd230 100644 --- a/frontend/src/pages/RequirementDetailPage.tsx +++ b/frontend/src/pages/RequirementDetailPage.tsx @@ -55,6 +55,17 @@ export default function RequirementDetailPage() { const [deletingCommentId, setDeletingCommentId] = useState(null) const [deletingReplyId, setDeletingReplyId] = useState(null) + // Delete confirmation modal state + const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false) + const [deleteConfirmData, setDeleteConfirmData] = useState<{ + type: 'comment' | 'reply' | 'link' + id: number + parentId?: number + title: string + message: string + } | null>(null) + const [deleteConfirmLoading, setDeleteConfirmLoading] = useState(false) + // Edit modal state const [showEditModal, setShowEditModal] = useState(false) const [editLoading, setEditLoading] = useState(false) @@ -299,16 +310,23 @@ export default function RequirementDetailPage() { } // Handle deleting a relationship link - const handleDeleteLink = async (linkId: number) => { - if (!confirm('Are you sure you want to delete this relationship?')) return + const openDeleteLinkModal = (linkId: number) => { + setDeleteConfirmData({ + type: 'link', + id: linkId, + title: 'Delete Relationship', + message: 'Are you sure you want to delete this relationship? This action cannot be undone.' + }) + setShowDeleteConfirmModal(true) + } + const executeDeleteLink = async (linkId: number) => { try { setDeletingLinkId(linkId) await relationshipService.deleteLink(linkId) setRelationshipLinks(prev => prev.filter(link => link.id !== linkId)) } catch (err) { console.error('Failed to delete link:', err) - alert(err instanceof Error ? err.message : 'Failed to delete link') } finally { setDeletingLinkId(null) } @@ -386,25 +404,41 @@ export default function RequirementDetailPage() { } // Delete a comment - const handleDeleteComment = async (commentId: number) => { - if (!confirm('Are you sure you want to delete this comment? This will also hide all replies.')) return + const openDeleteCommentModal = (commentId: number) => { + setDeleteConfirmData({ + type: 'comment', + id: commentId, + title: 'Delete Comment', + message: 'Are you sure you want to delete this comment? This will also hide all replies.' + }) + setShowDeleteConfirmModal(true) + } + const executeDeleteComment = async (commentId: number) => { try { setDeletingCommentId(commentId) await commentService.deleteComment(commentId) setComments(prev => prev.filter(c => c.id !== commentId)) } catch (err) { console.error('Failed to delete comment:', err) - alert('Failed to delete comment. Please try again.') } finally { setDeletingCommentId(null) } } // Delete a reply - const handleDeleteReply = async (replyId: number, commentId: number) => { - if (!confirm('Are you sure you want to delete this reply?')) return + const openDeleteReplyModal = (replyId: number, commentId: number) => { + setDeleteConfirmData({ + type: 'reply', + id: replyId, + parentId: commentId, + title: 'Delete Reply', + message: 'Are you sure you want to delete this reply?' + }) + setShowDeleteConfirmModal(true) + } + const executeDeleteReply = async (replyId: number, commentId: number) => { try { setDeletingReplyId(replyId) await commentService.deleteReply(replyId) @@ -415,12 +449,42 @@ export default function RequirementDetailPage() { )) } catch (err) { console.error('Failed to delete reply:', err) - alert('Failed to delete reply. Please try again.') } finally { setDeletingReplyId(null) } } + // Handle delete confirmation + const handleConfirmDelete = async () => { + if (!deleteConfirmData) return + + setDeleteConfirmLoading(true) + try { + switch (deleteConfirmData.type) { + case 'link': + await executeDeleteLink(deleteConfirmData.id) + break + case 'comment': + await executeDeleteComment(deleteConfirmData.id) + break + case 'reply': + if (deleteConfirmData.parentId) { + await executeDeleteReply(deleteConfirmData.id, deleteConfirmData.parentId) + } + break + } + } finally { + setDeleteConfirmLoading(false) + setShowDeleteConfirmModal(false) + setDeleteConfirmData(null) + } + } + + const closeDeleteConfirmModal = () => { + setShowDeleteConfirmModal(false) + setDeleteConfirmData(null) + } + // Open edit modal and load options const openEditModal = async () => { if (!requirement) return @@ -603,13 +667,18 @@ export default function RequirementDetailPage() { {validationStatus} {requirement.validated_by && ( - by @{requirement.validated_by} + + by {requirement.validated_by} + {requirement.validated_at && ( + on {new Date(requirement.validated_at).toLocaleDateString()} + )} + )}

Author:{' '} {requirement.author_username ? ( - @{requirement.author_username} + {requirement.author_username} ) : ( Unknown )} @@ -617,7 +686,7 @@ export default function RequirementDetailPage() { {requirement.last_editor_username && (

Last Edited By:{' '} - @{requirement.last_editor_username} + {requirement.last_editor_username}

)} {requirement.created_at && ( @@ -724,7 +793,7 @@ export default function RequirementDetailPage() { {canDeleteLink(link) && ( + + + {/* Modal Body */} +
+

{deleteConfirmData.message}

+
+ + {/* Modal Footer */} +
+ + +
+ + + )} ) } diff --git a/frontend/src/pages/RequirementsPage.tsx b/frontend/src/pages/RequirementsPage.tsx index 0a93b45..a6d309f 100644 --- a/frontend/src/pages/RequirementsPage.tsx +++ b/frontend/src/pages/RequirementsPage.tsx @@ -64,6 +64,14 @@ export default function RequirementsPage() { const [deletedRequirements, setDeletedRequirements] = useState([]) const [deletedLoading, setDeletedLoading] = useState(false) + // Caption modal state + const [showCaptionModal, setShowCaptionModal] = useState(false) + + // Delete confirmation modal state + const [showDeleteModal, setShowDeleteModal] = useState(false) + const [deleteTarget, setDeleteTarget] = useState<{ id: number; name: string } | null>(null) + const [deleteLoading, setDeleteLoading] = useState(false) + // Fetch data when project changes useEffect(() => { const fetchData = async () => { @@ -188,22 +196,34 @@ export default function RequirementsPage() { // Filtering is automatic, but this could trigger a fresh API call } - const handleRemove = async (id: number) => { - if (!confirm('Are you sure you want to delete this requirement?')) { - return - } + const openDeleteModal = (id: number, name: string) => { + setDeleteTarget({ id, name }) + setShowDeleteModal(true) + } + + const closeDeleteModal = () => { + setShowDeleteModal(false) + setDeleteTarget(null) + } + + const handleConfirmDelete = async () => { + if (!deleteTarget) return try { - await requirementService.deleteRequirement(id) + setDeleteLoading(true) + await requirementService.deleteRequirement(deleteTarget.id) // Remove from local state - setRequirements(prev => prev.filter(r => r.id !== id)) + setRequirements(prev => prev.filter(r => r.id !== deleteTarget.id)) // Refresh deleted requirements if panel is open if (showDeletedPanel) { fetchDeletedRequirements() } + closeDeleteModal() } catch (err) { console.error('Failed to delete requirement:', err) - alert('Failed to delete requirement. Please try again.') + // Keep modal open and show error could be added here + } finally { + setDeleteLoading(false) } } @@ -381,9 +401,8 @@ export default function RequirementsPage() { {/* Main Content */}
-
- {/* Main Panel */} -
+ {/* Main Panel - Now full width */} +
{/* New Requirement Button - Hidden for auditors */} {!isAuditor && (
@@ -478,7 +497,7 @@ export default function RequirementsPage() {
{/* Order By */} -
+
Order by: