feat: Added Head/Tail segmented toggle to EventLog with order param wir…
- "frontend/src/api/public-client.ts" - "frontend/src/pages/AdminPipeline.tsx" - "frontend/src/App.css" GSD-Task: S02/T02
This commit is contained in:
parent
cd9dd6d8f9
commit
d00639a2ea
4 changed files with 58 additions and 4 deletions
|
|
@ -2722,6 +2722,40 @@ body {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pipeline-events__view-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-events__view-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-events__view-btn:hover {
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
background: var(--color-accent-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-events__view-btn--active {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: var(--color-bg-page);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pipeline-events__view-btn--active:hover {
|
||||||
|
background: var(--color-accent-hover);
|
||||||
|
color: var(--color-bg-page);
|
||||||
|
}
|
||||||
|
|
||||||
.pipeline-events__empty {
|
.pipeline-events__empty {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
|
|
|
||||||
|
|
@ -439,13 +439,14 @@ export async function fetchPipelineVideos(): Promise<PipelineVideoListResponse>
|
||||||
|
|
||||||
export async function fetchPipelineEvents(
|
export async function fetchPipelineEvents(
|
||||||
videoId: string,
|
videoId: string,
|
||||||
params: { offset?: number; limit?: number; stage?: string; event_type?: string } = {},
|
params: { offset?: number; limit?: number; stage?: string; event_type?: string; order?: "asc" | "desc" } = {},
|
||||||
): Promise<PipelineEventListResponse> {
|
): Promise<PipelineEventListResponse> {
|
||||||
const qs = new URLSearchParams();
|
const qs = new URLSearchParams();
|
||||||
if (params.offset !== undefined) qs.set("offset", String(params.offset));
|
if (params.offset !== undefined) qs.set("offset", String(params.offset));
|
||||||
if (params.limit !== undefined) qs.set("limit", String(params.limit));
|
if (params.limit !== undefined) qs.set("limit", String(params.limit));
|
||||||
if (params.stage) qs.set("stage", params.stage);
|
if (params.stage) qs.set("stage", params.stage);
|
||||||
if (params.event_type) qs.set("event_type", params.event_type);
|
if (params.event_type) qs.set("event_type", params.event_type);
|
||||||
|
if (params.order) qs.set("order", params.order);
|
||||||
const query = qs.toString();
|
const query = qs.toString();
|
||||||
return request<PipelineEventListResponse>(
|
return request<PipelineEventListResponse>(
|
||||||
`${BASE}/admin/pipeline/events/${videoId}${query ? `?${query}` : ""}`,
|
`${BASE}/admin/pipeline/events/${videoId}${query ? `?${query}` : ""}`,
|
||||||
|
|
|
||||||
|
|
@ -103,13 +103,18 @@ function EventLog({ videoId }: { videoId: string }) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
|
const [viewMode, setViewMode] = useState<"head" | "tail">("tail");
|
||||||
const limit = 50;
|
const limit = 50;
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const res = await fetchPipelineEvents(videoId, { offset, limit });
|
const res = await fetchPipelineEvents(videoId, {
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
order: viewMode === "head" ? "asc" : "desc",
|
||||||
|
});
|
||||||
setEvents(res.items);
|
setEvents(res.items);
|
||||||
setTotal(res.total);
|
setTotal(res.total);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -117,7 +122,7 @@ function EventLog({ videoId }: { videoId: string }) {
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [videoId, offset]);
|
}, [videoId, offset, viewMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void load();
|
void load();
|
||||||
|
|
@ -134,6 +139,20 @@ function EventLog({ videoId }: { videoId: string }) {
|
||||||
<div className="pipeline-events">
|
<div className="pipeline-events">
|
||||||
<div className="pipeline-events__header">
|
<div className="pipeline-events__header">
|
||||||
<span className="pipeline-events__count">{total} event{total !== 1 ? "s" : ""}</span>
|
<span className="pipeline-events__count">{total} event{total !== 1 ? "s" : ""}</span>
|
||||||
|
<div className="pipeline-events__view-toggle">
|
||||||
|
<button
|
||||||
|
className={`pipeline-events__view-btn${viewMode === "head" ? " pipeline-events__view-btn--active" : ""}`}
|
||||||
|
onClick={() => { setViewMode("head"); setOffset(0); }}
|
||||||
|
>
|
||||||
|
Head
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`pipeline-events__view-btn${viewMode === "tail" ? " pipeline-events__view-btn--active" : ""}`}
|
||||||
|
onClick={() => { setViewMode("tail"); setOffset(0); }}
|
||||||
|
>
|
||||||
|
Tail
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button className="btn btn--small btn--secondary" onClick={() => void load()}>↻ Refresh</button>
|
<button className="btn btn--small btn--secondary" onClick={() => void load()}>↻ Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/public-client.ts","./src/components/ModeToggle.tsx","./src/components/ReportIssueModal.tsx","./src/components/StatusBadge.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/MomentDetail.tsx","./src/pages/ReviewQueue.tsx","./src/pages/SearchResults.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx"],"version":"5.6.3"}
|
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/ModeToggle.tsx","./src/components/ReportIssueModal.tsx","./src/components/StatusBadge.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/MomentDetail.tsx","./src/pages/ReviewQueue.tsx","./src/pages/SearchResults.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx"],"version":"5.6.3"}
|
||||||
Loading…
Add table
Reference in a new issue