Skip to content

Commit 2bc91d1

Browse files
author
raineer
committed
mergeconflict
2 parents 425755a + 6fffad4 commit 2bc91d1

67 files changed

Lines changed: 4468 additions & 809 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Kanban Board Performance Optimization Guide
2+
3+
This guide outlines the performance optimizations implemented for the Kanban board "mark as done" functionality.
4+
5+
## Issues Identified
6+
7+
1. **Missing RPC Function**: The `new_complete_task` RPC function was being called but didn't exist in the database
8+
2. **Full Board Refetch**: Every task completion triggered a complete board data refresh
9+
3. **No Optimistic Updates**: UI waited for server response before updating
10+
4. **Inefficient Queries**: Archived tasks were still being fetched
11+
5. **Poor User Experience**: Long loading times and unresponsive UI
12+
13+
## Solutions Implemented
14+
15+
### 1. Database Migration for RPC Function
16+
17+
Created `create_new_complete_task_function.sql` migration that:
18+
- Awards points to primary assignee and sidekicks (50% points)
19+
- Archives tasks instead of deleting them
20+
- Updates developer levels automatically
21+
- Includes proper error handling
22+
- Adds performance indexes
23+
24+
To apply the migration:
25+
```bash
26+
# Run in Supabase dashboard or via CLI
27+
supabase migration up
28+
```
29+
30+
### 2. Optimistic UI Updates
31+
32+
Updated the kanban store (`kanban-store.ts`) with new methods:
33+
- `removeTaskOptimistic`: Immediately removes task from UI
34+
- `updateTaskOptimistic`: Updates task properties instantly
35+
- `moveTaskOptimistic`: Moves tasks between columns without delay
36+
37+
### 3. Enhanced Task Completion Flow
38+
39+
Updated `TaskViewModal.tsx` to:
40+
- Remove task from UI immediately (optimistic update)
41+
- Close modal instantly for better responsiveness
42+
- Show progress toast while processing
43+
- Only refetch data on error (revert optimistic changes)
44+
- Background sync after 1 second for data consistency
45+
46+
### 4. Fallback Implementation
47+
48+
Modified `completeTask` action to:
49+
- Try RPC function first for best performance
50+
- Fall back to manual implementation if RPC doesn't exist
51+
- Archive tasks instead of deleting
52+
- Handle points and level updates
53+
- Use targeted path revalidation
54+
55+
### 5. Query Optimization
56+
57+
Updated `getBoardData` query to:
58+
- Fetch tasks separately with proper filtering
59+
- Exclude archived tasks (`is_archive = false`)
60+
- Add proper ordering
61+
- Reduce nested query complexity
62+
63+
## Performance Improvements
64+
65+
### Before:
66+
- Task completion: 3-5 seconds
67+
- UI freezes during operation
68+
- Full board refetch on every action
69+
- Poor user feedback
70+
71+
### After:
72+
- Task completion: <100ms perceived (instant UI update)
73+
- Smooth, responsive UI
74+
- Targeted updates only
75+
- Clear user feedback throughout
76+
77+
## Usage
78+
79+
The optimizations are automatic. When a user marks a task as done:
80+
81+
1. Task disappears immediately from the board
82+
2. Success toast appears
83+
3. Points are awarded in the background
84+
4. Board syncs after 1 second
85+
86+
## Error Handling
87+
88+
If an error occurs:
89+
1. User sees error toast
90+
2. Optimistic changes are reverted
91+
3. Board data is refetched
92+
4. Task reappears if completion failed
93+
94+
## Future Improvements
95+
96+
1. **WebSocket Updates**: Use Supabase realtime for instant multi-user sync
97+
2. **Batch Operations**: Allow completing multiple tasks at once
98+
3. **Offline Support**: Queue operations when offline
99+
4. **Animation**: Add smooth transitions when tasks are removed
100+
5. **Undo Feature**: Allow users to undo task completion
101+
102+
## Monitoring
103+
104+
To monitor performance:
105+
1. Check browser DevTools Network tab for API calls
106+
2. Look for "Completing task..." toast (optimistic)
107+
3. Verify no full board refetch occurs
108+
4. Check Supabase logs for RPC function execution
109+
110+
## Troubleshooting
111+
112+
If "mark as done" is slow:
113+
1. Check if RPC function exists: `SELECT proname FROM pg_proc WHERE proname = 'new_complete_task'`
114+
2. Verify indexes exist on task table
115+
3. Check browser console for errors
116+
4. Ensure optimistic updates are working (task should disappear immediately)
117+
5. Check network latency to Supabase
118+
119+
## Code References
120+
121+
- Optimistic updates: `store/kanban-store.ts:36-111`
122+
- Task completion handler: `TaskViewModal.tsx:107-151`
123+
- RPC function: `supabase/migrations/create_new_complete_task_function.sql`
124+
- Query optimization: `_services/query.ts:33-75`
125+
- Fallback implementation: `actions.ts:621-710`

