Skip to content

Commit

Permalink
Create dashboard organization home page
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Jun 4, 2024
1 parent 1b91216 commit c6cf4f7
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 25 deletions.
5 changes: 5 additions & 0 deletions lms/static/scripts/frontend_apps/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ export type Course = {
title: string;
};

/**
* Response for `/api/dashboard/organizations/{organization_public_id}` call.
*/
export type Courses = Course[];

/**
* Response for `/api/dashboard/assignments/{assignment_id}` call.
*/
Expand Down
2 changes: 1 addition & 1 deletion lms/static/scripts/frontend_apps/components/AppRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default function AppRoot({ initialConfig, services }: AppRootProps) {
<FilePickerApp />
</DataLoader>
</Route>
<Route path="/dashboard/organizations/:organizationId" nest>
<Route path="/dashboard/organizations/:organizationPublicId" nest>
<DashboardApp />
</Route>
<Route path="/email/preferences">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Card, CardContent, CardHeader } from '@hypothesis/frontend-shared';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { useParams } from 'wouter-preact';

Expand Down Expand Up @@ -31,7 +36,7 @@ export default function AssignmentActivity() {
<CardHeader
fullWidth
classes={classnames(
// Overwriting gap-x-2 and items-center from CardHeader
// Overwrite gap-x-2 and items-center from CardHeader
'flex-col !gap-x-0 !items-start',
)}
>
Expand All @@ -47,11 +52,11 @@ export default function AssignmentActivity() {
/>
</div>
)}
<h2 data-testid="title" className="text-lg text-brand font-semibold">
<CardTitle tagName="h2" data-testid="title">
{assignment.isLoading && 'Loading...'}
{assignment.error && 'Could not load assignment title'}
{assignment.data && title}
</h2>
</CardTitle>
</CardHeader>
<CardContent>
<OrderableActivityTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import {
CardTitle,
Link,
} from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { useMemo } from 'preact/hooks';
import { useParams, Link as RouterLink } from 'wouter-preact';

import type { AssignmentsStats, Course } from '../../api-types';
import { useConfig } from '../../config';
import { useAPIFetch } from '../../utils/api';
import { urlPath, useAPIFetch } from '../../utils/api';
import { formatDateTime } from '../../utils/date';
import { replaceURLParams } from '../../utils/url';
import DashboardBreadcrumbs from './DashboardBreadcrumbs';
import OrderableActivityTable from './OrderableActivityTable';

