Skip to content
Draft
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
14 changes: 14 additions & 0 deletions web2.0/src/pages/NgoAdmin/GuidesObservers/Page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {Route} from "@/routes/(app)/elections/$electionRoundId/guides";
import {useDebounce} from "@/hooks/use-debounce.ts";
import {useListMonitoringObservers} from "@/queries/monitoring-observers.ts";
import Table from "@/pages/NgoAdmin/GuidesObservers/components/Table.tsx";

export default function Page() {
const { electionRoundId } = Route.useParams();
const search = Route.useSearch();
const debouncedSearch = useDebounce(search, 200);
const { data } = useListMonitoringObservers(electionRoundId, debouncedSearch);

// @ts-ignore
return <Table data={data} />;
}
49 changes: 49 additions & 0 deletions web2.0/src/pages/NgoAdmin/GuidesObservers/components/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type {PageResponse} from "@/types/common.ts";
import type {GuidesObserverModel} from "@/types/guides-observer.ts";
import {DataTable} from "@/components/ui/data-table.tsx";
import {DataTableToolbar} from "@/components/data-table-toolbar.tsx";
import TableFilters from "@/pages/NgoAdmin/GuidesObservers/components/TableFilters.tsx";
import {useDataTable} from "@/hooks/use-data-table.ts";
import {useMemo, useState} from "react";
import {getGuidesObserversTableColumns} from "@/pages/NgoAdmin/GuidesObservers/components/TableColumns.tsx";
import type {DataTableRowAction} from "@/types/data-table.ts";
import { Route } from '@/routes/(app)/elections/$electionRoundId/guides'

export interface TableProps {
data?: PageResponse<GuidesObserverModel>;
}

export default function Table({ data } : TableProps) {
const [rowAction, setRowAction] = useState<DataTableRowAction<GuidesObserverModel> | null>(null);

const search = Route.useSearch();
const navigate = Route.useNavigate();

const columns = useMemo(
() =>
getGuidesObserversTableColumns({
setRowAction
}), [setRowAction]
)

const { table } = useDataTable({
tableName: "guides-observer",
data: data?.items || [],
columns,
pageCount: data ? Math.ceil(data.totalCount / data.pageSize) : 0,
initialState: {
sorting: [{ id: "displayName", desc: false }],
columnPinning: { right: ["actions"] },
},
getRowId: (originalRow) => originalRow.id,
search,
navigate,
});
return (
<DataTable table={table}>
<DataTableToolbar table={table}>
<TableFilters table={table} />
</DataTableToolbar>
</DataTable>
)
}
104 changes: 104 additions & 0 deletions web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use client";

import type {DataTableRowAction} from "@/types/data-table";
import type {ColumnDef} from "@tanstack/react-table";
import {Ellipsis} from "lucide-react";
import * as React from "react";

import {DataTableColumnHeader} from "@/components/data-table-column-header";
import {Button} from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type {GuidesObserverModel} from "@/types/guides-observer.ts";

interface GetTasksTableColumnsProps {
setRowAction: React.Dispatch<
React.SetStateAction<DataTableRowAction<GuidesObserverModel> | null>
>;
}

