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
16 changes: 16 additions & 0 deletions src/app/courseflow/services/course-map-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,22 @@ export class CourseMapStateService {
return true;
}

removeElectiveUnit(index: number): void {
const currentState = this.currentState;
// index validation
if (index < 0 || index >= currentState.electiveUnits.length) {
return;
}

const updatedElectiveUnits = [...currentState.electiveUnits];
updatedElectiveUnits.splice(index, 1);

this.updateState({
...currentState,
electiveUnits: updatedElectiveUnits,
});
}

removeUnitFromSlot(
yearIndex: number,
trimesterKey: 'trimester1' | 'trimester2' | 'trimester3',
Expand Down
6 changes: 5 additions & 1 deletion src/app/courseflow/states/coursemap/coursemap.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
[remainingSlots]="getRemainingElectiveSlots()"
[maxElectiveUnits]="state.maxElectiveUnits"
(dropEvent)="handleDrop($event)"
(removeUnit)="removeElectiveUnit($event)"
>
</elective-units-list>

<unit-search [availableUnits]="getAvailableUnits()" (unitAdded)="addElectiveUnit($event)">
<unit-search
[availableUnits]="getAvailableUnits()"
[addedUnits]="getAllAddedUnits()"
(unitAdded)="addElectiveUnit($event)">
</unit-search>
</div>
</div>
Expand Down
8 changes: 8 additions & 0 deletions src/app/courseflow/states/coursemap/coursemap.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,19 @@ export class CoursemapComponent implements OnInit, OnDestroy {
return this.stateService.addElectiveUnit(unit);
}

removeElectiveUnit(index: number): void {
this.stateService.removeElectiveUnit(index);
}

getAvailableUnits(): Unit[] {
const allRequiredIds = new Set(this.state.allRequiredUnits.map((u) => u.id));
return this.units.filter((unit) => !allRequiredIds.has(unit.id));
}

getAllAddedUnits(): Unit[] {
return [...this.state.allRequiredUnits, ...this.state.electiveUnits];
}

getRemainingElectiveSlots(): number {
return this.stateService.getRemainingElectiveSlots();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ <h3 class="electives-title">Elective Units</h3>
class="units-list"
>
<unit-card
*ngFor="let unit of units"
*ngFor="let unit of units; let i = index"
[unit]="unit"
[dragData]="getDragData(unit)"
[showMenu]="false"
[showMenu]="true"
(removeUnit)="onRemoveUnit(i)"
>
</unit-card>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ export class ElectiveUnitsListComponent {
@Input() maxElectiveUnits!: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@Output() dropEvent = new EventEmitter<any>();
@Output() removeUnit = new EventEmitter<number>(); // Emit the index of the unit to remove

// eslint-disable-next-line @typescript-eslint/no-explicit-any
onDrop(event: any): void {
this.dropEvent.emit(event);
}

onRemoveUnit(index: number): void {
this.removeUnit.emit(index);
}

getDragData(unit: Unit) {
return {
unit: unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
<input
class="search-bar"
matInput
[(ngModel)]="unitCode"
[formControl]="unitCodeControl"
[matAutocomplete]="auto"
name="unitCode"
id="unitCode"
placeholder="e.g., CS101"
placeholder="e.g., JDB001"
/>
<mat-autocomplete
#auto="matAutocomplete"
[displayWith]="displayUnit"
(optionSelected)="onUnitSelected($event.option.value)">
<mat-option *ngFor="let unit of filteredUnits | async" [value]="unit">
<span class="unit-code">{{ unit.code }}</span>
<span class="unit-name"> - {{ unit.name }}</span>
</mat-option>
</mat-autocomplete>
<button mat-flat-button class="add-unit-button" color="primary" type="submit" matSuffix>
Add
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,24 @@
gap: 8px;
font-weight: 800;
}

// Autocomplete option styling
.unit-code {
font-weight: 600;
color: #1976d2;
}

.unit-name {
color: #666;
font-style: italic;
}

::ng-deep .mat-autocomplete-panel {
max-height: 300px;
}

::ng-deep .mat-option {
line-height: 1.4 !important;
height: auto !important;
padding: 12px 16px !important;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {Component, Input, Output, EventEmitter} from '@angular/core';
import {Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatOptionModule} from '@angular/material/core';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
import {Unit} from 'src/app/api/models/doubtfire-model';

@Component({
Expand All @@ -15,34 +19,92 @@ import {Unit} from 'src/app/api/models/doubtfire-model';
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatIconModule,
MatAutocompleteModule,
MatOptionModule,
],
})
export class UnitSearchComponent {
export class UnitSearchComponent implements OnInit, OnChanges {
@Input() availableUnits!: Unit[];
@Input() addedUnits: Unit[] = []; // New input for already added units
@Output() unitAdded = new EventEmitter<Unit>();

unitCode = '';
unitCodeControl = new FormControl<string | Unit>('');
filteredUnits: Observable<Unit[]>;
errorMessage: string | null = null;

constructor() {
this.filteredUnits = this.unitCodeControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(this._normalizeValue(value)))
);
}

ngOnInit(): void {
// Component initialization if needed
}

ngOnChanges(changes: SimpleChanges): void {
// Reinitialize filtered units when addedUnits changes
if (changes['addedUnits']) {
this.filteredUnits = this.unitCodeControl.valueChanges.pipe(
startWith(this.unitCodeControl.value || ''),
map(value => this._filter(this._normalizeValue(value)))
);
}
}

private _normalizeValue(value: string | Unit | null): string {
if (!value) return '';
if (typeof value === 'string') return value;
return value.code; // If it's a Unit object, use the code
}

private _filter(value: string): Unit[] {
const filterValue = value.toLowerCase();
// Get unit codes that have already been added
const addedUnitCodes = this.addedUnits.map(unit => unit.code);

return this.availableUnits.filter(unit => (
(unit.code.toLowerCase().includes(filterValue) ||
unit.name.toLowerCase().includes(filterValue)) &&
// exclude already added units
!addedUnitCodes.includes(unit.code)
));
}

onSubmit(): void {
if (!this.unitCode) {
const unitCode = this.unitCodeControl.value;
if (!unitCode) {
this.errorMessage = 'Please enter a unit code';
return;
}

const trimmedCode = this.unitCode.trim().toUpperCase();
// Normalize the value to a string
const normalizedCode = this._normalizeValue(unitCode);
const trimmedCode = normalizedCode.trim().toUpperCase();
const foundUnit = this.availableUnits.find((unit) => unit.code === trimmedCode);

if (foundUnit) {
this.unitAdded.emit(foundUnit);
this.unitCode = '';
this.unitCodeControl.setValue('');
this.errorMessage = null;
} else {
this.errorMessage = `Unit code ${trimmedCode} not found in available units`;
}
}

onUnitSelected(unit: Unit): void {
this.unitAdded.emit(unit);
this.unitCodeControl.setValue('');
this.errorMessage = null;
}

displayUnit(unit: Unit): string {
return unit ? `${unit.code} - ${unit.name}` : '';
}
}