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 +>;