@@ -19,6 +19,14 @@ import { useMemo, useState } from 'react'
1919import type { CommentSpot } from '@/lib/enhancer'
2020import type { DraftStats } from '@/lib/enhancers/draftStats'
2121
22+ interface FilterState {
23+ hasLink : boolean
24+ hasImage : boolean
25+ hasCode : boolean
26+ sentFilter : 'all' | 'sent' | 'unsent'
27+ searchQuery : string
28+ }
29+
2230// CVA configuration for stat badges
2331const statBadge = cva (
2432 'inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-normal tracking-normal' ,
@@ -308,47 +316,45 @@ const timeAgo = (date: Date | number) => {
308316export const ClaudePrototype = ( ) => {
309317 const [ drafts ] = useState ( generateMockDrafts ( ) )
310318 const [ selectedIds , setSelectedIds ] = useState ( new Set ( ) )
311- const [ hasCodeFilter , setHasCodeFilter ] = useState ( false )
312- const [ hasImageFilter , setHasImageFilter ] = useState ( false )
313- const [ hasLinkFilter , setHasLinkFilter ] = useState ( false )
314- const [ sentFilter , setSentFilter ] = useState < 'all' | 'sent' | 'unsent' > ( 'all' )
315- const [ searchQuery , setSearchQuery ] = useState ( '' )
316- const [ sortBy , _setSortBy ] = useState ( 'edited-newest' )
319+ const [ filters , setFilters ] = useState < FilterState > ( {
320+ hasCode : false ,
321+ hasImage : false ,
322+ hasLink : false ,
323+ searchQuery : '' ,
324+ sentFilter : 'all' ,
325+ } )
326+
327+ const updateFilter = < K extends keyof FilterState > ( key : K , value : FilterState [ K ] ) => {
328+ setFilters ( ( prev ) => ( { ...prev , [ key ] : value } ) )
329+ }
317330 const [ showFilters , setShowFilters ] = useState ( false )
318331
319332 const filteredDrafts = useMemo ( ( ) => {
320333 let filtered = [ ...drafts ]
321- if ( hasCodeFilter ) {
334+ if ( filters . hasCode ) {
322335 filtered = filtered . filter ( ( d ) => d . latestDraft . stats . codeBlocks . length > 0 )
323336 }
324- if ( hasImageFilter ) {
337+ if ( filters . hasImage ) {
325338 filtered = filtered . filter ( ( d ) => d . latestDraft . stats . images . length > 0 )
326339 }
327- if ( hasLinkFilter ) {
340+ if ( filters . hasLink ) {
328341 filtered = filtered . filter ( ( d ) => d . latestDraft . stats . links . length > 0 )
329342 }
330- if ( sentFilter !== 'all' ) {
331- filtered = filtered . filter ( ( d ) => ( sentFilter === 'sent' ? d . isSent : ! d . isSent ) )
343+ if ( filters . sentFilter !== 'all' ) {
344+ filtered = filtered . filter ( ( d ) => ( filters . sentFilter === 'sent' ? d . isSent : ! d . isSent ) )
332345 }
333- if ( searchQuery ) {
334- const query = searchQuery . toLowerCase ( )
346+ if ( filters . searchQuery ) {
347+ const query = filters . searchQuery . toLowerCase ( )
335348 filtered = filtered . filter ( ( d ) =>
336349 [ d . spot . title , d . latestDraft . content , ( d . spot as any ) . slug , ( d . spot as any ) . subreddit ] . some (
337350 ( value ) => value && String ( value ) . toLowerCase ( ) . includes ( query ) ,
338351 ) ,
339352 )
340353 }
341- // Sort
342- switch ( sortBy ) {
343- case 'edited-newest' :
344- filtered . sort ( ( a , b ) => b . latestDraft . time - a . latestDraft . time )
345- break
346- case 'edited-oldest' :
347- filtered . sort ( ( a , b ) => a . latestDraft . time - b . latestDraft . time )
348- break
349- }
354+ // sort by newest
355+ filtered . sort ( ( a , b ) => b . latestDraft . time - a . latestDraft . time )
350356 return filtered
351- } , [ drafts , hasCodeFilter , hasImageFilter , hasLinkFilter , sentFilter , searchQuery , sortBy ] )
357+ } , [ drafts , filters ] )
352358
353359 const toggleSelection = ( id : string ) => {
354360 const newSelected = new Set ( selectedIds )
@@ -408,7 +414,11 @@ export const ClaudePrototype = () => {
408414
409415 if (
410416 filteredDrafts . length === 0 &&
411- ( searchQuery || hasCodeFilter || hasImageFilter || hasLinkFilter || sentFilter !== 'all' )
417+ ( filters . searchQuery ||
418+ filters . hasCode ||
419+ filters . hasImage ||
420+ filters . hasLink ||
421+ filters . sentFilter !== 'all' )
412422 ) {
413423 return (
414424 < div className = 'min-h-screen bg-white' >
@@ -421,8 +431,8 @@ export const ClaudePrototype = () => {
421431 < input
422432 type = 'text'
423433 placeholder = 'Search drafts...'
424- value = { searchQuery }
425- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
434+ value = { filters . searchQuery }
435+ onChange = { ( e ) => updateFilter ( 'searchQuery' , e . target . value ) }
426436 className = 'w-full pl-9 pr-3 py-1.5 border border-gray-300 rounded-md text-sm'
427437 />
428438 </ div >
@@ -434,11 +444,13 @@ export const ClaudePrototype = () => {
434444 < button
435445 type = 'button'
436446 onClick = { ( ) => {
437- setHasCodeFilter ( false )
438- setHasImageFilter ( false )
439- setHasLinkFilter ( false )
440- setSentFilter ( 'all' )
441- setSearchQuery ( '' )
447+ setFilters ( {
448+ hasCode : false ,
449+ hasImage : false ,
450+ hasLink : false ,
451+ searchQuery : '' ,
452+ sentFilter : 'all' ,
453+ } )
442454 } }
443455 className = 'text-blue-600 hover:underline'
444456 >
@@ -501,8 +513,8 @@ export const ClaudePrototype = () => {
501513 < input
502514 type = 'text'
503515 placeholder = 'Search drafts...'
504- value = { searchQuery }
505- onChange = { ( e ) => setSearchQuery ( e . target . value ) }
516+ value = { filters . searchQuery }
517+ onChange = { ( e ) => updateFilter ( 'searchQuery' , e . target . value ) }
506518 className = 'w-full pl-9 pr-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500'
507519 />
508520 </ div >
@@ -515,46 +527,7 @@ export const ClaudePrototype = () => {
515527 < Filter className = 'w-4 h-4 text-gray-600' />
516528 </ button >
517529 </ div >
518- { showFilters && (
519- < div className = 'absolute top-full right-0 mt-1 p-3 bg-white border border-gray-300 rounded-md shadow-lg z-10 min-w-48' >
520- < div className = 'space-y-3' >
521- < div className = 'space-y-2' >
522- < label className = 'flex items-center gap-2 cursor-pointer' >
523- < input
524- type = 'checkbox'
525- checked = { hasLinkFilter }
526- onChange = { ( e ) => setHasLinkFilter ( e . target . checked ) }
527- className = 'rounded'
528- />
529- < Badge type = 'link' />
530- </ label >
531- < label className = 'flex items-center gap-2 cursor-pointer' >
532- < input
533- type = 'checkbox'
534- checked = { hasImageFilter }
535- onChange = { ( e ) => setHasImageFilter ( e . target . checked ) }
536- className = 'rounded'
537- />
538- < Badge type = 'image' />
539- </ label >
540- < label className = 'flex items-center gap-2 cursor-pointer' >
541- < input
542- type = 'checkbox'
543- checked = { hasCodeFilter }
544- onChange = { ( e ) => setHasCodeFilter ( e . target . checked ) }
545- className = 'rounded'
546- />
547- < Badge type = 'code' />
548- </ label >
549- </ div >
550- < div className = 'flex rounded-md overflow-hidden' >
551- < Badge type = 'unsent' />
552- < Badge type = 'blank' text = 'both' />
553- < Badge type = 'sent' />
554- </ div >
555- </ div >
556- </ div >
557- ) }
530+ { showFilters && filterControls ( filters , updateFilter ) }
558531 </ div >
559532 </ th >
560533 </ tr >
@@ -569,6 +542,52 @@ export const ClaudePrototype = () => {
569542 </ div >
570543 )
571544}
545+ function filterControls (
546+ filters : FilterState ,
547+ updateFilter : < K extends keyof FilterState > ( key : K , value : FilterState [ K ] ) => void ,
548+ ) {
549+ return (
550+ < div className = 'absolute top-full right-0 mt-1 p-3 bg-white border border-gray-300 rounded-md shadow-lg z-10 min-w-48' >
551+ < div className = 'space-y-3' >
552+ < div className = 'space-y-2' >
553+ < label className = 'flex items-center gap-2 cursor-pointer' >
554+ < input
555+ type = 'checkbox'
556+ checked = { filters . hasLink }
557+ onChange = { ( e ) => updateFilter ( 'hasLink' , e . target . checked ) }
558+ className = 'rounded'
559+ />
560+ < Badge type = 'link' />
561+ </ label >
562+ < label className = 'flex items-center gap-2 cursor-pointer' >
563+ < input
564+ type = 'checkbox'
565+ checked = { filters . hasImage }
566+ onChange = { ( e ) => updateFilter ( 'hasImage' , e . target . checked ) }
567+ className = 'rounded'
568+ />
569+ < Badge type = 'image' />
570+ </ label >
571+ < label className = 'flex items-center gap-2 cursor-pointer' >
572+ < input
573+ type = 'checkbox'
574+ checked = { filters . hasCode }
575+ onChange = { ( e ) => updateFilter ( 'hasCode' , e . target . checked ) }
576+ className = 'rounded'
577+ />
578+ < Badge type = 'code' />
579+ </ label >
580+ </ div >
581+ < div className = 'flex rounded-md overflow-hidden' >
582+ < Badge type = 'unsent' />
583+ < Badge type = 'blank' text = 'both' />
584+ < Badge type = 'sent' />
585+ </ div >
586+ </ div >
587+ </ div >
588+ )
589+ }
590+
572591function commentRow (
573592 row : CommentTableRow ,
574593 selectedIds : Set < unknown > ,
0 commit comments