From a22f60b7fbc1fa0a981c26a6b53ad34c0fc07762 Mon Sep 17 00:00:00 2001 From: Lim Zi Yang Date: Wed, 15 May 2024 22:33:27 +0800 Subject: [PATCH] Integrate admin sr dashboard (#136) * feat: create use-pagination hook * feat: add pagination to FE * feat: use real pagination data * feat: add ErrPaginationParamsError * chore: remove TODO * chore: update bruno * refactor: add type to state * feat: add params as query keys * feat: add pagination state to react table hook * chore: remove commented code * feat: add page count * fix: update prev button icon * feat: remove hide option from table header * feat: integrate get service request for admin api * refactor: extract user info state management into a hook * feat: extract created by info into component * feat: add created by component to admin sr dashboard column * feat: integrate server side pagination for admin sr dashboard * feat: add page params to get admin sr api --------- Co-authored-by: Joshua Tan --- .../[admin] all service requests.bru | 4 +- .../_hooks/use-org-service-requests.ts | 43 ++++++++++++++++++- .../columns.tsx | 7 ++- .../admin-service-requests-dashboard/page.tsx | 15 ++++++- ...a-table-column-header-filterable-value.tsx | 5 --- .../src/components/data-table/data-table.tsx | 4 +- .../components/layouts/created-by-info.tsx | 16 +++++++ .../service-request-details-dialog.tsx | 19 ++------ frontend/src/hooks/use-user-info.ts | 27 ++++++++++++ frontend/src/lib/service.ts | 21 +++++++++ 10 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 frontend/src/components/layouts/created-by-info.tsx create mode 100644 frontend/src/hooks/use-user-info.ts diff --git a/flowforge_api_bruno/service_request/[admin] all service requests.bru b/flowforge_api_bruno/service_request/[admin] all service requests.bru index 0754da8d..6779ec58 100644 --- a/flowforge_api_bruno/service_request/[admin] all service requests.bru +++ b/flowforge_api_bruno/service_request/[admin] all service requests.bru @@ -5,11 +5,13 @@ meta { } get { - url: {{HOST}}/service_request/admin?org_id=1 + url: {{HOST}}/service_request/admin?org_id=1&page=1&page_size=1 body: none auth: inherit } query { org_id: 1 + page: 1 + page_size: 1 } diff --git a/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/_hooks/use-org-service-requests.ts b/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/_hooks/use-org-service-requests.ts index 4cb94255..64fa4de2 100644 --- a/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/_hooks/use-org-service-requests.ts +++ b/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/_hooks/use-org-service-requests.ts @@ -1,6 +1,11 @@ +import { toast } from "@/components/ui/use-toast" +import useOrganizationId from "@/hooks/use-organization-id" +import { getAllServiceRequestForAdmin } from "@/lib/service" import { FormFieldType, JsonFormComponents } from "@/types/json-form-components" import { StepStatus } from "@/types/pipeline" import { ServiceRequest, ServiceRequestStatus } from "@/types/service-request" +import { useQuery } from "@tanstack/react-query" +import { useMemo } from "react" const DUMMY_PIPELINE_FORM: JsonFormComponents = { fields: [ @@ -216,8 +221,42 @@ const DUMMY_SERVICE_REQUESTS: ServiceRequest[] = [ }, ] -const useOrgServiceRequests = () => { - return { serviceRequests: DUMMY_SERVICE_REQUESTS } +interface UseOrgServiceRequestsOptions { + page: number + pageSize: number +} + +const useOrgServiceRequests = ({ + page, + pageSize, +}: UseOrgServiceRequestsOptions) => { + const { organizationId } = useOrganizationId() + const { isLoading, data } = useQuery({ + queryKey: ["org_service_requests", organizationId, page, pageSize], + queryFn: () => + getAllServiceRequestForAdmin(organizationId, page, pageSize).catch( + (err) => { + console.error(err) + toast({ + title: "Fetching Service Requests Error", + description: + "Failed to fetch Service Requests for organization. Please try again later.", + variant: "destructive", + }) + } + ), + refetchInterval: 2000, + }) + + const noOfPages = useMemo( + () => + data?.metadata.total_count + ? Math.ceil(data?.metadata.total_count / pageSize) + : undefined, + [data, pageSize] + ) + + return { orgServiceRequestsData: data, noOfPages } } export default useOrgServiceRequests diff --git a/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/columns.tsx b/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/columns.tsx index 09366dd8..330ce60a 100644 --- a/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/columns.tsx +++ b/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/columns.tsx @@ -8,6 +8,7 @@ import AdminServiceRequestActions from "./_components/admin-service-request-acti import { ServiceRequestStatusBadge } from "@/components/layouts/service-request-status-badge" import { ExternalLink } from "lucide-react" import { DataTableColumnHeaderFilterableValue } from "@/components/data-table/data-table-column-header-filterable-value" +import CreatedByInfo from "@/components/layouts/created-by-info" export const orgServiceRequestColumns: ColumnDef[] = [ { @@ -57,8 +58,12 @@ export const orgServiceRequestColumns: ColumnDef[] = [ }, }, { - accessorKey: "created_by", header: "Created By", + cell: ({ row }) => { + const serviceRequest: ServiceRequest = row.original + const userId: string = serviceRequest.user_id + return + }, }, { accessorKey: "last_updated", diff --git a/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/page.tsx b/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/page.tsx index 364b0b15..84becbb9 100644 --- a/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/page.tsx +++ b/frontend/src/app/(authenticated)/(main)/admin-service-requests-dashboard/page.tsx @@ -4,9 +4,14 @@ import HeaderAccessory from "@/components/ui/header-accessory" import useOrgServiceRequests from "./_hooks/use-org-service-requests" import { orgServiceRequestColumns } from "./columns" import { DataTable } from "@/components/data-table/data-table" +import { usePagination } from "@/hooks/use-pagination" export default function ApproveServiceRequestPage() { - const { serviceRequests } = useOrgServiceRequests() + const { onPaginationChange, pagination } = usePagination() + const { orgServiceRequestsData, noOfPages } = useOrgServiceRequests({ + page: pagination.pageIndex + 1, // API is 1-based + pageSize: pagination.pageSize, + }) return (
@@ -16,7 +21,13 @@ export default function ApproveServiceRequestPage() {

- +
) diff --git a/frontend/src/components/data-table/data-table-column-header-filterable-value.tsx b/frontend/src/components/data-table/data-table-column-header-filterable-value.tsx index c0be59ff..997bb7f9 100644 --- a/frontend/src/components/data-table/data-table-column-header-filterable-value.tsx +++ b/frontend/src/components/data-table/data-table-column-header-filterable-value.tsx @@ -76,11 +76,6 @@ export function DataTableColumnHeaderFilterableValue({ ))} - - column.toggleVisibility(false)}> - - Hide - diff --git a/frontend/src/components/data-table/data-table.tsx b/frontend/src/components/data-table/data-table.tsx index b407042b..d8a52948 100644 --- a/frontend/src/components/data-table/data-table.tsx +++ b/frontend/src/components/data-table/data-table.tsx @@ -27,8 +27,8 @@ import { useMemo, useState } from "react" interface DataTableProps { columns: ColumnDef[] data?: TData[] | void - onPaginationChange?: (pagination: Updater) => void - pagination?: PaginationState + onPaginationChange: (pagination: Updater) => void + pagination: PaginationState pageCount?: number } diff --git a/frontend/src/components/layouts/created-by-info.tsx b/frontend/src/components/layouts/created-by-info.tsx new file mode 100644 index 00000000..24675934 --- /dev/null +++ b/frontend/src/components/layouts/created-by-info.tsx @@ -0,0 +1,16 @@ +import { Skeleton } from "@/components/ui/skeleton" +import useUserInfo from "@/hooks/use-user-info" + +interface CreatedByInfoProps { + userId: string +} + +export default function CreatedByInfo({ userId }: CreatedByInfoProps) { + const { user, isUserInfoLoading } = useUserInfo(userId) + + return isUserInfoLoading ? ( + + ) : ( +

{user?.name ?? "N.A."}

+ ) +} diff --git a/frontend/src/components/layouts/service-request-details-dialog.tsx b/frontend/src/components/layouts/service-request-details-dialog.tsx index a09833ef..18fa23e6 100644 --- a/frontend/src/components/layouts/service-request-details-dialog.tsx +++ b/frontend/src/components/layouts/service-request-details-dialog.tsx @@ -22,30 +22,17 @@ import { ExternalLink } from "lucide-react" import Link from "next/link" import { useEffect, useState } from "react" import PipelineStepper from "./pipeline-stepper" +import useUserInfo from "@/hooks/use-user-info" +import CreatedByInfo from "./created-by-info" interface ServiceRequestDetailsProps { serviceRequest: ServiceRequest } function ServiceRequestDetails({ serviceRequest }: ServiceRequestDetailsProps) { - const [user, setUser] = useState() - useEffect(() => { - getUserById(serviceRequest.user_id) - .then((user) => setUser(user)) - .catch((err) => { - console.error(err) - toast({ - title: "Fetching Service Requests Error", - description: - "Failed to fetch Service Requests for user. Please try again later.", - variant: "destructive", - }) - }) - }, [serviceRequest.user_id]) const { id: serviceRequestId, pipeline_version: pipelineVersion, - created_by: createdBy = "", created_on: createdOn = "", last_updated: lastUpdated = "", remarks, @@ -84,7 +71,7 @@ function ServiceRequestDetails({ serviceRequest }: ServiceRequestDetailsProps) {
- {user ?

{user.name}

: } +
{steps.some((step) => step.name === "Approval") && (
diff --git a/frontend/src/hooks/use-user-info.ts b/frontend/src/hooks/use-user-info.ts new file mode 100644 index 00000000..0c0caa59 --- /dev/null +++ b/frontend/src/hooks/use-user-info.ts @@ -0,0 +1,27 @@ +import { toast } from "@/components/ui/use-toast" +import { getUserById } from "@/lib/service" +import { UserInfo } from "@/types/user-profile" +import { useEffect, useState } from "react" + +const useUserInfo = (userId: string) => { + const [user, setUser] = useState() + const [isLoading, setIsLoading] = useState(true) + useEffect(() => { + setIsLoading(true) + getUserById(userId) + .then((user) => setUser(user)) + .catch((err) => { + console.error(err) + toast({ + title: "Fetching User Info Error", + description: "Failed to fetch user info. Please try again later.", + variant: "destructive", + }) + }) + .finally(() => setIsLoading(false)) + }, [userId]) + + return { user, isUserInfoLoading: isLoading } +} + +export default useUserInfo diff --git a/frontend/src/lib/service.ts b/frontend/src/lib/service.ts index 9e38bcdf..7ee3ae23 100644 --- a/frontend/src/lib/service.ts +++ b/frontend/src/lib/service.ts @@ -60,6 +60,27 @@ export async function getAllServiceRequest( .then((res) => res.data) } +export async function getAllServiceRequestForAdmin( + organizationId: number, + page?: number, + pageSize?: number +): Promise<{ + data: ServiceRequest[] + metadata: { + total_count: number + } +}> { + return apiClient + .get("/service_request/admin", { + params: { + org_id: organizationId, + page: page ?? 1, + page_size: pageSize ?? 10, + }, + }) + .then((res) => res.data) +} + export async function getServiceRequest( serviceRequestId: string ): Promise {