mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
chore: add sort order in URL params
This commit is contained in:
parent
e8005042e2
commit
9824d88d53
7 changed files with 148 additions and 59 deletions
|
|
@ -13,7 +13,7 @@ class RateLimiter:
|
|||
|
||||
def __init__(self):
|
||||
self.redis_client: Optional[aioredis.Redis] = None
|
||||
self.stale_call_timeout = 1800 # 30 minutes in seconds
|
||||
self.stale_call_timeout = 300 # 5 minutes in seconds
|
||||
|
||||
async def _get_redis(self) -> aioredis.Redis:
|
||||
"""Get or create Redis connection"""
|
||||
|
|
|
|||
2
pipecat
2
pipecat
|
|
@ -1 +1 @@
|
|||
Subproject commit 866bf1c5685e7fadf2af012d8769ebbc35297db0
|
||||
Subproject commit e618bb98dfde6224ef9f4e15769580790719b269
|
||||
|
|
@ -92,9 +92,14 @@ export default function RunsPage() {
|
|||
return decodeFiltersFromURL(searchParams, superadminFilterAttributes);
|
||||
});
|
||||
|
||||
// Sort state
|
||||
const [sortBy, setSortBy] = useState<string | null>(null);
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
// Sort state (initialized from URL)
|
||||
const [sortBy, setSortBy] = useState<string | null>(() => {
|
||||
return searchParams.get('sort_by') || null;
|
||||
});
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(() => {
|
||||
const order = searchParams.get('sort_order');
|
||||
return order === 'asc' ? 'asc' : 'desc';
|
||||
});
|
||||
|
||||
// Dialog state for comment editing
|
||||
const [isCommentDialogOpen, setIsCommentDialogOpen] = useState(false);
|
||||
|
|
@ -167,7 +172,7 @@ export default function RunsPage() {
|
|||
}
|
||||
}, [limit, accessToken]);
|
||||
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[]) => {
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => {
|
||||
const params = new URLSearchParams();
|
||||
params.set('page', page.toString());
|
||||
|
||||
|
|
@ -180,6 +185,12 @@ export default function RunsPage() {
|
|||
}
|
||||
}
|
||||
|
||||
// Add sort to URL if present
|
||||
if (sortByParam) {
|
||||
params.set('sort_by', sortByParam);
|
||||
params.set('sort_order', sortOrderParam || 'desc');
|
||||
}
|
||||
|
||||
router.push(`/superadmin/runs?${params.toString()}`);
|
||||
}, [router]);
|
||||
|
||||
|
|
@ -208,7 +219,7 @@ export default function RunsPage() {
|
|||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
updatePageInUrl(page, appliedFilters);
|
||||
updatePageInUrl(page, appliedFilters, sortBy, sortOrder);
|
||||
fetchRuns(page, appliedFilters, false, sortBy, sortOrder);
|
||||
};
|
||||
|
||||
|
|
@ -216,7 +227,7 @@ export default function RunsPage() {
|
|||
setIsExecutingFilters(true);
|
||||
setCurrentPage(1); // Reset to first page when applying filters
|
||||
setAppliedFilters(activeFilters); // Update applied filters
|
||||
updatePageInUrl(1, activeFilters);
|
||||
updatePageInUrl(1, activeFilters, sortBy, sortOrder);
|
||||
await fetchRuns(1, activeFilters, false, sortBy, sortOrder);
|
||||
setIsExecutingFilters(false);
|
||||
}, [activeFilters, fetchRuns, updatePageInUrl, sortBy, sortOrder]);
|
||||
|
|
@ -229,7 +240,7 @@ export default function RunsPage() {
|
|||
setIsExecutingFilters(true);
|
||||
setCurrentPage(1);
|
||||
setAppliedFilters([]); // Clear applied filters
|
||||
updatePageInUrl(1, []); // Clear filters from URL
|
||||
updatePageInUrl(1, [], sortBy, sortOrder); // Clear filters from URL
|
||||
await fetchRuns(1, [], false, sortBy, sortOrder); // Fetch all runs without filters
|
||||
setIsExecutingFilters(false);
|
||||
}, [fetchRuns, updatePageInUrl, sortBy, sortOrder]);
|
||||
|
|
@ -238,15 +249,16 @@ export default function RunsPage() {
|
|||
// Reset to first page when sort changes
|
||||
setCurrentPage(1);
|
||||
|
||||
const newSortBy = field;
|
||||
let newSortOrder: 'asc' | 'desc' = 'desc';
|
||||
if (sortBy === field) {
|
||||
// Toggle order if same field
|
||||
setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
|
||||
} else {
|
||||
// New field, default to desc
|
||||
setSortBy(field);
|
||||
setSortOrder('desc');
|
||||
newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
}, [sortBy]);
|
||||
|
||||
setSortBy(newSortBy);
|
||||
setSortOrder(newSortOrder);
|
||||
updatePageInUrl(1, appliedFilters, newSortBy, newSortOrder);
|
||||
}, [sortBy, sortOrder, updatePageInUrl, appliedFilters]);
|
||||
|
||||
// Save comment function declared outside JSX (requirement #2)
|
||||
const saveAdminComment = useCallback(async () => {
|
||||
|
|
@ -345,6 +357,7 @@ export default function RunsPage() {
|
|||
isExecuting={isExecutingFilters}
|
||||
autoRefresh={autoRefresh}
|
||||
onAutoRefreshChange={setAutoRefresh}
|
||||
hasAppliedFilters={appliedFilters.length > 0}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
|
|
|
|||
|
|
@ -30,9 +30,14 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti
|
|||
const [isExecutingFilters, setIsExecutingFilters] = useState(false);
|
||||
const [configuredAttributes, setConfiguredAttributes] = useState<FilterAttribute[]>(availableAttributes);
|
||||
|
||||
// Sort state
|
||||
const [sortBy, setSortBy] = useState<string | null>(null);
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
// Sort state (initialized from URL)
|
||||
const [sortBy, setSortBy] = useState<string | null>(() => {
|
||||
return searchParams.get('sort_by') || null;
|
||||
});
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(() => {
|
||||
const order = searchParams.get('sort_order');
|
||||
return order === 'asc' ? 'asc' : 'desc';
|
||||
});
|
||||
|
||||
const { accessToken } = useUserConfig();
|
||||
|
||||
|
|
@ -41,6 +46,11 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti
|
|||
return decodeFiltersFromURL(searchParams, availableAttributes);
|
||||
});
|
||||
|
||||
// Applied filters are the ones actually used for fetching (only updated on Apply click)
|
||||
const [appliedFilters, setAppliedFilters] = useState<ActiveFilter[]>(() => {
|
||||
return decodeFiltersFromURL(searchParams, availableAttributes);
|
||||
});
|
||||
|
||||
// Load disposition codes from workflow configuration
|
||||
const loadDispositionCodes = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
|
|
@ -130,7 +140,7 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti
|
|||
}
|
||||
}, [workflowId, accessToken]);
|
||||
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[]) => {
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => {
|
||||
const params = new URLSearchParams();
|
||||
params.set('page', page.toString());
|
||||
|
||||
|
|
@ -143,17 +153,24 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti
|
|||
}
|
||||
}
|
||||
|
||||
// Add sort to URL if present
|
||||
if (sortByParam) {
|
||||
params.set('sort_by', sortByParam);
|
||||
params.set('sort_order', sortOrderParam || 'desc');
|
||||
}
|
||||
|
||||
router.push(`/workflow/${workflowId}/runs?${params.toString()}`, { scroll: false });
|
||||
}, [router, workflowId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchWorkflowRuns(currentPage, activeFilters, sortBy, sortOrder);
|
||||
}, [currentPage, activeFilters, fetchWorkflowRuns, sortBy, sortOrder]);
|
||||
fetchWorkflowRuns(currentPage, appliedFilters, sortBy, sortOrder);
|
||||
}, [currentPage, appliedFilters, fetchWorkflowRuns, sortBy, sortOrder]);
|
||||
|
||||
const handleApplyFilters = useCallback(async () => {
|
||||
setIsExecutingFilters(true);
|
||||
setCurrentPage(1); // Reset to first page when applying filters
|
||||
updatePageInUrl(1, activeFilters);
|
||||
setAppliedFilters(activeFilters);
|
||||
updatePageInUrl(1, activeFilters, sortBy, sortOrder);
|
||||
await fetchWorkflowRuns(1, activeFilters, sortBy, sortOrder);
|
||||
setIsExecutingFilters(false);
|
||||
}, [activeFilters, fetchWorkflowRuns, updatePageInUrl, sortBy, sortOrder]);
|
||||
|
|
@ -165,29 +182,36 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti
|
|||
const handleClearFilters = useCallback(async () => {
|
||||
setIsExecutingFilters(true);
|
||||
setCurrentPage(1);
|
||||
updatePageInUrl(1, []); // Clear filters from URL
|
||||
setActiveFilters([]);
|
||||
setAppliedFilters([]);
|
||||
updatePageInUrl(1, [], sortBy, sortOrder); // Clear filters from URL
|
||||
await fetchWorkflowRuns(1, [], sortBy, sortOrder); // Fetch all workflows without filters
|
||||
setIsExecutingFilters(false);
|
||||
}, [fetchWorkflowRuns, updatePageInUrl, sortBy, sortOrder]);
|
||||
|
||||
const handlePageChange = useCallback((page: number) => {
|
||||
setCurrentPage(page);
|
||||
updatePageInUrl(page, activeFilters);
|
||||
}, [updatePageInUrl, activeFilters]);
|
||||
updatePageInUrl(page, appliedFilters, sortBy, sortOrder);
|
||||
}, [updatePageInUrl, appliedFilters, sortBy, sortOrder]);
|
||||
|
||||
const handleSort = useCallback((field: string) => {
|
||||
// Reset to first page when sort changes
|
||||
setCurrentPage(1);
|
||||
|
||||
const newSortBy = field;
|
||||
let newSortOrder: 'asc' | 'desc' = 'desc';
|
||||
if (sortBy === field) {
|
||||
// Toggle order if same field
|
||||
setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
|
||||
} else {
|
||||
// New field, default to desc
|
||||
setSortBy(field);
|
||||
setSortOrder('desc');
|
||||
newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
}, [sortBy]);
|
||||
|
||||
setSortBy(newSortBy);
|
||||
setSortOrder(newSortOrder);
|
||||
updatePageInUrl(1, appliedFilters, newSortBy, newSortOrder);
|
||||
}, [sortBy, sortOrder, updatePageInUrl, appliedFilters]);
|
||||
|
||||
const handleReload = useCallback(() => {
|
||||
fetchWorkflowRuns(currentPage, appliedFilters, sortBy, sortOrder);
|
||||
}, [fetchWorkflowRuns, currentPage, appliedFilters, sortBy, sortOrder]);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-8">
|
||||
|
|
@ -205,11 +229,13 @@ export function WorkflowExecutions({ workflowId, searchParams }: WorkflowExecuti
|
|||
onApplyFilters={handleApplyFilters}
|
||||
onClearFilters={handleClearFilters}
|
||||
isExecutingFilters={isExecutingFilters}
|
||||
hasAppliedFilters={appliedFilters.length > 0}
|
||||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
workflowId={workflowId}
|
||||
accessToken={accessToken}
|
||||
onReload={handleReload}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ interface FilterBuilderProps {
|
|||
isExecuting?: boolean;
|
||||
autoRefresh?: boolean;
|
||||
onAutoRefreshChange?: (enabled: boolean) => void;
|
||||
hasAppliedFilters?: boolean;
|
||||
}
|
||||
|
||||
export const FilterBuilder: React.FC<FilterBuilderProps> = ({
|
||||
|
|
@ -49,6 +50,7 @@ export const FilterBuilder: React.FC<FilterBuilderProps> = ({
|
|||
isExecuting = false,
|
||||
autoRefresh = false,
|
||||
onAutoRefreshChange,
|
||||
hasAppliedFilters = false,
|
||||
}) => {
|
||||
const [selectedAttribute, setSelectedAttribute] = useState<string>("");
|
||||
const [expandedFilters, setExpandedFilters] = useState<Set<number>>(new Set());
|
||||
|
|
@ -69,7 +71,8 @@ export const FilterBuilder: React.FC<FilterBuilderProps> = ({
|
|||
if (isModifierPressed && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
const allFiltersValid = activeFilters.every(f => f.isValid);
|
||||
if (activeFilters.length > 0 && allFiltersValid && !isExecuting) {
|
||||
const canApply = (activeFilters.length > 0 && allFiltersValid) || (activeFilters.length === 0 && hasAppliedFilters);
|
||||
if (canApply && !isExecuting) {
|
||||
onApplyFilters();
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +82,7 @@ export const FilterBuilder: React.FC<FilterBuilderProps> = ({
|
|||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [activeFilters, isExecuting, onApplyFilters]);
|
||||
}, [activeFilters, isExecuting, onApplyFilters, hasAppliedFilters]);
|
||||
|
||||
const addFilter = useCallback((attributeId: string) => {
|
||||
const attribute = availableAttributes.find(attr => attr.id === attributeId);
|
||||
|
|
@ -411,7 +414,7 @@ export const FilterBuilder: React.FC<FilterBuilderProps> = ({
|
|||
)}
|
||||
|
||||
{/* Apply Filters Button */}
|
||||
{activeFilters.length > 0 && (
|
||||
{(activeFilters.length > 0 || hasAppliedFilters) && (
|
||||
<div className="flex justify-between items-center gap-2 pt-2">
|
||||
{/* Auto-refresh toggle on the left */}
|
||||
{onAutoRefreshChange && (
|
||||
|
|
@ -440,7 +443,7 @@ export const FilterBuilder: React.FC<FilterBuilderProps> = ({
|
|||
</Button>
|
||||
<Button
|
||||
onClick={onApplyFilters}
|
||||
disabled={!allFiltersValid || isExecuting}
|
||||
disabled={(activeFilters.length > 0 && !allFiltersValid) || isExecuting}
|
||||
title={"Apply filters"}
|
||||
>
|
||||
{isExecuting ? "Applying..." : `Apply (${navigator.userAgent.toUpperCase().indexOf('MAC') >= 0 ? '⌘' : 'Ctrl'}+Enter)`}
|
||||
|
|
|
|||
|
|
@ -31,15 +31,25 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
const [isExecutingFilters, setIsExecutingFilters] = useState(false);
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
|
||||
// Sort state
|
||||
const [sortBy, setSortBy] = useState<string | null>(null);
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
// Sort state (initialized from URL)
|
||||
const [sortBy, setSortBy] = useState<string | null>(() => {
|
||||
return searchParams?.get('sort_by') || null;
|
||||
});
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(() => {
|
||||
const order = searchParams?.get('sort_order');
|
||||
return order === 'asc' ? 'asc' : 'desc';
|
||||
});
|
||||
|
||||
// Initialize filters from URL
|
||||
const [activeFilters, setActiveFilters] = useState<ActiveFilter[]>(() => {
|
||||
return searchParams ? decodeFiltersFromURL(searchParams, availableAttributes) : [];
|
||||
});
|
||||
|
||||
// Applied filters are the ones actually used for fetching (only updated on Apply click)
|
||||
const [appliedFilters, setAppliedFilters] = useState<ActiveFilter[]>(() => {
|
||||
return searchParams ? decodeFiltersFromURL(searchParams, availableAttributes) : [];
|
||||
});
|
||||
|
||||
// Get access token on mount
|
||||
useEffect(() => {
|
||||
const fetchToken = async () => {
|
||||
|
|
@ -103,7 +113,7 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
}
|
||||
}, [campaignId, accessToken]);
|
||||
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[]) => {
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => {
|
||||
const params = new URLSearchParams();
|
||||
params.set('page', page.toString());
|
||||
|
||||
|
|
@ -116,19 +126,26 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
}
|
||||
}
|
||||
|
||||
// Add sort to URL if present
|
||||
if (sortByParam) {
|
||||
params.set('sort_by', sortByParam);
|
||||
params.set('sort_order', sortOrderParam || 'desc');
|
||||
}
|
||||
|
||||
router.push(`/campaigns/${campaignId}?${params.toString()}`, { scroll: false });
|
||||
}, [router, campaignId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accessToken) {
|
||||
fetchCampaignRuns(currentPage, activeFilters, sortBy, sortOrder);
|
||||
fetchCampaignRuns(currentPage, appliedFilters, sortBy, sortOrder);
|
||||
}
|
||||
}, [currentPage, activeFilters, fetchCampaignRuns, accessToken, sortBy, sortOrder]);
|
||||
}, [currentPage, appliedFilters, fetchCampaignRuns, accessToken, sortBy, sortOrder]);
|
||||
|
||||
const handleApplyFilters = useCallback(async () => {
|
||||
setIsExecutingFilters(true);
|
||||
setCurrentPage(1);
|
||||
updatePageInUrl(1, activeFilters);
|
||||
setAppliedFilters(activeFilters);
|
||||
updatePageInUrl(1, activeFilters, sortBy, sortOrder);
|
||||
await fetchCampaignRuns(1, activeFilters, sortBy, sortOrder);
|
||||
setIsExecutingFilters(false);
|
||||
}, [activeFilters, fetchCampaignRuns, updatePageInUrl, sortBy, sortOrder]);
|
||||
|
|
@ -141,29 +158,35 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
setIsExecutingFilters(true);
|
||||
setCurrentPage(1);
|
||||
setActiveFilters([]);
|
||||
updatePageInUrl(1, []);
|
||||
setAppliedFilters([]);
|
||||
updatePageInUrl(1, [], sortBy, sortOrder);
|
||||
await fetchCampaignRuns(1, [], sortBy, sortOrder);
|
||||
setIsExecutingFilters(false);
|
||||
}, [fetchCampaignRuns, updatePageInUrl, sortBy, sortOrder]);
|
||||
|
||||
const handlePageChange = useCallback((page: number) => {
|
||||
setCurrentPage(page);
|
||||
updatePageInUrl(page, activeFilters);
|
||||
}, [updatePageInUrl, activeFilters]);
|
||||
updatePageInUrl(page, appliedFilters, sortBy, sortOrder);
|
||||
}, [updatePageInUrl, appliedFilters, sortBy, sortOrder]);
|
||||
|
||||
const handleSort = useCallback((field: string) => {
|
||||
// Reset to first page when sort changes
|
||||
setCurrentPage(1);
|
||||
|
||||
const newSortBy = field;
|
||||
let newSortOrder: 'asc' | 'desc' = 'desc';
|
||||
if (sortBy === field) {
|
||||
// Toggle order if same field
|
||||
setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
|
||||
} else {
|
||||
// New field, default to desc
|
||||
setSortBy(field);
|
||||
setSortOrder('desc');
|
||||
newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
}, [sortBy]);
|
||||
|
||||
setSortBy(newSortBy);
|
||||
setSortOrder(newSortOrder);
|
||||
updatePageInUrl(1, appliedFilters, newSortBy, newSortOrder);
|
||||
}, [sortBy, sortOrder, updatePageInUrl, appliedFilters]);
|
||||
|
||||
const handleReload = useCallback(() => {
|
||||
fetchCampaignRuns(currentPage, appliedFilters, sortBy, sortOrder);
|
||||
}, [fetchCampaignRuns, currentPage, appliedFilters, sortBy, sortOrder]);
|
||||
|
||||
// Use a subset of filter attributes relevant for campaigns
|
||||
const campaignFilterAttributes: FilterAttribute[] = availableAttributes.filter(
|
||||
|
|
@ -185,11 +208,13 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
onApplyFilters={handleApplyFilters}
|
||||
onClearFilters={handleClearFilters}
|
||||
isExecutingFilters={isExecutingFilters}
|
||||
hasAppliedFilters={appliedFilters.length > 0}
|
||||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
workflowId={workflowId}
|
||||
accessToken={accessToken}
|
||||
onReload={handleReload}
|
||||
title="Campaign Workflow Runs"
|
||||
emptyMessage="No workflow runs found for this campaign"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, ExternalLink } from "lucide-react";
|
||||
import { ArrowDown, ArrowUp, ArrowUpDown, ChevronLeft, ChevronRight, ExternalLink, RefreshCw } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { WorkflowRunResponseSchema } from "@/client/types.gen";
|
||||
|
|
@ -38,6 +38,7 @@ export interface WorkflowRunsTableProps {
|
|||
onApplyFilters: () => void;
|
||||
onClearFilters: () => void;
|
||||
isExecutingFilters: boolean;
|
||||
hasAppliedFilters?: boolean;
|
||||
|
||||
// Sorting
|
||||
sortBy?: string | null;
|
||||
|
|
@ -48,6 +49,9 @@ export interface WorkflowRunsTableProps {
|
|||
workflowId: number;
|
||||
accessToken: string | null;
|
||||
|
||||
// Reload
|
||||
onReload?: () => void;
|
||||
|
||||
// Optional customization
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
|
|
@ -69,11 +73,13 @@ export function WorkflowRunsTable({
|
|||
onApplyFilters,
|
||||
onClearFilters,
|
||||
isExecutingFilters,
|
||||
hasAppliedFilters = false,
|
||||
sortBy,
|
||||
sortOrder = 'desc',
|
||||
onSort,
|
||||
workflowId,
|
||||
accessToken,
|
||||
onReload,
|
||||
title = "Workflow Run History",
|
||||
subtitle,
|
||||
showFilters = true,
|
||||
|
|
@ -103,6 +109,7 @@ export function WorkflowRunsTable({
|
|||
onApplyFilters={onApplyFilters}
|
||||
onClearFilters={onClearFilters}
|
||||
isExecuting={isExecutingFilters}
|
||||
hasAppliedFilters={hasAppliedFilters}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -123,10 +130,25 @@ export function WorkflowRunsTable({
|
|||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Workflow Runs</CardTitle>
|
||||
<CardDescription>
|
||||
{subtitle || `Showing ${runs.length} of ${totalCount} total runs`}
|
||||
</CardDescription>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Workflow Runs</CardTitle>
|
||||
<CardDescription>
|
||||
{subtitle || `Showing ${runs.length} of ${totalCount} total runs`}
|
||||
</CardDescription>
|
||||
</div>
|
||||
{onReload && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={onReload}
|
||||
disabled={loading}
|
||||
title="Reload"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="bg-card border border-border rounded-lg overflow-hidden shadow-sm">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue