...
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-11-20 10:23:22 +08:00
parent 0bf3b718b2
commit 4eb44422d2
4 changed files with 108 additions and 72 deletions

View File

@@ -21,7 +21,9 @@ const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
{(folders.length === 0 && ( {(folders.length === 0 && (
<h1 className="text-2xl text-gray-900 font-light"> <h1 className="text-2xl text-gray-900 font-light">
{t("noFolders")} {t("noFolders")}
<Link className="text-blue-900 border-b" href={"/folders"}>folders</Link> <Link className="text-blue-900 border-b" href={"/folders"}>
folders
</Link>
</h1> </h1>
)) || ( )) || (
<> <>
@@ -29,26 +31,28 @@ const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
{t("selectFolder")} {t("selectFolder")}
</h1> </h1>
<div className="text-gray-900 border border-gray-200 rounded-2xl max-h-96 overflow-y-auto"> <div className="text-gray-900 border border-gray-200 rounded-2xl max-h-96 overflow-y-auto">
{folders.map((folder) => ( {folders
<div .toSorted((a, b) => a.id - b.id)
key={folder.id} .map((folder) => (
onClick={() => <div
router.push(`/memorize?folder_id=${folder.id}`) key={folder.id}
} onClick={() =>
className="flex flex-row justify-center items-center group p-2 gap-2 hover:cursor-pointer hover:bg-gray-50" router.push(`/memorize?folder_id=${folder.id}`)
> }
<Folder /> className="flex flex-row justify-center items-center group p-2 gap-2 hover:cursor-pointer hover:bg-gray-50"
<div className="flex-1 flex gap-2"> >
<span className="group-hover:text-blue-500"> <Folder />
{t("folderInfo", { <div className="flex-1 flex gap-2">
id: folder.id, <span className="group-hover:text-blue-500">
name: folder.name, {t("folderInfo", {
count: folder.total_pairs, id: folder.id,
})} name: folder.name,
</span> count: folder.total_pairs,
})}
</span>
</div>
</div> </div>
</div> ))}
))}
</div> </div>
</> </>
)} )}

View File

@@ -1,6 +1,12 @@
"use client"; "use client";
import { ChevronRight, Folder, FolderPlus, Trash2 } from "lucide-react"; import {
ChevronRight,
Folder,
FolderPen,
FolderPlus,
Trash2,
} from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Center } from "@/components/Center"; import { Center } from "@/components/Center";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
@@ -9,21 +15,26 @@ import {
createFolder, createFolder,
deleteFolderById, deleteFolderById,
getFoldersWithTotalPairsByOwner, getFoldersWithTotalPairsByOwner,
renameFolderById,
} from "@/lib/actions/services/folderService"; } from "@/lib/actions/services/folderService";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { toast } from "sonner";
interface FolderProps { interface FolderProps {
folder: folder & { total_pairs: number }; folder: folder & { total_pairs: number };
deleteCallback: () => void; refresh: () => void;
openCallback: () => void;
} }
const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => { const FolderCard = ({ folder, refresh }: FolderProps) => {
const router = useRouter();
const t = useTranslations("folders"); const t = useTranslations("folders");
return ( return (
<div <div
className="flex justify-between items-center group p-4 border-b border-gray-100 hover:bg-gray-50 cursor-pointer transition-colors" className="flex justify-between items-center group p-4 border-b border-gray-100 hover:bg-gray-50 cursor-pointer transition-colors"
onClick={openCallback} onClick={() => {
router.push(`/folders/${folder.id}`);
}}
> >
<div className="flex items-center gap-3 flex-1"> <div className="flex items-center gap-3 flex-1">
<div className="w-10 h-10 rounded-lg bg-linear-to-br from-blue-50 to-blue-100 flex items-center justify-center group-hover:from-blue-100 group-hover:to-blue-200 transition-colors"> <div className="w-10 h-10 rounded-lg bg-linear-to-br from-blue-50 to-blue-100 flex items-center justify-center group-hover:from-blue-100 group-hover:to-blue-200 transition-colors">
@@ -38,7 +49,6 @@ const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => {
totalPairs: folder.total_pairs, totalPairs: folder.total_pairs,
})} })}
</h3> </h3>
{/*<p className="text-sm text-gray-500">{} items</p>*/}
</div> </div>
<div className="text-xs text-gray-400">#{folder.id}</div> <div className="text-xs text-gray-400">#{folder.id}</div>
@@ -48,7 +58,22 @@ const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => {
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
deleteCallback(); const newName = prompt("Input a new name.")?.trim();
if (newName && newName.length > 0) {
renameFolderById(folder.id, newName).then(refresh);
}
}}
className="p-2 text-gray-400 hover:bg-red-50 rounded-lg transition-colors"
>
<FolderPen size={16} />
</button>
<button
onClick={(e) => {
e.stopPropagation();
const confirm = prompt(t("confirmDelete", { name: folder.name }));
if (confirm === folder.name) {
deleteFolderById(folder.id).then(refresh);
}
}} }}
className="p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors" className="p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors"
> >
@@ -66,21 +91,26 @@ export default function FoldersClient({ username }: { username: string }) {
[], [],
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const router = useRouter();
useEffect(() => { useEffect(() => {
getFoldersWithTotalPairsByOwner(username).then((folders) => { setLoading(true);
setFolders(folders); getFoldersWithTotalPairsByOwner(username)
}); .then((folders) => {
setFolders(folders);
setLoading(false);
})
.catch((error) => {
console.error(error);
toast.error("加载出错,请重试。");
});
}, [username]); }, [username]);
const updateFolders = async () => { const updateFolders = async () => {
setLoading(true);
try { try {
const updatedFolders = await getFoldersWithTotalPairsByOwner(username); const updatedFolders = await getFoldersWithTotalPairsByOwner(username);
setFolders(updatedFolders); setFolders(updatedFolders);
} finally { } catch (error) {
setLoading(false); console.error(error);
} }
}; };
return ( return (
@@ -123,23 +153,15 @@ export default function FoldersClient({ username }: { username: string }) {
</div> </div>
) : ( ) : (
<div className="rounded-xl border border-gray-200 overflow-hidden"> <div className="rounded-xl border border-gray-200 overflow-hidden">
{folders.map((folder) => ( {folders
<FolderCard .toSorted((a, b) => a.id - b.id)
key={folder.id} .map((folder) => (
folder={folder} <FolderCard
deleteCallback={() => { key={folder.id}
const confirm = prompt( folder={folder}
t("confirmDelete", { name: folder.name }), refresh={updateFolders}
); />
if (confirm === folder.name) { ))}
deleteFolderById(folder.id).then(updateFolders);
}
}}
openCallback={() => {
router.push(`/folders/${folder.id}`);
}}
/>
))}
</div> </div>
)} )}
</div> </div>

