Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2115b23
feat(table): implement custom table component with filtering function…
Ryan2486 Feb 22, 2026
7154e03
feat(level): implement API, service, and store for managing levels
Ryan2486 Feb 22, 2026
76490e5
feat(button): implement reusable button component with loading state …
Ryan2486 Feb 22, 2026
22f4c15
feat(routes): configure initial routing and add LevelComponent
Ryan2486 Feb 22, 2026
1d1b8a5
feat(dependencies): add lucide-angular package and update tsconfig ba…
Ryan2486 Feb 22, 2026
50f8e13
feat(group): implement API, service, and store for managing groups
Ryan2486 Feb 22, 2026
0b07faa
feat(level): add mock data support for levels API with delay simulation
Ryan2486 Feb 22, 2026
1a4a38a
feat(table): enhance table component with Tailwind CSS integration an…
Ryan2486 Feb 22, 2026
0f75bb4
feat(level): implement level and group management component with Tail…
Ryan2486 Feb 22, 2026
b13712f
feat(form): create a reusable form component with Tailwind CSS stylin…
Ryan2486 Feb 28, 2026
b85e866
feat(level): create level form component with validation and submissi…
Ryan2486 Feb 28, 2026
057f28e
feat(modal): implement modal component with customizable size and bac…
Ryan2486 Feb 28, 2026
31e059e
feat(level): add update level functionality with mock and API handling
Ryan2486 Feb 28, 2026
cceba53
feat(level): implement modal for adding and editing levels with form …
Ryan2486 Feb 28, 2026
b2b546c
feat(level): update create and update level methods to use LevelPost …
Ryan2486 Feb 28, 2026
05c3d8a
feat(group): update create and update group methods to use GroupPost …
Ryan2486 Feb 28, 2026
3b0edff
feat(group): implement group form component with dynamic level options
Ryan2486 Feb 28, 2026
ed1c1c0
feat(level): add group modal and integrate group form for level manag…
Ryan2486 Feb 28, 2026
d93c03d
feat(group): add delete functionality and update group action buttons…
Ryan2486 Feb 28, 2026
1be80b7
Merge branch 'master' into feat/Migrate-Study-Levels-&-Groups
Ryan2486 Feb 28, 2026
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
21 changes: 0 additions & 21 deletions LICENSE.txt

This file was deleted.

14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@angular/forms": "^21.1.0",
"@angular/platform-browser": "^21.1.0",
"@angular/router": "^21.1.0",
"lucide-angular": "^0.575.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
Expand All @@ -43,4 +44,4 @@
"typescript": "~5.9.2",
"vitest": "^4.0.8"
}
}
}
345 changes: 3 additions & 342 deletions src/app/app.html

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import { Routes } from '@angular/router';
import {Routes} from '@angular/router';
import {LevelComponent} from '@features/level/level.component';

export const routes: Routes = [];
export const routes: Routes = [
{
path: '',
redirectTo: 'level',
pathMatch: 'full',
},
{
path: 'level',
component: LevelComponent,
},
];
30 changes: 30 additions & 0 deletions src/app/core/domains/group/group.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Group, GroupPost} from './group.model';

@Injectable({ providedIn: 'root' })
export class GroupApi {

constructor(private http: HttpClient) {}

getAllGroups(): Observable<Group[]> {
return this.http.get<Group[]>('/groups');
}

getGroupsByLevel(levelId: number): Observable<Group[]> {
return this.http.get<Group[]>(`/levels/${levelId}/groups`);
}

createGroup(group: GroupPost): Observable<Group> {
return this.http.post<Group>('/groups', group);
}

deleteGroup(id: number): Observable<void> {
return this.http.delete<void>(`/groups/${id}`);
}

updateGroup(id: number, updatedGroup: GroupPost) {
return this.http.put<Group>(`/groups/${id}`, updatedGroup);
}
}
1 change: 0 additions & 1 deletion src/app/core/domains/group/group.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export interface Group {
type: string;
classe: string;
size: number;
levelAbr: string;
levelId: number;
}

