Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 92 additions & 32 deletions src/app/dashboard/_components/DashboardArticleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<SortBy>("created_at");
const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
const [status, setStatus] = useState<StatusFilter>("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;
Expand All @@ -52,11 +70,11 @@ 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"]);
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 {
Expand All @@ -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);
}
},
});
Expand All @@ -92,11 +110,11 @@ 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"]);
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 {
Expand All @@ -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<SortBy, string> = {
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 (
<div>
<div className="flex items-center gap-2 justify-between">
Expand All @@ -134,6 +164,58 @@ const DashboardArticleList = () => {
</Button>
</div>

{/* Sorter toolbar */}
<div className="flex flex-wrap items-center gap-2 mt-3">
{/* Status filter */}
<div className="flex items-center rounded-md border border-border overflow-hidden">
{statusFilters.map((filter) => (
<button
key={filter.value}
onClick={() => setStatus(filter.value)}
className={clsx(
"px-3 py-1.5 text-sm transition-colors",
status === filter.value
? "bg-primary text-primary-foreground"
: "hover:bg-muted"
)}
>
{filter.label}
</button>
))}
</div>

{/* Sort by */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="text-sm">
{sortByLabels[sortBy]}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{(Object.keys(sortByLabels) as SortBy[]).map((key) => (
<DropdownMenuItem key={key} onClick={() => setSortBy(key)}>
{sortByLabels[key]}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>

{/* Sort order toggle */}
<Button
variant="outline"
size="sm"
onClick={() => setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"))}
className="flex items-center gap-1"
>
{sortOrder === "asc" ? (
<ArrowUpIcon className="h-4 w-4" />
) : (
<ArrowDownIcon className="h-4 w-4" />
)}
<span className="text-sm">{sortOrder === "asc" ? _t("Ascending") : _t("Descending")}</span>
</Button>
</div>

<div className="flex flex-col divide-y divide-dashed divide-border-color mt-2">
{feedInfiniteQuery.isFetching &&
Array.from({ length: 10 }).map((_, i) => (
Expand Down Expand Up @@ -176,18 +258,6 @@ const DashboardArticleList = () => {

<div className="flex items-center gap-10 justify-between">
<div className="flex gap-4 items-center">
{/* {!article.approved_at && (
<p className="bg-yellow-400/30 rounded-sm px-2 py-1 text-sm">
🚧 {_t("অনুমোদনাধীন")}
</p>
)} */}

{/* {article.approved_at && (
<p className="bg-green-400/30 rounded-sm px-2 py-1 text-sm">
✅ {_t("অনুমোদিত")}
</p>
)} */}

{!Boolean(article?.published_at) && (
<p className="bg-yellow-400/30 rounded-sm px-2 py-1 text-sm">
🚧 {_t("Draft")}
Expand All @@ -199,16 +269,6 @@ const DashboardArticleList = () => {
✅ {_t("Published")}
</p>
)}

{/* <div className="text-forground-muted flex items-center gap-1">
<ChatBubbleIcon className="h-4 w-4" />
<p>{article?.comments_count || 0} </p>
</div> */}

{/* <div className="text-forground-muted flex items-center gap-1">
<ThickArrowUpIcon className="h-4 w-4" />
<p>{article?.votes?.score || 0} </p>
</div> */}
</div>
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-2">
Expand Down
17 changes: 14 additions & 3 deletions src/backend/services/article.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -664,8 +664,19 @@ export async function myArticles(
}

try {
const sortFn = input.sort_order === "asc" ? asc : desc;
const statusCondition =
input.status === "published"
? isNotNull<Article>("published_at")
: input.status === "draft"
? isNull<Article>("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",
Expand All @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions src/backend/services/inputs/article.input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down