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
16 changes: 16 additions & 0 deletions frontends/api/src/mitxonline/hooks/contracts/queries.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { queryOptions } from "@tanstack/react-query"
import type {
B2bApiB2bContractsRetrieveRequest,
B2bApiB2bManagerOrganizationsContractsCodesRetrieveRequest,
ContractPage,
ManagerContractDetail,
} from "@mitodl/mitxonline-api-axios/v2"

import { b2bApi } from "../../clients"
Expand All @@ -14,6 +16,9 @@ const contractKeys = {
"detail",
opts,
],
managerContractCodes: (
opts: B2bApiB2bManagerOrganizationsContractsCodesRetrieveRequest,
) => [...contractKeys.root, "manager", "codes", opts],
}

const contractQueries = {
Expand All @@ -31,6 +36,17 @@ const contractQueries = {
return b2bApi.b2bContractsRetrieve(opts).then((res) => res.data)
},
}),
managerContractCodes: (
opts: B2bApiB2bManagerOrganizationsContractsCodesRetrieveRequest,
) =>
queryOptions({
queryKey: contractKeys.managerContractCodes(opts),
queryFn: async (): Promise<ManagerContractDetail> => {
return b2bApi
.b2bManagerOrganizationsContractsCodesRetrieve(opts)
.then((res) => res.data)
},
}),
}

export { contractQueries, contractKeys }
130 changes: 130 additions & 0 deletions frontends/main/src/app-pages/ContractAdminPage/ContractAdminPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"use client"
import React from "react"
import { useQuery } from "@tanstack/react-query"
import {
Breadcrumbs,
Container,
Skeleton,
styled,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from "ol-components"
import { contractQueries } from "api/mitxonline-hooks/contracts"
import * as urls from "@/common/urls"

type EnrollmentCodeRow = {
id?: number | string
code?: string
is_redeemed?: boolean
redeemed_by?: string | null
redeemed_on?: string | null
}

type ContractAdminPageProps = {
orgId: number
contractId: number
}

const PageRoot = styled(Container)(({ theme }) => ({
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(6),
}))

const TableWrapper = styled("div")(({ theme }) => ({
marginTop: theme.spacing(3),
border: `1px solid ${theme.custom.colors.lightGray2}`,
borderRadius: "4px",
overflow: "hidden",
}))

const formatDate = (value?: string | null) => {
if (!value) {
return "—"
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return value
}
return date.toLocaleDateString()
}

const formatBoolean = (value?: boolean) => (value ? "Yes" : "No")

const ContractAdminPage: React.FC<ContractAdminPageProps> = ({
orgId,
contractId,
}) => {
const codesQuery = useQuery(
contractQueries.managerContractCodes({
id: contractId,
parent_lookup_organization: orgId,
}),
)

return (
<PageRoot>
<Breadcrumbs
variant="light"
ancestors={[{ href: urls.HOME, label: "Home" }]}
current="Contract Admin"
/>
<Typography variant="h3" component="h1">
{codesQuery.isLoading
? "Loading contract..."
: "Contract Admin"}
</Typography>
{/* {codesData?.description ? (
<Typography variant="body1">{codesData.description}</Typography>
) : null} */}

<Typography variant="h5" component="h2" sx={{ mt: 4 }}>
Enrollment Codes
</Typography>

{codesQuery.isLoading ? (
<Skeleton width="100%" height="200px" />
) : codesQuery.isError ? (
<Typography variant="body1">
Unable to load enrollment codes for this contract.
</Typography>
) : codesQuery.data && (codesQuery.data as unknown as EnrollmentCodeRow[]).length === 0 ? (
<Typography variant="body1">
No enrollment codes found for this contract.
</Typography>
) : (
<TableWrapper>
<TableContainer>
<Table aria-label="Enrollment codes">
<TableHead>
<TableRow>
<TableCell>Code</TableCell>
<TableCell>Redeemed</TableCell>
<TableCell>Redeemed By</TableCell>
<TableCell>Redeemed On</TableCell>
</TableRow>
</TableHead>
<TableBody>
{codesQuery.data && (codesQuery.data as unknown as EnrollmentCodeRow[]).map((code, index) => (
<TableRow key={code.id ?? index}>
<TableCell>{code.code ?? "—"}</TableCell>
<TableCell>{formatBoolean(code.is_redeemed)}</TableCell>
<TableCell>{code.redeemed_by ?? "—"}</TableCell>
<TableCell>{formatDate(code.redeemed_on)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</TableWrapper>
)}
</PageRoot>
)
}

export default ContractAdminPage
export type { ContractAdminPageProps }
32 changes: 32 additions & 0 deletions frontends/main/src/app/contractadmin/[orgId]/[contractId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react"
import { Metadata } from "next"
import { standardizeMetadata } from "@/common/metadata"
import invariant from "tiny-invariant"
import ContractAdminPage from "@/app-pages/ContractAdminPage/ContractAdminPage"

export const metadata: Metadata = standardizeMetadata({
title: "Contract Admin",
})

const Page: React.FC<
PageProps<"/contractadmin/[orgId]/[contractId]">
> = async ({ params }) => {
const resolved = await params
invariant(resolved?.orgId, "orgId is required")
invariant(resolved?.contractId, "contractId is required")

const orgId = Number(resolved.orgId)
const contractId = Number(resolved.contractId)
invariant(
Number.isFinite(orgId) && !Number.isNaN(orgId),
"orgId must be numeric",
)
invariant(
Number.isFinite(contractId) && !Number.isNaN(contractId),
"contractId must be numeric",
)

return <ContractAdminPage orgId={orgId} contractId={contractId} />
}

export default Page
7 changes: 7 additions & 0 deletions frontends/main/src/common/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ export const B2B_ATTACH_VIEW = "/enrollmentcode/[code]"
export const b2bAttachView = (code: string) =>
generatePath(B2B_ATTACH_VIEW, { code: code })

export const CONTRACT_ADMIN_VIEW = "/contractadmin/[orgId]/[contractId]"
export const contractAdminView = (orgId: number, contractId: number) =>
generatePath(CONTRACT_ADMIN_VIEW, {
orgId: String(orgId),
contractId: String(contractId),
})

export const FACEBOOK_SHARE_BASE_URL =
"https://www.facebook.com/sharer/sharer.php"
export const TWITTER_SHARE_BASE_URL = "https://x.com/share"
Expand Down
13 changes: 13 additions & 0 deletions frontends/ol-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ export type { SkeletonProps } from "@mui/material/Skeleton"
export { default as Stack } from "@mui/material/Stack"
export type { StackProps } from "@mui/material/Stack"

export { default as Table } from "@mui/material/Table"
export type { TableProps } from "@mui/material/Table"
export { default as TableBody } from "@mui/material/TableBody"
export type { TableBodyProps } from "@mui/material/TableBody"
export { default as TableCell } from "@mui/material/TableCell"
export type { TableCellProps } from "@mui/material/TableCell"
export { default as TableContainer } from "@mui/material/TableContainer"
export type { TableContainerProps } from "@mui/material/TableContainer"
export { default as TableHead } from "@mui/material/TableHead"
export type { TableHeadProps } from "@mui/material/TableHead"
export { default as TableRow } from "@mui/material/TableRow"
export type { TableRowProps } from "@mui/material/TableRow"

export { default as Tab } from "@mui/material/Tab"
export type { TabProps } from "@mui/material/Tab"

Expand Down
Loading