From fbd0a742449e0ed01ff7eb110d40909d077040b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:47:25 +0000 Subject: [PATCH 1/3] Initial plan From 1de564c17521b765a39794c5985439b1aee0d3d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:53:37 +0000 Subject: [PATCH 2/3] feat: add article sorter to dashboard article list - Add sort_by (title/created_at/published_at), sort_order (asc/desc), and status (all/published/draft) to myArticleInput schema - Update myArticles action to apply dynamic sorting and status filtering via sqlkit asc/isNull/isNotNull - Add sorter toolbar to DashboardArticleList with status filter tabs, sort field dropdown, and sort order toggle button Agent-Logs-Url: https://github.com/techdiary-dev/techdiary.dev/sessions/a0bc19b6-228e-48b9-8d7b-e5422cb0c559 Co-authored-by: kingRayhan <7611746+kingRayhan@users.noreply.github.com> --- .../_components/DashboardArticleList.tsx | 120 +++++++++++++----- src/backend/services/article.actions.ts | 17 ++- src/backend/services/inputs/article.input.ts | 3 + 3 files changed, 107 insertions(+), 33 deletions(-) diff --git a/src/app/dashboard/_components/DashboardArticleList.tsx b/src/app/dashboard/_components/DashboardArticleList.tsx index aa855f6..cf0907e 100644 --- a/src/app/dashboard/_components/DashboardArticleList.tsx +++ b/src/app/dashboard/_components/DashboardArticleList.tsx @@ -13,6 +13,8 @@ import VisibilitySensor from "@/components/VisibilitySensor"; import { useTranslation } from "@/i18n/use-translation"; import { actionPromisify, formattedTime } from "@/lib/utils"; import { + ArrowDownIcon, + ArrowUpIcon, CardStackIcon, DotsHorizontalIcon, Pencil1Icon, @@ -29,15 +31,31 @@ import clsx from "clsx"; import { addDays, differenceInHours } from "date-fns"; import { TrashIcon } from "lucide-react"; import Link from "next/link"; +import { useState } from "react"; + +type SortBy = "created_at" | "title" | "published_at"; +type SortOrder = "asc" | "desc"; +type StatusFilter = "all" | "published" | "draft"; const DashboardArticleList = () => { const { _t } = useTranslation(); const queryClient = useQueryClient(); const appConfirm = useAppConfirm(); + + const [sortBy, setSortBy] = useState("created_at"); + const [sortOrder, setSortOrder] = useState("desc"); + const [status, setStatus] = useState("all"); + const feedInfiniteQuery = useInfiniteQuery({ - queryKey: ["dashboard-articles"], + queryKey: ["dashboard-articles", sortBy, sortOrder, status], queryFn: ({ pageParam }) => - articleActions.myArticles({ limit: 10, page: pageParam }), + articleActions.myArticles({ + limit: 10, + page: pageParam, + sort_by: sortBy, + sort_order: sortOrder, + status, + }), initialPageParam: 1, getNextPageParam: (lastPage) => { const _page = lastPage?.meta?.currentPage ?? 1; @@ -54,9 +72,9 @@ const DashboardArticleList = () => { onMutate: async (article_id: string) => { await queryClient.cancelQueries({ queryKey: ["dashboard-articles"] }); - const previousData = queryClient.getQueryData(["dashboard-articles"]); + const previousData = queryClient.getQueryData(["dashboard-articles", sortBy, sortOrder, status]); - queryClient.setQueryData(["dashboard-articles"], (old: any) => { + queryClient.setQueryData(["dashboard-articles", sortBy, sortOrder, status], (old: any) => { if (!old) return old; return { @@ -80,7 +98,7 @@ const DashboardArticleList = () => { }, onError: (err, variables, context) => { if (context?.previousData) { - queryClient.setQueryData(["dashboard-articles"], context.previousData); + queryClient.setQueryData(["dashboard-articles", sortBy, sortOrder, status], context.previousData); } }, }); @@ -94,9 +112,9 @@ const DashboardArticleList = () => { onMutate: async (article_id: string) => { await queryClient.cancelQueries({ queryKey: ["dashboard-articles"] }); - const previousData = queryClient.getQueryData(["dashboard-articles"]); + const previousData = queryClient.getQueryData(["dashboard-articles", sortBy, sortOrder, status]); - queryClient.setQueryData(["dashboard-articles"], (old: any) => { + queryClient.setQueryData(["dashboard-articles", sortBy, sortOrder, status], (old: any) => { if (!old) return old; return { @@ -116,11 +134,23 @@ const DashboardArticleList = () => { }, onError: (_, __, context) => { if (context?.previousData) { - queryClient.setQueryData(["dashboard-articles"], context.previousData); + queryClient.setQueryData(["dashboard-articles", sortBy, sortOrder, status], context.previousData); } }, }); + const sortByLabels: Record = { + created_at: _t("Created at"), + title: _t("Title"), + published_at: _t("Published at"), + }; + + const statusFilters: { value: StatusFilter; label: string }[] = [ + { value: "all", label: _t("All") }, + { value: "published", label: _t("Published") }, + { value: "draft", label: _t("Draft") }, + ]; + return (
@@ -134,6 +164,58 @@ const DashboardArticleList = () => {
+ {/* Sorter toolbar */} +
+ {/* Status filter */} +
+ {statusFilters.map((filter) => ( + + ))} +
+ + {/* Sort by */} + + + + + + {(Object.keys(sortByLabels) as SortBy[]).map((key) => ( + setSortBy(key)}> + {sortByLabels[key]} + + ))} + + + + {/* Sort order toggle */} + +
+
{feedInfiniteQuery.isFetching && Array.from({ length: 10 }).map((_, i) => ( @@ -176,18 +258,6 @@ const DashboardArticleList = () => {
- {/* {!article.approved_at && ( -

- 🚧 {_t("āĻ…āύ⧁āĻŽā§‹āĻĻāύāĻžāϧ⧀āύ")} -

- )} */} - - {/* {article.approved_at && ( -

- ✅ {_t("āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ")} -

- )} */} - {!Boolean(article?.published_at) && (

🚧 {_t("Draft")} @@ -199,16 +269,6 @@ const DashboardArticleList = () => { ✅ {_t("Published")}

)} - - {/*
- -

{article?.comments_count || 0}

-
*/} - - {/*
- -

{article?.votes?.score || 0}

-
*/}
diff --git a/src/backend/services/article.actions.ts b/src/backend/services/article.actions.ts index 9be27b0..c576ee2 100644 --- a/src/backend/services/article.actions.ts +++ b/src/backend/services/article.actions.ts @@ -11,7 +11,7 @@ import { } from "@/lib/utils"; import { addDays } from "date-fns"; import * as sk from "sqlkit"; -import { and, desc, eq, like, neq, or } from "sqlkit"; +import { and, asc, desc, eq, isNotNull, isNull, like, neq, or } from "sqlkit"; import { z } from "zod/v4"; import { ActionResponse } from "../models/action-contracts"; import { Article, User } from "../models/domain-models"; @@ -664,8 +664,19 @@ export async function myArticles( } try { + const sortFn = input.sort_order === "asc" ? asc : desc; + const statusCondition = + input.status === "published" + ? isNotNull
("published_at") + : input.status === "draft" + ? isNull
("published_at") + : undefined; + const articles = await persistenceRepository.article.paginate({ - where: eq("author_id", sessionUserId!), + where: and( + eq("author_id", sessionUserId!), + ...(statusCondition ? [statusCondition] : []) + ), columns: [ "id", "title", @@ -677,7 +688,7 @@ export async function myArticles( ], limit: input.limit, page: input.page, - orderBy: [desc("created_at")], + orderBy: [sortFn(input.sort_by)], }); return articles; } catch (error) { diff --git a/src/backend/services/inputs/article.input.ts b/src/backend/services/inputs/article.input.ts index 597e811..9fd80a3 100644 --- a/src/backend/services/inputs/article.input.ts +++ b/src/backend/services/inputs/article.input.ts @@ -119,6 +119,9 @@ export const ArticleRepositoryInput = { myArticleInput: z.object({ page: z.number().default(1), limit: z.number().default(10), + sort_by: z.enum(["created_at", "title", "published_at"]).default("created_at"), + sort_order: z.enum(["asc", "desc"]).default("desc"), + status: z.enum(["all", "published", "draft"]).default("all"), }), tagFeedInput: z.object({ From 55ea86eea92682fcfc6a36eed81ea458adb5b310 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:55:08 +0000 Subject: [PATCH 3/3] fix: use full queryKey in cancelQueries for dashboard article mutations Agent-Logs-Url: https://github.com/techdiary-dev/techdiary.dev/sessions/a0bc19b6-228e-48b9-8d7b-e5422cb0c559 Co-authored-by: kingRayhan <7611746+kingRayhan@users.noreply.github.com> --- src/app/dashboard/_components/DashboardArticleList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/dashboard/_components/DashboardArticleList.tsx b/src/app/dashboard/_components/DashboardArticleList.tsx index cf0907e..381f61f 100644 --- a/src/app/dashboard/_components/DashboardArticleList.tsx +++ b/src/app/dashboard/_components/DashboardArticleList.tsx @@ -70,7 +70,7 @@ const DashboardArticleList = () => { enableToast: true, }), onMutate: async (article_id: string) => { - await queryClient.cancelQueries({ queryKey: ["dashboard-articles"] }); + await queryClient.cancelQueries({ queryKey: ["dashboard-articles", sortBy, sortOrder, status] }); const previousData = queryClient.getQueryData(["dashboard-articles", sortBy, sortOrder, status]); @@ -110,7 +110,7 @@ const DashboardArticleList = () => { { enableToast: true } ), onMutate: async (article_id: string) => { - await queryClient.cancelQueries({ queryKey: ["dashboard-articles"] }); + await queryClient.cancelQueries({ queryKey: ["dashboard-articles", sortBy, sortOrder, status] }); const previousData = queryClient.getQueryData(["dashboard-articles", sortBy, sortOrder, status]);