type AssignmentsTableRow = {
Expand Down Expand Up @@ -51,7 +53,16 @@ export default function CourseActivity() {

return (
<Card>
<CardHeader fullWidth>
<CardHeader
fullWidth
classes={classnames(
// Overwrite gap-x-2 and items-center from CardHeader
'flex-col !gap-x-0 !items-start',
)}
>
<div className="mb-3 mt-1 w-full">
<DashboardBreadcrumbs />
</div>
<CardTitle tagName="h2" data-testid="title">
{course.isLoading && 'Loading...'}
{course.error && 'Could not load course title'}
Expand Down Expand Up @@ -80,7 +91,10 @@ export default function CourseActivity() {
return <div className="text-right">{stats[field]}</div>;
} else if (field === 'title') {
return (
<RouterLink href={`/assignments/${stats.id}`} asChild>
<RouterLink
href={urlPath`/assignments/${String(stats.id)}`}
asChild
>
<Link>{stats.title}</Link>
</RouterLink>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import classnames from 'classnames';
import { Route, Switch } from 'wouter-preact';
import { Route, Switch, useParams } from 'wouter-preact';

import AssignmentActivity from './AssignmentActivity';
import CourseActivity from './CourseActivity';
import DashboardFooter from './DashboardFooter';
import OrganizationActivity from './OrganizationActivity';

export default function DashboardApp() {
const { organizationPublicId } = useParams<{
organizationPublicId: string;
}>();

return (
<div className="flex flex-col min-h-screen gap-5 bg-grey-2">
<div
Expand All @@ -29,6 +34,11 @@ export default function DashboardApp() {
<Route path="/courses/:courseId">
<CourseActivity />
</Route>
<Route path="">
<OrganizationActivity
organizationPublicId={organizationPublicId}
/>
</Route>
</Switch>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Link,
} from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { useMemo } from 'preact/hooks';
import { Link as RouterLink } from 'wouter-preact';

export type BreadcrumbLink = {
Expand All @@ -12,7 +13,7 @@ export type BreadcrumbLink = {
};

export type DashboardBreadcrumbsProps = {
links: BreadcrumbLink[];
links?: BreadcrumbLink[];
};

function BreadcrumbLink({ title, href }: BreadcrumbLink) {
Expand All @@ -30,15 +31,20 @@ function BreadcrumbLink({ title, href }: BreadcrumbLink) {
* Navigation breadcrumbs showing a list of links
*/
export default function DashboardBreadcrumbs({
links,
links = [],
}: DashboardBreadcrumbsProps) {
const linksWithHome = useMemo(
(): BreadcrumbLink[] => [{ title: 'Home', href: '' }, ...links],
[links],
);

return (
<div
className="flex flex-row gap-0.5 w-full font-semibold"
data-testid="breadcrumbs-container"
>
{links.map(({ title, href }, index) => {
const isLastLink = index === links.length - 1;
{linksWithHome.map(({ title, href }, index) => {
const isLastLink = index === linksWithHome.length - 1;
return (
<span
key={`${index}${href}`}
Expand All @@ -49,10 +55,10 @@ export default function DashboardBreadcrumbs({
// Distribute max width for every link as evenly as possible.
// These must be static values for Tailwind to detect them.
// See https://tailwindcss.com/docs/content-configuration#dynamic-class-names
'md:max-w-[50%]': links.length === 2,
'md:max-w-[33.333333%]': links.length === 3,
'md:max-w-[25%]': links.length === 4,
'md:max-w-[230px]': links.length > 4,
'md:max-w-[50%]': linksWithHome.length === 2,
'md:max-w-[33%]': linksWithHome.length === 3,
'md:max-w-[25%]': linksWithHome.length === 4,
'md:max-w-[230px]': linksWithHome.length > 4,
})}
>
<BreadcrumbLink href={href} title={title} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { useOrderedRows } from '@hypothesis/frontend-shared';
import type { OrderDirection } from '@hypothesis/frontend-shared/lib/types';
import { useMemo, useState } from 'preact/hooks';

import type { BaseDashboardStats } from '../../api-types';

export type OrderableActivityTableProps<T extends BaseDashboardStats> = Pick<
export type OrderableActivityTableProps<T> = Pick<
DataTableProps<T>,
'emptyMessage' | 'rows' | 'renderItem' | 'loading' | 'title'
> & {
Expand All @@ -32,7 +30,7 @@ const descendingOrderColumns: readonly string[] = [
* Annotation activity table for dashboard views. Includes built-in support for
* sorting columns.
*/
export default function OrderableActivityTable<T extends BaseDashboardStats>({
export default function OrderableActivityTable<T>({
defaultOrderField,
rows,
columnNames,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
Card,
CardContent,
CardHeader,
Link,
} from '@hypothesis/frontend-shared';
import { Link as RouterLink } from 'wouter-preact';

import type { Courses } from '../../api-types';
import { useConfig } from '../../config';
import { urlPath, useAPIFetch } from '../../utils/api';
import { replaceURLParams } from '../../utils/url';
import OrderableActivityTable from './OrderableActivityTable';

export type OrganizationActivityProps = {
organizationPublicId: string;
};

/**
* List of courses that belong to a specific organization
*/
export default function OrganizationActivity({
organizationPublicId,
}: OrganizationActivityProps) {
const { dashboard } = useConfig(['dashboard']);
const { routes } = dashboard;
const courses = useAPIFetch<Courses>(
replaceURLParams(routes.organization_courses, {
organization_public_id: organizationPublicId,
}),
);

return (
<Card>
<CardHeader title="Home" fullWidth />
<CardContent>
<OrderableActivityTable
loading={courses.isLoading}
title="Courses"
emptyMessage={
courses.error ? 'Could not load courses' : 'No courses found'
}
rows={courses.data ?? []}
columnNames={{ title: 'Title' }}
defaultOrderField="title"
renderItem={stats => (
<RouterLink href={urlPath`/courses/${String(stats.id)}`} asChild>
<Link>{stats.title}</Link>
</RouterLink>
)}
/>
</CardContent>
</Card>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('AssignmentActivity', () => {
fakeUseAPIFetch.returns({ isLoading: true });

const wrapper = createComponent();
const titleElement = wrapper.find('[data-testid="title"]');
const titleElement = wrapper.find('CardTitle[data-testid="title"]');
const tableElement = wrapper.find('OrderableActivityTable');

assert.equal(titleElement.text(), 'Loading...');
Expand All @@ -90,7 +90,7 @@ describe('AssignmentActivity', () => {
fakeUseAPIFetch.returns({ error: new Error('Something failed') });

const wrapper = createComponent();
const titleElement = wrapper.find('[data-testid="title"]');
const titleElement = wrapper.find('CardTitle[data-testid="title"]');
const tableElement = wrapper.find('OrderableActivityTable');

assert.equal(titleElement.text(), 'Could not load assignment title');
Expand All @@ -99,7 +99,7 @@ describe('AssignmentActivity', () => {

it('shows expected title', () => {
const wrapper = createComponent();
const titleElement = wrapper.find('[data-testid="title"]');
const titleElement = wrapper.find('CardTitle[data-testid="title"]');
const tableElement = wrapper.find('OrderableActivityTable');
const expectedTitle = 'Assignment: The title';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ describe('DashboardBreadcrumbs', () => {
links: links.map(title => ({ title, href: `/${title}` })),
});

assert.equal(wrapper.find('BreadcrumbLink').length, links.length);
// Breadcrumbs always renders a static extra link for the home page
assert.equal(wrapper.find('BreadcrumbLink').length, links.length + 1);
});
});

Expand Down
Loading

0 comments on commit c6cf4f7

Please sign in to comment.