Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<div class="mx-auto max-w-3xl p-8 bg-white rounded-md shadow-md">
<h2 class="text-3xl font-bold text-gray-900 mb-10">Grant Extension</h2>

<form [formGroup]="grantExtensionForm" (ngSubmit)="onSubmit()" class="space-y-8">

<!-- Student -->
<div>
<label class="block text-lg font-semibold text-gray-800 mb-2" for="student">Student</label>
<select
id="student"
formControlName="student"
class="w-full border border-gray-300 rounded-md px-4 py-3 text-base focus:ring-2 focus:ring-blue-500 focus:outline-none"
required
>
<option value="" disabled>Select a student</option>
<option *ngFor="let student of students" [value]="student.id">
{{ student.name }}
</option>
</select>
<!-- Error if not selected -->
<div *ngIf="grantExtensionForm.get('student')?.touched && grantExtensionForm.get('student')?.invalid" class="text-red-600 text-sm mt-2">
Please select a student.
</div>

<!-- Extension -->
<div>
<label for="extension" class="block text-lg font-semibold text-gray-800 mb-2">
Extension Duration:
<span class="font-bold">{{ grantExtensionForm.get('extension')?.value }}</span> day(s)
</label>
<input id="extension" type="range" formControlName="extension" min="1" max="30" class="w-full" />
</div>

<!-- Reason -->
<div>
<label for="reason" class="block text-lg font-semibold text-gray-800 mb-2">Reason</label>
<textarea
id="reason"
formControlName="reason"
rows="4"
class="w-full border border-gray-300 rounded-md px-4 py-3 text-base focus:ring-2 focus:ring-blue-500 focus:outline-none"
required
></textarea>
<!-- Error if empty -->
<div *ngIf="grantExtensionForm.get('reason')?.touched && grantExtensionForm.get('reason')?.invalid" class="text-red-600 text-sm mt-2">
Please provide a reason for the extension.
</div>

<!-- Notes -->
<div>
<label for="notes" class="block text-lg font-semibold text-gray-800 mb-2">Additional Notes (optional)</label>
<textarea
id="notes"
formControlName="notes"
rows="3"
class="w-full border border-gray-300 rounded-md px-4 py-3 text-base focus:ring-2 focus:ring-blue-500 focus:outline-none"
></textarea>
</div>

<!-- Submit -->
<div class="pt-4 text-right">
<button
type="submit"
[disabled]="isSubmitting"
[ngClass]="{
'bg-blue-700 cursor-not-allowed opacity-70': isSubmitting,
'bg-blue-600 hover:bg-blue-700': !isSubmitting
}"
class="inline-flex items-center text-white text-lg font-medium px-6 py-3 rounded-md transition"
>
Grant Extension
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GrantExtensionFormComponent } from './grant-extension-form.component';

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

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GrantExtensionFormComponent]
})
.compileComponents();

fixture = TestBed.createComponent(GrantExtensionFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {Component, OnInit} from '@angular/core';
import {FormGroup, FormBuilder, Validators, ReactiveFormsModule} from '@angular/forms';
import {CommonModule} from '@angular/common';

@Component({
selector: 'f-grant-extension-form',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
templateUrl: './grant-extension-form.component.html',
styleUrls: ['./grant-extension-form.component.scss']
})
export class GrantExtensionFormComponent implements OnInit {
grantExtensionForm!: FormGroup;
// Tracks if the form is currently submitting
isSubmitting = false;
// Test list of students (to be replaced by API data)
students = [
{ id: 1, name: 'Joe M' },
{ id: 2, name: 'Sahiru W' },
{ id: 3, name: 'Samindi M' }
];

constructor(private fb: FormBuilder) {}

ngOnInit(): void {
// Initialize the form and apply validators to required fields
this.grantExtensionForm = this.fb.group({
student: ['', Validators.required], // Student must be selected
extension: [1, [Validators.required, Validators.min(1)]], // Minimum value of 1
reason: ['', Validators.required], // Must provide reason
notes: [''],
});
}

onSubmit(): void {
// If form is invalid. Validation errors are shown
if (this.grantExtensionForm.invalid) {
this.grantExtensionForm.markAllAsTouched(); // Triggers validation messages
return;
}

this.isSubmitting = true; // Disables the button for loading state

// Submission delay test
setTimeout(() => {
console.log('Form submitted:', this.grantExtensionForm.value);

// Resets form values
this.grantExtensionForm.reset({
student: '',
extension: 1,
reason: '',
notes: ''
});
// Reset the submit button
this.isSubmitting = false;
}, 1000); // Delay for submission
}
}
16 changes: 16 additions & 0 deletions src/app/doubtfire.states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {ProjectRootState} from './projects/states/project-root-state.component';
import { TaskViewerState } from './units/task-viewer/task-viewer-state.component';
import {ScormPlayerComponent} from './common/scorm-player/scorm-player.component';
import { Ng2ViewDeclaration } from '@uirouter/angular';
import { GrantExtensionFormComponent } from './admin/modals/grant-extension-form/grant-extension-form.component';

/*
* Use this file to store any states that are sourced by angular components.
Expand Down Expand Up @@ -236,6 +237,20 @@ const ViewAllProjectsState: NgHybridStateDeclaration = {
},
};

const GrantExtensionState: NgHybridStateDeclaration = {
name: 'grant-extension',
url: '/grant-extension',
views: {
main: {
component: GrantExtensionFormComponent,
},
},
data: {
pageTitle: 'Grant Extension',
roleWhitelist: ['Admin']
}
};

const AdministerUnits: NgHybridStateDeclaration = {
name: 'admin/units', // This is the name of the state to jump to - so ui-sref="users" to jump here
url: '/admin/units', // You get here with this url
Expand Down Expand Up @@ -433,4 +448,5 @@ export const doubtfireStates = [
ScormPlayerNormalState,
ScormPlayerReviewState,
ScormPlayerStudentReviewState,
GrantExtensionState
];