Skip to content

Commit 6af9305

Browse files
Merge pull request #93 from TranHoan-backend-dev/feat/create-login-web
Feat/create login web
2 parents c73ddea + a137f41 commit 6af9305

2 files changed

Lines changed: 145 additions & 102 deletions

File tree

web/src/pages/dashboard/Dashboard.tsx

Lines changed: 66 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useMemo } from "react";
22
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
33
import { Button } from "@/components/ui/button";
4-
import { Plus, BookOpen, Star, Clock } from "lucide-react";
4+
import { Plus, BookOpen, Star } from "lucide-react";
55
import PaperCard from "@/components/PaperCard";
66
import { Link } from "react-router-dom";
77
import { useAuth } from "@/contexts/AuthContext";
@@ -13,14 +13,15 @@ import DashboardHeader from "./components/DashboardHeader";
1313
import SearchAndFilter from "./components/SearchAndFilter";
1414
import { CreatePaperRequest } from "@/types/paper.types";
1515
import { getWorkflowsByUser } from "@/services/progress.service";
16-
import { supabase } from "@/integrations/supabase/client";
1716
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
17+
import { supabase } from "@/integrations/supabase/client";
18+
1819

1920
const Dashboard = () => {
2021
const { user } = useAuth();
2122
const queryClient = useQueryClient();
2223
const [isImportOpen, setIsImportOpen] = useState(false);
23-
const [activeTab, setActiveTab] = useState<'all' | 'recently_read' | 'favourites'>('all');
24+
const [activeTab, setActiveTab] = useState<'all' | 'favourites'>('all');
2425
const [searchQuery, setSearchQuery] = useState('');
2526
const [newPaper, setNewPaper] = useState({
2627
authors: '',
@@ -55,7 +56,7 @@ const Dashboard = () => {
5556
// Apply search filter if provided
5657
if (searchQuery) {
5758
const searchLower = searchQuery.toLowerCase();
58-
favorites = favorites.filter((paper) =>
59+
favorites = favorites.filter((paper: any) =>
5960
paper.title?.toLowerCase().includes(searchLower) ||
6061
paper.authors?.toLowerCase().includes(searchLower) ||
6162
paper.journal?.toLowerCase().includes(searchLower)
@@ -64,22 +65,22 @@ const Dashboard = () => {
6465

6566
// Apply filters
6667
if (filters.author) {
67-
favorites = favorites.filter((paper) =>
68+
favorites = favorites.filter((paper: any) =>
6869
paper.authors?.toLowerCase().includes(filters.author.toLowerCase())
6970
);
7071
}
7172
if (filters.journal) {
72-
favorites = favorites.filter((paper) =>
73+
favorites = favorites.filter((paper: any) =>
7374
paper.journal?.toLowerCase().includes(filters.journal.toLowerCase())
7475
);
7576
}
7677
if (filters.yearFrom) {
77-
favorites = favorites.filter((paper) =>
78+
favorites = favorites.filter((paper: any) =>
7879
paper.publicationYear >= parseInt(filters.yearFrom)
7980
);
8081
}
8182
if (filters.yearTo) {
82-
favorites = favorites.filter((paper) =>
83+
favorites = favorites.filter((paper: any) =>
8384
paper.publicationYear <= parseInt(filters.yearTo)
8485
);
8586
}
@@ -97,68 +98,6 @@ const Dashboard = () => {
9798
totalPages: Math.max(1, Math.ceil(favorites.length / pageSize)),
9899
},
99100
};
100-
} else if (activeTab === 'recently_read') {
101-
// Fetch from Supabase for recently read
102-
let query = supabase
103-
.from('papers')
104-
.select('*')
105-
.eq('user_id', user!.id)
106-
.gt('last_read_page', 0);
107-
108-
// Apply search filter if provided
109-
if (searchQuery) {
110-
query = query.or(`title.ilike.%${searchQuery}%,journal.ilike.%${searchQuery}%`);
111-
}
112-
113-
// Apply filters
114-
if (filters.author) {
115-
query = query.contains('authors', [filters.author]);
116-
}
117-
if (filters.journal) {
118-
query = query.ilike('journal', `%${filters.journal}%`);
119-
}
120-
if (filters.yearFrom) {
121-
query = query.gte('year', parseInt(filters.yearFrom));
122-
}
123-
if (filters.yearTo) {
124-
query = query.lte('year', parseInt(filters.yearTo));
125-
}
126-
127-
query = query.order('updated_at', { ascending: false });
128-
129-
const { data: supabasePapers, error: supabaseError } = await query;
130-
131-
if (supabaseError) throw supabaseError;
132-
133-
// Transform Supabase papers to match API format
134-
const transformedPapers = (supabasePapers || []).map((paper) => ({
135-
id: paper.id,
136-
title: paper.title,
137-
authors: Array.isArray(paper.authors) ? paper.authors.join(', ') : paper.authors,
138-
journal: paper.journal || '',
139-
publicationYear: paper.year,
140-
doi: paper.doi || '',
141-
description: paper.abstract || '',
142-
keywords: paper.keywords || [],
143-
status: paper.status || null,
144-
priority: paper.priority || null,
145-
isFavorite: false, // Not from backend, so default to false
146-
last_read_page: paper.last_read_page || 0,
147-
}));
148-
149-
// Client-side pagination
150-
const startIndex = (page - 1) * pageSize;
151-
const endIndex = startIndex + pageSize;
152-
const paginatedPapers = transformedPapers.slice(startIndex, endIndex);
153-
154-
return {
155-
status: 200,
156-
data: {
157-
papers: paginatedPapers,
158-
totalElements: transformedPapers.length,
159-
totalPages: Math.max(1, Math.ceil(transformedPapers.length / pageSize)),
160-
},
161-
};
162101
} else {
163102
// Use existing API for 'all' tab
164103
return await getPaginatedPapers(page, pageSize, searchQuery, filters, user?.id);
@@ -194,20 +133,66 @@ const Dashboard = () => {
194133

195134
// Enhance papers with workflow data
196135
const papersWithProgress = useMemo(() => {
197-
return papers.map(paper => {
198-
const workflow = workflowMap.get(paper.id);
199-
// Calculate total_pages from progress if available
200-
let total_pages = null;
201-
if (workflow && workflow.progress > 0 && workflow.lastPage > 0) {
202-
total_pages = Math.ceil((workflow.lastPage / (workflow.progress / 100)));
136+
let processedPapers = papers.map(paper => {
137+
// Helper function to decode base64 encoded ID
138+
const decodeId = (encodedId: string): string => {
139+
try {
140+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(encodedId)) {
141+
return encodedId;
142+
}
143+
return atob(encodedId);
144+
} catch (e) {
145+
return encodedId;
146+
}
147+
};
148+
149+
// Try to find workflow by matching decoded paperId
150+
let workflow = null;
151+
if (workflows) {
152+
workflow = workflows.find(w => {
153+
const decodedWorkflowPaperId = decodeId(w.paperId);
154+
return decodedWorkflowPaperId === paper.id || w.paperId === paper.id;
155+
});
156+
}
157+
158+
// If not found in workflows array, try workflowMap (might have different key format)
159+
if (!workflow) {
160+
workflow = workflowMap.get(paper.id);
161+
// Also try with encoded paper.id
162+
if (!workflow) {
163+
const encodedPaperId = btoa(paper.id);
164+
workflow = workflowMap.get(encodedPaperId);
165+
}
166+
}
167+
168+
// Use workflow data if available, otherwise use paper data
169+
let last_read_page = paper.last_read_page;
170+
let total_pages = paper.total_pages || null;
171+
let progress = paper.progress || null;
172+
173+
if (workflow) {
174+
// Prefer workflow data
175+
last_read_page = workflow.lastPage || paper.last_read_page || null;
176+
progress = workflow.progress || paper.progress || null;
177+
178+
// Calculate total_pages from progress if available
179+
if (workflow.progress > 0 && workflow.lastPage > 0) {
180+
total_pages = Math.ceil((workflow.lastPage / (workflow.progress / 100)));
181+
} else if (!total_pages && last_read_page && last_read_page > 0) {
182+
// If we have last_read_page but no progress, estimate total_pages
183+
total_pages = null; // Keep null if we can't calculate accurately
184+
}
203185
}
204186
return {
205187
...paper,
206-
last_read_page: workflow?.lastPage ?? null,
188+
last_read_page: last_read_page,
207189
total_pages: total_pages,
190+
progress: progress,
208191
};
209192
});
210-
}, [papers, workflowMap]);
193+
194+
return processedPapers;
195+
}, [papers, workflowMap, activeTab, workflows]);
211196

212197
const importMutation = useMutation({
213198
mutationFn: async () => {
@@ -285,15 +270,11 @@ const Dashboard = () => {
285270

286271
{/* Tabs */}
287272
<Tabs value={activeTab} onValueChange={(v) => {
288-
setActiveTab(v as 'all' | 'recently_read' | 'favourites');
273+
setActiveTab(v as 'all' | 'favourites');
289274
setPage(1); // Reset to first page when switching tabs
290275
}}>
291-
<TabsList className="grid w-full grid-cols-3">
276+
<TabsList className="grid w-full grid-cols-2">
292277
<TabsTrigger value="all">All Papers</TabsTrigger>
293-
<TabsTrigger value="recently_read">
294-
<Clock className="h-4 w-4 mr-2" />
295-
Recently Read
296-
</TabsTrigger>
297278
<TabsTrigger value="favourites">
298279
<Star className="h-4 w-4 mr-2" />
299280
Favourites
@@ -330,18 +311,14 @@ const Dashboard = () => {
330311
<BookOpen className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
331312
<h3 className="text-lg font-semibold mb-2">
332313
{activeTab === 'favourites'
333-
? 'No favourite papers yet'
334-
: activeTab === 'recently_read'
335-
? 'No recently read papers'
314+
? 'No favourite papers yet'
336315
: 'No papers yet'}
337316
</h3>
338317
<p className="text-muted-foreground mb-4">
339318
{searchQuery
340319
? 'No papers found matching your search'
341320
: activeTab === 'favourites'
342321
? 'Mark papers as favourites to see them here'
343-
: activeTab === 'recently_read'
344-
? 'Papers you\'ve read will appear here'
345322
: 'Import your first paper to get started'}
346323
</p>
347324
{!searchQuery && activeTab === 'all' && (

web/src/pages/paper/PaperDetail.tsx

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,55 +99,77 @@ const PaperDetail = () => {
9999
const createPersonalLibraryMutation = useMutation({
100100
mutationFn: async () => {
101101
if (!user?.id) throw new Error('User not logged in');
102-
return await createCollection('Personal Library', user.id);
102+
const result = await createCollection('Personal Library', user.id);
103+
// Invalidate collections query to refresh
104+
queryClient.invalidateQueries({ queryKey: ['collections', 'my', user.id] });
105+
return result;
106+
},
107+
onSuccess: () => {
108+
console.log('Personal Library created successfully');
103109
},
104110
});
105111

106112
// Get collectionId - use personal library or first collection
107113
const collectionId = useMemo(() => {
108114
if (personalLibraryCollection) return personalLibraryCollection.id;
109-
// If no collections, create personal library
110-
if (myCollections?.content && myCollections.content.length === 0 && user?.id) {
115+
// If no collections, create personal library immediately
116+
if (myCollections?.content && myCollections.content.length === 0 && user?.id && !createPersonalLibraryMutation.isPending) {
111117
createPersonalLibraryMutation.mutate();
112118
}
113119
return null;
114-
}, [personalLibraryCollection, myCollections, user?.id]);
120+
}, [personalLibraryCollection, myCollections, user?.id, createPersonalLibraryMutation]);
115121

116122
const updateProgressMutation = useMutation({
117-
mutationFn: async ({ pageNumber, totalPages }: { pageNumber: number; totalPages: number }) => {
118-
if (!user?.id || !paper?.id || !collectionId) {
119-
throw new Error('Missing required fields');
123+
mutationFn: async ({
124+
pageNumber,
125+
totalPages,
126+
collectionId: providedCollectionId
127+
}: {
128+
pageNumber: number;
129+
totalPages: number;
130+
collectionId?: string;
131+
}) => {
132+
const currentCollectionId = providedCollectionId || collectionId;
133+
134+
if (!user?.id || !paper?.id || !currentCollectionId) {
135+
throw new Error('Missing required fields: user, paper, or collectionId');
120136
}
121137

122138
// Calculate progress percentage
123139
const progress = totalPages > 0 ? Math.round((pageNumber / totalPages) * 100) : 0;
124140

141+
console.log(`Updating progress: page ${pageNumber}/${totalPages} (${progress}%) for paper ${paper.id} in collection ${currentCollectionId}`);
142+
125143
// Try to update existing workflow, if fails create new one
126144
try {
127145
await updateReadingProgress({
128-
collectionId,
146+
collectionId: currentCollectionId,
129147
paperId: paper.id,
130148
usersid: user.id,
131149
lastPage: pageNumber,
132150
progress: Math.min(100, Math.max(0, progress))
133151
});
152+
console.log('Progress updated successfully');
134153
} catch (error: any) {
135154
// If workflow doesn't exist, create it first
136155
if (error.message?.includes('not found') || error.message?.includes('404')) {
156+
console.log('Workflow not found, creating new one...');
137157
await createReadingWorkflow({
138-
collectionId,
158+
collectionId: currentCollectionId,
139159
paperId: paper.id,
140160
usersid: user.id
141161
});
142162
// Then update progress
143163
await updateReadingProgress({
144-
collectionId,
164+
collectionId: currentCollectionId,
145165
paperId: paper.id,
146166
usersid: user.id,
147167
lastPage: pageNumber,
148168
progress: Math.min(100, Math.max(0, progress))
149169
});
170+
console.log('Workflow created and progress updated');
150171
} else {
172+
console.error('Error updating progress:', error);
151173
throw error;
152174
}
153175
}
@@ -160,9 +182,53 @@ const PaperDetail = () => {
160182
},
161183
});
162184

163-
const handleProgressUpdate = (pageNumber: number, totalPages: number) => {
164-
if (collectionId) {
165-
updateProgressMutation.mutate({ pageNumber, totalPages });
185+
const handleProgressUpdate = async (pageNumber: number, totalPages: number) => {
186+
// If no collectionId, try to get or create one first
187+
let currentCollectionId = collectionId;
188+
189+
if (!currentCollectionId) {
190+
// Wait for collections to load
191+
if (!myCollections?.content) {
192+
console.warn('Collections not loaded yet, cannot save progress');
193+
return;
194+
}
195+
196+
// Try to find Personal Library or use first collection
197+
let collection = myCollections.content.find(c => c.name === 'Personal Library');
198+
if (!collection && myCollections.content.length > 0) {
199+
collection = myCollections.content[0];
200+
}
201+
202+
// If still no collection, create Personal Library
203+
if (!collection) {
204+
try {
205+
if (!user?.id) {
206+
console.error('User not logged in');
207+
return;
208+
}
209+
const newCollection = await createCollection('Personal Library', user.id);
210+
currentCollectionId = newCollection.id;
211+
// Refresh collections query
212+
queryClient.invalidateQueries({ queryKey: ['collections', 'my', user.id] });
213+
console.log('Created Personal Library for progress tracking');
214+
} catch (error) {
215+
console.error('Failed to create Personal Library:', error);
216+
return;
217+
}
218+
} else {
219+
currentCollectionId = collection.id;
220+
}
221+
}
222+
223+
if (currentCollectionId) {
224+
// Update mutation to use current collectionId
225+
updateProgressMutation.mutate({
226+
pageNumber,
227+
totalPages,
228+
collectionId: currentCollectionId
229+
});
230+
} else {
231+
console.error('Cannot save progress: no collectionId available');
166232
}
167233
};
168234

0 commit comments

Comments
 (0)