Skip to content
Open
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
34 changes: 34 additions & 0 deletions src/app/common/project-card/project-card.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@if (project) {
<article class="project-card">
<div class="project-card__header">
<div>
@if (project.unitCode) {
<p class="project-card__unit">{{ project.unitCode }}</p>
}
<h3 class="project-card__title">{{ project.title }}</h3>
</div>

<span class="project-card__status">
<span class="project-card__status-label">Project status: </span>{{ project.status }}
</span>
</div>

<p class="project-card__summary">
{{ project.progressSummary }}
</p>

@if (project.description) {
<p class="project-card__description">
{{ project.description }}
</p>
}

<a
class="project-card__link"
[attr.aria-label]="'Open ' + project.title + ' project details'"
[href]="project.destinationUrl"
>
Open project
</a>
</article>
}
77 changes: 77 additions & 0 deletions src/app/common/project-card/project-card.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.project-card {
border: 1px solid #d6d6d6;
border-radius: 12px;
padding: 1rem;
background: #ffffff;
display: flex;
flex-direction: column;
gap: 0.75rem;
max-width: 420px;
}

.project-card__header {
display: flex;
justify-content: space-between;
gap: 1rem;
align-items: flex-start;
}

.project-card__unit {
margin: 0 0 0.25rem;
font-size: 0.8rem;
font-weight: 600;
color: #555555;
}

.project-card__title {
margin: 0;
font-size: 1.1rem;
line-height: 1.3;
}

.project-card__status {
border-radius: 999px;
padding: 0.25rem 0.6rem;
font-size: 0.8rem;
font-weight: 600;
background: #eeeeee;
white-space: nowrap;
}

.project-card__status-label {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.project-card__summary,
.project-card__description {
margin: 0;
line-height: 1.5;
}

.project-card__link {
align-self: flex-start;
font-weight: 600;
text-decoration: underline;
}

@media (max-width: 520px) {
.project-card {
max-width: 100%;
}

.project-card__header {
flex-direction: column;
}

.project-card__status {
align-self: flex-start;
}
}
74 changes: 74 additions & 0 deletions src/app/common/project-card/project-card.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ProjectCardComponent, ProjectCardData} from './project-card.component';

describe('ProjectCardComponent', () => {
let component: ProjectCardComponent;
let fixture: ComponentFixture<ProjectCardComponent>;

const sampleProject: ProjectCardData = {
title: 'Cross-Project Dashboard',
unitCode: 'CPD-F02',
status: 'In progress',
progressSummary: 'Reusable dashboard project-card component is being prepared.',
description: 'This card summarises one project without hardcoding the dashboard data.',
destinationUrl: '/projects/cpd-f02',
};

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ProjectCardComponent],
}).compileComponents();

fixture = TestBed.createComponent(ProjectCardComponent);
component = fixture.componentInstance;
component.project = sampleProject;
fixture.detectChanges();
});

it('should create the project card component', () => {
expect(component).toBeTruthy();
});

it('should display the supplied project details', () => {
const text = fixture.nativeElement.textContent;

expect(text).toContain('Cross-Project Dashboard');
expect(text).toContain('CPD-F02');
expect(text).toContain('In progress');
expect(text).toContain('Reusable dashboard project-card component is being prepared.');
});

it('should provide an accessible project link', () => {
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');

expect(link).toBeTruthy();
expect(link.getAttribute('href')).toBe('/projects/cpd-f02');
expect(link.getAttribute('aria-label')).toBe('Open Cross-Project Dashboard project details');
});

it('should expose the project status to assistive technology', () => {
const status: HTMLSpanElement = fixture.nativeElement.querySelector('.project-card__status');

expect(status.textContent.replace(/\s+/g, ' ').trim()).toBe('Project status: In progress');
});

it('should handle missing optional fields', () => {
fixture.componentRef.setInput('project', {
title: 'Sample Project',
status: 'Completed',
progressSummary: 'Core details are still displayed.',
destinationUrl: '/projects/sample',
});

fixture.detectChanges();

const text = fixture.nativeElement.textContent;

expect(text).toContain('Sample Project');
expect(text).toContain('Completed');
expect(text).toContain('Core details are still displayed.');

const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
expect(link.getAttribute('aria-label')).toBe('Open Sample Project project details');
});
});
20 changes: 20 additions & 0 deletions src/app/common/project-card/project-card.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Component, Input} from '@angular/core';

export interface ProjectCardData {
title: string;
status: string;
progressSummary: string;
destinationUrl: string;
unitCode?: string;
description?: string;
}

@Component({
selector: 'project-card',
standalone: false,
templateUrl: './project-card.component.html',
styleUrls: ['./project-card.component.scss'],
})
export class ProjectCardComponent {
@Input() project!: ProjectCardData;
}
2 changes: 2 additions & 0 deletions src/app/doubtfire-angular.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ import {IsActiveUnitRole} from './common/pipes/is-active-unit-role.pipe';
import {LocalizedDatePipe} from './common/pipes/localized-date.pipe';
import {MarkedPipe} from './common/pipes/marked.pipe';
import {SafePipe} from './common/pipes/safe.pipe';
import {ProjectCardComponent} from './common/project-card/project-card.component';
import {ProjectProgressBarComponent} from './common/project-progress-bar/project-progress-bar.component';
import {ProjectProgressGaugeComponent} from './common/project-progress/project-progress-gauge.component';
import {ScormPlayerComponent} from './common/scorm-player/scorm-player.component';
Expand Down Expand Up @@ -566,6 +567,7 @@ const DEFAULT_TOOLTIP_OPTIONS: MatTooltipDefaultOptions = {
CreateNewUnitModalContentComponent,
TiiActionLogComponent,
FChipComponent,
ProjectCardComponent,
UnitCodeComponent,
NewTeachingPeriodDialogComponent,
FileViewerComponent,
Expand Down