dev tickets and issues raised
This commit is contained in:
@@ -2,11 +2,12 @@ import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
|
||||
Paper, TextField, FormControl, InputLabel, Select, MenuItem, CircularProgress,
|
||||
Chip, TablePagination, Link, Tooltip,
|
||||
Chip, TablePagination, Link, Tooltip, Button,
|
||||
} from '@mui/material';
|
||||
import { adminService } from '../../api/adminService';
|
||||
import type { DevTicket } from '../../api/adminService';
|
||||
import { useToastStore } from '../../store/toastStore';
|
||||
import { RecordDetailsDialog } from './RecordDetailsDialog';
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
@@ -16,6 +17,30 @@ const gitStatusColor = (status: DevTicket['git_status']): 'success' | 'warning'
|
||||
return 'warning';
|
||||
};
|
||||
|
||||
// One unified lifecycle: a ticket is Resolved (from the UI or by closing
|
||||
// the Gitea issue), or its record is still Under Review / already handled.
|
||||
const statusChip = (t: DevTicket) => {
|
||||
if (t.resolved_at) {
|
||||
return (
|
||||
<Tooltip title={`Resolved by ${t.resolved_by || '—'} · ${new Date(t.resolved_at).toLocaleString()}`}>
|
||||
<Chip size="small" label="Resolved" color="success" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
switch (t.record_status) {
|
||||
case 'under_review': return <Chip size="small" label="Under Review" color="warning" />;
|
||||
case 'resolved': return <Chip size="small" label="Back in feed" color="success" variant="outlined" />;
|
||||
case 'audited': return <Chip size="small" label="Audited" color="info" />;
|
||||
case 'deleted': return <Chip size="small" label="Deleted" color="default" />;
|
||||
default:
|
||||
return (
|
||||
<Tooltip title="Record not found or its org DB is unreachable">
|
||||
<Chip size="small" label="Unknown" variant="outlined" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const truncate = (text: string, max = 80) => (text.length > max ? `${text.slice(0, max)}…` : text);
|
||||
|
||||
export const DevTickets: React.FC = () => {
|
||||
@@ -45,6 +70,8 @@ export const DevTickets: React.FC = () => {
|
||||
// afterward, so this is fetched separately from the main list.
|
||||
const [issueStates, setIssueStates] = useState<Record<number, 'open' | 'closed' | null>>({});
|
||||
const [issueStatesLoading, setIssueStatesLoading] = useState(false);
|
||||
const [resolvingId, setResolvingId] = useState<number | null>(null);
|
||||
const [detailsTicket, setDetailsTicket] = useState<DevTicket | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -96,6 +123,24 @@ export const DevTickets: React.FC = () => {
|
||||
.finally(() => setIssueStatesLoading(false));
|
||||
}, [records]);
|
||||
|
||||
const handleResolve = async (t: DevTicket) => {
|
||||
setResolvingId(t.id);
|
||||
try {
|
||||
const res = await adminService.resolveTicket(t.id);
|
||||
useToastStore.getState().showToast(
|
||||
res.recordReleased
|
||||
? `Resolved — record #${t.anomaly_id} returned to the feed${res.issueClosed ? ', git issue closed' : ''}.`
|
||||
: `Resolved — record was already handled${res.issueClosed ? '; git issue closed' : ''}.`,
|
||||
'success'
|
||||
);
|
||||
load();
|
||||
} catch (err: any) {
|
||||
useToastStore.getState().showToast(err?.response?.data?.message || 'Failed to resolve ticket.', 'error');
|
||||
} finally {
|
||||
setResolvingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 3, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
@@ -141,6 +186,7 @@ export const DevTickets: React.FC = () => {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Anomaly ID</TableCell>
|
||||
<TableCell>Org</TableCell>
|
||||
<TableCell>Asset / Plaza</TableCell>
|
||||
<TableCell>Dev</TableCell>
|
||||
<TableCell>Reporter</TableCell>
|
||||
@@ -148,13 +194,15 @@ export const DevTickets: React.FC = () => {
|
||||
<TableCell>RocketChat</TableCell>
|
||||
<TableCell>Git</TableCell>
|
||||
<TableCell>Issue status</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell>Created</TableCell>
|
||||
<TableCell align="right">Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={9} align="center" sx={{ color: 'text.secondary' }}>
|
||||
<TableCell colSpan={12} align="center" sx={{ color: 'text.secondary' }}>
|
||||
No dev tickets found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -162,8 +210,14 @@ export const DevTickets: React.FC = () => {
|
||||
{records.map((t) => {
|
||||
const liveState = t.git_status === 'success' && t.git_issue_number != null ? issueStates[t.id] : undefined;
|
||||
return (
|
||||
<TableRow key={t.id}>
|
||||
<TableRow
|
||||
key={t.id}
|
||||
hover
|
||||
onClick={() => setDetailsTicket(t)}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
<TableCell>{t.anomaly_id}</TableCell>
|
||||
<TableCell>{t.org_db_name || '—'}</TableCell>
|
||||
<TableCell>{[t.asset, t.plaza].filter(Boolean).join(' / ') || '—'}</TableCell>
|
||||
<TableCell>@{t.dev_handle}</TableCell>
|
||||
<TableCell>{t.reporter || '—'}</TableCell>
|
||||
@@ -184,7 +238,7 @@ export const DevTickets: React.FC = () => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell onClick={(e) => e.stopPropagation()}>
|
||||
{t.git_issue_url ? (
|
||||
<Link href={t.git_issue_url} target="_blank" rel="noopener noreferrer">
|
||||
<Chip size="small" label={`#${t.git_issue_number}`} color={gitStatusColor(t.git_status)} clickable />
|
||||
@@ -213,7 +267,18 @@ export const DevTickets: React.FC = () => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{statusChip(t)}</TableCell>
|
||||
<TableCell>{new Date(t.created_at).toLocaleString()}</TableCell>
|
||||
<TableCell align="right" onClick={(e) => e.stopPropagation()}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
disabled={!!t.resolved_at || t.record_status !== 'under_review' || resolvingId === t.id}
|
||||
onClick={() => handleResolve(t)}
|
||||
>
|
||||
{resolvingId === t.id ? <CircularProgress size={16} /> : 'Resolve'}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
@@ -229,6 +294,12 @@ export const DevTickets: React.FC = () => {
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<RecordDetailsDialog
|
||||
ticket={detailsTicket}
|
||||
liveIssueState={detailsTicket ? issueStates[detailsTicket.id] : undefined}
|
||||
onClose={() => setDetailsTicket(null)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user