diff --git a/web2.0/src/pages/NgoAdmin/GuidesObservers/Page.tsx b/web2.0/src/pages/NgoAdmin/GuidesObservers/Page.tsx
new file mode 100644
index 000000000..5cb688650
--- /dev/null
+++ b/web2.0/src/pages/NgoAdmin/GuidesObservers/Page.tsx
@@ -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
;
+}
\ No newline at end of file
diff --git a/web2.0/src/pages/NgoAdmin/GuidesObservers/components/Table.tsx b/web2.0/src/pages/NgoAdmin/GuidesObservers/components/Table.tsx
new file mode 100644
index 000000000..6b74ecc12
--- /dev/null
+++ b/web2.0/src/pages/NgoAdmin/GuidesObservers/components/Table.tsx
@@ -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;
+}
+
+export default function Table({ data } : TableProps) {
+ const [rowAction, setRowAction] = useState | 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 (
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableColumns.tsx b/web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableColumns.tsx
new file mode 100644
index 000000000..39dec5259
--- /dev/null
+++ b/web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableColumns.tsx
@@ -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 | null>
+ >;
+}
+
+export function getGuidesObserversTableColumns({
+ setRowAction,
+ }: GetTasksTableColumnsProps): ColumnDef[] {
+ return [
+ {
+ id: "title",
+ accessorKey: "title",
+ header: ({column}) => (
+
+ ),
+ cell: ({row}) => (
+ {row.original.title}
+ ),
+ meta: {
+ label: "Title",
+ },
+ enableSorting: true,
+ enableHiding: true,
+ },
+ {
+ id: "createdOn",
+ accessorKey: "createdOn",
+ header: ({column}) => (
+
+ ),
+ cell: ({row}) => {row.original.createdOn}
,
+ meta: {
+ label: "Uploaded On",
+ },
+ enableSorting: true,
+ enableHiding: true,
+ },
+ {
+ id: "createdBy",
+ accessorKey: "createdBy",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {row.original.createdBy}
,
+ meta: {
+ label: "Created By",
+ },
+ enableSorting: true,
+ enableHiding: true,
+ },
+ {
+ id: "actions",
+ cell: function Cell({row}) {
+ return (
+
+
+
+
+
+ setRowAction({row, variant: "update"})}
+ >
+ Edit
+
+
+
+ setRowAction({row, variant: "delete"})}
+ >
+ Delete
+
+
+
+ );
+ },
+ size: 40,
+ },
+ ];
+}
diff --git a/web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableFilters.tsx b/web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableFilters.tsx
new file mode 100644
index 000000000..b0c4f92ba
--- /dev/null
+++ b/web2.0/src/pages/NgoAdmin/GuidesObservers/components/TableFilters.tsx
@@ -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;
+}
+
+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 (
+
+
+ navigate({
+ search: (prev) => ({ ...prev, searchText: event.target.value }),
+ replace: true,
+ })
+ }
+ className="h-8 w-40 lg:w-56"
+ />
+
+ {isFiltered && (
+
+ )}
+
+ );
+}
+
+export default TableFilters;
diff --git a/web2.0/src/queries/guides-observers.ts b/web2.0/src/queries/guides-observers.ts
new file mode 100644
index 000000000..7e9e048f1
--- /dev/null
+++ b/web2.0/src/queries/guides-observers.ts
@@ -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));
diff --git a/web2.0/src/routes/(app)/elections/$electionRoundId/guides/index.tsx b/web2.0/src/routes/(app)/elections/$electionRoundId/guides/index.tsx
index 16135a024..04ce46ccb 100644
--- a/web2.0/src/routes/(app)/elections/$electionRoundId/guides/index.tsx
+++ b/web2.0/src/routes/(app)/elections/$electionRoundId/guides/index.tsx
@@ -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 Hello "/(app)/elections/$electionRoundId/guides/"!
;
-}
diff --git a/web2.0/src/services/api/guides-observers/list.api.ts b/web2.0/src/services/api/guides-observers/list.api.ts
new file mode 100644
index 000000000..e04291c40
--- /dev/null
+++ b/web2.0/src/services/api/guides-observers/list.api.ts
@@ -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> => {
+ return API.get(`election-rounds/${electionRoundId}/observer-guide`, {
+ params: buildURLSearchParams(search),
+ }).then((res) => res.data);
+};
diff --git a/web2.0/src/types/guides-observer.ts b/web2.0/src/types/guides-observer.ts
new file mode 100644
index 000000000..afe15717d
--- /dev/null
+++ b/web2.0/src/types/guides-observer.ts
@@ -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
+>;