feat(matching-runs): attach origin_run_id to new suggestions and expose pairs-per-run API
- Extend saveMatchingResults(eventId, suggestions, runId) and set originRunId - Scheduler/Admin run-now: always pass runId - Admin API: GET /api/admin/events/:slug/matching-runs/:runId/suggestions - Prisma: add compound index on (origin_run_id, status) - Frontend: add getRunSuggestions, expand row in MatchingRunsSection with 'Pairs created in this run' wording
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { adminAPI } from '../../services/api';
|
||||
import { RefreshCcw, Play } from 'lucide-react';
|
||||
import { RefreshCcw, Play, ChevronDown, ChevronRight } from 'lucide-react';
|
||||
|
||||
export default function MatchingRunsSection({ slug }) {
|
||||
const [runs, setRuns] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [running, setRunning] = useState(false);
|
||||
const [expandedRunId, setExpandedRunId] = useState(null);
|
||||
const [pairsByRun, setPairsByRun] = useState({});
|
||||
const [pairsLoading, setPairsLoading] = useState(false);
|
||||
|
||||
const loadRuns = async () => {
|
||||
try {
|
||||
@@ -43,6 +46,26 @@ export default function MatchingRunsSection({ slug }) {
|
||||
|
||||
const formatDateTime = (dt) => dt ? new Date(dt).toLocaleString() : '-';
|
||||
|
||||
const toggleViewPairs = async (runId) => {
|
||||
if (expandedRunId === runId) {
|
||||
setExpandedRunId(null);
|
||||
return;
|
||||
}
|
||||
setExpandedRunId(runId);
|
||||
if (!pairsByRun[runId]) {
|
||||
try {
|
||||
setPairsLoading(true);
|
||||
const res = await adminAPI.getRunSuggestions(slug, runId, { onlyAssigned: true, includeNotFound: false, limit: 100 });
|
||||
setPairsByRun((prev) => ({ ...prev, [runId]: res.suggestions || [] }));
|
||||
} catch (e) {
|
||||
console.error('Failed to load run pairs', e);
|
||||
setError(e.message || 'Failed to load run pairs');
|
||||
} finally {
|
||||
setPairsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
@@ -73,6 +96,7 @@ export default function MatchingRunsSection({ slug }) {
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-3 py-2"></th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Started</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ended</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Trigger</th>
|
||||
@@ -92,22 +116,59 @@ export default function MatchingRunsSection({ slug }) {
|
||||
</tr>
|
||||
) : (
|
||||
runs.map((run) => (
|
||||
<tr key={run.id}>
|
||||
<td className="px-3 py-2 text-sm text-gray-900">{formatDateTime(run.startedAt)}</td>
|
||||
<td className="px-3 py-2 text-sm text-gray-700">{formatDateTime(run.endedAt)}</td>
|
||||
<td className="px-3 py-2 text-sm text-gray-700 capitalize">{run.trigger}</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium
|
||||
${run.status === 'success' ? 'bg-green-50 text-green-700' : ''}
|
||||
${run.status === 'error' ? 'bg-red-50 text-red-700' : ''}
|
||||
${run.status === 'running' ? 'bg-blue-50 text-blue-700' : ''}
|
||||
`}>
|
||||
{run.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm text-right text-gray-700">{run.matchedCount}</td>
|
||||
<td className="px-3 py-2 text-sm text-right text-gray-700">{run.notFoundCount}</td>
|
||||
</tr>
|
||||
<>
|
||||
<tr key={run.id}>
|
||||
<td className="px-3 py-2 text-sm text-gray-700">
|
||||
<button
|
||||
onClick={() => toggleViewPairs(run.id)}
|
||||
className="inline-flex items-center text-primary-600 hover:text-primary-700"
|
||||
title="View pairs created in this run"
|
||||
>
|
||||
{expandedRunId === run.id ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm text-gray-900">{formatDateTime(run.startedAt)}</td>
|
||||
<td className="px-3 py-2 text-sm text-gray-700">{formatDateTime(run.endedAt)}</td>
|
||||
<td className="px-3 py-2 text-sm text-gray-700 capitalize">{run.trigger}</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium
|
||||
${run.status === 'success' ? 'bg-green-50 text-green-700' : ''}
|
||||
${run.status === 'error' ? 'bg-red-50 text-red-700' : ''}
|
||||
${run.status === 'running' ? 'bg-blue-50 text-blue-700' : ''}
|
||||
`}>
|
||||
{run.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm text-right text-gray-700">{run.matchedCount}</td>
|
||||
<td className="px-3 py-2 text-sm text-right text-gray-700">{run.notFoundCount}</td>
|
||||
</tr>
|
||||
{expandedRunId === run.id && (
|
||||
<tr>
|
||||
<td colSpan="7" className="px-3 py-2 bg-gray-50">
|
||||
<div className="text-sm text-gray-800 font-medium mb-2">Pairs created in this run</div>
|
||||
{pairsLoading && (!pairsByRun[run.id] || pairsByRun[run.id].length === 0) ? (
|
||||
<div className="text-gray-500">Loading pairs...</div>
|
||||
) : (pairsByRun[run.id] && pairsByRun[run.id].length > 0 ? (
|
||||
<ul className="space-y-1">
|
||||
{pairsByRun[run.id].map((p) => (
|
||||
<li key={p.id} className="flex justify-between items-center p-2 bg-white rounded border border-gray-200">
|
||||
<div className="flex items-center gap-2 text-gray-900">
|
||||
<span className="text-xs text-gray-500">{p.heat.label}</span>
|
||||
<span className="font-semibold">{p.dancer?.username}</span>
|
||||
<span className="text-gray-500">→</span>
|
||||
<span className="font-semibold">{p.recorder?.username}</span>
|
||||
</div>
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-green-50 text-green-700">{p.status}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="text-gray-500">No pairs created in this run</div>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
@@ -122,4 +183,3 @@ export default function MatchingRunsSection({ slug }) {
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user