Skip to content

Commit 3c33db3

Browse files
committed
refactor: remove unused pagination properties and simplify related components; add project card skeleton for loading state
1 parent dfef51b commit 3c33db3

File tree

16 files changed

+143
-337
lines changed

16 files changed

+143
-337
lines changed

packages/core/src/services/devlog-service.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -503,8 +503,6 @@ export class DevlogService {
503503
limit,
504504
total,
505505
totalPages,
506-
hasPreviousPage: page > 1,
507-
hasNextPage: offset + searchResults.length < total,
508506
},
509507
searchMeta,
510508
};
@@ -712,8 +710,8 @@ export class DevlogService {
712710
limit,
713711
total,
714712
totalPages: Math.ceil(total / limit),
715-
hasPreviousPage: page > 1,
716-
hasNextPage: offset + entries.length < total,
713+
// hasPreviousPage: page > 1,
714+
// hasNextPage: offset + entries.length < total,
717715
},
718716
};
719717
}

packages/core/src/types/core.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,4 @@ export interface PaginationMeta {
347347
total?: number;
348348
/** Total number of pages */
349349
totalPages?: number;
350-
/** Whether there is a previous page */
351-
hasPreviousPage?: boolean;
352-
/** Whether there is a next page */
353-
hasNextPage?: boolean;
354350
}

packages/core/src/utils/common.ts

Lines changed: 5 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,25 @@
1-
export function parseBoolean(value: any): boolean {
2-
if (typeof value === 'boolean') {
3-
return value;
4-
}
5-
if (typeof value === 'string') {
6-
const lower = value.toLowerCase();
7-
return lower === 'true' || lower === '1';
8-
}
9-
return false;
10-
}
11-
12-
/**
13-
* Extract error message with consistent fallback pattern
14-
* Replaces repeated pattern: error instanceof Error ? error.message : String(error)
15-
*/
16-
export function extractErrorMessage(error: unknown): string {
17-
return error instanceof Error ? error.message : String(error);
18-
}
19-
20-
/**
21-
* Create standardized error response format for MCP tools and APIs
22-
*/
23-
export function createErrorResponse(operation: string, error: unknown): {
24-
success: false;
25-
error: string;
26-
operation: string;
27-
timestamp: string;
28-
} {
29-
return {
30-
success: false,
31-
error: extractErrorMessage(error),
32-
operation,
33-
timestamp: new Date().toISOString(),
34-
};
35-
}
36-
37-
/**
38-
* Create a paginated result from an array of items
39-
*/
40-
export function createPaginatedResult<T>(
41-
items: T[],
42-
pagination: { page?: number; limit?: number; offset?: number } = {}
43-
): { items: T[], pagination: { page: number, limit: number, total: number, totalPages: number, hasPreviousPage: boolean, hasNextPage: boolean } } {
44-
const page = pagination.page || 1;
45-
const limit = pagination.limit || 100;
46-
const offset = pagination.offset || (page - 1) * limit;
47-
48-
const total = items.length;
49-
const totalPages = Math.ceil(total / limit);
50-
const paginatedItems = items.slice(offset, offset + limit);
51-
52-
return {
53-
items: paginatedItems,
54-
pagination: {
55-
page,
56-
limit,
57-
total,
58-
totalPages,
59-
hasPreviousPage: page > 1,
60-
hasNextPage: page < totalPages,
61-
}
62-
};
63-
}
64-
65-
/**
66-
* Type-safe way to check if an object has a specific property
67-
*/
68-
export function hasProperty<T extends object, K extends PropertyKey>(
69-
obj: T,
70-
key: K
71-
): obj is T & Record<K, unknown> {
72-
return key in obj;
73-
}
74-
75-
/**
76-
* Safely get nested property from object with dot notation
77-
*/
78-
export function getNestedProperty(obj: any, path: string): unknown {
79-
return path.split('.').reduce((current, key) => {
80-
return current && typeof current === 'object' ? current[key] : undefined;
81-
}, obj);
82-
}
83-
841
/**
852
* Create a deep copy of an object (for small objects)
863
*/
874
export function deepCopy<T>(obj: T): T {
885
if (obj === null || typeof obj !== 'object') {
896
return obj;
907
}
91-
8+
929
if (obj instanceof Date) {
9310
return new Date(obj.getTime()) as unknown as T;
9411
}
95-
12+
9613
if (Array.isArray(obj)) {
97-
return obj.map(item => deepCopy(item)) as unknown as T;
14+
return obj.map((item) => deepCopy(item)) as unknown as T;
9815
}
99-
16+
10017
const copy = {} as T;
10118
for (const key in obj) {
10219
if (Object.prototype.hasOwnProperty.call(obj, key)) {
10320
copy[key] = deepCopy(obj[key]);
10421
}
10522
}
106-
23+
10724
return copy;
10825
}

