@@ -71,6 +71,8 @@ type ApiCommandsPageResponse = {
7171 offset : number ;
7272} ;
7373
74+ type SortMode = "recent" | "mostCopied" ;
75+
7476type ApiCommandStatsResponse = {
7577 total : number ;
7678 favorites : number ;
@@ -138,13 +140,20 @@ export function useCommandVault(token: string) {
138140
139141 const [ stats , setStats ] = useState < CommandStats > ( { total : 0 , favorites : 0 , byGroup : { } } ) ;
140142
143+ const [ tags , setTags ] = useState < string [ ] > ( [ ] ) ;
144+
141145 const [ commandsLoading , setCommandsLoading ] = useState ( false ) ;
142146 const [ commandsLoadingMore , setCommandsLoadingMore ] = useState ( false ) ;
143147 const [ commandsHasMore , setCommandsHasMore ] = useState ( false ) ;
144148 const [ commandsTotal , setCommandsTotal ] = useState ( 0 ) ;
145149 const [ commandsOffset , setCommandsOffset ] = useState ( 0 ) ;
146150 const commandsLimit = 30 ;
147- const [ commandsView , setCommandsView ] = useState < { groupId ?: number ; favoritesOnly ?: boolean } > ( { } ) ;
151+ const [ commandsView , setCommandsView ] = useState < { groupId ?: number ; favoritesOnly ?: boolean ; tags ?: string [ ] ; sort ?: SortMode } > ( { sort : "recent" } ) ;
152+
153+ const fetchTags = useCallback ( async ( ) => {
154+ const res = await apiRequest < string [ ] > ( "/tags" , { token } ) ;
155+ setTags ( Array . isArray ( res ) ? res : [ ] ) ;
156+ } , [ token ] ) ;
148157
149158 const fetchStats = useCallback ( async ( ) => {
150159 const res = await apiRequest < ApiCommandStatsResponse > ( "/commands/stats" , { token } ) ;
@@ -156,12 +165,14 @@ export function useCommandVault(token: string) {
156165 setStats ( { total : res . total ?? 0 , favorites : res . favorites ?? 0 , byGroup } ) ;
157166 } , [ token ] ) ;
158167
159- const fetchCommandsPage = useCallback ( async ( opts : { groupId ?: number ; favoritesOnly ?: boolean ; limit : number ; offset : number } ) => {
168+ const fetchCommandsPage = useCallback ( async ( opts : { groupId ?: number ; favoritesOnly ?: boolean ; tags ?: string [ ] ; sort ?: SortMode ; limit : number ; offset : number } ) => {
160169 const res = await apiRequest < ApiCommandsPageResponse > ( "/commands/paged" , {
161170 token,
162171 query : {
163172 group_id : opts . groupId ,
164173 is_favorite : opts . favoritesOnly ? true : undefined ,
174+ tag : opts . tags ,
175+ sort : opts . sort ,
165176 limit : opts . limit ,
166177 offset : opts . offset ,
167178 } ,
@@ -175,7 +186,7 @@ export function useCommandVault(token: string) {
175186 } ;
176187 } , [ token ] ) ;
177188
178- const loadInitialCommands = useCallback ( async ( view : { groupId ?: number ; favoritesOnly ?: boolean } ) => {
189+ const loadInitialCommands = useCallback ( async ( view : { groupId ?: number ; favoritesOnly ?: boolean ; tags ?: string [ ] ; sort ?: SortMode } ) => {
179190 setCommandsLoading ( true ) ;
180191 setCommandsView ( view ) ;
181192 try {
@@ -225,10 +236,11 @@ export function useCommandVault(token: string) {
225236 setData ( ( prev ) => ( { ...prev , groups : groups . map ( mapGroup ) } ) ) ;
226237 await Promise . all ( [
227238 fetchStats ( ) ,
239+ fetchTags ( ) ,
228240 loadInitialCommands ( commandsView ) ,
229241 ] ) ;
230242 setLoading ( false ) ;
231- } , [ commandsView , fetchStats , loadInitialCommands , token ] ) ;
243+ } , [ commandsView , fetchStats , fetchTags , loadInitialCommands , token ] ) ;
232244
233245 useEffect ( ( ) => {
234246 let cancelled = false ;
@@ -242,6 +254,7 @@ export function useCommandVault(token: string) {
242254
243255 await Promise . all ( [
244256 fetchStats ( ) ,
257+ fetchTags ( ) ,
245258 loadInitialCommands ( { } ) ,
246259 ] ) ;
247260
@@ -257,14 +270,14 @@ export function useCommandVault(token: string) {
257270 return ( ) => {
258271 cancelled = true ;
259272 } ;
260- } , [ fetchStats , loadInitialCommands , token ] ) ;
273+ } , [ fetchStats , fetchTags , loadInitialCommands , token ] ) ;
261274
262275 const persist = useCallback ( ( next : VaultData ) => {
263276 // Backend is the source of truth; keep this for compatibility.
264277 setData ( next ) ;
265278 } , [ ] ) ;
266279
267- const setCommandsViewAndReload = useCallback ( async ( view : { groupId ?: number ; favoritesOnly ?: boolean } ) => {
280+ const setCommandsViewAndReload = useCallback ( async ( view : { groupId ?: number ; favoritesOnly ?: boolean ; tags ?: string [ ] ; sort ?: SortMode } ) => {
268281 await loadInitialCommands ( view ) ;
269282 } , [ loadInitialCommands ] ) ;
270283
@@ -286,7 +299,8 @@ export function useCommandVault(token: string) {
286299 groups : [ ...prev . groups , mapped ] . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ,
287300 } ) ) ;
288301 await fetchStats ( ) ;
289- } , [ fetchStats , token ] ) ;
302+ await fetchTags ( ) ;
303+ } , [ fetchStats , fetchTags , token ] ) ;
290304
291305 const updateGroup = useCallback ( async ( id : number , updates : Partial < Group > ) => {
292306 const updated = await apiRequest < ApiGroup > ( `/groups/${ id } ` , {
@@ -313,7 +327,8 @@ export function useCommandVault(token: string) {
313327 commands : prev . commands . filter ( ( c ) => c . groupId !== id ) ,
314328 } ) ) ;
315329 await fetchStats ( ) ;
316- } , [ fetchStats , token ] ) ;
330+ await fetchTags ( ) ;
331+ } , [ fetchStats , fetchTags , token ] ) ;
317332
318333 // Commands
319334 const addCommand = useCallback ( async ( c : Omit < Command , "id" | "createdAt" | "updatedAt" > ) => {
@@ -339,7 +354,8 @@ export function useCommandVault(token: string) {
339354 return { ...prev , commands : [ mapped , ...prev . commands ] } ;
340355 } ) ;
341356 await fetchStats ( ) ;
342- } , [ commandsView . favoritesOnly , commandsView . groupId , fetchStats , token ] ) ;
357+ await fetchTags ( ) ;
358+ } , [ commandsView . favoritesOnly , commandsView . groupId , fetchStats , fetchTags , token ] ) ;
343359
344360 const updateCommand = useCallback ( async ( id : number , updates : Partial < Command > ) => {
345361 const updated = await apiRequest < ApiCommand > ( `/commands/${ id } ` , {
@@ -376,13 +392,15 @@ export function useCommandVault(token: string) {
376392 return { ...prev , commands : prev . commands . map ( ( c ) => ( c . id === id ? mapped : c ) ) } ;
377393 } ) ;
378394 await fetchStats ( ) ;
379- } , [ commandsView . favoritesOnly , commandsView . groupId , fetchStats , token ] ) ;
395+ await fetchTags ( ) ;
396+ } , [ commandsView . favoritesOnly , commandsView . groupId , fetchStats , fetchTags , token ] ) ;
380397
381398 const deleteCommand = useCallback ( async ( id : number ) => {
382399 await apiRequest ( `/commands/${ id } ` , { method : "DELETE" , token } ) ;
383400 setData ( ( prev ) => ( { ...prev , commands : prev . commands . filter ( ( c ) => c . id !== id ) } ) ) ;
384401 await fetchStats ( ) ;
385- } , [ fetchStats , token ] ) ;
402+ await fetchTags ( ) ;
403+ } , [ fetchStats , fetchTags , token ] ) ;
386404
387405 const toggleFavorite = useCallback ( async ( id : number ) => {
388406 const current = data . commands . find ( ( c ) => c . id === id ) ;
@@ -462,7 +480,7 @@ export function useCommandVault(token: string) {
462480
463481 const searchCommands = useCallback ( async (
464482 q : string ,
465- options ?: { groupId ?: number ; limit ?: number ; tags ?: string [ ] ; isFavorite ?: boolean ; signal ?: AbortSignal }
483+ options ?: { groupId ?: number ; limit ?: number ; tags ?: string [ ] ; isFavorite ?: boolean ; sort ?: SortMode ; signal ?: AbortSignal }
466484 ) => {
467485 const res = await apiRequest < ApiSearchResponse > ( "/search" , {
468486 token,
@@ -472,6 +490,7 @@ export function useCommandVault(token: string) {
472490 group_id : options ?. groupId ,
473491 is_favorite : options ?. isFavorite ,
474492 tag : options ?. tags ,
493+ sort : options ?. sort ,
475494 limit : options ?. limit ?? 200 ,
476495 } ,
477496 } ) ;
@@ -494,6 +513,7 @@ export function useCommandVault(token: string) {
494513 commandsLoadingMore,
495514 commandsHasMore,
496515 commandsTotal,
516+ tags,
497517 stats,
498518 importing,
499519 error,
0 commit comments