Skip to content

Commit f25d4f7

Browse files
committed
feat: reconcile moderation queue with canonical comment statuses
1 parent e97e945 commit f25d4f7

4 files changed

Lines changed: 55 additions & 24 deletions

File tree

docs/plan/M03-agency-operations.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ Replace mock agency workflows with real data operations.
1717
- [x] Wire `/agency/users` to real user management page.
1818
- [x] Remove high-visibility "coming soon" placeholders in core agency routes.
1919
- [x] Connect docket pages to real Supabase tables/RPCs.
20-
- [ ] Ensure moderation queue reads/writes real moderation data.
20+
- [x] Ensure moderation queue reads/writes real moderation data.
2121

2222
## Acceptance criteria
2323

24-
- [ ] Agency can create and manage dockets without mock data.
25-
- [ ] Agency moderation actions persist and are auditable.
24+
- [x] Agency can create and manage dockets without mock data.
25+
- [x] Agency moderation actions persist and are auditable.
2626

2727
## Risks/blockers
2828

@@ -40,3 +40,4 @@ Replace mock agency workflows with real data operations.
4040
- 2026-02-11: Replaced public placeholder screens for data access and platform status with implemented content pages.
4141
- 2026-02-11: Replaced mock data in `src/pages/agency/DocketList.tsx` with real Supabase queries scoped to current agency membership.
4242
- 2026-02-11: Implemented real docket creation in `src/pages/agency/DocketWizard.tsx` including identity-mode, moderation/captcha settings, and best-effort supporting document persistence.
43+
- 2026-02-11: Updated moderation logic to canonical comment statuses (`under_review`/`published`) with compatibility mapping for legacy values and persisted moderation logging.

docs/plan/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ This folder tracks implementation progress for the OpenComments production roadm
3434
- `M00`: Completed
3535
- `M01`: In progress
3636
- `M02`: In progress
37-
- `M03`: In progress
37+
- `M03`: Completed
3838
- `M04`: In progress
3939
- `M05`: Completed
4040
- `M06`: Completed

src/hooks/useModerationQueue.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ import { useState, useEffect } from 'react'
22
import { supabase } from '../lib/supabase'
33
import { useAgency } from '../contexts/AgencyContext'
44

5-
export type CommentStatus = 'pending' | 'approved' | 'rejected' | 'flagged'
5+
export type CommentStatus =
6+
| 'pending'
7+
| 'submitted'
8+
| 'under_review'
9+
| 'approved'
10+
| 'published'
11+
| 'rejected'
12+
| 'flagged'
613
export type ModerationAction = 'approve' | 'reject' | 'flag' | 'unflag'
14+
type QueueFilterStatus = 'under_review' | 'published' | 'rejected' | 'flagged'
715

