feat(tickets): popup réutilisable de sélection de ticket (#18)
Ajoute TicketPicker, popup modale réutilisable de sélection de ticket, socle du chemin critique du sprint UI rework (#17 et #19 en dépendent). - TicketPicker.tsx : popup de recherche/sélection réutilisable - useTicketSearch.ts : hook de recherche/filtrage extrait et partagé - TicketFacetsBar.tsx : barre de facettes extraite de TicketsPanel pour réutilisation (listing principal + picker) - shared/ui/zIndex.ts : échelle z-index centralisée - TicketsPanel : consomme la barre de facettes extraite Tests : TicketPicker.test.tsx 8/8, suite tickets 37/37, tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -14,14 +14,13 @@ import { Button, Input, Panel, Spinner, cn } from "@/shared";
|
||||
import { useTickets } from "./useTickets";
|
||||
import { useProjectAgents } from "./useProjectAgents";
|
||||
import { SprintManager } from "./SprintManager";
|
||||
import { TicketFacetsBar } from "./TicketFacetsBar";
|
||||
import {
|
||||
PriorityBadge,
|
||||
StatusBadge,
|
||||
TICKET_PRIORITIES,
|
||||
TICKET_STATUSES,
|
||||
TicketRef,
|
||||
priorityLabel,
|
||||
statusLabel,
|
||||
} from "./ticketMeta";
|
||||
|
||||
const selectClass = cn(
|
||||
@ -152,60 +151,19 @@ export function TicketsPanel({ projectId, onOpen }: TicketsPanelProps) {
|
||||
|
||||
{/* ── Filters ── */}
|
||||
<div className="mb-3 flex flex-col gap-2">
|
||||
<Input
|
||||
aria-label="search tickets"
|
||||
placeholder="Search title/description…"
|
||||
value={vm.query.text ?? ""}
|
||||
onChange={(e) =>
|
||||
vm.setQuery({ ...vm.query, text: e.target.value || undefined })
|
||||
{/* Shared search + status/priority facets (#18). Assignee stays here —
|
||||
it needs the project agent roster and is out of the picker's scope. */}
|
||||
<TicketFacetsBar
|
||||
text={vm.query.text ?? ""}
|
||||
onTextChange={(text) =>
|
||||
vm.setQuery({ ...vm.query, text: text || undefined })
|
||||
}
|
||||
statuses={vm.query.statuses ?? []}
|
||||
priorities={vm.query.priorities ?? []}
|
||||
onToggleStatus={vm.toggleStatus}
|
||||
onTogglePriority={vm.togglePriority}
|
||||
onClearFacets={vm.clearFacets}
|
||||
/>
|
||||
{/* Multi-select facets (ticket #12): OR within a facet, AND across; no
|
||||
box ticked ⇒ that facet is unconstrained. */}
|
||||
<fieldset
|
||||
aria-label="filter by status"
|
||||
className="flex flex-wrap items-center gap-x-3 gap-y-1"
|
||||
>
|
||||
<legend className="mr-1 text-xs font-medium text-muted">Statut</legend>
|
||||
{TICKET_STATUSES.map((s) => (
|
||||
<label
|
||||
key={s}
|
||||
className="flex cursor-pointer items-center gap-1.5 text-xs"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={`filter status ${statusLabel(s)}`}
|
||||
className="accent-primary"
|
||||
checked={(vm.query.statuses ?? []).includes(s)}
|
||||
onChange={() => vm.toggleStatus(s)}
|
||||
/>
|
||||
<StatusBadge status={s} />
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
<fieldset
|
||||
aria-label="filter by priority"
|
||||
className="flex flex-wrap items-center gap-x-3 gap-y-1"
|
||||
>
|
||||
<legend className="mr-1 text-xs font-medium text-muted">
|
||||
Priorité
|
||||
</legend>
|
||||
{TICKET_PRIORITIES.map((p) => (
|
||||
<label
|
||||
key={p}
|
||||
className="flex cursor-pointer items-center gap-1.5 text-xs"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={`filter priority ${priorityLabel(p)}`}
|
||||
className="accent-primary"
|
||||
checked={(vm.query.priorities ?? []).includes(p)}
|
||||
onChange={() => vm.togglePriority(p)}
|
||||
/>
|
||||
<PriorityBadge priority={p} />
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<select
|
||||
aria-label="filter by assignee"
|
||||
@ -225,17 +183,6 @@ export function TicketsPanel({ projectId, onOpen }: TicketsPanelProps) {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{((vm.query.statuses?.length ?? 0) > 0 ||
|
||||
(vm.query.priorities?.length ?? 0) > 0) && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label="clear filters"
|
||||
onClick={() => vm.clearFacets()}
|
||||
>
|
||||
Effacer
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user