packages/web/app/DashboardPage.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,7 @@ import { DevlogEntry } from '@codervisor/devlog-core';
77
import { useRouter } from 'next/navigation';
88

99
export function DashboardPage() {
10-
const {
11-
devlogs: filteredDevlogs,
12-
loading: isLoadingDevlogs,
13-
stats,
14-
statsLoading: isLoadingStats,
15-
timeSeriesStats: timeSeriesData,
16-
timeSeriesLoading: isLoadingTimeSeries,
17-
} = useDevlogStore();
10+
const { devlogsContext, statsContext, timeSeriesStatsContext } = useDevlogStore();
1811
const router = useRouter();
1912

2013
const handleViewDevlog = (devlog: DevlogEntry) => {
@@ -24,11 +17,9 @@ export function DashboardPage() {
2417
return (
2518
<PageLayout>
2619
<Dashboard
27-
stats={stats}
28-
timeSeriesData={timeSeriesData}
29-
isLoadingTimeSeries={isLoadingTimeSeries}
30-
recentDevlogs={filteredDevlogs.slice(0, 10)}
31-
isLoadingDevlogs={isLoadingDevlogs}
20+
statsContext={statsContext}
21+
timeSeriesStatsContext={timeSeriesStatsContext}
22+
recentDevlogsContext={devlogsContext}
3223
onViewDevlog={handleViewDevlog}
3324
/>
3425
</PageLayout>

packages/web/app/api/projects/[id]/devlogs/search/route.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
2+
import { DevlogService, PaginationMeta, ProjectService } from '@codervisor/devlog-core';
33
import { ApiValidator, ProjectIdParamSchema, DevlogSearchQuerySchema } from '@/schemas';
44
import { ApiErrors, createSuccessResponse, ResponseTransformer } from '@/lib';
55

@@ -19,14 +19,7 @@ interface SearchResult {
1919
interface SearchResponse {
2020
query: string;
2121
results: SearchResult[];
22-
pagination: {
23-
page: number;
24-
limit: number;
25-
total: number;
26-
totalPages: number;
27-
hasPreviousPage: boolean;
28-
hasNextPage: boolean;
29-
};
22+
pagination: PaginationMeta;
3023
searchMeta: {
3124
searchTime: number;
3225
totalMatches: number;
@@ -109,7 +102,11 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
109102
matchedFields: item.matchedFields,
110103
highlights: item.highlights,
111104
})),
112-
pagination: result.pagination,
105+
pagination: {
106+
...result.pagination,
107+
total: result.pagination.total ?? 0,
108+
totalPages: result.pagination.totalPages ?? 0,
109+
},
113110
searchMeta: {
114111
searchTime: result.searchMeta.searchTime,
115112
totalMatches: result.searchMeta.totalMatches,

packages/web/app/components/common/Pagination.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function Pagination({
3030
pageSizeOptions = [10, 20, 50, 100],
3131
className,
3232
}: PaginationProps) {
33-
const { page, limit, total, totalPages, hasPreviousPage, hasNextPage } = pagination;
33+
const { page, limit, total, totalPages } = pagination;
3434

3535
// Calculate visible page numbers
3636
const getVisiblePages = () => {
@@ -102,7 +102,7 @@ export function Pagination({
102102
<Button
103103
variant="outline"
104104
size="sm"
105-
disabled={!hasPreviousPage}
105+
disabled={page <= 1}
106106
onClick={() => onPageChange(page - 1)}
107107
>
108108
<ChevronLeft className="h-4 w-4 mr-1" />
@@ -133,7 +133,7 @@ export function Pagination({
133133
<Button
134134
variant="outline"
135135
size="sm"
136-
disabled={!hasNextPage}
136+
disabled={page >= totalPages!}
137137
onClick={() => onPageChange(page + 1)}
138138
>
139139
Next
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
// Common Components
22
export * from '@/components/common/overview-stats/OverviewStats';
3+
export * from '@/components/common/project-card-skeleton';
34
export { Pagination } from './Pagination';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { Card, CardContent, CardHeader } from '@/components/ui/card';
5+
import { Skeleton } from '@/components/ui/skeleton';
6+
7+
export function ProjectCardSkeleton() {
8+
return (
9+
<Card className="w-96 h-48">
10+
<CardHeader className="pb-3">
11+
<div className="flex items-start justify-between">
12+
<Skeleton className="h-6 w-32" />
13+
<div className="flex items-center gap-2 h-7">
14+
<Skeleton className="h-4 w-4" />
15+
</div>
16+
</div>
17+
</CardHeader>
18+
19+
<CardContent className="pb-3">
20+
<div className="space-y-2 mb-4">
21+
<Skeleton className="h-4 w-full" />
22+
<Skeleton className="h-4 w-3/4" />
23+
</div>
24+
</CardContent>
25+
</Card>
26+
);
27+
}
28+
29+
export function ProjectGridSkeleton({ count = 6 }: { count?: number }) {
30+
return (
31+
<div className="flex items-center gap-6 flex-wrap">
32+
{Array.from({ length: count }).map((_, index) => (
33+
<ProjectCardSkeleton key={index} />
34+
))}
35+
</div>
36+
);
37+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ProjectCardSkeleton, ProjectGridSkeleton } from './ProjectCardSkeleton';

packages/web/app/components/layout/NavigationSidebar.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import React, { useEffect, useState } from 'react';
44
import { usePathname, useRouter } from 'next/navigation';
5-
import { useProjectStore } from '@/stores';
65
import {
76
Sidebar,
87
SidebarContent,
@@ -12,13 +11,16 @@ import {
1211
SidebarMenuItem,
1312
SidebarTrigger,
1413
} from '@/components/ui/sidebar';
15-
import { AppWindowIcon, FileTextIcon, LayoutDashboardIcon } from 'lucide-react';
14+
import { Home, Package, SquareKanban } from 'lucide-react';
1615

17-
interface NavigationSidebarProps {
18-
// No props needed - using built-in sidebar state
16+
interface SidebarItem {
17+
key: string;
18+
label: string;
19+
icon: React.ReactNode;
20+
path: string;
1921
}
2022

21-
export function NavigationSidebar(_props: NavigationSidebarProps) {
23+
export function NavigationSidebar() {
2224
const router = useRouter();
2325
const pathname = usePathname();
2426
const [mounted, setMounted] = useState(false);
@@ -47,7 +49,7 @@ export function NavigationSidebar(_props: NavigationSidebarProps) {
4749
{
4850
key: 'projects',
4951
label: 'Projects',
50-
icon: <AppWindowIcon size={16} />,
52+
icon: <Package size={16} />,
5153
},
5254
];
5355
}
@@ -58,23 +60,26 @@ export function NavigationSidebar(_props: NavigationSidebarProps) {
5860
{
5961
key: 'projects',
6062
label: 'Projects',
61-
icon: <AppWindowIcon size={16} />,
63+
icon: <Package size={16} />,
6264
},
6365
];
6466
}
6567

68+
if (pathname.match(/^\/projects\/\w+\/devlogs/)) {
69+
}
70+
6671
// Project detail page (/projects/[id])
6772
if (pathParts.length === 2 && pathParts[0] === 'projects') {
6873
return [
6974
{
7075
key: 'overview',
7176
label: 'Overview',
72-
icon: <LayoutDashboardIcon size={16} />,
77+
icon: <Home size={16} />,
7378
},
7479
{
7580
key: 'list',
7681
label: 'Devlogs',
77-
icon: <FileTextIcon size={16} />,
82+
icon: <SquareKanban size={16} />,
7883
},
7984
];
8085
}
@@ -85,12 +90,12 @@ export function NavigationSidebar(_props: NavigationSidebarProps) {
8590
{
8691
key: 'overview',
8792
label: 'Overview',
88-
icon: <LayoutDashboardIcon size={16} />,
93+
icon: <Home size={16} />,
8994
},
9095
{
9196
key: 'list',
9297
label: 'Devlogs',
93-
icon: <FileTextIcon size={16} />,
98+
icon: <SquareKanban size={16} />,
9499
},
95100
];
96101
}
@@ -101,12 +106,12 @@ export function NavigationSidebar(_props: NavigationSidebarProps) {
101106
{
102107
key: 'overview',
103108
label: 'Overview',
104-
icon: <LayoutDashboardIcon size={16} />,
109+
icon: <Home size={16} />,
105110
},
106111
{
107112
key: 'list',
108113
label: 'Devlogs',
109-
icon: <FileTextIcon size={16} />,
114+
icon: <SquareKanban size={16} />,
110115
},
111116
];
112117
}
@@ -116,12 +121,12 @@ export function NavigationSidebar(_props: NavigationSidebarProps) {
116121
{
117122
key: 'overview',
118123
label: 'Overview',
119-
icon: <LayoutDashboardIcon size={16} />,
124+
icon: <Home size={16} />,
120125
},
121126
{
122127
key: 'projects',
123128
label: 'Projects',
124-
icon: <AppWindowIcon size={16} />,
129+
icon: <Package size={16} />,
125130
},
126131
];
127132
};

0 commit comments

Comments
 (0)