# Frontend Development Guide Complete guide for developing the B12 SIS Angular frontend application. ## Prerequisites - Node.js 18+ and npm - Angular CLI 19+ - IDE with TypeScript support (VS Code recommended) ## Project Setup ```bash # Clone repository git clone b12-frontend cd b12-frontend # Install dependencies npm install # Start development server npm start ``` The development server runs at `http://localhost:4200`. ## Project Structure ``` b12-frontend/ ├── src/ │ ├── app/ │ │ ├── authentication/ # Login, signup, password reset │ │ ├── config/ # App configuration │ │ ├── core/ # Core services, guards, interceptors │ │ │ ├── base/ # Base components (list, detail, create) │ │ │ ├── guard/ # Route guards │ │ │ ├── i18n/ # Internationalization │ │ │ ├── interceptor/ # HTTP interceptors │ │ │ ├── models/ # Data models │ │ │ ├── services/ # Core services │ │ │ └── types/ # TypeScript types │ │ ├── features/ # Feature modules (75+ modules) │ │ ├── layout/ # App layouts (main, auth) │ │ └── shared/ # Shared components, directives, pipes │ ├── assets/ # Static assets │ └── environments/ # Environment configurations ├── angular.json # Angular CLI config ├── package.json └── tsconfig.json ``` ## Core Concepts ### Base Components The frontend uses base component classes to standardize CRUD operations: ```typescript // Base list component example export class StudentsListComponent extends BaseListComponent { override listConfig: GenericListConfig = { module: 'Student', title: 'Students', createRoute: '/module/students/create', detailRoute: '/module/students', showActionsColumn: true }; } ``` Available base components: - `BaseComponent` - Root component with destroy$ subject - `BaseListComponent` - List views with GenericListComponent - `BaseDetailComponent` - Detail/edit views - `BaseCreateComponent` - Create forms ### HTTP Interceptors Four interceptors handle cross-cutting concerns: | Interceptor | Purpose | |-------------|---------| | `JwtInterceptor` | Adds Bearer token to requests | | `TeamInterceptor` | Adds X-Team-ID header for multi-tenancy | | `AcademicSessionInterceptor` | Adds academic session context | | `ErrorInterceptor` | Handles HTTP errors globally | ### Route Guards ```typescript // AuthGuard protects all authenticated routes { path: '', component: MainLayoutComponent, canActivate: [AuthGuard], children: [...] } ``` ## Feature Modules Each feature module follows a consistent structure: ``` features/students/ ├── list/ │ ├── list.component.ts │ ├── list.component.html │ └── list.component.scss ├── detail/ │ ├── detail.component.ts │ ├── detail.component.html │ └── detail.component.scss ├── create/ │ ├── create.component.ts │ ├── create.component.html │ └── create.component.scss └── routes.ts ``` ### Creating a New Feature Module 1. **Create module directory**: ```bash ng generate component features/my-module/list ng generate component features/my-module/detail ng generate component features/my-module/create ``` 2. **Define routes** (`routes.ts`): ```typescript import { Route } from '@angular/router'; import { ListComponent } from './list/list.component'; import { DetailComponent } from './detail/detail.component'; import { CreateComponent } from './create/create.component'; export const ROUTE: Route[] = [ { path: 'list', component: ListComponent }, { path: 'create', component: CreateComponent }, { path: ':id', component: DetailComponent }, { path: '**', redirectTo: 'list' } ]; ``` 3. **Add to features routes** (`features/features.routes.ts`): ```typescript { path: 'my_modules', loadChildren: () => import('./my-module/routes').then((m) => m.ROUTE), }, ``` 4. **Implement list component**: ```typescript import { Component } from '@angular/core'; import { BaseListComponent } from '@core/base/list.base.component'; import { GenericListConfig } from '@shared/components/generic-list/generic-list.component'; @Component({ selector: 'app-my-module-list', templateUrl: './list.component.html', }) export class ListComponent extends BaseListComponent { override listConfig: GenericListConfig = { module: 'MyModule', title: 'My Modules', createRoute: '/module/my_modules/create', detailRoute: '/module/my_modules', showActionsColumn: true, showCreateButton: true, showDeleteButton: true }; } ``` ## Core Services ### ApiService Generic HTTP client wrapper: ```typescript import { ApiService } from '@core/services/api.service'; @Injectable() export class MyService { constructor(private api: ApiService) {} getItems() { return this.api.get('my_modules/list'); } createItem(data: any) { return this.api.post('my_modules', data); } updateItem(id: string, data: any) { return this.api.put(`my_modules/${id}`, data); } deleteItem(id: string) { return this.api.delete(`my_modules/${id}`); } } ``` ### AuthService Handles authentication state: ```typescript import { AuthService } from '@core/services/auth.service'; @Component({...}) export class MyComponent { constructor(private authService: AuthService) {} get currentUser() { return this.authService.currentUserValue; } get permissions() { return this.authService.currentUserValue.permissions; } get activeTeamId() { return this.authService.currentUserValue.active_team_id; } } ``` ### BackendService Higher-level API operations: ```typescript import { BackendService } from '@core/services/backend.service'; // Generic list with filtering this.backend.list('students', { filters: [...], page: 1, pageSize: 25 }); ``` ## Shared Components 113+ reusable components in `shared/components/`: ### Key Components | Component | Purpose | |-----------|---------| | `generic-list` | Standard list with filtering, pagination | | `generic-form` | Dynamic form generation | | `editable-field-renderer` | Field type rendering | | `file-upload` | File upload with S3 integration | | `breadcrumb` | Navigation breadcrumbs | ### Using GenericListComponent ```html ``` ## Internationalization (i18n) The app uses ngx-translate for multi-language support: ```typescript // In component constructor(private translate: TranslateService) {} // Switch language this.translate.use('vi'); // Get translation this.translate.instant('students.title'); ``` ```html {{ 'students.title' | translate }} ``` Languages are loaded from the backend API. ## Environment Configuration ```typescript // environments/environment.ts (development) export const environment = { production: false, apiUrl: 'http://localhost:8080/api' }; // environments/environment.prod.ts (production) export const environment = { production: true, apiUrl: 'https://api.school.edu/api' }; ``` ## Building & Deployment ```bash # Development build npm run build:dev # Production build npm run build:prod # Run tests npm test # Lint code npm run lint ``` ### Docker Build ```bash # Build image docker build -t b12-frontend . # Run container docker run -p 80:80 b12-frontend ``` ### Kubernetes Deployment ```bash # Interactive deployment make deploy # Select environment when prompted ``` ## Best Practices 1. **Extend base components** for consistent behavior 2. **Use lazy loading** for all feature modules 3. **Follow naming conventions**: `module_name` for paths, `ModuleName` for classes 4. **Add translations** for all user-facing text 5. **Use shared components** instead of duplicating code 6. **Handle errors** via the ErrorInterceptor 7. **Clean up subscriptions** using `takeUntil(this.destroy$)` ## Common Tasks ### Adding a Column to GenericList ```typescript listConfig: GenericListConfig = { module: 'Student', // Add custom columns extraColumns: [ { field: 'custom_field', header: 'Custom Field' } ] }; ``` ### Custom Row Actions ```typescript listConfig: GenericListConfig = { module: 'Student', extraActions: [ { icon: 'assignment', action: 'enroll', tooltip: 'Enroll' } ] }; onExtraActionClicked(event: { action: any; model: any }) { if (event.action === 'enroll') { this.enrollStudent(event.model); } } ``` ### Accessing Route Parameters ```typescript import { ActivatedRoute } from '@angular/router'; constructor(private route: ActivatedRoute) {} ngOnInit() { const id = this.route.snapshot.paramMap.get('id'); } ```