Skip to content

Commit 1f263cf

Browse files
authored
Merge pull request #384 from CodebilityDev/3-breadcrumb-navigation
Added semantic breadcrumb navigation
2 parents df0a67a + 2822614 commit 1f263cf

2 files changed

Lines changed: 149 additions & 61 deletions

File tree

‎apps/codebility/app/home/kanban/[projectId]/[id]/_components/KanbanBoard.tsx‎

Lines changed: 94 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
*/
2732
function 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

Comments
 (0)