export function getGuidesObserversTableColumns({
setRowAction,
}: GetTasksTableColumnsProps): ColumnDef<GuidesObserverModel>[] {
return [
{
id: "title",
accessorKey: "title",
header: ({column}) => (
<DataTableColumnHeader column={column} title="Title"/>
),
cell: ({row}) => (
<div className="truncate">{row.original.title}</div>
),
meta: {
label: "Title",
},
enableSorting: true,
enableHiding: true,
},
{
id: "createdOn",
accessorKey: "createdOn",
header: ({column}) => (
<DataTableColumnHeader column={column} title="Uploaded On"/>
),
cell: ({row}) => <div className="truncate">{row.original.createdOn}</div>,
meta: {
label: "Uploaded On",
},
enableSorting: true,
enableHiding: true,
},
{
id: "createdBy",
accessorKey: "createdBy",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Created By" />
),
cell: ({ row }) => <div className="truncate">{row.original.createdBy}</div>,
meta: {
label: "Created By",
},
enableSorting: true,
enableHiding: true,
},
{
id: "actions",
cell: function Cell({row}) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-label="Open menu"
variant="ghost"
className="flex size-8 p-0 data-[state=open]:bg-muted"
>
<Ellipsis className="size-4" aria-hidden="true"/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
onSelect={() => setRowAction({row, variant: "update"})}
>
Edit
</DropdownMenuItem>

<DropdownMenuSeparator/>
<DropdownMenuItem
onSelect={() => setRowAction({row, variant: "delete"})}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
size: 40,
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { getRouteApi } from "@tanstack/react-router";
import type { Table } from "@tanstack/react-table";
import { X } from "lucide-react";
import React from "react";
import type {GuidesObserverModel} from "@/types/guides-observer.ts";

interface DataTableToolbarProps extends React.ComponentProps<"div"> {
table: Table<GuidesObserverModel>;
}

const route = getRouteApi(
"/(app)/elections/$electionRoundId/guides/" as const
);

function TableFilters({ table }: DataTableToolbarProps) {
const search = route.useSearch();
const navigate = route.useNavigate();

const isFiltered = table.getState().columnFilters.length > 0;

// const onReset = React.useCallback(() => {
// table.resetColumnFilters();
// }, [table]);

const onReset = React.useCallback(() => {
console.log("reset");
}, []);

return (
<div className="flex flex-1 flex-wrap items-center gap-2">
<Input
placeholder="Search"
value={search.searchText ?? ""}
onChange={(event) =>
navigate({
search: (prev) => ({ ...prev, searchText: event.target.value }),
replace: true,
})
}
className="h-8 w-40 lg:w-56"
/>

{isFiltered && (
<Button
aria-label="Reset filters"
variant="outline"
size="sm"
className="border-dashed"
onClick={onReset}
>
<X />
Reset
</Button>
)}
</div>
);
}

export default TableFilters;
35 changes: 35 additions & 0 deletions web2.0/src/queries/guides-observers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { queryOptions, useQuery } from "@tanstack/react-query";
import type {GuidesObserversSearch} from "@/types/guides-observer.ts";
import {listGuidesObservers} from "@/services/api/guides-observers/list.api.ts";

export const guidesObserversKeys = {
all: (electionRoundId: string) =>
["guides-observers", electionRoundId] as const,
lists: (electionRoundId: string) =>
[...guidesObserversKeys.all(electionRoundId), "list"] as const,
list: (electionRoundId: string, search: GuidesObserversSearch) =>
[...guidesObserversKeys.lists(electionRoundId), { ...search }] as const,
details: (electionRoundId: string) =>
[...guidesObserversKeys.all(electionRoundId), "detail"] as const,
detail: (electionRoundId: string, id: string) =>
[...guidesObserversKeys.details(electionRoundId), id] as const,
};

const STALE_TIME = 1000 * 60 * 15; // 15 minutes

export const listGuidesObserversQueryOptions = (
electionRoundId: string,
search: GuidesObserversSearch
) =>
queryOptions({
queryKey: guidesObserversKeys.list(electionRoundId, search),
queryFn: async () => await listGuidesObservers(electionRoundId, search),
enabled: !!electionRoundId,
staleTime: STALE_TIME,
refetchOnWindowFocus: false,
});

export const useListGuidesObservers = (
electionRoundId: string,
search: GuidesObserversSearch
) => useQuery(listGuidesObserversQueryOptions(electionRoundId, search));
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createFileRoute } from "@tanstack/react-router";
import Page from "@/pages/NgoAdmin/GuidesObservers/Page"
import {zodValidator} from "@tanstack/zod-adapter";
import {guidesObserversSearchSchema} from "@/types/guides-observer.ts";

export const Route = createFileRoute(
"/(app)/elections/$electionRoundId/guides/"
)({
component: RouteComponent,
component: Page,
validateSearch: zodValidator(guidesObserversSearchSchema)
});

function RouteComponent() {
return <div>Hello "/(app)/elections/$electionRoundId/guides/"!</div>;
}
13 changes: 13 additions & 0 deletions web2.0/src/services/api/guides-observers/list.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { buildURLSearchParams } from "@/lib/utils";
import API from "@/services/api";
import type { PageResponse } from "@/types/common";
import type {GuidesObserverModel, GuidesObserversSearch} from "@/types/guides-observer.ts";

export const listGuidesObservers = (
electionRoundId: string,
search: GuidesObserversSearch
): Promise<PageResponse<GuidesObserverModel>> => {
return API.get(`election-rounds/${electionRoundId}/observer-guide`, {
params: buildURLSearchParams(search),
}).then((res) => res.data);
};
35 changes: 35 additions & 0 deletions web2.0/src/types/guides-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { z } from "zod";
import { SortOrder } from "./common";

export interface GuidesObserverModel {
id: string;
title: string;
fileName: string;
mimeType: string;
guideType: string;
text: string;
websiteUrl: string;
createdOn: string;
createdBy: string;
presignedUrl: string;
urlValidityInSeconds: string;
filePath: string;
uploadedFileName: string;
isGuideOwner: boolean;
}

export const guidesObserversSearchSchema = z.object({
title: z.string().optional(),
fileName: z.string().optional(),
mimeType: z.string().optional(),
guideType: z.string().optional(),
searchText: z.string().optional(),
sortColumnName: z.string().optional(),
sortOrder: z.enum(SortOrder).optional(),
pageNumber: z.number().default(1),
pageSize: z.number().default(25),
});

export type GuidesObserversSearch = z.infer<
typeof guidesObserversSearchSchema
>;