first comit

This commit is contained in:
Johan
2025-12-17 09:49:48 +01:00
commit 69f2bea0e7
23 changed files with 10487 additions and 0 deletions

24
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HeaderComponent } from './core/layout/header/header.component';
@Component({
selector: 'app-root',
template: `
<app-header />
<main class="container">
<router-outlet></router-outlet>
</main>
`,
styles: `
.container {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterOutlet, HeaderComponent],
})
export class AppComponent {
}

13
src/app/app.config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, withComponentInputBinding())
]
};

19
src/app/app.routes.ts Normal file
View File

@@ -0,0 +1,19 @@
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path:'todos',
// Lazy loading des routes de la fonctionnalité "todos"
loadChildren: () =>
import('./features/todo/todo.routes').then((m) => m.TODO_ROUTES),
},
{
path:'',
redirectTo:'todos',
pathMatch: 'full',
}
];

View File

@@ -0,0 +1,42 @@
:host {
display: block; // Assure que le header prend toute la largeur
}
.header {
background-color: var(--surface-color);
padding: 0 2rem;
box-shadow: var(--box-shadow);
border-bottom: 1px solid var(--border-color);
}
.header-nav {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
height: 64px;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
}
.nav-link {
font-size: 1rem;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
transition: background-color 0.2s ease, color 0.2s ease;
&.active {
background-color: var(--primary-color);
color: var(--text-color-light);
}
&:not(.active):hover {
background-color: var(--background-color);
}
}

View File

@@ -0,0 +1,20 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {RouterLink, RouterLinkActive} from '@angular/router';
@Component({
selector: 'app-header',
template: `
<header class="header">
<nav class="header-nav">
<a class="logo" routerLink="/">ToDo List</a>
<a class="nav-link" routerLink="/todos" routerLinkActive="active">
Liste des tâches
</a>
</nav>
</header>
`,
styleUrls: ['./header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterLink, RouterLinkActive],
})
export class HeaderComponent {}

View File

@@ -0,0 +1,5 @@
export interface Todo {
id: number;
title: string;
completed: boolean;
}

View File

@@ -0,0 +1,29 @@
<div class="todo-container">
<header>
<h1>Ma Todo List</h1>
<div class="todo-input-group">
<input
type="text"
placeholder="Ajouter une nouvelle tâche..."
/>
<button>Ajouter</button>
</div>
</header>
<ul class="todo-list">
<li>
<span class="todo-title">
Tâche d'exemple
</span>
<button class="btn-remove">X</button>
</li>
</ul>
<footer>
<span>X tâche(s) restante(s)</span>
</footer>
<p class="empty-state">Bravo, aucune tâche pour le moment !</p>
</div>

View File

@@ -0,0 +1,118 @@
.todo-container {
max-width: 600px;
margin: 2rem auto;
background: var(--surface-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 2rem;
h1 {
text-align: center;
color: var(--primary-color);
margin-bottom: 1.5rem;
}
}
.todo-input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
input[type='text'] {
flex-grow: 1;
padding: 0.75rem;
font-size: 1rem;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
transition: border-color 0.2s, box-shadow 0.2s;
&:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(103, 58, 183, 0.2);
}
}
button {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: bold;
color: var(--text-color-light);
background-color: var(--primary-color);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
transition: background-color 0.2s;
&:hover:not(:disabled) {
background-color: var(--primary-color-dark);
}
&:disabled {
background-color: #ccc;
cursor: not-allowed;
}
}
}
.todo-list {
list-style: none;
padding: 0;
margin: 0;
}
li {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s;
&:last-child {
border-bottom: none;
}
&.completed .todo-title {
text-decoration: line-through;
color: #999;
}
}
.todo-title {
flex-grow: 1;
cursor: pointer;
}
.btn-remove {
background-color: transparent;
border: none;
color: var(--error-color);
font-weight: bold;
font-size: 1.2rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 50%;
line-height: 1;
width: 30px;
height: 30px;
transition: background-color 0.2s, color 0.2s;
&:hover {
background-color: rgba(244, 67, 54, 0.1);
}
}
footer {
margin-top: 1.5rem;
text-align: center;
color: #777;
font-size: 0.9rem;
}
.empty-state {
text-align: center;
padding: 2rem;
color: #888;
font-style: italic;
}

View File

@@ -0,0 +1,67 @@
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { Todo } from '../../../core/models/todo.model';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule],
})
export class TodoListComponent {
// --- ÉTAT (State) ---
// TODO 1.1: Créer un signal pour stocker la liste des tâches.
// Utilisez `signal<Todo[]>()` et initialisez-le avec le tableau ci-dessous.
/*
[
{ id: 1, title: 'Apprendre les bases d\'Angular', completed: true },
{ id: 2, title: 'Comprendre les signals', completed: false },
{ id: 3, title: 'Créer un premier composant', completed: false },
]
*/
readonly todos = signal<Todo[]>([]); // <- REMPLACER [] par le tableau ci-dessus
// TODO 1.2: Créer un signal "computed" pour calculer le nombre de tâches restantes.
// Utilisez `computed(() => ...)` et filtrez le signal `todos` pour ne compter
// que les tâches où `completed` est `false`.
readonly remainingTodos = computed(() => 0); // <- REMPLACER 0 par le calcul
// TODO 1.3: Créer un FormControl pour le champ de saisie d'une nouvelle tâche.
// Utilisez `new FormControl()` avec une chaîne vide comme valeur initiale.
// Dans les options, ajoutez `nonNullable: true` et des validateurs :
// `Validators.required` et `Validators.minLength(3)`.
readonly newTodoTitle = new FormControl(''); // <- AJOUTER les options
// --- ACTIONS ---
/** Ajoute une nouvelle tâche à la liste. */
addTodo(): void {
// TODO 1.4: Implémenter la logique d'ajout.
// 1. Vérifiez si `this.newTodoTitle` est invalide. Si c'est le cas, arrêtez la fonction avec `return;`.
// 2. Créez un objet `newTodo` de type `Todo` avec un `id` unique (Date.now()),
// le titre provenant de `this.newTodoTitle.value`, et `completed: false`.
// 3. Mettez à jour le signal `todos` avec `this.todos.update(currentTodos => [...currentTodos, newTodo])`.
// 4. Réinitialisez le champ de saisie avec `this.newTodoTitle.reset()`.
}
/** Supprime une tâche de la liste. */
removeTodo(id: number): void {
// TODO 1.5: Implémenter la logique de suppression.
// Utilisez `this.todos.update(currentTodos => ...)` et la méthode `.filter()`
// pour retourner un nouveau tableau sans la tâche correspondant à l'`id`.
}
/** Bascule l'état "complété" d'une tâche. */
toggleTodo(id: number): void {
// TODO 1.6: Implémenter la logique de bascule.
// Utilisez `this.todos.update(currentTodos => ...)` et la méthode `.map()`.
// Pour chaque `todo` dans le tableau, si son `id` correspond, retournez un nouvel objet
// avec la propriété `completed` inversée (`!todo.completed`). Sinon, retournez le `todo` original.
}
}

View File

@@ -0,0 +1,11 @@
import { Routes } from '@angular/router';
import {TodoListComponent} from './todo-list/todo-list.component';
export const TODO_ROUTES: Routes = [
{
path: '',
component: TodoListComponent,
title: 'Todo List',
},
];

13
src/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Signals</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

6
src/main.ts Normal file
View File

@@ -0,0 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

53
src/styles.scss Normal file
View File

@@ -0,0 +1,53 @@
/* --- Variables de couleur et de style --- */
:root {
--primary-color: #673ab7;
--primary-color-dark: #512da8;
--accent-color: #ff4081;
--text-color: #333;
--text-color-light: #f5f5f5;
--background-color: #f0f2f5;
--surface-color: #ffffff;
--border-color: #e0e0e0;
--success-color: #4caf50;
--error-color: #f44336;
--border-radius: 8px;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* --- Réinitialisation et styles de base --- */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s ease-in-out;
}
a:hover {
color: var(--primary-color-dark);
}
main.container {
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}