feat(ticket): add global ticket assignment page

This commit is contained in:
2026-07-27 18:31:12 +05:30
parent 224361f144
commit 20f611f35a
13 changed files with 540 additions and 126 deletions

View File

@@ -0,0 +1,102 @@
'use client';
import { useEffect, useMemo, useState } from 'react';
import { Search } from 'lucide-react';
import { MultiSelectPopover } from '@/components/form/MultiSelectPopover';
import { Input } from '@/components/ui/input';
import { useDebounce } from '@/hooks/useDebounce';
import { useTicketDetectionsQuery } from '../../../hooks/useTicketQueries';
import { useIssueColumns } from './IssueColumns';
import { IssueTable } from './IssueTable';
const DEFAULT_PAGE_SIZE = 10;
interface IssueTypeOption {
class_name: string;
display_name: string;
count: number;
}
interface ChooseIssuesSectionProps {
videoId?: string;
issueTypes: IssueTypeOption[];
}
export function ChooseIssuesSection({
videoId,
issueTypes,
}: ChooseIssuesSectionProps) {
const [selectedIssueTypes, setSelectedIssueTypes] = useState<string[]>([]);
const [detectionSearch, setDetectionSearch] = useState('');
const [skip, setSkip] = useState(0);
const [limit, setLimit] = useState(DEFAULT_PAGE_SIZE);
const debouncedSearch = useDebounce(detectionSearch.trim(), 350);
const columns = useIssueColumns();
const issueTypeOptions = useMemo(
() =>
issueTypes.map((issue) => ({
value: issue.class_name,
label: `${issue.display_name} (${issue.count})`,
})),
[issueTypes],
);
useEffect(() => {
setSkip(0);
}, [debouncedSearch, selectedIssueTypes]);
const queryParams = useMemo(
() => ({
skip,
limit,
class_name:
selectedIssueTypes.length > 0 ? selectedIssueTypes : undefined,
search: debouncedSearch || undefined,
sort: 'timestamp_asc',
}),
[debouncedSearch, limit, selectedIssueTypes, skip],
);
const detectionsQuery = useTicketDetectionsQuery(videoId, queryParams);
const result = detectionsQuery.data;
return (
<IssueTable
columns={columns}
issues={result?.items ?? []}
isLoading={detectionsQuery.isLoading || detectionsQuery.isFetching}
isError={detectionsQuery.isError}
toolbar={
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<div className="w-fit">
<MultiSelectPopover
label="Issue Types"
options={issueTypeOptions}
values={selectedIssueTypes}
onValuesChange={setSelectedIssueTypes}
searchPlaceholder="Search issue types"
emptyMessage="No issue types found."
align="start"
/>
</div>
<div className="relative w-full sm:ml-auto sm:w-72">
<Search className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={detectionSearch}
onChange={(event) => setDetectionSearch(event.target.value)}
placeholder="Search by detection ID"
aria-label="Search by detection ID"
className="pl-9"
/>
</div>
</div>
}
skip={skip}
limit={limit}
total={result?.total ?? 0}
onPageChange={setSkip}
onLimitChange={setLimit}
/>
);
}