Expand Down
95 changes: 95 additions & 0 deletions src/app/core/domains/group/group.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {Injectable} from '@angular/core';
import {GroupApi} from './group.api';
import {GroupStore} from './group.store';
import {Group, GroupPost} from './group.model';
import {catchError, finalize, tap} from 'rxjs/operators';
import {throwError} from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class GroupService {

readonly groups$;
readonly isLoading$
readonly error$;

constructor(
private groupApi: GroupApi,
private groupStore: GroupStore
) {
this.groups$ = this.groupStore.groups$;
this.isLoading$ = this.groupStore.isLoading$;
this.error$ = this.groupStore.error$;
}

loadAllGroups(): void {
this.groupStore.setLoading(true);
this.groupStore.setError(null);

this.groupApi.getAllGroups().pipe(
tap((groups: Group[]) => this.groupStore.setGroups(groups)),
catchError((error) => {
this.groupStore.setError('Erreur lors du chargement des groupes');
return throwError(() => error);
}),
finalize(() => this.groupStore.setLoading(false))
).subscribe();
}

loadGroupsByLevel(levelId: number): void {
this.groupStore.setLoading(true);
this.groupStore.setError(null);

this.groupApi.getGroupsByLevel(levelId).pipe(
tap((groups: Group[]) => this.groupStore.setGroups(groups)),
catchError((error) => {
this.groupStore.setError('Erreur lors du chargement des groupes du niveau');
return throwError(() => error);
}),
finalize(() => this.groupStore.setLoading(false))
).subscribe();
}

createGroup(newGroup: GroupPost): void {
this.groupStore.setLoading(true);

this.groupApi.createGroup(newGroup).pipe(
tap((createdGroup: Group) => {
this.groupStore.addGroup(createdGroup);
}),
catchError((error) => {
this.groupStore.setError('Erreur lors de la création du groupe');
return throwError(() => error);
}),
finalize(() => this.groupStore.setLoading(false))
).subscribe();
}

updateGroup(id: number, updatedGroup: GroupPost): void {
this.groupStore.setLoading(true);

this.groupApi.updateGroup(id, updatedGroup).pipe(
tap((group: Group) => {
this.groupStore.updateGroup(id,group);
}),
catchError((error) => {
this.groupStore.setError('Erreur lors de la mise à jour du groupe');
return throwError(() => error);
}),
finalize(() => this.groupStore.setLoading(false))
).subscribe();
}

deleteGroup(id: number): void {
this.groupApi.deleteGroup(id).pipe(
tap(() => {
this.groupStore.removeGroup(id);
}),
catchError((error) => {
this.groupStore.setError('Erreur lors de la suppression du groupe');
return throwError(() => error);
})
).subscribe();
}
}
48 changes: 48 additions & 0 deletions src/app/core/domains/group/group.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
import {Group, GroupPost} from './group.model';

@Injectable({ providedIn: 'root' })
export class GroupStore {

private readonly _groups = new BehaviorSubject<Group[]>([]);
private readonly _isLoading = new BehaviorSubject<boolean>(false);
private readonly _error = new BehaviorSubject<string | null>(null);

readonly groups$ = this._groups.asObservable();
readonly isLoading$ = this._isLoading.asObservable();
readonly error$ = this._error.asObservable();

setGroups(groups: Group[]) {
this._groups.next(groups);
}

addGroup(group: Group) {
const currentGroups = this._groups.getValue();
this._groups.next([...currentGroups, group]);
}

updateGroup(id: number, group: GroupPost) {
const currentGroups = this._groups.getValue();
const index = currentGroups.findIndex(g => g.id === id);
if (index !== -1) {
const updatedGroup: Group = { ...currentGroups[index], ...group };
const updatedGroups = [...currentGroups];
updatedGroups[index] = updatedGroup;
this._groups.next(updatedGroups);
}
}

removeGroup(id: number) {
const currentGroups = this._groups.getValue();
this._groups.next(currentGroups.filter(g => g.id !== id));
}

setLoading(isLoading: boolean) {
this._isLoading.next(isLoading);
}

setError(error: string | null) {
this._error.next(error);
}
}
55 changes: 55 additions & 0 deletions src/app/core/domains/level/level.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {delay, Observable, of} from 'rxjs';
import {Level, LevelPost} from './level.model';
import {mockLevels} from './level.mock';

@Injectable({ providedIn: 'root' })
export class LevelApi {
private readonly API_URL = '/levels';
private readonly USE_MOCK = true; // Mettre à false pour utiliser l'API réelle
private mockData: Level[] = [...mockLevels];

constructor(private http: HttpClient) {}

getAllLevels(): Observable<Level[]> {
if (this.USE_MOCK) {
return of(this.mockData).pipe(delay(500));
}
return this.http.get<Level[]>(this.API_URL);
}

createLevel(level: LevelPost): Observable<Level> {
if (this.USE_MOCK) {
const newLevel: Level = {
id: this.mockData.at(-1)?.id ? this.mockData.at(-1)!.id + 1 : 1,
name: level.name || '',
abr: level.abr || ''
};
this.mockData.push(newLevel);
return of(newLevel).pipe(delay(300));
}
return this.http.post<Level>(this.API_URL, level);
}

updateLevel(id: number, level: LevelPost): Observable<Level> {
if (this.USE_MOCK) {
const index = this.mockData.findIndex(l => l.id === id);
if (index !== -1) {
this.mockData[index] = { ...this.mockData[index], ...level };
return of(this.mockData[index]).pipe(delay(300));
} else {
throw new Error(`Level id ${id} not found`);
}
}
return this.http.put<Level>(`${this.API_URL}/${id}`, level);
}

deleteLevel(id: number): Observable<void> {
if (this.USE_MOCK) {
this.mockData = this.mockData.filter(level => level.id !== id);
return of(void 0).pipe(delay(300));
}
return this.http.delete<void>(`${this.API_URL}/${id}`);
}
}
7 changes: 7 additions & 0 deletions src/app/core/domains/level/level.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {Level} from './level.model';

export const mockLevels: Level[] = [
{ id: 1, name: 'Niveau 1', abr: 'N1' },
{ id: 2, name: 'Niveau 2', abr: 'N2' },
{ id: 3, name: 'Niveau 3', abr: 'N3' }
];
Loading