11"use client" ;
22
3+ import { useDebouncedState } from "@tanstack/react-pacer/debouncer" ;
4+ import { useTranslations } from "next-intl" ;
5+ import { Activity , useState } from "react" ;
36import type { ControllerRenderProps , FieldValues } from "react-hook-form" ;
47
8+ import { cn } from "@/lib/utils" ;
59import type { Attribute } from "@/types/attributes" ;
610import type { PublicBlock } from "@/types/blocks" ;
711import type { PublicParticipant } from "@/types/participant" ;
812
913import { AttributeInputBlock } from "./attribute-input-block" ;
14+ import { Checkbox } from "./ui/checkbox" ;
15+ import { Field , FieldError , FieldGroup , FieldLabel } from "./ui/field" ;
1016import { FormControl , FormItem , FormLabel } from "./ui/form" ;
17+ import { Input } from "./ui/input" ;
1118import { RadioGroup , RadioGroupItem } from "./ui/radio-group" ;
1219
20+ function includeBlock (
21+ block : PublicBlock ,
22+ searchText : string ,
23+ hideFullBlocks : boolean ,
24+ ) : boolean {
25+ const doesBlockIncludeSearchText =
26+ searchText === "" ||
27+ block . name . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) ;
28+ const isBlockNotFull =
29+ ! hideFullBlocks ||
30+ block . capacity === null ||
31+ ( block . meta . participantsInBlockCount ?? 0 ) < block . capacity ;
32+ return doesBlockIncludeSearchText && isBlockNotFull ;
33+ }
34+
1335/**
1436 * This component wraps all block entries for a block type attribute.
1537 * It provides an accordion root element for block occupants accordion items.
@@ -26,31 +48,88 @@ export function AttributeBlocksWrapper({
2648 eventBlocks : PublicBlock [ ] ;
2749 attribute : Attribute ;
2850} ) {
51+ const t = useTranslations ( "Form" ) ;
52+ const [ searchText , setSearchText ] = useDebouncedState ( "" , {
53+ wait : 200 ,
54+ } ) ;
55+ const [ hideFullBlocks , setHideFullBlocks ] = useState ( false ) ;
56+
57+ const filteredBlocks = eventBlocks . filter ( ( block ) =>
58+ includeBlock ( block , searchText , hideFullBlocks ) ,
59+ ) ;
2960 return (
3061 < div
31- className = { `mt-4 ${ eventBlocks . length >= 3 ? "flex justify-center" : "" } ` }
62+ className = { cn (
63+ "mt-4" ,
64+ eventBlocks . length >= 3 && "flex flex-col items-center justify-center" ,
65+ ) }
3266 >
67+ { eventBlocks . length >= 5 && (
68+ < FieldGroup className = "mb-2 gap-2" >
69+ < div className = "flex w-full flex-col gap-4 md:flex-row" >
70+ < Field >
71+ < Input
72+ aria-label = { t ( "searchBlocksLabel" ) }
73+ onChange = { ( event ) => {
74+ setSearchText ( event . target . value . trim ( ) ) ;
75+ } }
76+ placeholder = { t ( "searchBlocksPlaceholder" ) }
77+ />
78+ </ Field >
79+ < Field orientation = "horizontal" className = "w-min" >
80+ < Checkbox
81+ id = "hide-full-checkbox"
82+ name = "hide-full-checkbox"
83+ checked = { hideFullBlocks }
84+ onCheckedChange = { ( checked ) => {
85+ setHideFullBlocks ( checked === true ) ;
86+ } }
87+ />
88+ < FieldLabel
89+ htmlFor = "hide-full-checkbox"
90+ className = "whitespace-nowrap"
91+ >
92+ { t ( "hideFullBlocks" ) }
93+ </ FieldLabel >
94+ </ Field >
95+ </ div >
96+ < Activity mode = { filteredBlocks . length === 0 ? "visible" : "hidden" } >
97+ < p className = "text-muted-foreground text-sm font-normal" >
98+ { t ( "noBlocksFound" ) }
99+ </ p >
100+ </ Activity >
101+ </ FieldGroup >
102+ ) }
33103 < RadioGroup
34104 onValueChange = { field . onChange }
35105 defaultValue = { String ( field . value ) }
36- className = { `mt-4 ${ eventBlocks . length >= 3 ? "xl:min-w-xl xl:grid-cols-2" : "" } ` }
106+ className = { cn (
107+ "mt-4" ,
108+ eventBlocks . length >= 3 && "w-full xl:min-w-xl xl:grid-cols-2" ,
109+ ) }
37110 >
38- < FormItem className = "flex flex-col rounded-md border border-slate-500 p-4 [&>button:first-of-type]:m-0" >
39- < div className = "flex items-center gap-4" >
40- < FormControl >
41- < RadioGroupItem value = { "null" } />
42- </ FormControl >
43- < FormLabel >
44- < p > Żaden</ p >
45- </ FormLabel >
46- </ div >
47- < span className = "h-14" > (zrezygnuj lub wypisz się)</ span >
48- </ FormItem >
49- { eventBlocks . map ( ( childBlock ) => (
111+ < Activity
112+ mode = {
113+ filteredBlocks . length === eventBlocks . length ? "visible" : "hidden"
114+ }
115+ >
116+ < FormItem className = "flex flex-col rounded-md border border-slate-500 p-4 [&>button:first-of-type]:m-0" >
117+ < div className = "flex items-center gap-4" >
118+ < FormControl >
119+ < RadioGroupItem value = { "null" } />
120+ </ FormControl >
121+ < FormLabel >
122+ < p > { t ( "noBlockOption" ) } </ p >
123+ </ FormLabel >
124+ </ div >
125+ < span className = "h-14" > { t ( "noBlockOptionDescription" ) } </ span >
126+ </ FormItem >
127+ </ Activity >
128+ { filteredBlocks . map ( ( childBlock ) => (
50129 < AttributeInputBlock
51130 userData = { userData }
52131 block = { childBlock }
53- key = { childBlock . name }
132+ key = { childBlock . id }
54133 displayedAttributes = { attribute . options ?? [ ] }
55134 />
56135 ) ) }
0 commit comments