Fixed pagination problem: The numbering of aliases were incorrect

This commit is contained in:
Penguin-71630
2025-12-13 00:53:41 +08:00
parent a05adb982f
commit 31598a9c96
29 changed files with 229 additions and 247 deletions

View File

@@ -12,6 +12,7 @@ import type { Image, Alias } from './types';
function App() {
const [isLoginMode, setIsLoginMode] = useState(false);
const [isInitializing, setIsInitializing] = useState(true);
const [images, setImages] = useState<Image[]>([]);
const [allAliases, setAllAliases] = useState<Alias[]>([]);
const [searchQuery, setSearchQuery] = useState('');
@@ -20,8 +21,6 @@ function App() {
const [showUploadModal, setShowUploadModal] = useState(false);
const [loading, setLoading] = useState(true);
console.log("Hello");
// Check if URL has a login token
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
@@ -36,22 +35,26 @@ function App() {
window.history.replaceState({}, document.title, '/');
// Exit login mode and load data
setIsLoginMode(false);
setIsInitializing(false);
loadData();
})
.catch((error) => {
console.error("Login failed:", error);
setIsLoginMode(true); // Show login page on error
setIsLoginMode(true);
setIsInitializing(false);
});
} else {
setIsInitializing(false);
loadData();
}
}, []);
useEffect(() => {
if (!isLoginMode) {
// Don't load data during initial mount when login is in progress
if (!isLoginMode && !isInitializing) {
loadData();
}
}, [searchQuery, isLoginMode]);
}, [searchQuery, isLoginMode, isInitializing]);
useEffect(() => {
// Reset to page 1 when search query changes
@@ -105,6 +108,8 @@ function App() {
const handleDeleteImage = async (imageId: number) => {
await api.deleteImage(imageId);
await loadData();
// After deletion, check if current page is still valid
// This will be handled by the useEffect below
};
// Show login page if in login mode
@@ -121,6 +126,14 @@ function App() {
// Pagination calculations
const totalPages = Math.max(1, Math.ceil(filteredAliases.length / ALIASES_PER_PAGE));
// Adjust current page if it's now out of bounds
useEffect(() => {
if (currentPage > totalPages) {
setCurrentPage(Math.max(1, totalPages));
}
}, [totalPages, currentPage]);
const startIndex = (currentPage - 1) * ALIASES_PER_PAGE;
const endIndex = startIndex + ALIASES_PER_PAGE;
const currentPageAliases = filteredAliases.slice(startIndex, endIndex);

View File

@@ -3,7 +3,7 @@ import type { Image, Alias } from './types';
const API_BASE_URL = 'http://localhost:8080';
// Pagination configuration
export const ALIASES_PER_PAGE = 5; // Number of aliases to show per page
export const ALIASES_PER_PAGE = 10; // Number of aliases to show per page
class ApiService {
// Authentication
@@ -111,6 +111,8 @@ class ApiService {
// PUT /api/image/{id}/aliases
// Body: {aliases: string[]}
async updateImageAliases(id: number, aliasNames: string[]): Promise<void> {
console.log("Update aliases of image ", id);
console.log("Alias names: ", aliasNames);
const response = await fetch(`${API_BASE_URL}/api/image/${id}/aliases`, {
method: 'PUT',
headers: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

View File

@@ -1,137 +0,0 @@
{
"images": [
{
"id": "1",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-20T12:00:00Z",
"aliases": ["daisuke"],
"url": "/api/images/1/file"
},
{
"id": "2",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-20T12:05:00Z",
"aliases": ["huh"],
"url": "/api/images/2/file"
},
{
"id": "3",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-21T09:00:00Z",
"aliases": ["killme"],
"url": "/api/images/3/file"
},
{
"id": "4",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-21T09:30:00Z",
"aliases": ["nofriend"],
"url": "/api/images/4/file"
},
{
"id": "5",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-21T10:00:00Z",
"aliases": ["orz"],
"url": "/api/images/5/file"
},
{
"id": "6",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-22T11:20:00Z",
"aliases": ["ramen"],
"url": "/api/images/6/file"
},
{
"id": "7",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-22T14:00:00Z",
"aliases": ["rrrr"],
"url": "/api/images/7/file"
},
{
"id": "8",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-23T08:00:00Z",
"aliases": ["sleep"],
"url": "/api/images/8/file"
},
{
"id": "9",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-23T08:05:00Z",
"aliases": ["sleep"],
"url": "/api/images/9/file"
},
{
"id": "10",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-24T15:00:00Z",
"aliases": ["你要出多少"],
"url": "/api/images/10/file"
},
{
"id": "11",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-24T16:00:00Z",
"aliases": ["好ㄘ"],
"url": "/api/images/11/file"
},
{
"id": "12",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-25T10:00:00Z",
"aliases": ["宅斃了"],
"url": "/api/images/12/file"
},
{
"id": "13",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-25T10:10:00Z",
"aliases": ["宅斃了"],
"url": "/api/images/13/file"
},
{
"id": "14",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-25T10:20:00Z",
"aliases": ["宅斃了"],
"url": "/api/images/14/file"
},
{
"id": "15",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-26T12:00:00Z",
"aliases": ["幹波大的"],
"url": "/api/images/15/file"
},
{
"id": "16",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-26T13:00:00Z",
"aliases": ["我什麼都沒有"],
"url": "/api/images/16/file"
},
{
"id": "17",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-27T09:00:00Z",
"aliases": ["欸嘿"],
"url": "/api/images/17/file"
},
{
"id": "18",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-25T10:20:00Z",
"aliases": ["宅斃了"],
"url": "/api/images/18/file"
},
{
"id": "19",
"uploaded_user_id": "konchin.shih",
"uploaded_at": "2023-10-25T10:20:00Z",
"aliases": ["宅斃了"],
"url": "/api/images/19/file"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -8,19 +8,23 @@ interface ImageGridProps {
}
export default function ImageGrid({ images, aliases, onImageClick }: ImageGridProps) {
// Create a map of alias ID to alias name
// Create a map of alias ID to alias name for current page aliases only
const aliasMap = new Map(aliases.map(a => [a.id, a.name]));
const currentPageAliasIds = new Set(aliases.map(a => a.id));
// Group images by alias
// Group images by alias, but only for aliases on the current page
const groupedImages = images.reduce((acc, image) => {
if (image.aliasesIds.length === 0) {
if (!acc['__no_alias__']) acc['__no_alias__'] = [];
acc['__no_alias__'].push(image);
} else {
// Only process aliases that are on the current page
image.aliasesIds.forEach((aliasId) => {
const aliasName = aliasMap.get(aliasId) || `Alias #${aliasId}`;
if (!acc[aliasName]) acc[aliasName] = [];
acc[aliasName].push(image);
if (currentPageAliasIds.has(aliasId)) {
const aliasName = aliasMap.get(aliasId) || `Alias #${aliasId}`;
if (!acc[aliasName]) acc[aliasName] = [];
acc[aliasName].push(image);
}
});
}
return acc;

View File

@@ -105,64 +105,36 @@ export default function ImageModal({
<div className="space-y-2">
{aliases.map((alias) => (
<div key={alias} className="flex items-center gap-2">
{(
<button
onClick={() => handleRemoveAlias(alias)}
className="text-red-600 hover:text-red-700"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
)}
<input
type="text"
value={alias}
readOnly
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg bg-gray-50"
/>
<button
onClick={() => handleRemoveAlias(alias)}
className="w-20 px-3 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium"
>
REMOVE
</button>
</div>
))}
{(
<div className="flex items-center gap-2">
<button
onClick={handleAddAlias}
className="text-green-600 hover:text-green-700"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
</button>
<input
type="text"
placeholder="Add a new alias"
value={newAlias}
onChange={(e) => setNewAlias(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
)}
<div className="flex items-center gap-2">
<input
type="text"
placeholder="press Enter or click ADD to add alias"
value={newAlias}
onChange={(e) => setNewAlias(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleAddAlias}
className="w-20 px-3 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
>
ADD
</button>
</div>
</div>
</div>
</div>
@@ -188,7 +160,7 @@ export default function ImageModal({
{(
<button
onClick={handleSave}
disabled={isSaving}
disabled={isSaving || aliases.length === 0}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium disabled:opacity-50"
>
{isSaving ? 'Saving...' : 'Save'}

View File

@@ -24,10 +24,10 @@ export default function Sidebar({
</div>
</div>
<ul className="space-y-1">
{aliases.map((alias) => (
{aliases.map((alias, index) => (
<li key={alias.id}>
<div className="w-full text-left px-3 py-2 rounded-lg bg-blue-50 text-blue-700">
<span className="text-xs text-blue-500 mr-2">#{alias.id}</span>
<span className="text-xs text-blue-500 mr-2">#{index + 1}</span>
{alias.name}
</div>
</li>

View File

@@ -171,61 +171,35 @@ export default function UploadModal({ onClose, onUpload }: UploadModalProps) {
<div className="space-y-2">
{aliases.map((alias) => (
<div key={alias} className="flex items-center gap-2">
{/* Red "X" Button: Remove this alias from the list */}
<button
onClick={() => handleRemoveAlias(alias)}
className="text-red-600 hover:text-red-700"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
<input
type="text"
value={alias}
readOnly
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg bg-gray-50"
/>
<button
onClick={() => handleRemoveAlias(alias)}
className="w-20 px-3 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors font-medium"
>
REMOVE
</button>
</div>
))}
<div className="flex items-center gap-2">
{/* Green "+" Button: Add new alias to the list */}
<button
onClick={handleAddAlias}
className="text-green-600 hover:text-green-700"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
</button>
<input
type="text"
placeholder="Add a new alias"
placeholder="press Enter or click ADD to add alias"
value={newAlias}
onChange={(e) => setNewAlias(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddAlias()}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={handleAddAlias}
className="w-20 px-3 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
>
ADD
</button>
</div>
</div>
</div>