1import {
2 applyListFilters,
3 type FilterCondition,
4} from "@/components/listViewFilters";
5import { views } from "@/composables/useView";
6import { router } from "@/router";
7import { useAuthStore } from "@/stores/auth";
8import { __ } from "@/translation";
9import type { View } from "@/types";
10import { getIcon } from "@/utils";
11import {
12 priorityKeywords,
13 priorityOptions,
14 statusKeywords,
15 statusOptions,
16} from "./optionCommands";
17import { CONTEXT_WEIGHT, GROUP, type Command } from "./paletteTypes";
18
19import LucideCircleDot from "~icons/lucide/circle-dot";
20import LucideFilterX from "~icons/lucide/filter-x";
21import LucideFlag from "~icons/lucide/flag";
22import LucideLayoutList from "~icons/lucide/layout-list";
23import LucideUser from "~icons/lucide/user";
24
25/** List-route actions. Filters travel as a URL query — the palette lives in the
26 * sidebar, outside the list's provide chain, so it cannot inject its actions. */
27export function ticketListCommands(): Command[] {
28 return [
29 {
30 id: "list-filter-status",
31 title: __("Filter by status"),
32 group: GROUP.list,
33 weight: CONTEXT_WEIGHT,
34 icon: LucideCircleDot,
35 // No "f" hint: that key opens the whole filter popover, not this row.
36 keywords: statusKeywords(),
37 children: () => statusFilterChildren(),
38 },
39 {
40 id: "list-filter-priority",
41 title: __("Filter by priority"),
42 group: GROUP.list,
43 weight: CONTEXT_WEIGHT,
44 icon: LucideFlag,
45 keywords: priorityKeywords(),
46 children: () => priorityFilterChildren(),
47 },
48 {
49 id: "list-assigned-to-me",
50 title: __("Show tickets assigned to me"),
51 group: GROUP.list,
52 weight: CONTEXT_WEIGHT,
53 icon: LucideUser,
54 keywords: "mine my assignment",
55 perform: () =>
56 applyFilter([["_assign", "like", `%${useAuthStore().userId}%`]]),
57 },
58 {
59 id: "list-switch-view",
60 title: __("Switch view"),
61 group: GROUP.list,
62 weight: CONTEXT_WEIGHT,
63 icon: LucideLayoutList,
64 keywords: "saved public private preset",
65 children: () => viewChildren(),
66 },
67 {
68 id: "list-clear-filters",
69 title: __("Clear filters"),
70 group: GROUP.list,
71 weight: CONTEXT_WEIGHT,
72 icon: LucideFilterX,
73 keywords: "reset remove all",
74 perform: () => applyFilter([]),
75 },
76 ];
77}
78
79function statusFilterChildren(): Command[] {
80 return statusOptions({
81 group: "Filter by status",
82 onPick: (status) => applyFilter([["status", "=", status]]),
83 });
84}
85
86function priorityFilterChildren(): Command[] {
87 return priorityOptions({
88 group: "Filter by priority",
89 onPick: (priority) => applyFilter([["priority", "=", priority]]),
90 });
91}
92
93function viewChildren(): Command[] {
94 // The active view travels in the route query; absent means the default list.
95 const currentView = String(router.currentRoute.value.query.view ?? "");
96 const ticketViews = (views.data ?? []).filter(
97 (view: View) => view.dt === "OD Ticket"
98 );
99 return [
100 {
101 id: "list-view-default",
102 title: __("List"),
103 group: "Switch view",
104 icon: LucideLayoutList,
105 checked: !currentView,
106 perform: () => router.push({ name: "TicketsAgent" }),
107 },
108 ...ticketViews.map((view: View) => ({
109 id: `list-view-${view.name}`,
110 title: view.label ?? view.name,
111 subtitle: viewScope(view),
112 group: "Switch view",
113 // Same resolver as the sidebar: emoji, lucide name, or the ticket default.
114 icon: () => getIcon(view.icon),
115 checked: view.name === currentView,
116 perform: () =>
117 router.push({ name: "TicketsAgent", query: { view: view.name } }),
118 })),
119 ];
120}
121
122function viewScope(view: View): string {
123 if (view.public) return __("Public");
124 if (view.pinned) return __("Pinned");
125 return __("Private");
126}
127
128/** Layers onto the list's filters; empty clears. URL fallback only when no list
129 * is mounted — the popover never reads the URL back. */
130function applyFilter(conditions: FilterCondition[]): void {
131 if (applyListFilters(conditions)) return;
132 router.push({
133 name: "TicketsAgent",
134 query: {
135 ...currentViewQuery(),
136 filters: JSON.stringify(conditions),
137 },
138 });
139}
140
141/** Keep the active view when filtering, so filters layer onto it. */
142function currentViewQuery(): Record<string, string> {
143 const view = router.currentRoute.value.query.view;
144 return view ? { view: String(view) } : {};
145}
146