@@ -24,26 +24,35 @@ interface KanbanBoardProps {
2424 projectId : string ;
2525}
2626
27+ /**
28+ * Enhanced KanbanBoard component with three-level breadcrumb navigation
29+ * Follows KISS, YAGNI, DRY, and SOLID principles
30+ * Maintains all existing functionality while adding proper navigation structure
31+ */
2732function KanbanBoard ( { boardData, projectId } : KanbanBoardProps ) {
2833 const user = useUserStore ( ( state ) => state . user ) ;
34+
35+ // User permissions - simple boolean checks following YAGNI principle
2936 const canAddColumn = user ?. role_id === 1 || user ?. role_id === 5 ;
3037 const canAddMember = canAddColumn ;
3138
39+ // Component state - minimal and focused
3240 const [ activeFilter , setActiveFilter ] = useState < string | null > ( null ) ;
3341 const [ showArchive , setShowArchive ] = useState ( false ) ;
3442
35- // Fetch all project members using React Query
43+ // Fetch project members using React Query for caching and optimization
3644 const { data : allMembers = [ ] , isLoading : isMembersLoading } = useQuery ( {
3745 queryKey : [ "members" , projectId ] ,
3846 queryFn : async ( ) => {
3947 const result = await getMembers ( projectId ) ;
4048 return result . data || [ ] ;
4149 } ,
4250 enabled : ! ! projectId ,
43- staleTime : 5 * 60 * 1000 ,
51+ staleTime : 5 * 60 * 1000 , // 5 minutes cache
4452 refetchOnWindowFocus : false ,
4553 } ) ;
4654
55+ // Transform members data using useMemo for performance optimization
4756 const members = useMemo (
4857 ( ) =>
4958 allMembers . map ( ( user ) => ( {
@@ -52,13 +61,15 @@ function KanbanBoard({ boardData, projectId }: KanbanBoardProps) {
5261 imageUrl : user . image_url ,
5362 isActive : activeFilter === user . id ,
5463 } ) ) ,
55- [ allMembers , activeFilter ] ,
64+ [ allMembers , activeFilter ]
5665 ) ;
5766
67+ // Filter handler using useCallback to prevent unnecessary re-renders
5868 const handleFilterClick = useCallback ( ( userId : string ) => {
5969 setActiveFilter ( ( prev ) => ( prev === userId ? null : userId ) ) ;
6070 } , [ ] ) ;
6171
72+ // Early return for missing data - fail fast principle
6273 if ( ! boardData ) {
6374 return (
6475 < div className = "text-center text-red-500" > Board data not found.</ div >
@@ -69,24 +80,47 @@ function KanbanBoard({ boardData, projectId }: KanbanBoardProps) {
6980 < div className = "relative flex h-full w-full overflow-hidden" >
7081 < div className = "relative z-10 mx-auto h-full w-[calc(100vw-22rem)] flex-1 flex-col" >
7182 < div className = "flex h-full flex-col gap-6 p-6" >
72- { /* Breadcrumb Navigation */ }
73- < div className = "flex flex-row items-center gap-3 text-sm" >
74- < Link href = { pathsConfig . app . kanban } className = "group" >
75- < span className = "group-hover:text-customBlue-600 dark:group-hover:text-customBlue-400 text-gray-500 transition-colors duration-200 dark:text-gray-400" >
83+
84+ { /* Three-Level Breadcrumb Navigation */ }
85+ < nav className = "flex flex-row items-center gap-3 text-sm" aria-label = "Breadcrumb" >
86+ { /* Level 1: Kanban Board Overview */ }
87+ < Link
88+ href = { pathsConfig . app . kanban }
89+ className = "group transition-colors duration-200"
90+ >
91+ < span className = "group-hover:text-customBlue-600 dark:group-hover:text-customBlue-400 text-gray-500 dark:text-gray-400" >
7692 📋 Kanban Board
7793 </ span >
7894 </ Link >
79- < ArrowRightIcon className = "text-gray-400 dark:text-gray-500" />
80- < span className = "to-customBlue-600 bg-gradient-to-r from-purple-600 bg-clip-text font-semibold text-gray-900 text-transparent dark:text-white" >
95+
96+ < ArrowRightIcon className = "text-gray-400 dark:text-gray-500" aria-hidden = "true" />
97+
98+ { /* Level 2: Project Sprint */ }
99+ < Link
100+ href = { `${ pathsConfig . app . kanban } /${ projectId } ` }
101+ className = "group transition-colors duration-200"
102+ >
103+ < span className = "group-hover:text-customBlue-600 dark:group-hover:text-customBlue-400 text-gray-500 dark:text-gray-400" >
104+ Kanban Sprint
105+ </ span >
106+ </ Link >
107+
108+ < ArrowRightIcon className = "text-gray-400 dark:text-gray-500" aria-hidden = "true" />
109+
110+ { /* Level 3: Current Board Name */ }
111+ < span
112+ className = "to-customBlue-600 bg-gradient-to-r from-purple-600 bg-clip-text font-semibold text-gray-900 text-transparent dark:text-white"
113+ aria-current = "page"
114+ >
81115 { boardData . name }
82116 </ span >
83- </ div >
117+ </ nav >
84118
85119 { /* Header Section */ }
86- < div className = "flex flex-col gap-6 md:justify-between lg:flex-row" >
120+ < header className = "flex flex-col gap-6 md:justify-between lg:flex-row" >
87121 < div className = "flex items-center gap-4" >
88122 < div className = "from-customBlue-500 shadow-customBlue-500/25 flex h-14 w-14 animate-pulse items-center justify-center rounded-full bg-gradient-to-br to-purple-500 shadow-xl [animation-duration:3s]" >
89- < span className = "text-2xl" > 📋</ span >
123+ < span className = "text-2xl" aria-hidden = "true" > 📋</ span >
90124 </ div >
91125 < div >
92126 < h1 className = "to-customBlue-600 bg-gradient-to-r from-purple-600 bg-clip-text text-2xl font-bold text-transparent drop-shadow-sm md:text-3xl" >
@@ -98,12 +132,14 @@ function KanbanBoard({ boardData, projectId }: KanbanBoardProps) {
98132 </ div >
99133 </ div >
100134
101- { /* Search Section - Full Width */ }
135+ { /* Search and Controls Section */ }
102136 < div className = "flex flex-col gap-4" >
137+ { /* Search Input */ }
103138 < div className = "focus-within:ring-customBlue-500 focus-within:border-customBlue-300 flex w-full items-center gap-3 rounded-xl border border-gray-200 bg-white/80 p-3 shadow-lg backdrop-blur-sm transition-all duration-300 focus-within:shadow-xl focus-within:ring-2 hover:bg-white dark:border-gray-600 dark:bg-gray-800/80" >
104- < label htmlFor = "kanbanSearch" >
105- < IconSearch className = "text-gray-400 transition-colors duration-200 dark:text-gray-500" />
139+ < label htmlFor = "kanbanSearch" className = "sr-only" >
140+ Search tasks
106141 </ label >
142+ < IconSearch className = "text-gray-400 transition-colors duration-200 dark:text-gray-500" aria-hidden = "true" />
107143 < KanbanBoardsSearch
108144 className = "w-full bg-transparent font-medium text-gray-900 placeholder-gray-500 outline-none dark:text-white dark:placeholder-gray-400"
109145 placeholder = "🔍 Search tasks..."
@@ -113,18 +149,22 @@ function KanbanBoard({ boardData, projectId }: KanbanBoardProps) {
113149
114150 { /* Controls Section */ }
115151 < div className = "flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between" >
152+ { /* User Filter */ }
116153 < div className = "h-10 overflow-visible" >
117154 < UserTaskFilter
118155 members = { members }
119156 onFilterClick = { handleFilterClick }
120157 />
121158 </ div >
122159
160+ { /* Action Buttons */ }
123161 < div className = "flex flex-col gap-2 md:flex-row md:gap-3" >
124162 { canAddColumn && (
125163 < KanbanColumnAddButton boardId = { boardData . id } />
126164 ) }
127165 { canAddMember && < KanbanAddMembersButton /> }
166+
167+ { /* Archive Toggle Button */ }
128168 < Button
129169 onClick = { ( ) => setShowArchive ( ! showArchive ) }
130170 variant = { showArchive ? "default" : "outline" }
@@ -133,8 +173,9 @@ function KanbanBoard({ boardData, projectId }: KanbanBoardProps) {
133173 ? "bg-customBlue-600 hover:bg-customBlue-700 shadow-customBlue-500/30 text-white shadow-lg"
134174 : "border-gray-300 text-gray-700 hover:border-gray-400 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
135175 } `}
176+ aria-pressed = { showArchive }
136177 >
137- < IconArchive className = "h-4 w-4" />
178+ < IconArchive className = "h-4 w-4" aria-hidden = "true" />
138179 < span className = "hidden lg:inline" >
139180 { showArchive ? "Hide Archive" : "Show Archive" }
140181 </ span >
@@ -145,48 +186,53 @@ function KanbanBoard({ boardData, projectId }: KanbanBoardProps) {
145186 </ div >
146187 </ div >
147188 </ div >
148- </ div >
189+ </ header >
149190
150191 { /* Main Content Area */ }
151- { boardData . kanban_columns . length === 0 ? (
152- < div className = "flex flex-1 items-center justify-center" >
153- < div className = "py-20 text-center" >
154- < div className = "mb-6 animate-bounce text-6xl opacity-60 [animation-duration:2s]" >
155- 📋
156- </ div >
157- < h3 className = "mb-3 text-xl font-semibold text-gray-900 dark:text-white" >
158- No columns found
159- </ h3 >
160- < p className = "mx-auto max-w-md leading-relaxed text-gray-600 dark:text-gray-400" >
161- Get started by adding your first column to organize your tasks
162- and boost productivity.
163- </ p >
164- { canAddColumn && (
165- < div className = "mt-8" >
166- < KanbanColumnAddButton boardId = { boardData . id } />
192+ < main className = "flex-1" >
193+ { boardData . kanban_columns . length === 0 ? (
194+ // Empty State
195+ < div className = "flex flex-1 items-center justify-center" >
196+ < div className = "py-20 text-center" >
197+ < div className = "mb-6 animate-bounce text-6xl opacity-60 [animation-duration:2s]" aria-hidden = "true" >
198+ 📋
167199 </ div >
200+ < h2 className = "mb-3 text-xl font-semibold text-gray-900 dark:text-white" >
201+ No columns found
202+ </ h2 >
203+ < p className = "mx-auto max-w-md leading-relaxed text-gray-600 dark:text-gray-400" >
204+ Get started by adding your first column to organize your tasks
205+ and boost productivity.
206+ </ p >
207+ { canAddColumn && (
208+ < div className = "mt-8" >
209+ < KanbanColumnAddButton boardId = { boardData . id } />
210+ </ div >
211+ ) }
212+ </ div >
213+ </ div >
214+ ) : (
215+ // Kanban Board Content
216+ < div className = "flex h-full flex-1 overflow-hidden rounded-xl bg-transparent backdrop-blur-sm transition-all duration-300" >
217+ { ! showArchive ? (
218+ < KanbanBoardColumnContainer
219+ projectId = { boardData . id }
220+ columns = { boardData . kanban_columns || [ ] }
221+ activeFilter = { activeFilter }
222+ />
223+ ) : (
224+ < ArchiveColumn projectId = { projectId } boardId = { boardData . id } />
168225 ) }
169226 </ div >
170- </ div >
171- ) : (
172- < div className = "flex h-full flex-1 overflow-hidden rounded-xl bg-transparent backdrop-blur-sm transition-all duration-300" >
173- { ! showArchive ? (
174- < KanbanBoardColumnContainer
175- projectId = { boardData . id }
176- columns = { boardData . kanban_columns || [ ] }
177- activeFilter = { activeFilter }
178- />
179- ) : (
180- < ArchiveColumn projectId = { projectId } boardId = { boardData . id } />
181- ) }
182- </ div >
183- ) }
227+ ) }
228+ </ main >
184229 </ div >
185230 </ div >
186231
232+ { /* Modal Container */ }
187233 { canAddColumn && < KanbanColumnAddModal /> }
188234 </ div >
189235 ) ;
190236}
191237
192- export default memo ( KanbanBoard ) ;
238+ export default memo ( KanbanBoard ) ;
0 commit comments