Skip to content

Commit

Permalink
Use newer Learning Resource list cards in Learning Paths lists (#1256)
Browse files Browse the repository at this point in the history
* Refactor ListDetailsPage ListDetailsComponent to page-component ItemsListing

* Condensed flag for reuse between user list and learning path items

* Remove no items empty message while loading

* Update test path. Variable spacing.

* Margin around ItemsListingComponent
  • Loading branch information
jonkafton authored Jul 17, 2024
1 parent 50f26b1 commit eead173
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@ type ItemsListingProps = {
isRefetching?: boolean
emptyMessage: string
sortable?: boolean
condensed?: boolean
}

const ItemsListingViewOnly: React.FC<{
items: NonNullable<ItemsListingProps["items"]>
}> = ({ items }) => {
condensed?: boolean
}> = ({ items, condensed }) => {
return (
<PlainList itemSpacing={1}>
<PlainList itemSpacing={condensed ? 1 : 2}>
{items.map((item) => {
return (
<li key={item.id}>
<ResourceListCard resource={item.resource} condensed />
<ResourceListCard resource={item.resource} condensed={condensed} />
</li>
)
})}
Expand Down Expand Up @@ -139,11 +141,13 @@ const ItemsListing: React.FC<ItemsListingProps> = ({
isRefetching,
emptyMessage,
sortable = false,
condensed = false,
}) => {
return (
<>
{isLoading && <LoadingSpinner loading />}
{items.length === 0 ? (
{isLoading ? (
<LoadingSpinner loading />
) : items.length === 0 ? (
<EmptyMessage>{emptyMessage}</EmptyMessage>
) : sortable ? (
<ItemsListingSortable
Expand All @@ -152,7 +156,7 @@ const ItemsListing: React.FC<ItemsListingProps> = ({
isRefetching={isRefetching}
/>
) : (
<ItemsListingViewOnly items={items} />
<ItemsListingViewOnly items={items} condensed={condensed} />
)}
</>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from "react"
import { Grid, Button, Typography, styled } from "ol-components"
import { RiPencilFill, RiArrowUpDownLine } from "@remixicon/react"
import { useUserMe } from "api/hooks/user"
import { useToggle, pluralize } from "ol-utilities"
import { GridColumn, GridContainer } from "@/components/GridLayout/GridLayout"
import ItemsListing from "./ItemsListing"
import type { LearningResourceListItem } from "./ItemsListing"

const Container = styled(GridContainer)`
margin-top: 30px;
margin-bottom: 100px;
`

const TitleContainer = styled(Grid)`
margin-top: 10px;
margin-bottom: 20px;
`

type OnEdit = () => void
type ListData = {
title: string
description?: string | null
item_count: number
}

type ItemsListingComponentProps = {
listType: string
list?: ListData
items: LearningResourceListItem[]
isLoading: boolean
isFetching: boolean
handleEdit: OnEdit
condensed?: boolean
}

const ItemsListingComponent: React.FC<ItemsListingComponentProps> = ({
listType,
list,
items,
isLoading,
isFetching,
handleEdit,
condensed = false,
}) => {
const { data: user } = useUserMe()
const [isSorting, toggleIsSorting] = useToggle(false)

const canEdit = user?.is_learning_path_editor
const showSort = canEdit && !!items.length
const count = list?.item_count

return (
<Container>
<GridColumn variant="single-full">
<Grid container>
<TitleContainer item xs={12}>
<Typography variant="h3" component="h1">
{list?.title}
</Typography>
{list?.description && <p>{list.description}</p>}
</TitleContainer>
<Grid
item
xs={6}
container
alignItems="center"
justifyContent="space-between"
>
{showSort && (
<Button
variant="text"
disabled={count === 0}
startIcon={isSorting ? undefined : <RiArrowUpDownLine />}
onClick={toggleIsSorting.toggle}
>
{isSorting ? "Done ordering" : "Reorder"}
</Button>
)}
{count !== undefined && count > 0
? `${count} ${pluralize("item", count)}`
: null}
</Grid>
<Grid
item
xs={6}
container
justifyContent="flex-end"
alignItems="center"
display="flex"
>
{canEdit ? (
<Button
variant="text"
startIcon={<RiPencilFill />}
onClick={handleEdit}
>
Edit
</Button>
) : null}
</Grid>
</Grid>
<ItemsListing
listType={listType}
items={items}
isLoading={isLoading}
isRefetching={isFetching}
sortable={isSorting}
emptyMessage="There are no items in this list yet."
condensed={condensed}
/>
</GridColumn>
</Container>
)
}

export default ItemsListingComponent
export type { ItemsListingComponentProps }
35 changes: 3 additions & 32 deletions frontends/mit-open/src/pages/DashboardPage/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React, { useCallback, useState } from "react"
import {
RiAccountCircleFill,
RiDashboardLine,
Expand All @@ -19,19 +20,12 @@ import {
styled,
} from "ol-components"
import { MetaTags } from "ol-utilities"
import React, { useCallback, useMemo, useState } from "react"
import { Link } from "react-router-dom"
import { useUserMe } from "api/hooks/user"
import { useLocation } from "react-router"
import { UserListListingComponent } from "../UserListListingPage/UserListListingPage"
import { UserList } from "api"
import {
useInfiniteUserListItems,
useUserListsDetail,
} from "api/hooks/learningResources"
import { ListDetailsComponent } from "../ListDetailsPage/ListDetailsPage"
import { ListType } from "api/constants"
import { manageListDialogs } from "@/page-components/ManageListDialogs/ManageListDialogs"

import { ProfileEditForm } from "./ProfileEditForm"
import { useProfileMeQuery } from "api/hooks/profile"
import {
Expand All @@ -43,6 +37,7 @@ import {
FREE_COURSES_CAROUSEL,
} from "./carousels"
import ResourceCarousel from "@/page-components/ResourceCarousel/ResourceCarousel"
import UserListDetailsTab from "./UserListDetailsTab"

/**
*
Expand Down Expand Up @@ -304,30 +299,6 @@ const keyFromHash = (hash: string) => {
return match ?? "home"
}

interface UserListDetailsTabProps {
userListId: number
}

const UserListDetailsTab: React.FC<UserListDetailsTabProps> = (props) => {
const { userListId } = props
const listQuery = useUserListsDetail(userListId)
const itemsQuery = useInfiniteUserListItems({ userlist_id: userListId })
const items = useMemo(() => {
const pages = itemsQuery.data?.pages
return pages?.flatMap((p) => p.results ?? []) ?? []
}, [itemsQuery.data])
return (
<ListDetailsComponent
listType={ListType.UserList}
list={listQuery.data}
items={items}
isLoading={itemsQuery.isLoading}
isFetching={itemsQuery.isFetching}
handleEdit={() => manageListDialogs.upsertUserList(listQuery.data)}
/>
)
}

const DashboardPage: React.FC = () => {
const { isLoading: isLoadingUser, data: user } = useUserMe()
const { isLoading: isLoadingProfile, data: profile } = useProfileMeQuery()
Expand Down
36 changes: 36 additions & 0 deletions frontends/mit-open/src/pages/DashboardPage/UserListDetailsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useMemo } from "react"
import {
useInfiniteUserListItems,
useUserListsDetail,
} from "api/hooks/learningResources"
import { ListType } from "api/constants"
import { manageListDialogs } from "@/page-components/ManageListDialogs/ManageListDialogs"
import ItemsListingComponent from "@/page-components/ItemsListing/ItemsListingComponent"

interface UserListDetailsTabProps {
userListId: number
}

const UserListDetailsTab: React.FC<UserListDetailsTabProps> = (props) => {
const { userListId } = props
const listQuery = useUserListsDetail(userListId)
const itemsQuery = useInfiniteUserListItems({ userlist_id: userListId })

const items = useMemo(() => {
const pages = itemsQuery.data?.pages
return pages?.flatMap((p) => p.results ?? []) ?? []
}, [itemsQuery.data])

return (
<ItemsListingComponent
listType={ListType.UserList}
list={listQuery.data}
items={items}
isLoading={itemsQuery.isLoading}
isFetching={itemsQuery.isFetching}
handleEdit={() => manageListDialogs.upsertUserList(listQuery.data)}
/>
)
}

export default UserListDetailsTab
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
PaginatedLearningPathRelationshipList,
} from "api"
import { manageListDialogs } from "@/page-components/ManageListDialogs/ManageListDialogs"
import ItemsListing from "./ItemsListing"
import ItemsListing from "@/page-components/ItemsListing/ItemsListing"
import { learningPathsView } from "@/common/urls"
import {
screen,
Expand All @@ -20,8 +20,10 @@ import { User } from "../../types/settings"
import { ControlledPromise } from "ol-test-utilities"
import invariant from "tiny-invariant"

jest.mock("./ItemsListing", () => {
const actual = jest.requireActual("./ItemsListing")
jest.mock("../../page-components/ItemsListing/ItemsListing", () => {
const actual = jest.requireActual(
"../../page-components/ItemsListing/ItemsListing",
)
return {
__esModule: true,
...actual,
Expand Down
Loading

0 comments on commit eead173

Please sign in to comment.