View File

@@ -46,14 +46,11 @@ export default function InFolder({ folderId }: { folderId: number }) {
}, [folderId]); }, [folderId]);
const refreshTextPairs = async () => { const refreshTextPairs = async () => {
setLoading(true);
try { try {
const data = await getTextPairsByFolderId(folderId); const data = await getTextPairsByFolderId(folderId);
setTextPairs(data as TextPair[]); setTextPairs(data as TextPair[]);
} catch (error) { } catch (error) {
console.error("Failed to fetch text pairs:", error); console.error("Failed to fetch text pairs:", error);
} finally {
setLoading(false);
} }
}; };
@@ -114,17 +111,19 @@ export default function InFolder({ folderId }: { folderId: number }) {
</div> </div>
) : ( ) : (
<div className="divide-y divide-gray-100"> <div className="divide-y divide-gray-100">
{textPairs.map((textPair) => ( {textPairs
<TextPairCard .toSorted((a, b) => a.id - b.id)
key={textPair.id} .map((textPair) => (
textPair={textPair} <TextPairCard
onDel={() => { key={textPair.id}
deleteTextPairById(textPair.id); textPair={textPair}
refreshTextPairs(); onDel={() => {
}} deleteTextPairById(textPair.id);
refreshTextPairs={refreshTextPairs} refreshTextPairs();
/> }}
))} refreshTextPairs={refreshTextPairs}
/>
))}
</div> </div>
)} )}
</div> </div>

View File

@@ -15,23 +15,34 @@ export async function getFoldersByOwner(owner: string) {
return folders; return folders;
} }
export async function renameFolderById(id: number, newName: string) {
await prisma.folder.update({
where: {
id: id,
},
data: {
name: newName,
},
});
}
export async function getFoldersWithTotalPairsByOwner(owner: string) { export async function getFoldersWithTotalPairsByOwner(owner: string) {
const folders = await prisma.folder.findMany({ const folders = await prisma.folder.findMany({
where: { where: {
owner: owner owner: owner,
}, },
include: { include: {
text_pair: { text_pair: {
select: { select: {
id: true id: true,
} },
} },
} },
}); });
return folders.map(folder => ({ return folders.map((folder) => ({
...folder, ...folder,
total_pairs: folder.text_pair.length total_pairs: folder.text_pair.length,
})); }));
} }