816
export interface QueueComment {
917
id: string
@@ -27,8 +35,8 @@ export interface QueueComment {
2735
}
2836

2937
export interface ModerationStats {
30-
pending: number
31-
approved: number
38+
under_review: number
39+
published: number
3240
rejected: number
3341
flagged: number
3442
total: number
@@ -38,16 +46,16 @@ export const useModerationQueue = () => {
3846
const { currentAgency } = useAgency()
3947
const [comments, setComments] = useState<QueueComment[]>([])
4048
const [stats, setStats] = useState<ModerationStats>({
41-
pending: 0,
42-
approved: 0,
49+
under_review: 0,
50+
published: 0,
4351
rejected: 0,
4452
flagged: 0,
4553
total: 0
4654
})
4755
const [loading, setLoading] = useState(true)
4856
const [error, setError] = useState<string | null>(null)
4957

50-
const fetchComments = async (status?: CommentStatus) => {
58+
const fetchComments = async (status?: QueueFilterStatus) => {
5159
if (!currentAgency) return
5260

5361
setLoading(true)
@@ -60,7 +68,11 @@ export const useModerationQueue = () => {
6068
.eq('agency_id', currentAgency.id)
6169
.order('created_at', { ascending: false })
6270

63-
if (status) {
71+
if (status === 'under_review') {
72+
query = query.in('status', ['pending', 'submitted', 'under_review'])
73+
} else if (status === 'published') {
74+
query = query.in('status', ['approved', 'published'])
75+
} else if (status) {
6476
query = query.eq('status', status)
6577
}
6678

@@ -101,9 +113,17 @@ export const useModerationQueue = () => {
101113
return acc
102114
}, {} as Record<string, number>) || {}
103115

116+
const underReviewCount =
117+
(statusCounts.pending || 0) +
118+
(statusCounts.submitted || 0) +
119+
(statusCounts.under_review || 0)
120+
const publishedCount =
121+
(statusCounts.approved || 0) +
122+
(statusCounts.published || 0)
123+
104124
setStats({
105-
pending: statusCounts.pending || 0,
106-
approved: statusCounts.approved || 0,
125+
under_review: underReviewCount,
126+
published: publishedCount,
107127
rejected: statusCounts.rejected || 0,
108128
flagged: statusCounts.flagged || 0,
109129
total: statusCounts.total || 0
@@ -119,9 +139,9 @@ export const useModerationQueue = () => {
119139
reason?: string
120140
): Promise<void> => {
121141
try {
122-
const newStatus: CommentStatus = action === 'approve' ? 'approved' :
142+
const newStatus: CommentStatus = action === 'approve' ? 'published' :
123143
action === 'reject' ? 'rejected' :
124-
action === 'flag' ? 'flagged' : 'pending'
144+
action === 'flag' ? 'flagged' : 'under_review'
125145

126146
// Update comment status
127147
const { error: updateError } = await supabase
@@ -165,9 +185,9 @@ export const useModerationQueue = () => {
165185
reason?: string
166186
): Promise<void> => {
167187
try {
168-
const newStatus: CommentStatus = action === 'approve' ? 'approved' :
188+
const newStatus: CommentStatus = action === 'approve' ? 'published' :
169189
action === 'reject' ? 'rejected' :
170-
action === 'flag' ? 'flagged' : 'pending'
190+
action === 'flag' ? 'flagged' : 'under_review'
171191

172192
// Update all comments
173193
const { error: updateError } = await supabase
@@ -240,4 +260,4 @@ export const useModerationQueue = () => {
240260
getAttachmentSignedUrl,
241261
refresh: () => Promise.all([fetchComments(), fetchStats()])
242262
}
243-
}
263+
}

src/pages/agency/ModerationQueue.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,18 @@ const ModerationQueue = () => {
5656
}, [location.pathname])
5757

5858
// Filter comments based on active tab and search
59+
const statusMatchesTab = (status: CommentStatus, tab: TabType) => {
60+
if (tab === 'pending') {
61+
return status === 'pending' || status === 'submitted' || status === 'under_review'
62+
}
63+
if (tab === 'approved') {
64+
return status === 'approved' || status === 'published'
65+
}
66+
return status === tab
67+
}
68+
5969
const filteredComments = comments.filter(comment => {
60-
const matchesTab = comment.status === activeTab
70+
const matchesTab = statusMatchesTab(comment.status, activeTab)
6171
const matchesSearch = !searchQuery ||
6272
comment.content.toLowerCase().includes(searchQuery.toLowerCase()) ||
6373
comment.docket_title.toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -68,7 +78,7 @@ const ModerationQueue = () => {
6878

6979
// Load comments when tab changes
7080
useEffect(() => {
71-
fetchComments(activeTab)
81+
fetchComments(activeTab === 'pending' ? 'under_review' : activeTab === 'approved' ? 'published' : activeTab)
7282
}, [activeTab])
7383

7484
const handleTabChange = (tab: TabType) => {
@@ -215,7 +225,7 @@ const ModerationQueue = () => {
215225
<Clock className="w-5 h-5 text-yellow-600 mr-2" />
216226
<div>
217227
<p className="text-sm font-medium text-gray-600">Pending</p>
218-
<p className="text-xl font-bold text-gray-900">{stats.pending}</p>
228+
<p className="text-xl font-bold text-gray-900">{stats.under_review}</p>
219229
</div>
220230
</div>
221231
</div>
@@ -235,7 +245,7 @@ const ModerationQueue = () => {
235245
<CheckCircle className="w-5 h-5 text-green-600 mr-2" />
236246
<div>
237247
<p className="text-sm font-medium text-gray-600">Approved</p>
238-
<p className="text-xl font-bold text-gray-900">{stats.approved}</p>
248+
<p className="text-xl font-bold text-gray-900">{stats.published}</p>
239249
</div>
240250
</div>
241251
</div>
@@ -257,9 +267,9 @@ const ModerationQueue = () => {
257267
<div className="border-b border-gray-200">
258268
<nav className="-mb-px flex space-x-8 px-6">
259269
{[
260-
{ id: 'pending', label: 'Pending', count: stats.pending, icon: Clock },
270+
{ id: 'pending', label: 'Pending', count: stats.under_review, icon: Clock },
261271
{ id: 'flagged', label: 'Flagged', count: stats.flagged, icon: Flag },
262-
{ id: 'approved', label: 'Approved', count: stats.approved, icon: CheckCircle },
272+
{ id: 'approved', label: 'Published', count: stats.published, icon: CheckCircle },
263273
{ id: 'rejected', label: 'Rejected', count: stats.rejected, icon: XCircle }
264274
].map(tab => {
265275
const IconComponent = tab.icon

0 commit comments

Comments
 (0)