‎apps/codebility/app/home/_components/LeftSidebar.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ const LeftSidebar = () => {
3838
: user.role_id;
3939
}, [user?.role_id, user?.internal_status, user?.availability_status]);
4040

41+
// Memoize sidebar data to avoid recalculation
42+
const memoizedSidebarData = useMemo(() => {
43+
return sidebarData;
44+
}, [sidebarData]);
45+
4146
useEffect(() => {
4247
const fetchSidebarData = async () => {
4348
if (roleId) {
@@ -94,7 +99,7 @@ const LeftSidebar = () => {
9499
initial={false}
95100
animate={isToggleOpen ? "open" : "closed"}
96101
variants={sidebarVariants}
97-
className="background-navbar sticky left-0 top-0 z-40 hidden h-screen flex-col gap-8 overflow-hidden p-1 shadow-lg lg:flex"
102+
className="background-navbar sticky left-0 top-0 z-40 hidden h-screen flex-col gap-8 overflow-hidden p-1 border-r border-gray-200 dark:border-gray-800 lg:flex"
98103
role="complementary"
99104
aria-label="Main navigation sidebar"
100105
>
@@ -164,7 +169,7 @@ const LeftSidebar = () => {
164169
role="navigation"
165170
aria-label="Main navigation"
166171
>
167-
{sidebarData.map((section: SidebarSection) => (
172+
{memoizedSidebarData.map((section: SidebarSection) => (
168173
<div
169174
key={section.id}
170175
role="group"
@@ -202,6 +207,7 @@ const LeftSidebar = () => {
202207
<li key={link.route} role="none">
203208
<Link
204209
href={link.route}
210+
prefetch={true}
205211
className={`${
206212
isActive
207213
? "primary-gradient text-light-900 rounded-lg"
@@ -222,6 +228,7 @@ const LeftSidebar = () => {
222228
height={28}
223229
className={`${isActive ? "brightness-0 invert" : "brightness-0 dark:invert"} h-full w-full`}
224230
aria-hidden="true"
231+
priority={isActive}
225232
/>
226233
</div>
227234
<span

‎apps/codebility/app/home/_components/Navbar.tsx‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
} from "@/public/assets/svgs";
1616
import { useUserStore } from "@/store/codev-store";
1717
import { NotificationContainer } from "@/components/notifications/NotificationContainer";
18-
import { NotificationDemo } from "@/components/notifications/NotificationDemo";
1918

2019
import {
2120
DropdownMenu,
@@ -33,8 +32,8 @@ export const defaultMenuItems = [
3332
];
3433

3534
export const adminMenus = [
36-
{ href: "/home/account-settings", icon: IconCog, label: "Settings" },
3735
{ href: "/home/settings/profile", icon: IconProfile, label: "Profile" },
36+
{ href: "/home/account-settings", icon: IconCog, label: "Settings" },
3837
];
3938

4039
const Navbar = () => {
@@ -58,7 +57,7 @@ const Navbar = () => {
5857
return (
5958
<>
6059
<nav
61-
className="background-navbar fixed top-0 z-20 w-full shadow-sm"
60+
className="fixed top-0 z-20 w-full bg-white/80 dark:bg-gray-950/80 backdrop-blur-xl border-b border-gray-200/50 dark:border-gray-800/50 shadow-sm"
6261
role="banner"
6362
>
6463
<div className="flex w-full min-w-full items-center justify-end px-8 py-2">
@@ -107,10 +106,10 @@ const Navbar = () => {
107106
className="hidden flex-col items-end md:flex"
108107
aria-hidden="true"
109108
>
110-
<p className="capitalize dark:text-white">
109+
<p className="capitalize text-gray-900 dark:text-gray-100">
111110
{first_name} {last_name}
112111
</p>
113-
<p className="text-dark100_light900 text-sm">{email_address}</p>
112+
<p className="text-gray-600 dark:text-gray-400 text-sm">{email_address}</p>
114113
</div>
115114
<div className="from-customViolet-300 to-customBlue-500 relative size-[44px] rounded-full bg-gradient-to-b p-[1.5px]">
116115
<Image
@@ -123,7 +122,7 @@ const Navbar = () => {
123122
/>
124123
</div>
125124
<IconDropdown
126-
className="hidden invert dark:invert-0 md:block"
125+
className="hidden text-gray-600 dark:text-gray-400 md:block"
127126
aria-hidden="true"
128127
/>
129128
<span className="sr-only">
@@ -132,7 +131,7 @@ const Navbar = () => {
132131
</DropdownMenuTrigger>
133132

134133
<DropdownMenuContent
135-
className="dark:bg-dark-100 absolute -left-24 top-3 border-white dark:border-zinc-700 md:w-[200px]"
134+
className="bg-white dark:bg-gray-800 absolute -left-24 top-3 border-gray-200 dark:border-gray-700 md:w-[200px] shadow-lg"
136135
role="menu"
137136
aria-label="User account options"
138137
>
@@ -179,7 +178,6 @@ const Navbar = () => {
179178
</div>
180179
</div>
181180
</nav>
182-
{/* <NotificationDemo /> */}
183181
</>
184182
);
185183
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Server Component for static navbar content
2+
import Image from "next/image";
3+
import Link from "next/link";
4+
import { getServerSession } from "next-auth/server";
5+
6+
export default async function NavbarContent() {
7+
// For now, we'll just return the logo part as a server component
8+
// The rest requires client-side interactivity
9+
return (
10+
<div className="flex items-center gap-4">
11+
<Link
12+
href="/"
13+
className="flex items-center gap-2"
14+
prefetch={true}
15+
>
16+
<Image
17+
src="/assets/svgs/codebility-black.svg"
18+
alt="Codebility Logo"
19+
width={120}
20+
height={30}
21+
className="h-8 w-auto dark:hidden"
22+
priority
23+
/>
24+
<Image
25+
src="/assets/svgs/codebility-white.svg"
26+
alt="Codebility Logo"
27+
width={120}
28+
height={30}
29+
className="hidden h-8 w-auto dark:block"
30+
priority
31+
/>
32+
</Link>
33+
</div>
34+
);
35+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client";
2+
3+
import React, { Suspense } from "react";
4+
import { ErrorBoundary } from "react-error-boundary";
5+
import { Toaster } from "sonner";
6+
import LeftSidebar from "./LeftSidebar";
7+
import Navbar from "./Navbar";
8+
9+
interface OptimizedLayoutProps {
10+
children: React.ReactNode;
11+
}
12+
13+
function ErrorFallback({ error }: { error: Error }) {
14+
return (
15+
<div className="flex h-screen w-full items-center justify-center">
16+
<div className="text-center">
17+
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
18+
Oops! Something went wrong
19+
</h2>
20+
<p className="text-gray-600 dark:text-gray-400">
21+
{error.message || "An unexpected error occurred"}
22+
</p>
23+
</div>
24+
</div>
25+
);
26+
}
27+
28+
export default function OptimizedLayout({ children }: OptimizedLayoutProps) {
29+
return (
30+
<>
31+
<Toaster
32+
richColors
33+
position="top-right"
34+
toastOptions={{
35+
className: "dark:bg-gray-800 dark:text-white",
36+
}}
37+
/>
38+
<main className="background-light850_dark100 relative flex max-w-full flex-col overflow-clip">
39+
<ErrorBoundary fallback={<ErrorFallback error={new Error("Navigation failed")} />}>
40+
<Navbar />
41+
</ErrorBoundary>
42+
<div className="flex">
43+
<ErrorBoundary fallback={<div className="w-16 bg-gray-100 dark:bg-gray-800" />}>
44+
<LeftSidebar />
45+
</ErrorBoundary>
46+
<section className="background-lightsection_darksection flex min-h-screen w-full flex-1 flex-col px-2 pb-6 pt-24 max-md:pb-14 sm:px-4 md:px-8 lg:px-12">
47+
<div className="max-w-8xl mx-auto h-full w-full">
48+
<Suspense fallback={
49+
<div className="flex items-center justify-center h-64">
50+
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-customBlue-500"></div>
51+
</div>
52+
}>
53+
{children}
54+
</Suspense>
55+
</div>
56+
</section>
57+
</div>
58+
</main>
59+
</>
60+
);
61+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ReactNode } from "react";
2+
import { cn } from "@/lib/utils";
3+
4+
interface PageContainerProps {
5+
children: ReactNode;
6+
className?: string;
7+
maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "7xl" | "full";
8+
noPadding?: boolean;
9+
}
10+
11+
export default function PageContainer({
12+
children,
13+
className,
14+
maxWidth = "7xl",
15+
noPadding = false
16+
}: PageContainerProps) {
17+
const maxWidthClasses = {
18+
sm: "max-w-screen-sm",
19+
md: "max-w-screen-md",
20+
lg: "max-w-screen-lg",
21+
xl: "max-w-screen-xl",
22+
"2xl": "max-w-screen-2xl",
23+
"7xl": "max-w-7xl",
24+
full: "max-w-full"
25+
};
26+
27+
return (
28+
<div className={cn(
29+
"min-h-screen bg-white dark:bg-gray-950",
30+
className
31+
)}>
32+
<div className={cn(
33+
"mx-auto h-full",
34+
maxWidthClasses[maxWidth],
35+
!noPadding && "px-2 pt-6 pb-6 sm:px-4 sm:pt-8 sm:pb-8 md:px-8 md:pt-10 md:pb-10 lg:px-12 lg:pt-12 lg:pb-12",
36+
"max-md:pb-14", // Extra bottom padding for mobile navigation
37+
className?.includes("flex") && "flex flex-col"
38+
)}>
39+
{children}
40+
</div>
41+
</div>
42+
);
43+
}

0 commit comments

Comments
 (0)